turbo-rails 2.0.10 → 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.6
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;
@@ -181,7 +181,7 @@ function activateScriptElement(element) {
181
181
  return element;
182
182
  } else {
183
183
  const createdScriptElement = document.createElement("script");
184
- const cspNonce = getMetaContent("csp-nonce");
184
+ const cspNonce = getCspNonce();
185
185
  if (cspNonce) {
186
186
  createdScriptElement.nonce = cspNonce;
187
187
  }
@@ -353,6 +353,14 @@ function getMetaContent(name) {
353
353
  return element && element.content;
354
354
  }
355
355
 
356
+ function getCspNonce() {
357
+ const element = getMetaElement("csp-nonce");
358
+ if (element) {
359
+ const {nonce: nonce, content: content} = element;
360
+ return nonce == "" ? content : nonce;
361
+ }
362
+ }
363
+
356
364
  function setMetaContent(name, content) {
357
365
  let element = getMetaElement(name);
358
366
  if (!element) {
@@ -1531,12 +1539,13 @@ function createPlaceholderForPermanentElement(permanentElement) {
1531
1539
 
1532
1540
  class Renderer {
1533
1541
  #activeElement=null;
1534
- constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1542
+ static renderElement(currentElement, newElement) {}
1543
+ constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
1535
1544
  this.currentSnapshot = currentSnapshot;
1536
1545
  this.newSnapshot = newSnapshot;
1537
1546
  this.isPreview = isPreview;
1538
1547
  this.willRender = willRender;
1539
- this.renderElement = renderElement;
1548
+ this.renderElement = this.constructor.renderElement;
1540
1549
  this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
1541
1550
  resolve: resolve,
1542
1551
  reject: reject
@@ -1678,8 +1687,8 @@ function readScrollBehavior(value, defaultValue) {
1678
1687
  }
1679
1688
 
1680
1689
  var Idiomorph = function() {
1681
- let EMPTY_SET = new Set;
1682
- let defaults = {
1690
+ const noOp = () => {};
1691
+ const defaults = {
1683
1692
  morphStyle: "outerHTML",
1684
1693
  callbacks: {
1685
1694
  beforeNodeAdded: noOp,
@@ -1692,235 +1701,346 @@ var Idiomorph = function() {
1692
1701
  },
1693
1702
  head: {
1694
1703
  style: "merge",
1695
- shouldPreserve: function(elt) {
1696
- return elt.getAttribute("im-preserve") === "true";
1697
- },
1698
- shouldReAppend: function(elt) {
1699
- return elt.getAttribute("im-re-append") === "true";
1700
- },
1704
+ shouldPreserve: elt => elt.getAttribute("im-preserve") === "true",
1705
+ shouldReAppend: elt => elt.getAttribute("im-re-append") === "true",
1701
1706
  shouldRemove: noOp,
1702
1707
  afterHeadMorphed: noOp
1703
- }
1708
+ },
1709
+ restoreFocus: true
1704
1710
  };
1705
1711
  function morph(oldNode, newContent, config = {}) {
1706
- if (oldNode instanceof Document) {
1707
- 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
+ }
1708
1787
  }
1709
- if (typeof newContent === "string") {
1710
- 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
+ }
1711
1802
  }
1712
- let normalizedContent = normalizeContent(newContent);
1713
- let ctx = createMorphContext(oldNode, normalizedContent, config);
1714
- return morphNormalizedContent(oldNode, normalizedContent, ctx);
1715
- }
1716
- function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
1717
- if (ctx.head.block) {
1718
- let oldHead = oldNode.querySelector("head");
1719
- let newHead = normalizedNewContent.querySelector("head");
1720
- if (oldHead && newHead) {
1721
- let promises = handleHeadElement(newHead, oldHead, ctx);
1722
- Promise.all(promises).then((function() {
1723
- morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
1724
- head: {
1725
- block: false,
1726
- 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;
1727
1813
  }
1728
- }));
1729
- }));
1730
- 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);
1731
1857
  }
1732
1858
  }
1733
- if (ctx.morphStyle === "innerHTML") {
1734
- morphChildren(normalizedNewContent, oldNode, ctx);
1735
- return oldNode.children;
1736
- } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
1737
- let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
1738
- let previousSibling = bestMatch?.previousSibling;
1739
- let nextSibling = bestMatch?.nextSibling;
1740
- let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
1741
- if (bestMatch) {
1742
- 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
+ }
1743
1893
  } else {
1744
- return [];
1894
+ parentNode.insertBefore(element, after);
1745
1895
  }
