turbo-rails 2.0.11 → 2.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  /*!
2
- Turbo 8.0.12
3
- Copyright © 2024 37signals LLC
2
+ Turbo 8.0.13
3
+ Copyright © 2025 37signals LLC
4
4
  */
5
5
  (function(prototype) {
6
6
  if (typeof prototype.requestSubmit == "function") return;
@@ -1687,8 +1687,8 @@ function readScrollBehavior(value, defaultValue) {
1687
1687
  }
1688
1688
 
1689
1689
  var Idiomorph = function() {
1690
- let EMPTY_SET = new Set;
1691
- let defaults = {
1690
+ const noOp = () => {};
1691
+ const defaults = {
1692
1692
  morphStyle: "outerHTML",
1693
1693
  callbacks: {
1694
1694
  beforeNodeAdded: noOp,
@@ -1701,235 +1701,346 @@ var Idiomorph = function() {
1701
1701
  },
1702
1702
  head: {
1703
1703
  style: "merge",
1704
- shouldPreserve: function(elt) {
1705
- return elt.getAttribute("im-preserve") === "true";
1706
- },
1707
- shouldReAppend: function(elt) {
1708
- return elt.getAttribute("im-re-append") === "true";
1709
- },
1704
+ shouldPreserve: elt => elt.getAttribute("im-preserve") === "true",
1705
+ shouldReAppend: elt => elt.getAttribute("im-re-append") === "true",
1710
1706
  shouldRemove: noOp,
1711
1707
  afterHeadMorphed: noOp
1712
- }
1708
+ },
1709
+ restoreFocus: true
1713
1710
  };
1714
1711
  function morph(oldNode, newContent, config = {}) {
1715
- if (oldNode instanceof Document) {
1716
- oldNode = oldNode.documentElement;
1712
+ oldNode = normalizeElement(oldNode);
1713
+ const newNode = normalizeParent(newContent);
1714
+ const ctx = createMorphContext(oldNode, newNode, config);
1715
+ const morphedNodes = saveAndRestoreFocus(ctx, (() => withHeadBlocking(ctx, oldNode, newNode, (ctx => {
1716
+ if (ctx.morphStyle === "innerHTML") {
1717
+ morphChildren(ctx, oldNode, newNode);
1718
+ return Array.from(oldNode.childNodes);
1719
+ } else {
1720
+ return morphOuterHTML(ctx, oldNode, newNode);
1721
+ }
1722
+ }))));
1723
+ ctx.pantry.remove();
1724
+ return morphedNodes;
1725
+ }
1726
+ function morphOuterHTML(ctx, oldNode, newNode) {
1727
+ const oldParent = normalizeParent(oldNode);
1728
+ let childNodes = Array.from(oldParent.childNodes);
1729
+ const index = childNodes.indexOf(oldNode);
1730
+ const rightMargin = childNodes.length - (index + 1);
1731
+ morphChildren(ctx, oldParent, newNode, oldNode, oldNode.nextSibling);
1732
+ childNodes = Array.from(oldParent.childNodes);
1733
+ return childNodes.slice(index, childNodes.length - rightMargin);
1734
+ }
1735
+ function saveAndRestoreFocus(ctx, fn) {
1736
+ if (!ctx.config.restoreFocus) return fn();
1737
+ let activeElement = document.activeElement;
1738
+ if (!(activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement)) {
1739
+ return fn();
1740
+ }
1741
+ const {id: activeElementId, selectionStart: selectionStart, selectionEnd: selectionEnd} = activeElement;
1742
+ const results = fn();
1743
+ if (activeElementId && activeElementId !== document.activeElement?.id) {
1744
+ activeElement = ctx.target.querySelector(`#${activeElementId}`);
1745
+ activeElement?.focus();
1746
+ }
1747
+ if (activeElement && !activeElement.selectionEnd && selectionEnd) {
1748
+ activeElement.setSelectionRange(selectionStart, selectionEnd);
1749
+ }
1750
+ return results;
1751
+ }
1752
+ const morphChildren = function() {
1753
+ function morphChildren(ctx, oldParent, newParent, insertionPoint = null, endPoint = null) {
1754
+ if (oldParent instanceof HTMLTemplateElement && newParent instanceof HTMLTemplateElement) {
1755
+ oldParent = oldParent.content;
1756
+ newParent = newParent.content;
1757
+ }
1758
+ insertionPoint ||= oldParent.firstChild;
1759
+ for (const newChild of newParent.childNodes) {
1760
+ if (insertionPoint && insertionPoint != endPoint) {
1761
+ const bestMatch = findBestMatch(ctx, newChild, insertionPoint, endPoint);
1762
+ if (bestMatch) {
1763
+ if (bestMatch !== insertionPoint) {
1764
+ removeNodesBetween(ctx, insertionPoint, bestMatch);
1765
+ }
1766
+ morphNode(bestMatch, newChild, ctx);
1767
+ insertionPoint = bestMatch.nextSibling;
1768
+ continue;
1769
+ }
1770
+ }
1771
+ if (newChild instanceof Element && ctx.persistentIds.has(newChild.id)) {
1772
+ const movedChild = moveBeforeById(oldParent, newChild.id, insertionPoint, ctx);
1773
+ morphNode(movedChild, newChild, ctx);
1774
+ insertionPoint = movedChild.nextSibling;
1775
+ continue;
1776
+ }
1777
+ const insertedNode = createNode(oldParent, newChild, insertionPoint, ctx);
1778
+ if (insertedNode) {
1779
+ insertionPoint = insertedNode.nextSibling;
1780
+ }
1781
+ }
1782
+ while (insertionPoint && insertionPoint != endPoint) {
1783
+ const tempNode = insertionPoint;
1784
+ insertionPoint = insertionPoint.nextSibling;
1785
+ removeNode(ctx, tempNode);
1786
+ }
1717
1787
  }
1718
- if (typeof newContent === "string") {
1719
- newContent = parseContent(newContent);
1788
+ function createNode(oldParent, newChild, insertionPoint, ctx) {
1789
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return null;
1790
+ if (ctx.idMap.has(newChild)) {
1791
+ const newEmptyChild = document.createElement(newChild.tagName);
1792
+ oldParent.insertBefore(newEmptyChild, insertionPoint);
1793
+ morphNode(newEmptyChild, newChild, ctx);
1794
+ ctx.callbacks.afterNodeAdded(newEmptyChild);
1795
+ return newEmptyChild;
1796
+ } else {
1797
+ const newClonedChild = document.importNode(newChild, true);
1798
+ oldParent.insertBefore(newClonedChild, insertionPoint);
1799
+ ctx.callbacks.afterNodeAdded(newClonedChild);
1800
+ return newClonedChild;
1801
+ }
1720
1802
  }
1721
- let normalizedContent = normalizeContent(newContent);
1722
- let ctx = createMorphContext(oldNode, normalizedContent, config);
1723
- return morphNormalizedContent(oldNode, normalizedContent, ctx);
1724
- }
1725
- function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
1726
- if (ctx.head.block) {
1727
- let oldHead = oldNode.querySelector("head");
1728
- let newHead = normalizedNewContent.querySelector("head");
1729
- if (oldHead && newHead) {
1730
- let promises = handleHeadElement(newHead, oldHead, ctx);
1731
- Promise.all(promises).then((function() {
1732
- morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
1733
- head: {
1734
- block: false,
1735
- ignore: true
1803
+ const findBestMatch = function() {
1804
+ function findBestMatch(ctx, node, startPoint, endPoint) {
1805
+ let softMatch = null;
1806
+ let nextSibling = node.nextSibling;
1807
+ let siblingSoftMatchCount = 0;
1808
+ let cursor = startPoint;
1809
+ while (cursor && cursor != endPoint) {
1810
+ if (isSoftMatch(cursor, node)) {
1811
+ if (isIdSetMatch(ctx, cursor, node)) {
1812
+ return cursor;
1736
1813
  }
1737
- }));
1738
- }));
1739
- return;
1814
+ if (softMatch === null) {
1815
+ if (!ctx.idMap.has(cursor)) {
1816
+ softMatch = cursor;
1817
+ }
1818
+ }
1819
+ }
1820
+ if (softMatch === null && nextSibling && isSoftMatch(cursor, nextSibling)) {
1821
+ siblingSoftMatchCount++;
1822
+ nextSibling = nextSibling.nextSibling;
1823
+ if (siblingSoftMatchCount >= 2) {
1824
+ softMatch = undefined;
1825
+ }
1826
+ }
1827
+ if (cursor.contains(document.activeElement)) break;
1828
+ cursor = cursor.nextSibling;
1829
+ }
1830
+ return softMatch || null;
1831
+ }
1832
+ function isIdSetMatch(ctx, oldNode, newNode) {
1833
+ let oldSet = ctx.idMap.get(oldNode);
1834
+ let newSet = ctx.idMap.get(newNode);
1835
+ if (!newSet || !oldSet) return false;
1836
+ for (const id of oldSet) {
1837
+ if (newSet.has(id)) {
1838
+ return true;
1839
+ }
1840
+ }
1841
+ return false;
1842
+ }
1843
+ function isSoftMatch(oldNode, newNode) {
1844
+ const oldElt = oldNode;
1845
+ const newElt = newNode;
1846
+ return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && (!oldElt.id || oldElt.id === newElt.id);
1847
+ }
1848
+ return findBestMatch;
1849
+ }();
1850
+ function removeNode(ctx, node) {
1851
+ if (ctx.idMap.has(node)) {
1852
+ moveBefore(ctx.pantry, node, null);
1853
+ } else {
1854
+ if (ctx.callbacks.beforeNodeRemoved(node) === false) return;
1855
+ node.parentNode?.removeChild(node);
1856
+ ctx.callbacks.afterNodeRemoved(node);
1740
1857
  }
1741
1858
  }
1742
- if (ctx.morphStyle === "innerHTML") {
1743
- morphChildren(normalizedNewContent, oldNode, ctx);
1744
- return oldNode.children;
1745
- } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
1746
- let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
1747
- let previousSibling = bestMatch?.previousSibling;
1748
- let nextSibling = bestMatch?.nextSibling;
1749
- let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
1750
- if (bestMatch) {
1751
- return insertSiblings(previousSibling, morphedNode, nextSibling);
1859
+ function removeNodesBetween(ctx, startInclusive, endExclusive) {
1860
+ let cursor = startInclusive;
1861
+ while (cursor && cursor !== endExclusive) {
1862
+ let tempNode = cursor;
1863
+ cursor = cursor.nextSibling;
1864
+ removeNode(ctx, tempNode);
1865
+ }
1866
+ return cursor;
1867
+ }
1868
+ function moveBeforeById(parentNode, id, after, ctx) {
1869
+ const target = ctx.target.querySelector(`#${id}`) || ctx.pantry.querySelector(`#${id}`);
1870
+ removeElementFromAncestorsIdMaps(target, ctx);
1871
+ moveBefore(parentNode, target, after);
1872
+ return target;
1873
+ }
1874
+ function removeElementFromAncestorsIdMaps(element, ctx) {
1875
+ const id = element.id;
1876
+ while (element = element.parentNode) {
1877
+ let idSet = ctx.idMap.get(element);
1878
+ if (idSet) {
1879
+ idSet.delete(id);
1880
+ if (!idSet.size) {
1881
+ ctx.idMap.delete(element);
1882
+ }
1883
+ }
1884
+ }
1885
+ }
1886
+ function moveBefore(parentNode, element, after) {
1887
+ if (parentNode.moveBefore) {
1888
+ try {
1889
+ parentNode.moveBefore(element, after);
1890
+ } catch (e) {
1891
+ parentNode.insertBefore(element, after);
1892
+ }
1752
1893
  } else {
1753
- return [];
1894
+ parentNode.insertBefore(element, after);
1754
1895
  }
1755
- } else {
1756
- throw "Do not understand how to morph style " + ctx.morphStyle;
1757
1896
  }
1758
- }
1759
- function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
1760
- return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
1761
- }
1762
- function morphOldNodeTo(oldNode, newContent, ctx) {
1763
- if (ctx.ignoreActive && oldNode === document.activeElement) ; else if (newContent == null) {
1764
- if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
1765
- oldNode.remove();
1766
- ctx.callbacks.afterNodeRemoved(oldNode);
1767
- return null;
1768
- } else if (!isSoftMatch(oldNode, newContent)) {
1769
- if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
1770
- if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;
1771
- oldNode.parentElement.replaceChild(newContent, oldNode);
1772
- ctx.callbacks.afterNodeAdded(newContent);
1773
- ctx.callbacks.afterNodeRemoved(oldNode);
1774
- return newContent;
1775
- } else {
1776
- if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode;
1897
+ return morphChildren;
1898
+ }();
1899
+ const morphNode = function() {
1900
+ function morphNode(oldNode, newContent, ctx) {
1901
+ if (ctx.ignoreActive && oldNode === document.activeElement) {
1902
+ return null;
1903
+ }
1904
+ if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) {
1905
+ return oldNode;
1906
+ }
1777
1907
  if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
1778
- handleHeadElement(newContent, oldNode, ctx);
1908
+ handleHeadElement(oldNode, newContent, ctx);
1779
1909
  } else {
1780
- syncNodeFrom(newContent, oldNode, ctx);
1910
+ morphAttributes(oldNode, newContent, ctx);
1781
1911
  if (!ignoreValueOfActiveElement(oldNode, ctx)) {
1782
- morphChildren(newContent, oldNode, ctx);
1912
+ morphChildren(ctx, oldNode, newContent);
1783
1913
  }
1784
1914
  }
1785
1915
  ctx.callbacks.afterNodeMorphed(oldNode, newContent);
1786
1916
  return oldNode;
1787
1917
  }
1788
- }
1789
- function morphChildren(newParent, oldParent, ctx) {
1790
- let nextNewChild = newParent.firstChild;
1791
- let insertionPoint = oldParent.firstChild;
1792
- let newChild;
1793
- while (nextNewChild) {
1794
- newChild = nextNewChild;
1795
- nextNewChild = newChild.nextSibling;
1796
- if (insertionPoint == null) {
1797
- if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
1798
- oldParent.appendChild(newChild);
1799
- ctx.callbacks.afterNodeAdded(newChild);
1800
- removeIdsFromConsideration(ctx, newChild);
1801
- continue;
1802
- }
1803
- if (isIdSetMatch(newChild, insertionPoint, ctx)) {
1804
- morphOldNodeTo(insertionPoint, newChild, ctx);
1805
- insertionPoint = insertionPoint.nextSibling;
1806
- removeIdsFromConsideration(ctx, newChild);
1807
- continue;
1808
- }
1809
- let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
1810
- if (idSetMatch) {
1811
- insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
1812
- morphOldNodeTo(idSetMatch, newChild, ctx);
1813
- removeIdsFromConsideration(ctx, newChild);
1814
- continue;
1918
+ function morphAttributes(oldNode, newNode, ctx) {
1919
+ let type = newNode.nodeType;
1920
+ if (type === 1) {
1921
+ const oldElt = oldNode;
1922
+ const newElt = newNode;
1923
+ const oldAttributes = oldElt.attributes;
1924
+ const newAttributes = newElt.attributes;
1925
+ for (const newAttribute of newAttributes) {
1926
+ if (ignoreAttribute(newAttribute.name, oldElt, "update", ctx)) {
1927
+ continue;
1928
+ }
1929
+ if (oldElt.getAttribute(newAttribute.name) !== newAttribute.value) {
1930
+ oldElt.setAttribute(newAttribute.name, newAttribute.value);
1931
+ }
1932
+ }
1933
+ for (let i = oldAttributes.length - 1; 0 <= i; i--) {
1934
+ const oldAttribute = oldAttributes[i];
1935
+ if (!oldAttribute) continue;
1936
+ if (!newElt.hasAttribute(oldAttribute.name)) {
1937
+ if (ignoreAttribute(oldAttribute.name, oldElt, "remove", ctx)) {
1938
+ continue;
1939
+ }
1940
+ oldElt.removeAttribute(oldAttribute.name);
1941
+ }
1942
+ }
1943
+ if (!ignoreValueOfActiveElement(oldElt, ctx)) {
1944
+ syncInputValue(oldElt, newElt, ctx);
1945
+ }
1815
1946
  }
1816
- let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
1817
- if (softMatch) {
1818
- insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
1819
- morphOldNodeTo(softMatch, newChild, ctx);
1820
- removeIdsFromConsideration(ctx, newChild);
1821
- continue;
1947
+ if (type === 8 || type === 3) {
1948
+ if (oldNode.nodeValue !== newNode.nodeValue) {
1949
+ oldNode.nodeValue = newNode.nodeValue;
1950
+ }
1822
1951
  }
1823
- if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
1824
- oldParent.insertBefore(newChild, insertionPoint);
1825
- ctx.callbacks.afterNodeAdded(newChild);
1826
- removeIdsFromConsideration(ctx, newChild);
1827
- }
1828
- while (insertionPoint !== null) {
1829
- let tempNode = insertionPoint;
1830
- insertionPoint = insertionPoint.nextSibling;
1831
- removeNode(tempNode, ctx);
1832
- }
1833
- }
1834
- function ignoreAttribute(attr, to, updateType, ctx) {
1835
- if (attr === "value" && ctx.ignoreActiveValue && to === document.activeElement) {
1836
- return true;
1837
1952
  }
1838
- return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
1839
- }
1840
- function syncNodeFrom(from, to, ctx) {
1841
- let type = from.nodeType;
1842
- if (type === 1) {
1843
- const fromAttributes = from.attributes;
1844
- const toAttributes = to.attributes;
1845
- for (const fromAttribute of fromAttributes) {
1846
- if (ignoreAttribute(fromAttribute.name, to, "update", ctx)) {
1847
- continue;
1953
+ function syncInputValue(oldElement, newElement, ctx) {
1954
+ if (oldElement instanceof HTMLInputElement && newElement instanceof HTMLInputElement && newElement.type !== "file") {
1955
+ let newValue = newElement.value;
1956
+ let oldValue = oldElement.value;
1957
+ syncBooleanAttribute(oldElement, newElement, "checked", ctx);
1958
+ syncBooleanAttribute(oldElement, newElement, "disabled", ctx);
1959
+ if (!newElement.hasAttribute("value")) {
1960
+ if (!ignoreAttribute("value", oldElement, "remove", ctx)) {
1961
+ oldElement.value = "";
1962
+ oldElement.removeAttribute("value");
1963
+ }
1964
+ } else if (oldValue !== newValue) {
1965
+ if (!ignoreAttribute("value", oldElement, "update", ctx)) {
1966
+ oldElement.setAttribute("value", newValue);
1967
+ oldElement.value = newValue;
1968
+ }
1848
1969
  }
1849
- if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
1850
- to.setAttribute(fromAttribute.name, fromAttribute.value);
1970
+ } else if (oldElement instanceof HTMLOptionElement && newElement instanceof HTMLOptionElement) {
1971
+ syncBooleanAttribute(oldElement, newElement, "selected", ctx);
1972
+ } else if (oldElement instanceof HTMLTextAreaElement && newElement instanceof HTMLTextAreaElement) {
1973
+ let newValue = newElement.value;
1974
+ let oldValue = oldElement.value;
1975
+ if (ignoreAttribute("value", oldElement, "update", ctx)) {
1976
+ return;
1851
1977
  }
1852
- }
1853
- for (let i = toAttributes.length - 1; 0 <= i; i--) {
1854
- const toAttribute = toAttributes[i];
1855
- if (ignoreAttribute(toAttribute.name, to, "remove", ctx)) {
1856
- continue;
1978
+ if (newValue !== oldValue) {
1979
+ oldElement.value = newValue;
1857
1980
  }
1858
- if (!from.hasAttribute(toAttribute.name)) {
1859
- to.removeAttribute(toAttribute.name);
1981
+ if (oldElement.firstChild && oldElement.firstChild.nodeValue !== newValue) {
1982
+ oldElement.firstChild.nodeValue = newValue;
1860
1983
  }
1861
1984
  }
1862
1985
  }
1863
- if (type === 8 || type === 3) {
1864
- if (to.nodeValue !== from.nodeValue) {
1865
- to.nodeValue = from.nodeValue;
1866
- }
1867
- }
1868
- if (!ignoreValueOfActiveElement(to, ctx)) {
1869
- syncInputValue(from, to, ctx);
1870
- }
1871
- }
1872
- function syncBooleanAttribute(from, to, attributeName, ctx) {
1873
- if (from[attributeName] !== to[attributeName]) {
1874
- let ignoreUpdate = ignoreAttribute(attributeName, to, "update", ctx);
1875
- if (!ignoreUpdate) {
1876
- to[attributeName] = from[attributeName];
1877
- }
1878
- if (from[attributeName]) {
1986
+ function syncBooleanAttribute(oldElement, newElement, attributeName, ctx) {
1987
+ const newLiveValue = newElement[attributeName], oldLiveValue = oldElement[attributeName];
1988
+ if (newLiveValue !== oldLiveValue) {
1989
+ const ignoreUpdate = ignoreAttribute(attributeName, oldElement, "update", ctx);
1879
1990
  if (!ignoreUpdate) {
1880
- to.setAttribute(attributeName, from[attributeName]);
1991
+ oldElement[attributeName] = newElement[attributeName];
1881
1992
  }
1882
- } else {
1883
- if (!ignoreAttribute(attributeName, to, "remove", ctx)) {
1884
- to.removeAttribute(attributeName);
1993
+ if (newLiveValue) {
1994
+ if (!ignoreUpdate) {
1995
+ oldElement.setAttribute(attributeName, "");
1996
+ }
1997
+ } else {
1998
+ if (!ignoreAttribute(attributeName, oldElement, "remove", ctx)) {
1999
+ oldElement.removeAttribute(attributeName);
2000
+ }
1885
2001
  }
1886
2002
  }
1887
2003
  }
1888
- }
1889
- function syncInputValue(from, to, ctx) {
1890
- if (from instanceof HTMLInputElement && to instanceof HTMLInputElement && from.type !== "file") {
1891
- let fromValue = from.value;
1892
- let toValue = to.value;
1893
- syncBooleanAttribute(from, to, "checked", ctx);
1894
- syncBooleanAttribute(from, to, "disabled", ctx);
1895
- if (!from.hasAttribute("value")) {
1896
- if (!ignoreAttribute("value", to, "remove", ctx)) {
1897
- to.value = "";
1898
- to.removeAttribute("value");
1899
- }
1900
- } else if (fromValue !== toValue) {
1901
- if (!ignoreAttribute("value", to, "update", ctx)) {
1902
- to.setAttribute("value", fromValue);
1903
- to.value = fromValue;
1904
- }
1905
- }
1906
- } else if (from instanceof HTMLOptionElement) {
1907
- syncBooleanAttribute(from, to, "selected", ctx);
1908
- } else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
1909
- let fromValue = from.value;
1910
- let toValue = to.value;
1911
- if (ignoreAttribute("value", to, "update", ctx)) {
1912
- return;
1913
- }
1914
- if (fromValue !== toValue) {
1915
- to.value = fromValue;
2004
+ function ignoreAttribute(attr, element, updateType, ctx) {
2005
+ if (attr === "value" && ctx.ignoreActiveValue && element === document.activeElement) {
2006
+ return true;
1916
2007
  }
1917
- if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
1918
- to.firstChild.nodeValue = fromValue;
2008
+ return ctx.callbacks.beforeAttributeUpdated(attr, element, updateType) === false;
2009
+ }
2010
+ function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
2011
+ return !!ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
2012
+ }
2013
+ return morphNode;
2014
+ }();
2015
+ function withHeadBlocking(ctx, oldNode, newNode, callback) {
2016
+ if (ctx.head.block) {
2017
+ const oldHead = oldNode.querySelector("head");
2018
+ const newHead = newNode.querySelector("head");
2019
+ if (oldHead && newHead) {
2020
+ const promises = handleHeadElement(oldHead, newHead, ctx);
2021
+ return Promise.all(promises).then((() => {
2022
+ const newCtx = Object.assign(ctx, {
2023
+ head: {
2024
+ block: false,
2025
+ ignore: true
2026
+ }
2027
+ });
2028
+ return callback(newCtx);
2029
+ }));
1919
2030
  }
1920
2031
  }
2032
+ return callback(ctx);
1921
2033
  }
1922
- function handleHeadElement(newHeadTag, currentHead, ctx) {
2034
+ function handleHeadElement(oldHead, newHead, ctx) {
1923
2035
  let added = [];
1924
2036
  let removed = [];
1925
2037
  let preserved = [];
1926
2038
  let nodesToAppend = [];
1927
- let headMergeStyle = ctx.head.style;
1928
2039
  let srcToNewHeadNodes = new Map;
1929
- for (const newHeadChild of newHeadTag.children) {
2040
+ for (const newHeadChild of newHead.children) {
1930
2041
  srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
1931
2042
  }
1932
- for (const currentHeadElt of currentHead.children) {
2043
+ for (const currentHeadElt of oldHead.children) {
1933
2044
  let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
1934
2045
  let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
1935
2046
  let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
@@ -1941,7 +2052,7 @@ var Idiomorph = function() {
1941
2052
  preserved.push(currentHeadElt);
1942
2053
  }
1943
2054
  } else {
1944
- if (headMergeStyle === "append") {
2055
+ if (ctx.head.style === "append") {
1945
2056
  if (isReAppended) {
1946
2057
  removed.push(currentHeadElt);
1947
2058
  nodesToAppend.push(currentHeadElt);
@@ -1958,8 +2069,8 @@ var Idiomorph = function() {
1958
2069
  for (const newNode of nodesToAppend) {
1959
2070
  let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
1960
2071
  if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
1961
- if (newElt.href || newElt.src) {
1962
- let resolve = null;
2072
+ if ("href" in newElt && newElt.href || "src" in newElt && newElt.src) {
2073
+ let resolve;
1963
2074
  let promise = new Promise((function(_resolve) {
1964
2075
  resolve = _resolve;
1965
2076
  }));
@@ -1968,258 +2079,195 @@ var Idiomorph = function() {
1968
2079
  }));
1969
2080
  promises.push(promise);
1970
2081
  }
1971
- currentHead.appendChild(newElt);
2082
+ oldHead.appendChild(newElt);
1972
2083
  ctx.callbacks.afterNodeAdded(newElt);
1973
2084
  added.push(newElt);
1974
2085
  }
1975
2086
  }
1976
2087
  for (const removedElement of removed) {
1977
2088
  if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
1978
- currentHead.removeChild(removedElement);
2089
+ oldHead.removeChild(removedElement);
1979
2090
  ctx.callbacks.afterNodeRemoved(removedElement);
1980
2091
  }
1981
2092
  }
1982
- ctx.head.afterHeadMorphed(currentHead, {
2093
+ ctx.head.afterHeadMorphed(oldHead, {
1983
2094
  added: added,
1984
2095
  kept: preserved,
1985
2096
  removed: removed
1986
2097
  });
1987
2098
  return promises;
1988
2099
  }
1989
- function noOp() {}
1990
- function mergeDefaults(config) {
1991
- let finalConfig = {};
1992
- Object.assign(finalConfig, defaults);
1993
- Object.assign(finalConfig, config);
1994
- finalConfig.callbacks = {};
1995
- Object.assign(finalConfig.callbacks, defaults.callbacks);
1996
- Object.assign(finalConfig.callbacks, config.callbacks);
1997
- finalConfig.head = {};
1998
- Object.assign(finalConfig.head, defaults.head);
1999
- Object.assign(finalConfig.head, config.head);
2000
- return finalConfig;
2001
- }
2002
- function createMorphContext(oldNode, newContent, config) {
2003
- config = mergeDefaults(config);
2004
- return {
2005
- target: oldNode,
2006
- newContent: newContent,
2007
- config: config,
2008
- morphStyle: config.morphStyle,
2009
- ignoreActive: config.ignoreActive,
2010
- ignoreActiveValue: config.ignoreActiveValue,
2011
- idMap: createIdMap(oldNode, newContent),
2012
- deadIds: new Set,
2013
- callbacks: config.callbacks,
2014
- head: config.head
2015
- };
2016
- }
2017
- function isIdSetMatch(node1, node2, ctx) {
2018
- if (node1 == null || node2 == null) {
2019
- return false;
2100
+ const createMorphContext = function() {
2101
+ function createMorphContext(oldNode, newContent, config) {
2102
+ const {persistentIds: persistentIds, idMap: idMap} = createIdMaps(oldNode, newContent);
2103
+ const mergedConfig = mergeDefaults(config);
2104
+ const morphStyle = mergedConfig.morphStyle || "outerHTML";
2105
+ if (![ "innerHTML", "outerHTML" ].includes(morphStyle)) {
2106
+ throw `Do not understand how to morph style ${morphStyle}`;
2107
+ }
2108
+ return {
2109
+ target: oldNode,
2110
+ newContent: newContent,
2111
+ config: mergedConfig,
2112
+ morphStyle: morphStyle,
2113
+ ignoreActive: mergedConfig.ignoreActive,
2114
+ ignoreActiveValue: mergedConfig.ignoreActiveValue,
2115
+ restoreFocus: mergedConfig.restoreFocus,
2116
+ idMap: idMap,
2117
+ persistentIds: persistentIds,
2118
+ pantry: createPantry(),
2119
+ callbacks: mergedConfig.callbacks,
2120
+ head: mergedConfig.head
2121
+ };
2020
2122
  }
2021
- if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
2022
- if (node1.id !== "" && node1.id === node2.id) {
2023
- return true;
2024
- } else {
2025
- return getIdIntersectionCount(ctx, node1, node2) > 0;
2123
+ function mergeDefaults(config) {
2124
+ let finalConfig = Object.assign({}, defaults);
2125
+ Object.assign(finalConfig, config);
2126
+ finalConfig.callbacks = Object.assign({}, defaults.callbacks, config.callbacks);
2127
+ finalConfig.head = Object.assign({}, defaults.head, config.head);
2128
+ return finalConfig;
2129
+ }
2130
+ function createPantry() {
2131
+ const pantry = document.createElement("div");
2132
+ pantry.hidden = true;
2133
+ document.body.insertAdjacentElement("afterend", pantry);
2134
+ return pantry;
2135
+ }
2136
+ function findIdElements(root) {
2137
+ let elements = Array.from(root.querySelectorAll("[id]"));
2138
+ if (root.id) {
2139
+ elements.push(root);
2140
+ }
2141
+ return elements;
2142
+ }
2143
+ function populateIdMapWithTree(idMap, persistentIds, root, elements) {
2144
+ for (const elt of elements) {
2145
+ if (persistentIds.has(elt.id)) {
2146
+ let current = elt;
2147
+ while (current) {
2148
+ let idSet = idMap.get(current);
2149
+ if (idSet == null) {
2150
+ idSet = new Set;
2151
+ idMap.set(current, idSet);
2152
+ }
2153
+ idSet.add(elt.id);
2154
+ if (current === root) break;
2155
+ current = current.parentElement;
2156
+ }
2157
+ }
2026
2158
  }
2027
2159
  }
2028
- return false;
2029
- }
2030
- function isSoftMatch(node1, node2) {
2031
- if (node1 == null || node2 == null) {
2032
- return false;
2160
+ function createIdMaps(oldContent, newContent) {
2161
+ const oldIdElements = findIdElements(oldContent);
2162
+ const newIdElements = findIdElements(newContent);
2163
+ const persistentIds = createPersistentIds(oldIdElements, newIdElements);
2164
+ let idMap = new Map;
2165
+ populateIdMapWithTree(idMap, persistentIds, oldContent, oldIdElements);
2166
+ const newRoot = newContent.__idiomorphRoot || newContent;
2167
+ populateIdMapWithTree(idMap, persistentIds, newRoot, newIdElements);
2168
+ return {
2169
+ persistentIds: persistentIds,
2170
+ idMap: idMap
2171
+ };
2033
2172
  }
2034
- return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName;
2035
- }
2036
- function removeNodesBetween(startInclusive, endExclusive, ctx) {
2037
- while (startInclusive !== endExclusive) {
2038
- let tempNode = startInclusive;
2039
- startInclusive = startInclusive.nextSibling;
2040
- removeNode(tempNode, ctx);
2041
- }
2042
- removeIdsFromConsideration(ctx, endExclusive);
2043
- return endExclusive.nextSibling;
2044
- }
2045
- function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
2046
- let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
2047
- let potentialMatch = null;
2048
- if (newChildPotentialIdCount > 0) {
2049
- let potentialMatch = insertionPoint;
2050
- let otherMatchCount = 0;
2051
- while (potentialMatch != null) {
2052
- if (isIdSetMatch(newChild, potentialMatch, ctx)) {
2053
- return potentialMatch;
2054
- }
2055
- otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
2056
- if (otherMatchCount > newChildPotentialIdCount) {
2057
- return null;
2173
+ function createPersistentIds(oldIdElements, newIdElements) {
2174
+ let duplicateIds = new Set;
2175
+ let oldIdTagNameMap = new Map;
2176
+ for (const {id: id, tagName: tagName} of oldIdElements) {
2177
+ if (oldIdTagNameMap.has(id)) {
2178
+ duplicateIds.add(id);
2179
+ } else {
2180
+ oldIdTagNameMap.set(id, tagName);
2058
2181
  }
2059
- potentialMatch = potentialMatch.nextSibling;
2060
2182
  }
2061
- }
2062
- return potentialMatch;
2063
- }
2064
- function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
2065
- let potentialSoftMatch = insertionPoint;
2066
- let nextSibling = newChild.nextSibling;
2067
- let siblingSoftMatchCount = 0;
2068
- while (potentialSoftMatch != null) {
2069
- if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
2070
- return null;
2183
+ let persistentIds = new Set;
2184
+ for (const {id: id, tagName: tagName} of newIdElements) {
2185
+ if (persistentIds.has(id)) {
2186
+ duplicateIds.add(id);
2187
+ } else if (oldIdTagNameMap.get(id) === tagName) {
2188
+ persistentIds.add(id);
2189
+ }
2071
2190
  }
2072
- if (isSoftMatch(newChild, potentialSoftMatch)) {
2073
- return potentialSoftMatch;
2191
+ for (const id of duplicateIds) {
2192
+ persistentIds.delete(id);
2074
2193
  }
2075
- if (isSoftMatch(nextSibling, potentialSoftMatch)) {
2076
- siblingSoftMatchCount++;
2077
- nextSibling = nextSibling.nextSibling;
2078
- if (siblingSoftMatchCount >= 2) {
2079
- return null;
2080
- }
2194
+ return persistentIds;
2195
+ }
2196
+ return createMorphContext;
2197
+ }();
2198
+ const {normalizeElement: normalizeElement, normalizeParent: normalizeParent} = function() {
2199
+ const generatedByIdiomorph = new WeakSet;
2200
+ function normalizeElement(content) {
2201
+ if (content instanceof Document) {
2202
+ return content.documentElement;
2203
+ } else {
2204
+ return content;
2081
2205
  }
2082
- potentialSoftMatch = potentialSoftMatch.nextSibling;
2083
2206
  }
2084
- return potentialSoftMatch;
2085
- }
2086
- function parseContent(newContent) {
2087
- let parser = new DOMParser;
2088
- let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
2089
- if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
2090
- let content = parser.parseFromString(newContent, "text/html");
2091
- if (contentWithSvgsRemoved.match(/<\/html>/)) {
2092
- content.generatedByIdiomorph = true;
2093
- return content;
2094
- } else {
2095
- let htmlElement = content.firstChild;
2096
- if (htmlElement) {
2097
- htmlElement.generatedByIdiomorph = true;
2098
- return htmlElement;
2207
+ function normalizeParent(newContent) {
2208
+ if (newContent == null) {
2209
+ return document.createElement("div");
2210
+ } else if (typeof newContent === "string") {
2211
+ return normalizeParent(parseContent(newContent));
2212
+ } else if (generatedByIdiomorph.has(newContent)) {
2213
+ return newContent;
2214
+ } else if (newContent instanceof Node) {
2215
+ if (newContent.parentNode) {
2216
+ return createDuckTypedParent(newContent);
2099
2217
  } else {
2100
- return null;
2218
+ const dummyParent = document.createElement("div");
2219
+ dummyParent.append(newContent);
2220
+ return dummyParent;
2101
2221
  }
2222
+ } else {
2223
+ const dummyParent = document.createElement("div");
2224
+ for (const elt of [ ...newContent ]) {
2225
+ dummyParent.append(elt);
2226
+ }
2227
+ return dummyParent;
2102
2228
  }
2103
- } else {
2104
- let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
2105
- let content = responseDoc.body.querySelector("template").content;
2106
- content.generatedByIdiomorph = true;
2107
- return content;
2108
- }
2109
- }
2110
- function normalizeContent(newContent) {
2111
- if (newContent == null) {
2112
- const dummyParent = document.createElement("div");
2113
- return dummyParent;
2114
- } else if (newContent.generatedByIdiomorph) {
2115
- return newContent;
2116
- } else if (newContent instanceof Node) {
2117
- const dummyParent = document.createElement("div");
2118
- dummyParent.append(newContent);
2119
- return dummyParent;
2120
- } else {
2121
- const dummyParent = document.createElement("div");
2122
- for (const elt of [ ...newContent ]) {
2123
- dummyParent.append(elt);
2124
- }
2125
- return dummyParent;
2126
- }
2127
- }
2128
- function insertSiblings(previousSibling, morphedNode, nextSibling) {
2129
- let stack = [];
2130
- let added = [];
2131
- while (previousSibling != null) {
2132
- stack.push(previousSibling);
2133
- previousSibling = previousSibling.previousSibling;
2134
- }
2135
- while (stack.length > 0) {
2136
- let node = stack.pop();
2137
- added.push(node);
2138
- morphedNode.parentElement.insertBefore(node, morphedNode);
2139
- }
2140
- added.push(morphedNode);
2141
- while (nextSibling != null) {
2142
- stack.push(nextSibling);
2143
- added.push(nextSibling);
2144
- nextSibling = nextSibling.nextSibling;
2145
- }
2146
- while (stack.length > 0) {
2147
- morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
2148
- }
2149
- return added;
2150
- }
2151
- function findBestNodeMatch(newContent, oldNode, ctx) {
2152
- let currentElement;
2153
- currentElement = newContent.firstChild;
2154
- let bestElement = currentElement;
2155
- let score = 0;
2156
- while (currentElement) {
2157
- let newScore = scoreElement(currentElement, oldNode, ctx);
2158
- if (newScore > score) {
2159
- bestElement = currentElement;
2160
- score = newScore;
2161
- }
2162
- currentElement = currentElement.nextSibling;
2163
- }
2164
- return bestElement;
2165
- }
2166
- function scoreElement(node1, node2, ctx) {
2167
- if (isSoftMatch(node1, node2)) {
2168
- return .5 + getIdIntersectionCount(ctx, node1, node2);
2169
- }
2170
- return 0;
2171
- }
2172
- function removeNode(tempNode, ctx) {
2173
- removeIdsFromConsideration(ctx, tempNode);
2174
- if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
2175
- tempNode.remove();
2176
- ctx.callbacks.afterNodeRemoved(tempNode);
2177
- }
2178
- function isIdInConsideration(ctx, id) {
2179
- return !ctx.deadIds.has(id);
2180
- }
2181
- function idIsWithinNode(ctx, id, targetNode) {
2182
- let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
2183
- return idSet.has(id);
2184
- }
2185
- function removeIdsFromConsideration(ctx, node) {
2186
- let idSet = ctx.idMap.get(node) || EMPTY_SET;
2187
- for (const id of idSet) {
2188
- ctx.deadIds.add(id);
2189
2229
  }
2190
- }
2191
- function getIdIntersectionCount(ctx, node1, node2) {
2192
- let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
2193
- let matchCount = 0;
2194
- for (const id of sourceSet) {
2195
- if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
2196
- ++matchCount;
2197
- }
2230
+ function createDuckTypedParent(newContent) {
2231
+ return {
2232
+ childNodes: [ newContent ],
2233
+ querySelectorAll: s => {
2234
+ const elements = newContent.querySelectorAll(s);
2235
+ return newContent.matches(s) ? [ newContent, ...elements ] : elements;
2236
+ },
2237
+ insertBefore: (n, r) => newContent.parentNode.insertBefore(n, r),
2238
+ moveBefore: (n, r) => newContent.parentNode.moveBefore(n, r),
2239
+ get __idiomorphRoot() {
2240
+ return newContent;
2241
+ }
2242
+ };
2198
2243
  }
2199
- return matchCount;
2200
- }
2201
- function populateIdMapForNode(node, idMap) {
2202
- let nodeParent = node.parentElement;
2203
- let idElements = node.querySelectorAll("[id]");
2204
- for (const elt of idElements) {
2205
- let current = elt;
2206
- while (current !== nodeParent && current != null) {
2207
- let idSet = idMap.get(current);
2208
- if (idSet == null) {
2209
- idSet = new Set;
2210
- idMap.set(current, idSet);
2244
+ function parseContent(newContent) {
2245
+ let parser = new DOMParser;
2246
+ let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
2247
+ if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
2248
+ let content = parser.parseFromString(newContent, "text/html");
2249
+ if (contentWithSvgsRemoved.match(/<\/html>/)) {
2250
+ generatedByIdiomorph.add(content);
2251
+ return content;
2252
+ } else {
2253
+ let htmlElement = content.firstChild;
2254
+ if (htmlElement) {
2255
+ generatedByIdiomorph.add(htmlElement);
2256
+ }
2257
+ return htmlElement;
2211
2258
  }
2212
- idSet.add(elt.id);
2213
- current = current.parentElement;
2259
+ } else {
2260
+ let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
2261
+ let content = responseDoc.body.querySelector("template").content;
2262
+ generatedByIdiomorph.add(content);
2263
+ return content;
2214
2264
  }
2215
2265
  }
2216
- }
2217
- function createIdMap(oldContent, newContent) {
2218
- let idMap = new Map;
2219
- populateIdMapForNode(oldContent, idMap);
2220
- populateIdMapForNode(newContent, idMap);
2221
- return idMap;
2222
- }
2266
+ return {
2267
+ normalizeElement: normalizeElement,
2268
+ normalizeParent: normalizeParent
2269
+ };
2270
+ }();
2223
2271
  return {
2224
2272
  morph: morph,
2225
2273
  defaults: defaults
@@ -2234,7 +2282,7 @@ function morphElements(currentElement, newElement, {callbacks: callbacks, ...opt
2234
2282
  }
2235
2283
 
2236
2284
  function morphChildren(currentElement, newElement) {
2237
- morphElements(currentElement, newElement.children, {
2285
+ morphElements(currentElement, newElement.childNodes, {
2238
2286
  morphStyle: "innerHTML"
2239
2287
  });
2240
2288
  }
@@ -2905,16 +2953,6 @@ class Visit {
2905
2953
  ...this.timingMetrics
2906
2954
  };
2907
2955
  }
2908
- getHistoryMethodForAction(action) {
2909
- switch (action) {
2910
- case "replace":
2911
- return history.replaceState;
2912
-
2913
- case "advance":
2914
- case "restore":
2915
- return history.pushState;
2916
- }
2917
- }
2918
2956
  hasPreloadedResponse() {
2919
2957
  return typeof this.response == "object";
2920
2958
  }
@@ -3017,6 +3055,9 @@ class BrowserAdapter {
3017
3055
  this.hideVisitProgressBar();
3018
3056
  }
3019
3057
  visitRendered(_visit) {}
3058
+ linkPrefetchingIsEnabledForLocation(location) {
3059
+ return true;
3060
+ }
3020
3061
  formSubmissionStarted(_formSubmission) {
3021
3062
  this.progressBar.setValue(0);
3022
3063
  this.showFormProgressBarAfterDelay();
@@ -3473,6 +3514,12 @@ class Navigator {
3473
3514
  this.adapter.formSubmissionFinished(formSubmission);
3474
3515
  }
3475
3516
  }
3517
+ linkPrefetchingIsEnabledForLocation(location) {
3518
+ if (typeof this.adapter.linkPrefetchingIsEnabledForLocation === "function") {
3519
+ return this.adapter.linkPrefetchingIsEnabledForLocation(location);
3520
+ }
3521
+ return true;
3522
+ }
3476
3523
  visitStarted(visit) {
3477
3524
  this.delegate.visitStarted(visit);
3478
3525
  }
@@ -4211,7 +4258,8 @@ class Session {
4211
4258
  }
4212
4259
  refresh(url, requestId) {
4213
4260
  const isRecentRequest = requestId && this.recentRequests.has(requestId);
4214
- if (!isRecentRequest && !this.navigator.currentVisit) {
4261
+ const isCurrentUrl = url === document.baseURI;
4262
+ if (!isRecentRequest && !this.navigator.currentVisit && isCurrentUrl) {
4215
4263
  this.visit(url, {
4216
4264
  action: "replace",
4217
4265
  shouldCacheSnapshot: false
@@ -4300,7 +4348,7 @@ class Session {
4300
4348
  }
4301
4349
  submittedFormLinkToLocation() {}
4302
4350
  canPrefetchRequestToLocation(link, location) {
4303
- return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
4351
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.navigator.linkPrefetchingIsEnabledForLocation(location);
4304
4352
  }
4305
4353
  willFollowLinkToLocation(link, location, event) {
4306
4354
  return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
@@ -5117,9 +5165,9 @@ class StreamElement extends HTMLElement {
5117
5165
  this.duplicateChildren.forEach((c => c.remove()));
5118
5166
  }
5119
5167
  get duplicateChildren() {
5120
- const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
5121
- const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
5122
- return existingChildren.filter((c => newChildrenIds.includes(c.id)));
5168
+ const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.getAttribute("id")));
5169
+ const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.getAttribute("id"))).map((c => c.getAttribute("id")));
5170
+ return existingChildren.filter((c => newChildrenIds.includes(c.getAttribute("id"))));
5123
5171
  }
5124
5172
  get performAction() {
5125
5173
  if (this.action) {