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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/assets/javascripts/turbo.js +500 -445
- data/app/assets/javascripts/turbo.min.js +7 -7
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams/broadcasts.rb +4 -2
- data/app/controllers/turbo/frames/frame_request.rb +1 -0
- data/app/controllers/turbo/native/navigation.rb +11 -8
- 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;
|
@@ -181,7 +181,7 @@ function activateScriptElement(element) {
|
|
181
181
|
return element;
|
182
182
|
} else {
|
183
183
|
const createdScriptElement = document.createElement("script");
|
184
|
-
const cspNonce =
|
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
|
-
|
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
|
-
|
1682
|
-
|
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:
|
1696
|
-
|
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
|
-
|
1707
|
-
|
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
|
-
|
1710
|
-
|
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
|
-
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
1716
|
-
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
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
|
-
|
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
|
-
|
1734
|
-
|
1735
|
-
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
1739
|
-
|
1740
|
-
|
1741
|
-
|
1742
|
-
|
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
|
-
|
1894
|
+
parentNode.insertBefore(element, after);
|
1745
1895
|
}
|
1746
|
-
} else {
|
1747
|
-
throw "Do not understand how to morph style " + ctx.morphStyle;
|
1748
1896
|
}
|
1749
|
-
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
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(
|
1908
|
+
handleHeadElement(oldNode, newContent, ctx);
|
1770
1909
|
} else {
|
1771
|
-
|
1910
|
+
morphAttributes(oldNode, newContent, ctx);
|
1772
1911
|
if (!ignoreValueOfActiveElement(oldNode, ctx)) {
|
1773
|
-
morphChildren(
|
1912
|
+
morphChildren(ctx, oldNode, newContent);
|
1774
1913
|
}
|
1775
1914
|
}
|
1776
1915
|
ctx.callbacks.afterNodeMorphed(oldNode, newContent);
|
1777
1916
|
return oldNode;
|
1778
1917
|
}
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
1786
|
-
|
1787
|
-
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
1792
|
-
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
1804
|
-
|
1805
|
-
|
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
|
-
|
1808
|
-
|
1809
|
-
|
1810
|
-
|
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
|
-
|
1820
|
-
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
1825
|
-
|
1826
|
-
|
1827
|
-
|
1828
|
-
|
1829
|
-
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
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
|
-
|
1841
|
-
|
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
|
-
|
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 (
|
1850
|
-
|
1981
|
+
if (oldElement.firstChild && oldElement.firstChild.nodeValue !== newValue) {
|
1982
|
+
oldElement.firstChild.nodeValue = newValue;
|
1851
1983
|
}
|
1852
1984
|
}
|
1853
1985
|
}
|
1854
|
-
|
1855
|
-
|
1856
|
-
|
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
|
-
|
1991
|
+
oldElement[attributeName] = newElement[attributeName];
|
1872
1992
|
}
|
1873
|
-
|
1874
|
-
|
1875
|
-
|
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
|
-
|
1881
|
-
|
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
|
-
|
1909
|
-
|
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(
|
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
|
2040
|
+
for (const newHeadChild of newHead.children) {
|
1921
2041
|
srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
|
1922
2042
|
}
|
1923
|
-
for (const currentHeadElt of
|
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 (
|
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
|
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
|
-
|
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
|
-
|
2089
|
+
oldHead.removeChild(removedElement);
|
1970
2090
|
ctx.callbacks.afterNodeRemoved(removedElement);
|
1971
2091
|
}
|
1972
2092
|
}
|
1973
|
-
ctx.head.afterHeadMorphed(
|
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
|
1981
|
-
|
1982
|
-
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
1987
|
-
|
1988
|
-
|
1989
|
-
|
1990
|
-
|
1991
|
-
|
1992
|
-
|
1993
|
-
|
1994
|
-
|
1995
|
-
|
1996
|
-
|
1997
|
-
|
1998
|
-
|
1999
|
-
|
2000
|
-
|
2001
|
-
|
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
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
}
|
2016
|
-
|
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
|
-
|
2020
|
-
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
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
|
-
|
2026
|
-
|
2027
|
-
|
2028
|
-
|
2029
|
-
|
2030
|
-
|
2031
|
-
|
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
|
-
|
2054
|
-
|
2055
|
-
|
2056
|
-
|
2057
|
-
|
2058
|
-
|
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
|
-
|
2064
|
-
|
2191
|
+
for (const id of duplicateIds) {
|
2192
|
+
persistentIds.delete(id);
|
2065
2193
|
}
|
2066
|
-
|
2067
|
-
|
2068
|
-
|
2069
|
-
|
2070
|
-
|
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
|
-
|
2076
|
-
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
2080
|
-
|
2081
|
-
|
2082
|
-
if (
|
2083
|
-
|
2084
|
-
|
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
|
-
|
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
|
-
|
2162
|
-
|
2163
|
-
|
2164
|
-
|
2165
|
-
|
2166
|
-
|
2167
|
-
|
2168
|
-
|
2169
|
-
|
2170
|
-
|
2171
|
-
|
2172
|
-
|
2173
|
-
|
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
|
-
|
2191
|
-
|
2192
|
-
|
2193
|
-
|
2194
|
-
|
2195
|
-
|
2196
|
-
|
2197
|
-
|
2198
|
-
|
2199
|
-
|
2200
|
-
|
2201
|
-
|
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
|
-
|
2204
|
-
|
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
|
-
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
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.
|
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
|
-
|
2384
|
-
|
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,
|
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,
|
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
|
-
|
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
|
-
|
4640
|
-
|
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
|
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) {
|