1746
- } else {
1747
- throw "Do not understand how to morph style " + ctx.morphStyle;
1748
1896
  }
1749
- }
1750
- function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
1751
- return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
1752
- }
1753
- function morphOldNodeTo(oldNode, newContent, ctx) {
1754
- if (ctx.ignoreActive && oldNode === document.activeElement) ; else if (newContent == null) {
1755
- if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
1756
- oldNode.remove();
1757
- ctx.callbacks.afterNodeRemoved(oldNode);
1758
- return null;
1759
- } else if (!isSoftMatch(oldNode, newContent)) {
1760
- if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
1761
- if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;
1762
- oldNode.parentElement.replaceChild(newContent, oldNode);
1763
- ctx.callbacks.afterNodeAdded(newContent);
1764
- ctx.callbacks.afterNodeRemoved(oldNode);
1765
- return newContent;
1766
- } else {
1767
- 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
+ }
1768
1907
  if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
1769
- handleHeadElement(newContent, oldNode, ctx);
1908
+ handleHeadElement(oldNode, newContent, ctx);
1770
1909
  } else {
1771
- syncNodeFrom(newContent, oldNode, ctx);
1910
+ morphAttributes(oldNode, newContent, ctx);
1772
1911
  if (!ignoreValueOfActiveElement(oldNode, ctx)) {
1773
- morphChildren(newContent, oldNode, ctx);
1912
+ morphChildren(ctx, oldNode, newContent);
1774
1913
  }
1775
1914
  }
1776
1915
  ctx.callbacks.afterNodeMorphed(oldNode, newContent);
1777
1916
  return oldNode;
1778
1917
  }
