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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/turbo.js +474 -426
- data/app/assets/javascripts/turbo.min.js +5 -5
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams/broadcasts.rb +2 -0
- data/app/controllers/turbo/frames/frame_request.rb +1 -0
- data/app/helpers/turbo/streams/action_helper.rb +7 -2
- data/app/models/concerns/turbo/broadcastable.rb +1 -1
- data/app/models/turbo/streams/tag_builder.rb +2 -2
- data/lib/turbo/engine.rb +1 -1
- data/lib/turbo/system_test_helper.rb +4 -4
- data/lib/turbo/version.rb +1 -1
- metadata +3 -3
@@ -1,6 +1,6 @@
|
|
1
1
|
/*!
|
2
|
-
Turbo 8.0.
|
3
|
-
Copyright ©
|
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
|
-
|
1691
|
-
|
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:
|
1705
|
-
|
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
|
-
|
1716
|
-
|
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
|
-
|
1719
|
-
|
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
|
-
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1725
|
-
|
1726
|
-
|
1727
|
-
|
1728
|
-
|
1729
|
-
|
1730
|
-
|
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
|
-
|
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
|
-
|
1743
|
-
|
1744
|
-
|
1745
|
-
|
1746
|
-
|
1747
|
-
|
1748
|
-
|
1749
|
-
|
1750
|
-
|
1751
|
-
|
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
|
-
|
1894
|
+
parentNode.insertBefore(element, after);
|
1754
1895
|
}
|
1755
|
-
} else {
|
1756
|
-
throw "Do not understand how to morph style " + ctx.morphStyle;
|
1757
1896
|
}
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
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(
|
1908
|
+
handleHeadElement(oldNode, newContent, ctx);
|
1779
1909
|
} else {
|
1780
|
-
|
1910
|
+
morphAttributes(oldNode, newContent, ctx);
|
1781
1911
|
if (!ignoreValueOfActiveElement(oldNode, ctx)) {
|
1782
|
-
morphChildren(
|
1912
|
+
morphChildren(ctx, oldNode, newContent);
|
1783
1913
|
}
|
1784
1914
|
}
|
1785
1915
|
ctx.callbacks.afterNodeMorphed(oldNode, newContent);
|
1786
1916
|
return oldNode;
|
1787
1917
|
}
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
1792
|
-
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
1804
|
-
|
1805
|
-
|
1806
|
-
|
1807
|
-
|
1808
|
-
|
1809
|
-
|
1810
|
-
|
1811
|
-
|
1812
|
-
|
1813
|
-
|
1814
|
-
|
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
|
-
|
1817
|
-
|
1818
|
-
|
1819
|
-
|
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
|
-
|
1839
|
-
|
1840
|
-
|
1841
|
-
|
1842
|
-
|
1843
|
-
|
1844
|
-
|
1845
|
-
|
1846
|
-
|
1847
|
-
|
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
|
-
|
1850
|
-
|
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
|
-
|
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 (
|
1859
|
-
|
1981
|
+
if (oldElement.firstChild && oldElement.firstChild.nodeValue !== newValue) {
|
1982
|
+
oldElement.firstChild.nodeValue = newValue;
|
1860
1983
|
}
|
1861
1984
|
}
|
1862
1985
|
}
|
1863
|
-
|
1864
|
-
|
1865
|
-
|
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
|
-
|
1991
|
+
oldElement[attributeName] = newElement[attributeName];
|
1881
1992
|
}
|
1882
|
-
|
1883
|
-
|
1884
|
-
|
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
|
-
|
1890
|
-
|
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
|
-
|
1918
|
-
|
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(
|
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
|
2040
|
+
for (const newHeadChild of newHead.children) {
|
1930
2041
|
srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
|
1931
2042
|
}
|
1932
|
-
for (const currentHeadElt of
|
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 (
|
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
|
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
|
-
|
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
|
-
|
2089
|
+
oldHead.removeChild(removedElement);
|
1979
2090
|
ctx.callbacks.afterNodeRemoved(removedElement);
|
1980
2091
|
}
|
1981
2092
|
}
|
1982
|
-
ctx.head.afterHeadMorphed(
|
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
|
1990
|
-
|
1991
|
-
|
1992
|
-
|
1993
|
-
|
1994
|
-
|
1995
|
-
|
1996
|
-
|
1997
|
-
|
1998
|
-
|
1999
|
-
|
2000
|
-
|
2001
|
-
|
2002
|
-
|
2003
|
-
|
2004
|
-
|
2005
|
-
|
2006
|
-
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2010
|
-
|
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
|
-
|
2022
|
-
|
2023
|
-
|
2024
|
-
}
|
2025
|
-
|
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
|
-
|
2029
|
-
|
2030
|
-
|
2031
|
-
|
2032
|
-
|
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
|
-
|
2035
|
-
|
2036
|
-
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2040
|
-
|
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
|
-
|
2063
|
-
|
2064
|
-
|
2065
|
-
|
2066
|
-
|
2067
|
-
|
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
|
-
|
2073
|
-
|
2191
|
+
for (const id of duplicateIds) {
|
2192
|
+
persistentIds.delete(id);
|
2074
2193
|
}
|
2075
|
-
|
2076
|
-
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
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
|
-
|
2085
|
-
|
2086
|
-
|
2087
|
-
|
2088
|
-
|
2089
|
-
|
2090
|
-
|
2091
|
-
if (
|
2092
|
-
|
2093
|
-
|
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
|
-
|
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
|
-
|
2192
|
-
|
2193
|
-
|
2194
|
-
|
2195
|
-
|
2196
|
-
|
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
|
-
|
2200
|
-
|
2201
|
-
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2205
|
-
|
2206
|
-
|
2207
|
-
|
2208
|
-
|
2209
|
-
|
2210
|
-
|
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
|
-
|
2213
|
-
|
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
|
-
|
2218
|
-
|
2219
|
-
|
2220
|
-
|
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.
|
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
|
-
|
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) {
|