1779
- }
1780
- function morphChildren(newParent, oldParent, ctx) {
1781
- let nextNewChild = newParent.firstChild;
1782
- let insertionPoint = oldParent.firstChild;
1783
- let newChild;
1784
- while (nextNewChild) {
1785
- newChild = nextNewChild;
1786
- nextNewChild = newChild.nextSibling;
1787
- if (insertionPoint == null) {
1788
- if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
1789
- oldParent.appendChild(newChild);
1790
- ctx.callbacks.afterNodeAdded(newChild);
1791
- removeIdsFromConsideration(ctx, newChild);
1792
- continue;
1793
- }
1794
- if (isIdSetMatch(newChild, insertionPoint, ctx)) {
1795
- morphOldNodeTo(insertionPoint, newChild, ctx);
1796
- insertionPoint = insertionPoint.nextSibling;
1797
- removeIdsFromConsideration(ctx, newChild);
1798
- continue;
1799
- }
1800
- let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
1801
- if (idSetMatch) {
1802
- insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
1803
- morphOldNodeTo(idSetMatch, newChild, ctx);
1804
- removeIdsFromConsideration(ctx, newChild);
1805
- 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
+ }
1806
1946
  }
1807
- let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
1808
- if (softMatch) {
1809
- insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
1810
- morphOldNodeTo(softMatch, newChild, ctx);
1811
- removeIdsFromConsideration(ctx, newChild);
1812
- continue;
1947
+ if (type === 8 || type === 3) {
1948
+ if (oldNode.nodeValue !== newNode.nodeValue) {
1949
+ oldNode.nodeValue = newNode.nodeValue;
1950
+ }
1813
1951
  }
1814
- if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
1815
- oldParent.insertBefore(newChild, insertionPoint);
1816
- ctx.callbacks.afterNodeAdded(newChild);
1817
- removeIdsFromConsideration(ctx, newChild);
1818
1952
  }
1819
- while (insertionPoint !== null) {
1820
- let tempNode = insertionPoint;
1821
- insertionPoint = insertionPoint.nextSibling;
1822
- removeNode(tempNode, ctx);
1823
- }
1824
- }
1825
- function ignoreAttribute(attr, to, updateType, ctx) {
1826
- if (attr === "value" && ctx.ignoreActiveValue && to === document.activeElement) {
1827
- return true;
1828
- }
1829
- return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
1830
- }
1831
- function syncNodeFrom(from, to, ctx) {
1832
- let type = from.nodeType;
1833
- if (type === 1) {
1834
- const fromAttributes = from.attributes;
1835
- const toAttributes = to.attributes;
1836
- for (const fromAttribute of fromAttributes) {
1837
- if (ignoreAttribute(fromAttribute.name, to, "update", ctx)) {
1838
- 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
+ }
1839
1969
  }
1840
- if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
1841
- 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;
1842
1977
  }
1843
- }
1844
- for (let i = toAttributes.length - 1; 0 <= i; i--) {
1845
- const toAttribute = toAttributes[i];
1846
- if (ignoreAttribute(toAttribute.name, to, "remove", ctx)) {
1847
- continue;
1978
+ if (newValue !== oldValue) {
1979
+ oldElement.value = newValue;
1848
1980
  }
1849
- if (!from.hasAttribute(toAttribute.name)) {
1850
- to.removeAttribute(toAttribute.name);
1981
+ if (oldElement.firstChild && oldElement.firstChild.nodeValue !== newValue) {
1982
+ oldElement.firstChild.nodeValue = newValue;
1851
1983
  }
1852
1984
  }
1853
1985
  }
1854
- if (type === 8 || type === 3) {
1855
- if (to.nodeValue !== from.nodeValue) {
1856
- to.nodeValue = from.nodeValue;
1857
- }
1858
- }
1859
- if (!ignoreValueOfActiveElement(to, ctx)) {
1860
- syncInputValue(from, to, ctx);
1861
- }
1862
- }
1863
- function syncBooleanAttribute(from, to, attributeName, ctx) {
1864
- if (from[attributeName] !== to[attributeName]) {
1865
- let ignoreUpdate = ignoreAttribute(attributeName, to, "update", ctx);
1866
- if (!ignoreUpdate) {
1867
- to[attributeName] = from[attributeName];
1868
- }
1869
- 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);
1870
1990
  if (!ignoreUpdate) {
1871
- to.setAttribute(attributeName, from[attributeName]);
1991
+ oldElement[attributeName] = newElement[attributeName];
1872
1992
  }
1873
- } else {
1874
- if (!ignoreAttribute(attributeName, to, "remove", ctx)) {
1875
- 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
+ }
1876
2001
  }
1877
2002
  }
1878
2003
  }
1879
- }
1880
- function syncInputValue(from, to, ctx) {
1881
- if (from instanceof HTMLInputElement && to instanceof HTMLInputElement && from.type !== "file") {
1882
- let fromValue = from.value;
1883
- let toValue = to.value;
1884
- syncBooleanAttribute(from, to, "checked", ctx);
1885
- syncBooleanAttribute(from, to, "disabled", ctx);
1886
- if (!from.hasAttribute("value")) {
1887
- if (!ignoreAttribute("value", to, "remove", ctx)) {
1888
- to.value = "";
1889
- to.removeAttribute("value");
1890
- }
1891
- } else if (fromValue !== toValue) {
1892
- if (!ignoreAttribute("value", to, "update", ctx)) {
1893
- to.setAttribute("value", fromValue);
1894
- to.value = fromValue;
1895
- }
1896
- }
1897
- } else if (from instanceof HTMLOptionElement) {
1898
- syncBooleanAttribute(from, to, "selected", ctx);
1899
- } else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
1900
- let fromValue = from.value;
1901
- let toValue = to.value;
1902
- if (ignoreAttribute("value", to, "update", ctx)) {
1903
- return;
1904
- }
1905
- if (fromValue !== toValue) {
1906
- to.value = fromValue;
2004
+ function ignoreAttribute(attr, element, updateType, ctx) {
2005
+ if (attr === "value" && ctx.ignoreActiveValue && element === document.activeElement) {
2006
+ return true;
1907
2007
  }
1908
- if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
1909
- 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
+ }));
1910
2030
  }
1911
2031
  }
2032
+ return callback(ctx);
1912
2033
  }
1913
- function handleHeadElement(newHeadTag, currentHead, ctx) {
2034
+ function handleHeadElement(oldHead, newHead, ctx) {
1914
2035
  let added = [];
1915
2036
  let removed = [];
1916
2037
  let preserved = [];
1917
2038
  let nodesToAppend = [];
1918
- let headMergeStyle = ctx.head.style;
1919
2039
  let srcToNewHeadNodes = new Map;
1920
- for (const newHeadChild of newHeadTag.children) {
2040
+ for (const newHeadChild of newHead.children) {
1921
2041
  srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
1922
2042
  }
1923
- for (const currentHeadElt of currentHead.children) {
2043
+ for (const currentHeadElt of oldHead.children) {
1924
2044
  let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
1925
2045
  let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
1926
2046
  let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
@@ -1932,7 +2052,7 @@ var Idiomorph = function() {
1932
2052
  preserved.push(currentHeadElt);
1933
2053
  }
1934
2054
  } else {
1935
- if (headMergeStyle === "append") {
2055
+ if (ctx.head.style === "append") {
1936
2056
  if (isReAppended) {
1937
2057
  removed.push(currentHeadElt);
1938
2058
  nodesToAppend.push(currentHeadElt);
@@ -1949,8 +2069,8 @@ var Idiomorph = function() {
1949
2069
  for (const newNode of nodesToAppend) {
1950
2070
  let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
1951
2071
  if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
1952
- if (newElt.href || newElt.src) {
1953
- let resolve = null;
2072
+ if ("href" in newElt && newElt.href || "src" in newElt && newElt.src) {
2073
+ let resolve;
1954
2074
  let promise = new Promise((function(_resolve) {
1955
2075
  resolve = _resolve;
1956
2076
  }));
@@ -1959,258 +2079,195 @@ var Idiomorph = function() {
1959
2079
  }));
1960
2080
  promises.push(promise);
1961
2081
  }
1962
- currentHead.appendChild(newElt);
2082
+ oldHead.appendChild(newElt);
1963
2083
  ctx.callbacks.afterNodeAdded(newElt);
1964
2084
  added.push(newElt);
1965
2085
  }
1966
2086
  }
1967
2087
  for (const removedElement of removed) {
1968
2088
  if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
1969
- currentHead.removeChild(removedElement);
2089
+ oldHead.removeChild(removedElement);
1970
2090
  ctx.callbacks.afterNodeRemoved(removedElement);
1971
2091
  }
1972
2092
  }
1973
- ctx.head.afterHeadMorphed(currentHead, {
2093
+ ctx.head.afterHeadMorphed(oldHead, {
1974
2094
  added: added,
1975
2095
  kept: preserved,
1976
2096
  removed: removed
1977
2097
  });
1978
2098
  return promises;
1979
2099
  }
1980
- function noOp() {}
1981
- function mergeDefaults(config) {
1982
- let finalConfig = {};
1983
- Object.assign(finalConfig, defaults);
1984
- Object.assign(finalConfig, config);
1985
- finalConfig.callbacks = {};
1986
- Object.assign(finalConfig.callbacks, defaults.callbacks);
1987
- Object.assign(finalConfig.callbacks, config.callbacks);
1988
- finalConfig.head = {};
1989
- Object.assign(finalConfig.head, defaults.head);
1990
- Object.assign(finalConfig.head, config.head);
1991
- return finalConfig;
1992
- }
1993
- function createMorphContext(oldNode, newContent, config) {
1994
- config = mergeDefaults(config);
1995
- return {
1996
- target: oldNode,
1997
- newContent: newContent,
1998
- config: config,
1999
- morphStyle: config.morphStyle,
2000
- ignoreActive: config.ignoreActive,
2001
- ignoreActiveValue: config.ignoreActiveValue,
2002
- idMap: createIdMap(oldNode, newContent),
2003
- deadIds: new Set,
2004
- callbacks: config.callbacks,
2005
- head: config.head
2006
- };
2007
- }
2008
- function isIdSetMatch(node1, node2, ctx) {
2009
- if (node1 == null || node2 == null) {
2010
- 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
+ };
2011
2122
  }
2012
- if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
2013
- if (node1.id !== "" && node1.id === node2.id) {
2014
- return true;
2015
- } else {
2016
- 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
+ }
2017
2158
  }
2018
2159
  }
2019
- return false;
2020
- }
2021
- function isSoftMatch(node1, node2) {
2022
- if (node1 == null || node2 == null) {
2023
- 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
+ };
2024
2172
  }
2025
- return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName;
2026
- }
2027
- function removeNodesBetween(startInclusive, endExclusive, ctx) {
2028
- while (startInclusive !== endExclusive) {
2029
- let tempNode = startInclusive;
2030
- startInclusive = startInclusive.nextSibling;
2031
- removeNode(tempNode, ctx);
2032
- }
2033
- removeIdsFromConsideration(ctx, endExclusive);
2034
- return endExclusive.nextSibling;
2035
- }
2036
- function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
2037
- let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
2038
- let potentialMatch = null;
2039
- if (newChildPotentialIdCount > 0) {
2040
- let potentialMatch = insertionPoint;
2041
- let otherMatchCount = 0;
2042
- while (potentialMatch != null) {
2043
- if (isIdSetMatch(newChild, potentialMatch, ctx)) {
2044
- return potentialMatch;
2045
- }
2046
- otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
2047
- if (otherMatchCount > newChildPotentialIdCount) {
2048
- 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);
2049
2181
  }
2050
- potentialMatch = potentialMatch.nextSibling;
2051
2182
  }
2052
- }
2053
- return potentialMatch;
2054
- }
2055
- function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
2056
- let potentialSoftMatch = insertionPoint;
2057
- let nextSibling = newChild.nextSibling;
2058
- let siblingSoftMatchCount = 0;
2059
- while (potentialSoftMatch != null) {
2060
- if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
2061
- 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
+ }
2062
2190
  }
2063
- if (isSoftMatch(newChild, potentialSoftMatch)) {
2064
- return potentialSoftMatch;
2191
+ for (const id of duplicateIds) {
2192
+ persistentIds.delete(id);
2065
2193
  }
2066
- if (isSoftMatch(nextSibling, potentialSoftMatch)) {
2067
- siblingSoftMatchCount++;
2068
- nextSibling = nextSibling.nextSibling;
2069
- if (siblingSoftMatchCount >= 2) {
2070
- return null;
2071
- }
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;
2072
2205
  }
2073
- potentialSoftMatch = potentialSoftMatch.nextSibling;
2074
2206
  }
2075
- return potentialSoftMatch;
2076
- }
2077
- function parseContent(newContent) {
2078
- let parser = new DOMParser;
2079
- let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
2080
- if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
2081
- let content = parser.parseFromString(newContent, "text/html");
2082
- if (contentWithSvgsRemoved.match(/<\/html>/)) {
2083
- content.generatedByIdiomorph = true;
2084
- return content;
2085
- } else {
2086
- let htmlElement = content.firstChild;
2087
- if (htmlElement) {
2088
- htmlElement.generatedByIdiomorph = true;
2089
- 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);
2090
2217
  } else {
2091
- return null;
2218
+ const dummyParent = document.createElement("div");
2219
+ dummyParent.append(newContent);
2220
+ return dummyParent;
2092
2221
  }
2222
+ } else {
2223
+ const dummyParent = document.createElement("div");
2224
+ for (const elt of [ ...newContent ]) {
2225
+ dummyParent.append(elt);
2226
+ }
2227
+ return dummyParent;
2093
2228
  }
2094
- } else {
2095
- let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
2096
- let content = responseDoc.body.querySelector("template").content;
2097
- content.generatedByIdiomorph = true;
2098
- return content;
2099
- }
2100
- }
2101
- function normalizeContent(newContent) {
2102
- if (newContent == null) {
2103
- const dummyParent = document.createElement("div");
2104
- return dummyParent;
2105
- } else if (newContent.generatedByIdiomorph) {
2106
- return newContent;
2107
- } else if (newContent instanceof Node) {
2108
- const dummyParent = document.createElement("div");
2109
- dummyParent.append(newContent);
2110
- return dummyParent;
2111
- } else {
2112
- const dummyParent = document.createElement("div");
2113
- for (const elt of [ ...newContent ]) {
2114
- dummyParent.append(elt);
2115
- }
2116
- return dummyParent;
2117
- }
2118
- }
2119
- function insertSiblings(previousSibling, morphedNode, nextSibling) {
2120
- let stack = [];
2121
- let added = [];
2122
- while (previousSibling != null) {
2123
- stack.push(previousSibling);
2124
- previousSibling = previousSibling.previousSibling;
2125
- }
2126
- while (stack.length > 0) {
2127
- let node = stack.pop();
2128
- added.push(node);
2129
- morphedNode.parentElement.insertBefore(node, morphedNode);
2130
- }
2131
- added.push(morphedNode);
2132
- while (nextSibling != null) {
2133
- stack.push(nextSibling);
2134
- added.push(nextSibling);
2135
- nextSibling = nextSibling.nextSibling;
2136
- }
2137
- while (stack.length > 0) {
2138
- morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
2139
- }
2140
- return added;
2141
- }
2142
- function findBestNodeMatch(newContent, oldNode, ctx) {
2143
- let currentElement;
2144
- currentElement = newContent.firstChild;
2145
- let bestElement = currentElement;
2146
- let score = 0;
2147
- while (currentElement) {
2148
- let newScore = scoreElement(currentElement, oldNode, ctx);
2149
- if (newScore > score) {
2150
- bestElement = currentElement;
2151
- score = newScore;
2152
- }
2153
- currentElement = currentElement.nextSibling;
2154
- }
2155
- return bestElement;
2156
- }
2157
- function scoreElement(node1, node2, ctx) {
2158
- if (isSoftMatch(node1, node2)) {
2159
- return .5 + getIdIntersectionCount(ctx, node1, node2);
2160
2229
  }
2161
- return 0;
2162
- }
2163
- function removeNode(tempNode, ctx) {
2164
- removeIdsFromConsideration(ctx, tempNode);
2165
- if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
2166
- tempNode.remove();
2167
- ctx.callbacks.afterNodeRemoved(tempNode);
2168
- }
2169
- function isIdInConsideration(ctx, id) {
2170
- return !ctx.deadIds.has(id);
2171
- }
2172
- function idIsWithinNode(ctx, id, targetNode) {
2173
- let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
2174
- return idSet.has(id);
2175
- }
2176
- function removeIdsFromConsideration(ctx, node) {
2177
- let idSet = ctx.idMap.get(node) || EMPTY_SET;
2178
- for (const id of idSet) {
2179
- ctx.deadIds.add(id);
2180
- }
2181
- }
2182
- function getIdIntersectionCount(ctx, node1, node2) {
2183
- let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
2184
- let matchCount = 0;
2185
- for (const id of sourceSet) {
2186
- if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
2187
- ++matchCount;
2188
- }
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
+ };
2189
2243
  }
2190
- return matchCount;
2191
- }
2192
- function populateIdMapForNode(node, idMap) {
2193
- let nodeParent = node.parentElement;
2194
- let idElements = node.querySelectorAll("[id]");
2195
- for (const elt of idElements) {
2196
- let current = elt;
2197
- while (current !== nodeParent && current != null) {
2198
- let idSet = idMap.get(current);
2199
- if (idSet == null) {
2200
- idSet = new Set;
2201
- 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;
2202
2258
  }
2203
- idSet.add(elt.id);
2204
- 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;
2205
2264
  }
2206
2265
  }
2207
- }
2208
- function createIdMap(oldContent, newContent) {
2209
- let idMap = new Map;
2210
- populateIdMapForNode(oldContent, idMap);
2211
- populateIdMapForNode(newContent, idMap);
2212
- return idMap;
2213
- }
2266
+ return {
2267
+ normalizeElement: normalizeElement,
2268
+ normalizeParent: normalizeParent
2269
+ };
2270
+ }();
2214
2271
  return {
2215
2272
  morph: morph,
2216
2273
  defaults: defaults
@@ -2225,7 +2282,7 @@ function morphElements(currentElement, newElement, {callbacks: callbacks, ...opt
2225
2282
  }
2226
2283
 
2227
2284
  function morphChildren(currentElement, newElement) {
2228
- morphElements(currentElement, newElement.children, {
2285
+ morphElements(currentElement, newElement.childNodes, {
2229
2286
  morphStyle: "innerHTML"
2230
2287
  });
2231
2288
  }
@@ -2289,6 +2346,9 @@ class MorphingFrameRenderer extends FrameRenderer {
2289
2346
  });
2290
2347
  morphChildren(currentElement, newElement);
2291
2348
  }
2349
+ async preservingPermanentElements(callback) {
2350
+ return await callback();
2351
+ }
2292
2352
  }
2293
2353
 
2294
2354
  class ProgressBar {
@@ -2380,8 +2440,9 @@ class ProgressBar {
2380
2440
  const element = document.createElement("style");
2381
2441
  element.type = "text/css";
2382
2442
  element.textContent = ProgressBar.defaultCSS;
2383
- if (this.cspNonce) {
2384
- element.nonce = this.cspNonce;
2443
+ const cspNonce = getCspNonce();
2444
+ if (cspNonce) {
2445
+ element.nonce = cspNonce;
2385
2446
  }
2386
2447
  return element;
2387
2448
  }
@@ -2390,9 +2451,6 @@ class ProgressBar {
2390
2451
  element.className = "turbo-progress-bar";
2391
2452
  return element;
2392
2453
  }
2393
- get cspNonce() {
2394
- return getMetaContent("csp-nonce");
2395
- }
2396
2454
  }
2397
2455
 
2398
2456
  class HeadSnapshot extends Snapshot {
@@ -2895,16 +2953,6 @@ class Visit {
2895
2953
  ...this.timingMetrics
2896
2954
  };
2897
2955
  }
2898
- getHistoryMethodForAction(action) {
2899
- switch (action) {
2900
- case "replace":
2901
- return history.replaceState;
2902
-
2903
- case "advance":
2904
- case "restore":
2905
- return history.pushState;
2906
- }
2907
- }
2908
2956
  hasPreloadedResponse() {
2909
2957
  return typeof this.response == "object";
2910
2958
  }
@@ -3007,6 +3055,9 @@ class BrowserAdapter {
3007
3055
  this.hideVisitProgressBar();
3008
3056
  }
3009
3057
  visitRendered(_visit) {}
3058
+ linkPrefetchingIsEnabledForLocation(location) {
3059
+ return true;
3060
+ }
3010
3061
  formSubmissionStarted(_formSubmission) {
3011
3062
  this.progressBar.setValue(0);
3012
3063
  this.showFormProgressBarAfterDelay();
@@ -3463,6 +3514,12 @@ class Navigator {
3463
3514
  this.adapter.formSubmissionFinished(formSubmission);
3464
3515
  }
3465
3516
  }
3517
+ linkPrefetchingIsEnabledForLocation(location) {
3518
+ if (typeof this.adapter.linkPrefetchingIsEnabledForLocation === "function") {
3519
+ return this.adapter.linkPrefetchingIsEnabledForLocation(location);
3520
+ }
3521
+ return true;
3522
+ }
3466
3523
  visitStarted(visit) {
3467
3524
  this.delegate.visitStarted(visit);
3468
3525
  }
@@ -4013,7 +4070,7 @@ class PageView extends View {
4013
4070
  renderPage(snapshot, isPreview = false, willRender = true, visit) {
4014
4071
  const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
4015
4072
  const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
4016
- const renderer = new rendererClass(this.snapshot, snapshot, rendererClass.renderElement, isPreview, willRender);
4073
+ const renderer = new rendererClass(this.snapshot, snapshot, isPreview, willRender);
4017
4074
  if (!renderer.shouldRender) {
4018
4075
  this.forceReloaded = true;
4019
4076
  } else {
@@ -4023,7 +4080,7 @@ class PageView extends View {
4023
4080
  }
4024
4081
  renderError(snapshot, visit) {
4025
4082
  visit?.changeHistory();
4026
- const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
4083
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
4027
4084
  return this.render(renderer);
4028
4085
  }
4029
4086
  clearSnapshotCache() {
@@ -4201,7 +4258,8 @@ class Session {
4201
4258
  }
4202
4259
  refresh(url, requestId) {
4203
4260
  const isRecentRequest = requestId && this.recentRequests.has(requestId);
4204
- if (!isRecentRequest && !this.navigator.currentVisit) {
4261
+ const isCurrentUrl = url === document.baseURI;
4262
+ if (!isRecentRequest && !this.navigator.currentVisit && isCurrentUrl) {
4205
4263
  this.visit(url, {
4206
4264
  action: "replace",
4207
4265
  shouldCacheSnapshot: false
@@ -4290,7 +4348,7 @@ class Session {
4290
4348
  }
4291
4349
  submittedFormLinkToLocation() {}
4292
4350
  canPrefetchRequestToLocation(link, location) {
4293
- return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
4351
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.navigator.linkPrefetchingIsEnabledForLocation(location);
4294
4352
  }
4295
4353
  willFollowLinkToLocation(link, location, event) {
4296
4354
  return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
@@ -4589,6 +4647,7 @@ class FrameController {
4589
4647
  #connected=false;
4590
4648
  #hasBeenLoaded=false;
4591
4649
  #ignoredAttributes=new Set;
4650
+ #shouldMorphFrame=false;
4592
4651
  action=null;
4593
4652
  constructor(element) {
4594
4653
  this.element = element;
@@ -4636,14 +4695,8 @@ class FrameController {
4636
4695
  }
4637
4696
  }
4638
4697
  sourceURLReloaded() {
4639
- if (this.element.shouldReloadWithMorph) {
4640
- this.element.addEventListener("turbo:before-frame-render", (({detail: detail}) => {
4641
- detail.render = MorphingFrameRenderer.renderElement;
4642
- }), {
4643
- once: true
4644
- });
4645
- }
4646
- const {src: src} = this.element;
4698
+ const {refresh: refresh, src: src} = this.element;
4699
+ this.#shouldMorphFrame = src && refresh === "morph";
4647
4700
  this.element.removeAttribute("complete");
4648
4701
  this.element.src = null;
4649
4702
  this.element.src = src;
@@ -4681,6 +4734,7 @@ class FrameController {
4681
4734
  }
4682
4735
  }
4683
4736
  } finally {
4737
+ this.#shouldMorphFrame = false;
4684
4738
  this.fetchResponseLoaded = () => Promise.resolve();
4685
4739
  }
4686
4740
  }
@@ -4793,9 +4847,10 @@ class FrameController {
4793
4847
  };
4794
4848
  async #loadFrameResponse(fetchResponse, document) {
4795
4849
  const newFrameElement = await this.extractForeignFrameElement(document.body);
4850
+ const rendererClass = this.#shouldMorphFrame ? MorphingFrameRenderer : FrameRenderer;
4796
4851
  if (newFrameElement) {
4797
4852
  const snapshot = new Snapshot(newFrameElement);
4798
- const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
4853
+ const renderer = new rendererClass(this, this.view.snapshot, snapshot, false, false);
4799
4854
  if (this.view.renderPromise) await this.view.renderPromise;
4800
4855
  this.changeHistory();
4801
4856
  await this.view.render(renderer);
@@ -5110,9 +5165,9 @@ class StreamElement extends HTMLElement {
5110
5165
  this.duplicateChildren.forEach((c => c.remove()));
5111
5166
  }
5112
5167
  get duplicateChildren() {
5113
- const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
5114
- const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
5115
- 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"))));
5116
5171
  }
5117
5172
  get performAction() {
5118
5173
  if (this.action) {