@hotwired/turbo-rails 8.0.19 → 8.0.21
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.
- package/app/assets/javascripts/turbo.js +283 -286
- package/package.json +2 -2
|
@@ -1,68 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
Turbo 8.0.
|
|
3
|
-
Copyright ©
|
|
2
|
+
Turbo 8.0.21
|
|
3
|
+
Copyright © 2026 37signals LLC
|
|
4
4
|
*/
|
|
5
|
-
(function(prototype) {
|
|
6
|
-
if (typeof prototype.requestSubmit == "function") return;
|
|
7
|
-
prototype.requestSubmit = function(submitter) {
|
|
8
|
-
if (submitter) {
|
|
9
|
-
validateSubmitter(submitter, this);
|
|
10
|
-
submitter.click();
|
|
11
|
-
} else {
|
|
12
|
-
submitter = document.createElement("input");
|
|
13
|
-
submitter.type = "submit";
|
|
14
|
-
submitter.hidden = true;
|
|
15
|
-
this.appendChild(submitter);
|
|
16
|
-
submitter.click();
|
|
17
|
-
this.removeChild(submitter);
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
function validateSubmitter(submitter, form) {
|
|
21
|
-
submitter instanceof HTMLElement || raise(TypeError, "parameter 1 is not of type 'HTMLElement'");
|
|
22
|
-
submitter.type == "submit" || raise(TypeError, "The specified element is not a submit button");
|
|
23
|
-
submitter.form == form || raise(DOMException, "The specified element is not owned by this form element", "NotFoundError");
|
|
24
|
-
}
|
|
25
|
-
function raise(errorConstructor, message, name) {
|
|
26
|
-
throw new errorConstructor("Failed to execute 'requestSubmit' on 'HTMLFormElement': " + message + ".", name);
|
|
27
|
-
}
|
|
28
|
-
})(HTMLFormElement.prototype);
|
|
29
|
-
|
|
30
|
-
const submittersByForm = new WeakMap;
|
|
31
|
-
|
|
32
|
-
function findSubmitterFromClickTarget(target) {
|
|
33
|
-
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
|
34
|
-
const candidate = element ? element.closest("input, button") : null;
|
|
35
|
-
return candidate?.type == "submit" ? candidate : null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function clickCaptured(event) {
|
|
39
|
-
const submitter = findSubmitterFromClickTarget(event.target);
|
|
40
|
-
if (submitter && submitter.form) {
|
|
41
|
-
submittersByForm.set(submitter.form, submitter);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
(function() {
|
|
46
|
-
if ("submitter" in Event.prototype) return;
|
|
47
|
-
let prototype = window.Event.prototype;
|
|
48
|
-
if ("SubmitEvent" in window) {
|
|
49
|
-
const prototypeOfSubmitEvent = window.SubmitEvent.prototype;
|
|
50
|
-
if (/Apple Computer/.test(navigator.vendor) && !("submitter" in prototypeOfSubmitEvent)) {
|
|
51
|
-
prototype = prototypeOfSubmitEvent;
|
|
52
|
-
} else {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
addEventListener("click", clickCaptured, true);
|
|
57
|
-
Object.defineProperty(prototype, "submitter", {
|
|
58
|
-
get() {
|
|
59
|
-
if (this.type == "submit" && this.target instanceof HTMLFormElement) {
|
|
60
|
-
return submittersByForm.get(this.target);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
})();
|
|
65
|
-
|
|
66
5
|
const FrameLoadingStyle = {
|
|
67
6
|
eager: "eager",
|
|
68
7
|
lazy: "lazy"
|
|
@@ -240,10 +179,6 @@ function nextEventLoopTick() {
|
|
|
240
179
|
return new Promise((resolve => setTimeout((() => resolve()), 0)));
|
|
241
180
|
}
|
|
242
181
|
|
|
243
|
-
function nextMicrotask() {
|
|
244
|
-
return Promise.resolve();
|
|
245
|
-
}
|
|
246
|
-
|
|
247
182
|
function parseHTMLDocument(html = "") {
|
|
248
183
|
return (new DOMParser).parseFromString(html, "text/html");
|
|
249
184
|
}
|
|
@@ -273,7 +208,7 @@ function uuid() {
|
|
|
273
208
|
} else if (i == 19) {
|
|
274
209
|
return (Math.floor(Math.random() * 4) + 8).toString(16);
|
|
275
210
|
} else {
|
|
276
|
-
return Math.floor(Math.random() *
|
|
211
|
+
return Math.floor(Math.random() * 16).toString(16);
|
|
277
212
|
}
|
|
278
213
|
})).join("");
|
|
279
214
|
}
|
|
@@ -411,15 +346,13 @@ function doesNotTargetIFrame(name) {
|
|
|
411
346
|
function findLinkFromClickTarget(target) {
|
|
412
347
|
const link = findClosestRecursively(target, "a[href], a[xlink\\:href]");
|
|
413
348
|
if (!link) return null;
|
|
349
|
+
if (link.href.startsWith("#")) return null;
|
|
414
350
|
if (link.hasAttribute("download")) return null;
|
|
415
|
-
|
|
351
|
+
const linkTarget = link.getAttribute("target");
|
|
352
|
+
if (linkTarget && linkTarget !== "_self") return null;
|
|
416
353
|
return link;
|
|
417
354
|
}
|
|
418
355
|
|
|
419
|
-
function getLocationForLink(link) {
|
|
420
|
-
return expandURL(link.getAttribute("href") || "");
|
|
421
|
-
}
|
|
422
|
-
|
|
423
356
|
function debounce(fn, delay) {
|
|
424
357
|
let timeoutId = null;
|
|
425
358
|
return (...args) => {
|
|
@@ -500,6 +433,10 @@ function locationIsVisitable(location, rootLocation) {
|
|
|
500
433
|
return isPrefixedBy(location, rootLocation) && !config.drive.unvisitableExtensions.has(getExtension(location));
|
|
501
434
|
}
|
|
502
435
|
|
|
436
|
+
function getLocationForLink(link) {
|
|
437
|
+
return expandURL(link.getAttribute("href") || "");
|
|
438
|
+
}
|
|
439
|
+
|
|
503
440
|
function getRequestURL(url) {
|
|
504
441
|
const anchor = getAnchor(url);
|
|
505
442
|
return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
|
|
@@ -871,34 +808,94 @@ function importStreamElements(fragment) {
|
|
|
871
808
|
return fragment;
|
|
872
809
|
}
|
|
873
810
|
|
|
811
|
+
const identity = key => key;
|
|
812
|
+
|
|
813
|
+
class LRUCache {
|
|
814
|
+
keys=[];
|
|
815
|
+
entries={};
|
|
816
|
+
#toCacheKey;
|
|
817
|
+
constructor(size, toCacheKey = identity) {
|
|
818
|
+
this.size = size;
|
|
819
|
+
this.#toCacheKey = toCacheKey;
|
|
820
|
+
}
|
|
821
|
+
has(key) {
|
|
822
|
+
return this.#toCacheKey(key) in this.entries;
|
|
823
|
+
}
|
|
824
|
+
get(key) {
|
|
825
|
+
if (this.has(key)) {
|
|
826
|
+
const entry = this.read(key);
|
|
827
|
+
this.touch(key);
|
|
828
|
+
return entry;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
put(key, entry) {
|
|
832
|
+
this.write(key, entry);
|
|
833
|
+
this.touch(key);
|
|
834
|
+
return entry;
|
|
835
|
+
}
|
|
836
|
+
clear() {
|
|
837
|
+
for (const key of Object.keys(this.entries)) {
|
|
838
|
+
this.evict(key);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
read(key) {
|
|
842
|
+
return this.entries[this.#toCacheKey(key)];
|
|
843
|
+
}
|
|
844
|
+
write(key, entry) {
|
|
845
|
+
this.entries[this.#toCacheKey(key)] = entry;
|
|
846
|
+
}
|
|
847
|
+
touch(key) {
|
|
848
|
+
key = this.#toCacheKey(key);
|
|
849
|
+
const index = this.keys.indexOf(key);
|
|
850
|
+
if (index > -1) this.keys.splice(index, 1);
|
|
851
|
+
this.keys.unshift(key);
|
|
852
|
+
this.trim();
|
|
853
|
+
}
|
|
854
|
+
trim() {
|
|
855
|
+
for (const key of this.keys.splice(this.size)) {
|
|
856
|
+
this.evict(key);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
evict(key) {
|
|
860
|
+
delete this.entries[key];
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
874
864
|
const PREFETCH_DELAY = 100;
|
|
875
865
|
|
|
876
|
-
class PrefetchCache {
|
|
866
|
+
class PrefetchCache extends LRUCache {
|
|
877
867
|
#prefetchTimeout=null;
|
|
878
|
-
#
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
}
|
|
868
|
+
#maxAges={};
|
|
869
|
+
constructor(size = 1, prefetchDelay = PREFETCH_DELAY) {
|
|
870
|
+
super(size, toCacheKey);
|
|
871
|
+
this.prefetchDelay = prefetchDelay;
|
|
883
872
|
}
|
|
884
|
-
|
|
885
|
-
this.clear();
|
|
873
|
+
putLater(url, request, ttl) {
|
|
886
874
|
this.#prefetchTimeout = setTimeout((() => {
|
|
887
875
|
request.perform();
|
|
888
|
-
this.
|
|
876
|
+
this.put(url, request, ttl);
|
|
889
877
|
this.#prefetchTimeout = null;
|
|
890
|
-
}),
|
|
878
|
+
}), this.prefetchDelay);
|
|
891
879
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
request: request,
|
|
896
|
-
expire: new Date((new Date).getTime() + ttl)
|
|
897
|
-
};
|
|
880
|
+
put(url, request, ttl = cacheTtl) {
|
|
881
|
+
super.put(url, request);
|
|
882
|
+
this.#maxAges[toCacheKey(url)] = new Date((new Date).getTime() + ttl);
|
|
898
883
|
}
|
|
899
884
|
clear() {
|
|
885
|
+
super.clear();
|
|
900
886
|
if (this.#prefetchTimeout) clearTimeout(this.#prefetchTimeout);
|
|
901
|
-
|
|
887
|
+
}
|
|
888
|
+
evict(key) {
|
|
889
|
+
super.evict(key);
|
|
890
|
+
delete this.#maxAges[key];
|
|
891
|
+
}
|
|
892
|
+
has(key) {
|
|
893
|
+
if (super.has(key)) {
|
|
894
|
+
const maxAge = this.#maxAges[toCacheKey(key)];
|
|
895
|
+
return maxAge && maxAge > Date.now();
|
|
896
|
+
} else {
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
902
899
|
}
|
|
903
900
|
}
|
|
904
901
|
|
|
@@ -1723,12 +1720,8 @@ var Idiomorph = function() {
|
|
|
1723
1720
|
}
|
|
1724
1721
|
function morphOuterHTML(ctx, oldNode, newNode) {
|
|
1725
1722
|
const oldParent = normalizeParent(oldNode);
|
|
1726
|
-
let childNodes = Array.from(oldParent.childNodes);
|
|
1727
|
-
const index = childNodes.indexOf(oldNode);
|
|
1728
|
-
const rightMargin = childNodes.length - (index + 1);
|
|
1729
1723
|
morphChildren(ctx, oldParent, newNode, oldNode, oldNode.nextSibling);
|
|
1730
|
-
|
|
1731
|
-
return childNodes.slice(index, childNodes.length - rightMargin);
|
|
1724
|
+
return Array.from(oldParent.childNodes);
|
|
1732
1725
|
}
|
|
1733
1726
|
function saveAndRestoreFocus(ctx, fn) {
|
|
1734
1727
|
if (!ctx.config.restoreFocus) return fn();
|
|
@@ -1738,8 +1731,8 @@ var Idiomorph = function() {
|
|
|
1738
1731
|
}
|
|
1739
1732
|
const {id: activeElementId, selectionStart: selectionStart, selectionEnd: selectionEnd} = activeElement;
|
|
1740
1733
|
const results = fn();
|
|
1741
|
-
if (activeElementId && activeElementId !== document.activeElement?.id) {
|
|
1742
|
-
activeElement = ctx.target.querySelector(
|
|
1734
|
+
if (activeElementId && activeElementId !== document.activeElement?.getAttribute("id")) {
|
|
1735
|
+
activeElement = ctx.target.querySelector(`[id="${activeElementId}"]`);
|
|
1743
1736
|
activeElement?.focus();
|
|
1744
1737
|
}
|
|
1745
1738
|
if (activeElement && !activeElement.selectionEnd && selectionEnd) {
|
|
@@ -1766,11 +1759,14 @@ var Idiomorph = function() {
|
|
|
1766
1759
|
continue;
|
|
1767
1760
|
}
|
|
1768
1761
|
}
|
|
1769
|
-
if (newChild instanceof Element
|
|
1770
|
-
const
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1762
|
+
if (newChild instanceof Element) {
|
|
1763
|
+
const newChildId = newChild.getAttribute("id");
|
|
1764
|
+
if (ctx.persistentIds.has(newChildId)) {
|
|
1765
|
+
const movedChild = moveBeforeById(oldParent, newChildId, insertionPoint, ctx);
|
|
1766
|
+
morphNode(movedChild, newChild, ctx);
|
|
1767
|
+
insertionPoint = movedChild.nextSibling;
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1774
1770
|
}
|
|
1775
1771
|
const insertedNode = createNode(oldParent, newChild, insertionPoint, ctx);
|
|
1776
1772
|
if (insertedNode) {
|
|
@@ -1822,7 +1818,7 @@ var Idiomorph = function() {
|
|
|
1822
1818
|
softMatch = undefined;
|
|
1823
1819
|
}
|
|
1824
1820
|
}
|
|
1825
|
-
if (
|
|
1821
|
+
if (ctx.activeElementAndParents.includes(cursor)) break;
|
|
1826
1822
|
cursor = cursor.nextSibling;
|
|
1827
1823
|
}
|
|
1828
1824
|
return softMatch || null;
|
|
@@ -1841,7 +1837,7 @@ var Idiomorph = function() {
|
|
|
1841
1837
|
function isSoftMatch(oldNode, newNode) {
|
|
1842
1838
|
const oldElt = oldNode;
|
|
1843
1839
|
const newElt = newNode;
|
|
1844
|
-
return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && (!oldElt.id || oldElt.id === newElt.id);
|
|
1840
|
+
return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && (!oldElt.getAttribute?.("id") || oldElt.getAttribute?.("id") === newElt.getAttribute?.("id"));
|
|
1845
1841
|
}
|
|
1846
1842
|
return findBestMatch;
|
|
1847
1843
|
}();
|
|
@@ -1864,13 +1860,13 @@ var Idiomorph = function() {
|
|
|
1864
1860
|
return cursor;
|
|
1865
1861
|
}
|
|
1866
1862
|
function moveBeforeById(parentNode, id, after, ctx) {
|
|
1867
|
-
const target = ctx.target.querySelector(
|
|
1863
|
+
const target = ctx.target.getAttribute?.("id") === id && ctx.target || ctx.target.querySelector(`[id="${id}"]`) || ctx.pantry.querySelector(`[id="${id}"]`);
|
|
1868
1864
|
removeElementFromAncestorsIdMaps(target, ctx);
|
|
1869
1865
|
moveBefore(parentNode, target, after);
|
|
1870
1866
|
return target;
|
|
1871
1867
|
}
|
|
1872
1868
|
function removeElementFromAncestorsIdMaps(element, ctx) {
|
|
1873
|
-
const id = element.id;
|
|
1869
|
+
const id = element.getAttribute("id");
|
|
1874
1870
|
while (element = element.parentNode) {
|
|
1875
1871
|
let idSet = ctx.idMap.get(element);
|
|
1876
1872
|
if (idSet) {
|
|
@@ -2114,6 +2110,7 @@ var Idiomorph = function() {
|
|
|
2114
2110
|
idMap: idMap,
|
|
2115
2111
|
persistentIds: persistentIds,
|
|
2116
2112
|
pantry: createPantry(),
|
|
2113
|
+
activeElementAndParents: createActiveElementAndParents(oldNode),
|
|
2117
2114
|
callbacks: mergedConfig.callbacks,
|
|
2118
2115
|
head: mergedConfig.head
|
|
2119
2116
|
};
|
|
@@ -2131,16 +2128,29 @@ var Idiomorph = function() {
|
|
|
2131
2128
|
document.body.insertAdjacentElement("afterend", pantry);
|
|
2132
2129
|
return pantry;
|
|
2133
2130
|
}
|
|
2131
|
+
function createActiveElementAndParents(oldNode) {
|
|
2132
|
+
let activeElementAndParents = [];
|
|
2133
|
+
let elt = document.activeElement;
|
|
2134
|
+
if (elt?.tagName !== "BODY" && oldNode.contains(elt)) {
|
|
2135
|
+
while (elt) {
|
|
2136
|
+
activeElementAndParents.push(elt);
|
|
2137
|
+
if (elt === oldNode) break;
|
|
2138
|
+
elt = elt.parentElement;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
return activeElementAndParents;
|
|
2142
|
+
}
|
|
2134
2143
|
function findIdElements(root) {
|
|
2135
2144
|
let elements = Array.from(root.querySelectorAll("[id]"));
|
|
2136
|
-
if (root.id) {
|
|
2145
|
+
if (root.getAttribute?.("id")) {
|
|
2137
2146
|
elements.push(root);
|
|
2138
2147
|
}
|
|
2139
2148
|
return elements;
|
|
2140
2149
|
}
|
|
2141
2150
|
function populateIdMapWithTree(idMap, persistentIds, root, elements) {
|
|
2142
2151
|
for (const elt of elements) {
|
|
2143
|
-
|
|
2152
|
+
const id = elt.getAttribute("id");
|
|
2153
|
+
if (persistentIds.has(id)) {
|
|
2144
2154
|
let current = elt;
|
|
2145
2155
|
while (current) {
|
|
2146
2156
|
let idSet = idMap.get(current);
|
|
@@ -2148,7 +2158,7 @@ var Idiomorph = function() {
|
|
|
2148
2158
|
idSet = new Set;
|
|
2149
2159
|
idMap.set(current, idSet);
|
|
2150
2160
|
}
|
|
2151
|
-
idSet.add(
|
|
2161
|
+
idSet.add(id);
|
|
2152
2162
|
if (current === root) break;
|
|
2153
2163
|
current = current.parentElement;
|
|
2154
2164
|
}
|
|
@@ -2211,7 +2221,7 @@ var Idiomorph = function() {
|
|
|
2211
2221
|
return newContent;
|
|
2212
2222
|
} else if (newContent instanceof Node) {
|
|
2213
2223
|
if (newContent.parentNode) {
|
|
2214
|
-
return
|
|
2224
|
+
return new SlicedParentNode(newContent);
|
|
2215
2225
|
} else {
|
|
2216
2226
|
const dummyParent = document.createElement("div");
|
|
2217
2227
|
dummyParent.append(newContent);
|
|
@@ -2225,19 +2235,43 @@ var Idiomorph = function() {
|
|
|
2225
2235
|
return dummyParent;
|
|
2226
2236
|
}
|
|
2227
2237
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2238
|
+
class SlicedParentNode {
|
|
2239
|
+
constructor(node) {
|
|
2240
|
+
this.originalNode = node;
|
|
2241
|
+
this.realParentNode = node.parentNode;
|
|
2242
|
+
this.previousSibling = node.previousSibling;
|
|
2243
|
+
this.nextSibling = node.nextSibling;
|
|
2244
|
+
}
|
|
2245
|
+
get childNodes() {
|
|
2246
|
+
const nodes = [];
|
|
2247
|
+
let cursor = this.previousSibling ? this.previousSibling.nextSibling : this.realParentNode.firstChild;
|
|
2248
|
+
while (cursor && cursor != this.nextSibling) {
|
|
2249
|
+
nodes.push(cursor);
|
|
2250
|
+
cursor = cursor.nextSibling;
|
|
2239
2251
|
}
|
|
2240
|
-
|
|
2252
|
+
return nodes;
|
|
2253
|
+
}
|
|
2254
|
+
querySelectorAll(selector) {
|
|
2255
|
+
return this.childNodes.reduce(((results, node) => {
|
|
2256
|
+
if (node instanceof Element) {
|
|
2257
|
+
if (node.matches(selector)) results.push(node);
|
|
2258
|
+
const nodeList = node.querySelectorAll(selector);
|
|
2259
|
+
for (let i = 0; i < nodeList.length; i++) {
|
|
2260
|
+
results.push(nodeList[i]);
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
return results;
|
|
2264
|
+
}), []);
|
|
2265
|
+
}
|
|
2266
|
+
insertBefore(node, referenceNode) {
|
|
2267
|
+
return this.realParentNode.insertBefore(node, referenceNode);
|
|
2268
|
+
}
|
|
2269
|
+
moveBefore(node, referenceNode) {
|
|
2270
|
+
return this.realParentNode.moveBefore(node, referenceNode);
|
|
2271
|
+
}
|
|
2272
|
+
get __idiomorphRoot() {
|
|
2273
|
+
return this.originalNode;
|
|
2274
|
+
}
|
|
2241
2275
|
}
|
|
2242
2276
|
function parseContent(newContent) {
|
|
2243
2277
|
let parser = new DOMParser;
|
|
@@ -2287,7 +2321,11 @@ function morphChildren(currentElement, newElement, options = {}) {
|
|
|
2287
2321
|
}
|
|
2288
2322
|
|
|
2289
2323
|
function shouldRefreshFrameWithMorphing(currentFrame, newFrame) {
|
|
2290
|
-
return currentFrame instanceof FrameElement &&
|
|
2324
|
+
return currentFrame instanceof FrameElement && currentFrame.shouldReloadWithMorph && (!newFrame || areFramesCompatibleForRefreshing(currentFrame, newFrame)) && !currentFrame.closest("[data-turbo-permanent]");
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
function areFramesCompatibleForRefreshing(currentFrame, newFrame) {
|
|
2328
|
+
return newFrame instanceof Element && newFrame.nodeName === "TURBO-FRAME" && currentFrame.id === newFrame.id && (!newFrame.getAttribute("src") || urlsAreEqual(currentFrame.src, newFrame.getAttribute("src")));
|
|
2291
2329
|
}
|
|
2292
2330
|
|
|
2293
2331
|
function closestFrameReloadableWithMorphing(node) {
|
|
@@ -2588,11 +2626,17 @@ class PageSnapshot extends Snapshot {
|
|
|
2588
2626
|
for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
|
|
2589
2627
|
clonedPasswordInput.value = "";
|
|
2590
2628
|
}
|
|
2629
|
+
for (const clonedNoscriptElement of clonedElement.querySelectorAll("noscript")) {
|
|
2630
|
+
clonedNoscriptElement.remove();
|
|
2631
|
+
}
|
|
2591
2632
|
return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot);
|
|
2592
2633
|
}
|
|
2593
2634
|
get lang() {
|
|
2594
2635
|
return this.documentElement.getAttribute("lang");
|
|
2595
2636
|
}
|
|
2637
|
+
get dir() {
|
|
2638
|
+
return this.documentElement.getAttribute("dir");
|
|
2639
|
+
}
|
|
2596
2640
|
get headElement() {
|
|
2597
2641
|
return this.headSnapshot.element;
|
|
2598
2642
|
}
|
|
@@ -2616,11 +2660,11 @@ class PageSnapshot extends Snapshot {
|
|
|
2616
2660
|
const viewTransitionEnabled = this.getSetting("view-transition") === "true" || this.headSnapshot.getMetaValue("view-transition") === "same-origin";
|
|
2617
2661
|
return viewTransitionEnabled && !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
2618
2662
|
}
|
|
2619
|
-
get
|
|
2620
|
-
return this.getSetting("refresh-method")
|
|
2663
|
+
get refreshMethod() {
|
|
2664
|
+
return this.getSetting("refresh-method");
|
|
2621
2665
|
}
|
|
2622
|
-
get
|
|
2623
|
-
return this.getSetting("refresh-scroll")
|
|
2666
|
+
get refreshScroll() {
|
|
2667
|
+
return this.getSetting("refresh-scroll");
|
|
2624
2668
|
}
|
|
2625
2669
|
getSetting(name) {
|
|
2626
2670
|
return this.headSnapshot.getMetaValue(`turbo-${name}`);
|
|
@@ -2653,7 +2697,8 @@ const defaultOptions = {
|
|
|
2653
2697
|
willRender: true,
|
|
2654
2698
|
updateHistory: true,
|
|
2655
2699
|
shouldCacheSnapshot: true,
|
|
2656
|
-
acceptsStreamResponse: false
|
|
2700
|
+
acceptsStreamResponse: false,
|
|
2701
|
+
refresh: {}
|
|
2657
2702
|
};
|
|
2658
2703
|
|
|
2659
2704
|
const TimingMetric = {
|
|
@@ -2698,7 +2743,7 @@ class Visit {
|
|
|
2698
2743
|
this.delegate = delegate;
|
|
2699
2744
|
this.location = location;
|
|
2700
2745
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
|
2701
|
-
const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse, direction: direction} = {
|
|
2746
|
+
const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse, direction: direction, refresh: refresh} = {
|
|
2702
2747
|
...defaultOptions,
|
|
2703
2748
|
...options
|
|
2704
2749
|
};
|
|
@@ -2708,7 +2753,6 @@ class Visit {
|
|
|
2708
2753
|
this.snapshot = snapshot;
|
|
2709
2754
|
this.snapshotHTML = snapshotHTML;
|
|
2710
2755
|
this.response = response;
|
|
2711
|
-
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
|
2712
2756
|
this.isPageRefresh = this.view.isPageRefresh(this);
|
|
2713
2757
|
this.visitCachedSnapshot = visitCachedSnapshot;
|
|
2714
2758
|
this.willRender = willRender;
|
|
@@ -2717,6 +2761,7 @@ class Visit {
|
|
|
2717
2761
|
this.shouldCacheSnapshot = shouldCacheSnapshot;
|
|
2718
2762
|
this.acceptsStreamResponse = acceptsStreamResponse;
|
|
2719
2763
|
this.direction = direction || Direction[action];
|
|
2764
|
+
this.refresh = refresh;
|
|
2720
2765
|
}
|
|
2721
2766
|
get adapter() {
|
|
2722
2767
|
return this.delegate.adapter;
|
|
@@ -2730,9 +2775,6 @@ class Visit {
|
|
|
2730
2775
|
get restorationData() {
|
|
2731
2776
|
return this.history.getRestorationDataForIdentifier(this.restorationIdentifier);
|
|
2732
2777
|
}
|
|
2733
|
-
get silent() {
|
|
2734
|
-
return this.isSamePage;
|
|
2735
|
-
}
|
|
2736
2778
|
start() {
|
|
2737
2779
|
if (this.state == VisitState.initialized) {
|
|
2738
2780
|
this.recordTimingMetric(TimingMetric.visitStart);
|
|
@@ -2851,7 +2893,7 @@ class Visit {
|
|
|
2851
2893
|
const isPreview = this.shouldIssueRequest();
|
|
2852
2894
|
this.render((async () => {
|
|
2853
2895
|
this.cacheSnapshot();
|
|
2854
|
-
if (this.
|
|
2896
|
+
if (this.isPageRefresh) {
|
|
2855
2897
|
this.adapter.visitRendered(this);
|
|
2856
2898
|
} else {
|
|
2857
2899
|
if (this.view.renderPromise) await this.view.renderPromise;
|
|
@@ -2875,16 +2917,6 @@ class Visit {
|
|
|
2875
2917
|
this.followedRedirect = true;
|
|
2876
2918
|
}
|
|
2877
2919
|
}
|
|
2878
|
-
goToSamePageAnchor() {
|
|
2879
|
-
if (this.isSamePage) {
|
|
2880
|
-
this.render((async () => {
|
|
2881
|
-
this.cacheSnapshot();
|
|
2882
|
-
this.performScroll();
|
|
2883
|
-
this.changeHistory();
|
|
2884
|
-
this.adapter.visitRendered(this);
|
|
2885
|
-
}));
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
2920
|
prepareRequest(request) {
|
|
2889
2921
|
if (this.acceptsStreamResponse) {
|
|
2890
2922
|
request.acceptResponseType(StreamMessage.contentType);
|
|
@@ -2943,9 +2975,6 @@ class Visit {
|
|
|
2943
2975
|
} else {
|
|
2944
2976
|
this.scrollToAnchor() || this.view.scrollToTop();
|
|
2945
2977
|
}
|
|
2946
|
-
if (this.isSamePage) {
|
|
2947
|
-
this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);
|
|
2948
|
-
}
|
|
2949
2978
|
this.scrolled = true;
|
|
2950
2979
|
}
|
|
2951
2980
|
}
|
|
@@ -2975,9 +3004,7 @@ class Visit {
|
|
|
2975
3004
|
return typeof this.response == "object";
|
|
2976
3005
|
}
|
|
2977
3006
|
shouldIssueRequest() {
|
|
2978
|
-
if (this.
|
|
2979
|
-
return false;
|
|
2980
|
-
} else if (this.action == "restore") {
|
|
3007
|
+
if (this.action == "restore") {
|
|
2981
3008
|
return !this.hasCachedSnapshot();
|
|
2982
3009
|
} else {
|
|
2983
3010
|
return this.willRender;
|
|
@@ -3032,7 +3059,6 @@ class BrowserAdapter {
|
|
|
3032
3059
|
this.redirectedToLocation = null;
|
|
3033
3060
|
visit.loadCachedSnapshot();
|
|
3034
3061
|
visit.issueRequest();
|
|
3035
|
-
visit.goToSamePageAnchor();
|
|
3036
3062
|
}
|
|
3037
3063
|
visitRequestStarted(visit) {
|
|
3038
3064
|
this.progressBar.setValue(0);
|
|
@@ -3126,7 +3152,6 @@ class BrowserAdapter {
|
|
|
3126
3152
|
|
|
3127
3153
|
class CacheObserver {
|
|
3128
3154
|
selector="[data-turbo-temporary]";
|
|
3129
|
-
deprecatedSelector="[data-turbo-cache=false]";
|
|
3130
3155
|
started=false;
|
|
3131
3156
|
start() {
|
|
3132
3157
|
if (!this.started) {
|
|
@@ -3146,14 +3171,7 @@ class CacheObserver {
|
|
|
3146
3171
|
}
|
|
3147
3172
|
};
|
|
3148
3173
|
get temporaryElements() {
|
|
3149
|
-
return [ ...document.querySelectorAll(this.selector)
|
|
3150
|
-
}
|
|
3151
|
-
get temporaryElementsWithDeprecation() {
|
|
3152
|
-
const elements = document.querySelectorAll(this.deprecatedSelector);
|
|
3153
|
-
if (elements.length) {
|
|
3154
|
-
console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);
|
|
3155
|
-
}
|
|
3156
|
-
return [ ...elements ];
|
|
3174
|
+
return [ ...document.querySelectorAll(this.selector) ];
|
|
3157
3175
|
}
|
|
3158
3176
|
}
|
|
3159
3177
|
|
|
@@ -3221,7 +3239,6 @@ class History {
|
|
|
3221
3239
|
restorationIdentifier=uuid();
|
|
3222
3240
|
restorationData={};
|
|
3223
3241
|
started=false;
|
|
3224
|
-
pageLoaded=false;
|
|
3225
3242
|
currentIndex=0;
|
|
3226
3243
|
constructor(delegate) {
|
|
3227
3244
|
this.delegate = delegate;
|
|
@@ -3229,7 +3246,6 @@ class History {
|
|
|
3229
3246
|
start() {
|
|
3230
3247
|
if (!this.started) {
|
|
3231
3248
|
addEventListener("popstate", this.onPopState, false);
|
|
3232
|
-
addEventListener("load", this.onPageLoad, false);
|
|
3233
3249
|
this.currentIndex = history.state?.turbo?.restorationIndex || 0;
|
|
3234
3250
|
this.started = true;
|
|
3235
3251
|
this.replace(new URL(window.location.href));
|
|
@@ -3238,7 +3254,6 @@ class History {
|
|
|
3238
3254
|
stop() {
|
|
3239
3255
|
if (this.started) {
|
|
3240
3256
|
removeEventListener("popstate", this.onPopState, false);
|
|
3241
|
-
removeEventListener("load", this.onPageLoad, false);
|
|
3242
3257
|
this.started = false;
|
|
3243
3258
|
}
|
|
3244
3259
|
}
|
|
@@ -3284,28 +3299,19 @@ class History {
|
|
|
3284
3299
|
}
|
|
3285
3300
|
}
|
|
3286
3301
|
onPopState=event => {
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3302
|
+
const {turbo: turbo} = event.state || {};
|
|
3303
|
+
this.location = new URL(window.location.href);
|
|
3304
|
+
if (turbo) {
|
|
3305
|
+
const {restorationIdentifier: restorationIdentifier, restorationIndex: restorationIndex} = turbo;
|
|
3306
|
+
this.restorationIdentifier = restorationIdentifier;
|
|
3307
|
+
const direction = restorationIndex > this.currentIndex ? "forward" : "back";
|
|
3308
|
+
this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
|
|
3309
|
+
this.currentIndex = restorationIndex;
|
|
3310
|
+
} else {
|
|
3311
|
+
this.currentIndex++;
|
|
3312
|
+
this.delegate.historyPoppedWithEmptyState(this.location);
|
|
3297
3313
|
}
|
|
3298
3314
|
};
|
|
3299
|
-
onPageLoad=async _event => {
|
|
3300
|
-
await nextMicrotask();
|
|
3301
|
-
this.pageLoaded = true;
|
|
3302
|
-
};
|
|
3303
|
-
shouldHandlePopState() {
|
|
3304
|
-
return this.pageIsLoaded();
|
|
3305
|
-
}
|
|
3306
|
-
pageIsLoaded() {
|
|
3307
|
-
return this.pageLoaded || document.readyState == "complete";
|
|
3308
|
-
}
|
|
3309
3315
|
}
|
|
3310
3316
|
|
|
3311
3317
|
class LinkPrefetchObserver {
|
|
@@ -3361,7 +3367,7 @@ class LinkPrefetchObserver {
|
|
|
3361
3367
|
this.#prefetchedLink = link;
|
|
3362
3368
|
const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, target);
|
|
3363
3369
|
fetchRequest.fetchOptions.priority = "low";
|
|
3364
|
-
prefetchCache.
|
|
3370
|
+
prefetchCache.putLater(location, fetchRequest, this.#cacheTtl);
|
|
3365
3371
|
}
|
|
3366
3372
|
}
|
|
3367
3373
|
};
|
|
@@ -3374,7 +3380,7 @@ class LinkPrefetchObserver {
|
|
|
3374
3380
|
};
|
|
3375
3381
|
#tryToUsePrefetchedRequest=event => {
|
|
3376
3382
|
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
|
|
3377
|
-
const cached = prefetchCache.get(event.detail.url
|
|
3383
|
+
const cached = prefetchCache.get(event.detail.url);
|
|
3378
3384
|
if (cached) {
|
|
3379
3385
|
event.detail.fetchRequest = cached;
|
|
3380
3386
|
}
|
|
@@ -3523,7 +3529,7 @@ class Navigator {
|
|
|
3523
3529
|
} else {
|
|
3524
3530
|
await this.view.renderPage(snapshot, false, true, this.currentVisit);
|
|
3525
3531
|
}
|
|
3526
|
-
if (
|
|
3532
|
+
if (snapshot.refreshScroll !== "preserve") {
|
|
3527
3533
|
this.view.scrollToTop();
|
|
3528
3534
|
}
|
|
3529
3535
|
this.view.clearSnapshotCache();
|
|
@@ -3551,13 +3557,7 @@ class Navigator {
|
|
|
3551
3557
|
delete this.currentVisit;
|
|
3552
3558
|
}
|
|
3553
3559
|
locationWithActionIsSamePage(location, action) {
|
|
3554
|
-
|
|
3555
|
-
const currentAnchor = getAnchor(this.view.lastRenderedLocation);
|
|
3556
|
-
const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
|
|
3557
|
-
return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
|
|
3558
|
-
}
|
|
3559
|
-
visitScrolledToSamePageLocation(oldURL, newURL) {
|
|
3560
|
-
this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
|
|
3560
|
+
return false;
|
|
3561
3561
|
}
|
|
3562
3562
|
get location() {
|
|
3563
3563
|
return this.history.location;
|
|
@@ -3888,12 +3888,17 @@ class PageRenderer extends Renderer {
|
|
|
3888
3888
|
}
|
|
3889
3889
|
#setLanguage() {
|
|
3890
3890
|
const {documentElement: documentElement} = this.currentSnapshot;
|
|
3891
|
-
const {lang: lang} = this.newSnapshot;
|
|
3891
|
+
const {dir: dir, lang: lang} = this.newSnapshot;
|
|
3892
3892
|
if (lang) {
|
|
3893
3893
|
documentElement.setAttribute("lang", lang);
|
|
3894
3894
|
} else {
|
|
3895
3895
|
documentElement.removeAttribute("lang");
|
|
3896
3896
|
}
|
|
3897
|
+
if (dir) {
|
|
3898
|
+
documentElement.setAttribute("dir", dir);
|
|
3899
|
+
} else {
|
|
3900
|
+
documentElement.removeAttribute("dir");
|
|
3901
|
+
}
|
|
3897
3902
|
}
|
|
3898
3903
|
async mergeHead() {
|
|
3899
3904
|
const mergedHeadElements = this.mergeProvisionalElements();
|
|
@@ -3973,8 +3978,14 @@ class PageRenderer extends Renderer {
|
|
|
3973
3978
|
}
|
|
3974
3979
|
activateNewBody() {
|
|
3975
3980
|
document.adoptNode(this.newElement);
|
|
3981
|
+
this.removeNoscriptElements();
|
|
3976
3982
|
this.activateNewBodyScriptElements();
|
|
3977
3983
|
}
|
|
3984
|
+
removeNoscriptElements() {
|
|
3985
|
+
for (const noscriptElement of this.newElement.querySelectorAll("noscript")) {
|
|
3986
|
+
noscriptElement.remove();
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3978
3989
|
activateNewBodyScriptElements() {
|
|
3979
3990
|
for (const inertScriptElement of this.newBodyScriptElements) {
|
|
3980
3991
|
const activatedScriptElement = activateScriptElement(inertScriptElement);
|
|
@@ -4038,47 +4049,12 @@ class MorphingPageRenderer extends PageRenderer {
|
|
|
4038
4049
|
}
|
|
4039
4050
|
}
|
|
4040
4051
|
|
|
4041
|
-
class SnapshotCache {
|
|
4042
|
-
keys=[];
|
|
4043
|
-
snapshots={};
|
|
4052
|
+
class SnapshotCache extends LRUCache {
|
|
4044
4053
|
constructor(size) {
|
|
4045
|
-
|
|
4046
|
-
}
|
|
4047
|
-
has(location) {
|
|
4048
|
-
return toCacheKey(location) in this.snapshots;
|
|
4049
|
-
}
|
|
4050
|
-
get(location) {
|
|
4051
|
-
if (this.has(location)) {
|
|
4052
|
-
const snapshot = this.read(location);
|
|
4053
|
-
this.touch(location);
|
|
4054
|
-
return snapshot;
|
|
4055
|
-
}
|
|
4056
|
-
}
|
|
4057
|
-
put(location, snapshot) {
|
|
4058
|
-
this.write(location, snapshot);
|
|
4059
|
-
this.touch(location);
|
|
4060
|
-
return snapshot;
|
|
4061
|
-
}
|
|
4062
|
-
clear() {
|
|
4063
|
-
this.snapshots = {};
|
|
4064
|
-
}
|
|
4065
|
-
read(location) {
|
|
4066
|
-
return this.snapshots[toCacheKey(location)];
|
|
4054
|
+
super(size, toCacheKey);
|
|
4067
4055
|
}
|
|
4068
|
-
|
|
4069
|
-
this.
|
|
4070
|
-
}
|
|
4071
|
-
touch(location) {
|
|
4072
|
-
const key = toCacheKey(location);
|
|
4073
|
-
const index = this.keys.indexOf(key);
|
|
4074
|
-
if (index > -1) this.keys.splice(index, 1);
|
|
4075
|
-
this.keys.unshift(key);
|
|
4076
|
-
this.trim();
|
|
4077
|
-
}
|
|
4078
|
-
trim() {
|
|
4079
|
-
for (const key of this.keys.splice(this.size)) {
|
|
4080
|
-
delete this.snapshots[key];
|
|
4081
|
-
}
|
|
4056
|
+
get snapshots() {
|
|
4057
|
+
return this.entries;
|
|
4082
4058
|
}
|
|
4083
4059
|
}
|
|
4084
4060
|
|
|
@@ -4090,7 +4066,7 @@ class PageView extends View {
|
|
|
4090
4066
|
return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions;
|
|
4091
4067
|
}
|
|
4092
4068
|
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
|
4093
|
-
const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.
|
|
4069
|
+
const shouldMorphPage = this.isPageRefresh(visit) && (visit?.refresh?.method || this.snapshot.refreshMethod) === "morph";
|
|
4094
4070
|
const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
|
|
4095
4071
|
const renderer = new rendererClass(this.snapshot, snapshot, isPreview, willRender);
|
|
4096
4072
|
if (!renderer.shouldRender) {
|
|
@@ -4125,7 +4101,7 @@ class PageView extends View {
|
|
|
4125
4101
|
return !visit || this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === "replace";
|
|
4126
4102
|
}
|
|
4127
4103
|
shouldPreserveScrollPosition(visit) {
|
|
4128
|
-
return this.isPageRefresh(visit) && this.snapshot.
|
|
4104
|
+
return this.isPageRefresh(visit) && (visit?.refresh?.scroll || this.snapshot.refreshScroll) === "preserve";
|
|
4129
4105
|
}
|
|
4130
4106
|
get snapshot() {
|
|
4131
4107
|
return PageSnapshot.fromElement(this.element);
|
|
@@ -4278,13 +4254,21 @@ class Session {
|
|
|
4278
4254
|
this.navigator.proposeVisit(expandURL(location), options);
|
|
4279
4255
|
}
|
|
4280
4256
|
}
|
|
4281
|
-
refresh(url,
|
|
4257
|
+
refresh(url, options = {}) {
|
|
4258
|
+
options = typeof options === "string" ? {
|
|
4259
|
+
requestId: options
|
|
4260
|
+
} : options;
|
|
4261
|
+
const {method: method, requestId: requestId, scroll: scroll} = options;
|
|
4282
4262
|
const isRecentRequest = requestId && this.recentRequests.has(requestId);
|
|
4283
4263
|
const isCurrentUrl = url === document.baseURI;
|
|
4284
4264
|
if (!isRecentRequest && !this.navigator.currentVisit && isCurrentUrl) {
|
|
4285
4265
|
this.visit(url, {
|
|
4286
4266
|
action: "replace",
|
|
4287
|
-
shouldCacheSnapshot: false
|
|
4267
|
+
shouldCacheSnapshot: false,
|
|
4268
|
+
refresh: {
|
|
4269
|
+
method: method,
|
|
4270
|
+
scroll: scroll
|
|
4271
|
+
}
|
|
4288
4272
|
});
|
|
4289
4273
|
}
|
|
4290
4274
|
}
|
|
@@ -4360,6 +4344,11 @@ class Session {
|
|
|
4360
4344
|
});
|
|
4361
4345
|
}
|
|
4362
4346
|
}
|
|
4347
|
+
historyPoppedWithEmptyState(location) {
|
|
4348
|
+
this.history.replace(location);
|
|
4349
|
+
this.view.lastRenderedLocation = location;
|
|
4350
|
+
this.view.cacheSnapshot();
|
|
4351
|
+
}
|
|
4363
4352
|
scrollPositionChanged(position) {
|
|
4364
4353
|
this.history.updateRestorationData({
|
|
4365
4354
|
scrollPosition: position
|
|
@@ -4384,7 +4373,7 @@ class Session {
|
|
|
4384
4373
|
});
|
|
4385
4374
|
}
|
|
4386
4375
|
allowsVisitingLocationWithAction(location, action) {
|
|
4387
|
-
return this.
|
|
4376
|
+
return this.applicationAllowsVisitingLocation(location);
|
|
4388
4377
|
}
|
|
4389
4378
|
visitProposedToLocation(location, options) {
|
|
4390
4379
|
extendURLWithDeprecatedProperties(location);
|
|
@@ -4396,21 +4385,13 @@ class Session {
|
|
|
4396
4385
|
this.view.markVisitDirection(visit.direction);
|
|
4397
4386
|
}
|
|
4398
4387
|
extendURLWithDeprecatedProperties(visit.location);
|
|
4399
|
-
|
|
4400
|
-
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
|
|
4401
|
-
}
|
|
4388
|
+
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
|
|
4402
4389
|
}
|
|
4403
4390
|
visitCompleted(visit) {
|
|
4404
4391
|
this.view.unmarkVisitDirection();
|
|
4405
4392
|
clearBusyState(document.documentElement);
|
|
4406
4393
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
|
4407
4394
|
}
|
|
4408
|
-
locationWithActionIsSamePage(location, action) {
|
|
4409
|
-
return this.navigator.locationWithActionIsSamePage(location, action);
|
|
4410
|
-
}
|
|
4411
|
-
visitScrolledToSamePageLocation(oldURL, newURL) {
|
|
4412
|
-
this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
|
|
4413
|
-
}
|
|
4414
4395
|
willSubmitForm(form, submitter) {
|
|
4415
4396
|
const action = getAction$1(form, submitter);
|
|
4416
4397
|
return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
|
|
@@ -4432,9 +4413,7 @@ class Session {
|
|
|
4432
4413
|
this.renderStreamMessage(message);
|
|
4433
4414
|
}
|
|
4434
4415
|
viewWillCacheSnapshot() {
|
|
4435
|
-
|
|
4436
|
-
this.notifyApplicationBeforeCachingSnapshot();
|
|
4437
|
-
}
|
|
4416
|
+
this.notifyApplicationBeforeCachingSnapshot();
|
|
4438
4417
|
}
|
|
4439
4418
|
allowsImmediateRender({element: element}, options) {
|
|
4440
4419
|
const event = this.notifyApplicationBeforeRender(element, options);
|
|
@@ -4521,12 +4500,6 @@ class Session {
|
|
|
4521
4500
|
}
|
|
4522
4501
|
});
|
|
4523
4502
|
}
|
|
4524
|
-
notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
|
|
4525
|
-
dispatchEvent(new HashChangeEvent("hashchange", {
|
|
4526
|
-
oldURL: oldURL.toString(),
|
|
4527
|
-
newURL: newURL.toString()
|
|
4528
|
-
}));
|
|
4529
|
-
}
|
|
4530
4503
|
notifyApplicationAfterFrameLoad(frame) {
|
|
4531
4504
|
return dispatch("turbo:frame-load", {
|
|
4532
4505
|
target: frame
|
|
@@ -4592,7 +4565,7 @@ const deprecatedLocationPropertyDescriptors = {
|
|
|
4592
4565
|
|
|
4593
4566
|
const session = new Session(recentRequests);
|
|
4594
4567
|
|
|
4595
|
-
const {cache: cache, navigator: navigator
|
|
4568
|
+
const {cache: cache, navigator: navigator} = session;
|
|
4596
4569
|
|
|
4597
4570
|
function start() {
|
|
4598
4571
|
session.start();
|
|
@@ -4618,11 +4591,6 @@ function renderStreamMessage(message) {
|
|
|
4618
4591
|
session.renderStreamMessage(message);
|
|
4619
4592
|
}
|
|
4620
4593
|
|
|
4621
|
-
function clearCache() {
|
|
4622
|
-
console.warn("Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
|
|
4623
|
-
session.clearCache();
|
|
4624
|
-
}
|
|
4625
|
-
|
|
4626
4594
|
function setProgressBarDelay(delay) {
|
|
4627
4595
|
console.warn("Please replace `Turbo.setProgressBarDelay(delay)` with `Turbo.config.drive.progressBarDelay = delay`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
|
|
4628
4596
|
config.drive.progressBarDelay = delay;
|
|
@@ -4648,7 +4616,7 @@ function morphTurboFrameElements(currentFrame, newFrame) {
|
|
|
4648
4616
|
|
|
4649
4617
|
var Turbo = Object.freeze({
|
|
4650
4618
|
__proto__: null,
|
|
4651
|
-
navigator: navigator
|
|
4619
|
+
navigator: navigator,
|
|
4652
4620
|
session: session,
|
|
4653
4621
|
cache: cache,
|
|
4654
4622
|
PageRenderer: PageRenderer,
|
|
@@ -4662,7 +4630,6 @@ var Turbo = Object.freeze({
|
|
|
4662
4630
|
connectStreamSource: connectStreamSource,
|
|
4663
4631
|
disconnectStreamSource: disconnectStreamSource,
|
|
4664
4632
|
renderStreamMessage: renderStreamMessage,
|
|
4665
|
-
clearCache: clearCache,
|
|
4666
4633
|
setProgressBarDelay: setProgressBarDelay,
|
|
4667
4634
|
setConfirmMethod: setConfirmMethod,
|
|
4668
4635
|
setFormMode: setFormMode,
|
|
@@ -4712,15 +4679,23 @@ class FrameController {
|
|
|
4712
4679
|
this.formLinkClickObserver.stop();
|
|
4713
4680
|
this.linkInterceptor.stop();
|
|
4714
4681
|
this.formSubmitObserver.stop();
|
|
4682
|
+
if (!this.element.hasAttribute("recurse")) {
|
|
4683
|
+
this.#currentFetchRequest?.cancel();
|
|
4684
|
+
}
|
|
4715
4685
|
}
|
|
4716
4686
|
}
|
|
4717
4687
|
disabledChanged() {
|
|
4718
|
-
if (this.
|
|
4688
|
+
if (this.disabled) {
|
|
4689
|
+
this.#currentFetchRequest?.cancel();
|
|
4690
|
+
} else if (this.loadingStyle == FrameLoadingStyle.eager) {
|
|
4719
4691
|
this.#loadSourceURL();
|
|
4720
4692
|
}
|
|
4721
4693
|
}
|
|
4722
4694
|
sourceURLChanged() {
|
|
4723
4695
|
if (this.#isIgnoringChangesTo("src")) return;
|
|
4696
|
+
if (!this.sourceURL) {
|
|
4697
|
+
this.#currentFetchRequest?.cancel();
|
|
4698
|
+
}
|
|
4724
4699
|
if (this.element.isConnected) {
|
|
4725
4700
|
this.complete = false;
|
|
4726
4701
|
}
|
|
@@ -4798,11 +4773,12 @@ class FrameController {
|
|
|
4798
4773
|
}
|
|
4799
4774
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
|
4800
4775
|
const {fetchRequest: fetchRequest} = this.formSubmission;
|
|
4801
|
-
this
|
|
4776
|
+
const frame = this.#findFrameElement(element, submitter);
|
|
4777
|
+
this.prepareRequest(fetchRequest, frame);
|
|
4802
4778
|
this.formSubmission.start();
|
|
4803
4779
|
}
|
|
4804
|
-
prepareRequest(request) {
|
|
4805
|
-
request.headers["Turbo-Frame"] =
|
|
4780
|
+
prepareRequest(request, frame = this) {
|
|
4781
|
+
request.headers["Turbo-Frame"] = frame.id;
|
|
4806
4782
|
if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
|
|
4807
4783
|
request.acceptResponseType(StreamMessage.contentType);
|
|
4808
4784
|
}
|
|
@@ -4996,7 +4972,8 @@ class FrameController {
|
|
|
4996
4972
|
}
|
|
4997
4973
|
#findFrameElement(element, submitter) {
|
|
4998
4974
|
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
|
4999
|
-
|
|
4975
|
+
const target = this.#getFrameElementById(id);
|
|
4976
|
+
return target instanceof FrameElement ? target : this.element;
|
|
5000
4977
|
}
|
|
5001
4978
|
async extractForeignFrameElement(container) {
|
|
5002
4979
|
let element;
|
|
@@ -5030,9 +5007,11 @@ class FrameController {
|
|
|
5030
5007
|
return false;
|
|
5031
5008
|
}
|
|
5032
5009
|
if (id) {
|
|
5033
|
-
const frameElement = getFrameElementById(id);
|
|
5010
|
+
const frameElement = this.#getFrameElementById(id);
|
|
5034
5011
|
if (frameElement) {
|
|
5035
5012
|
return !frameElement.disabled;
|
|
5013
|
+
} else if (id == "_parent") {
|
|
5014
|
+
return false;
|
|
5036
5015
|
}
|
|
5037
5016
|
}
|
|
5038
5017
|
if (!session.elementIsNavigatable(element)) {
|
|
@@ -5046,8 +5025,11 @@ class FrameController {
|
|
|
5046
5025
|
get id() {
|
|
5047
5026
|
return this.element.id;
|
|
5048
5027
|
}
|
|
5028
|
+
get disabled() {
|
|
5029
|
+
return this.element.disabled;
|
|
5030
|
+
}
|
|
5049
5031
|
get enabled() {
|
|
5050
|
-
return !this.
|
|
5032
|
+
return !this.disabled;
|
|
5051
5033
|
}
|
|
5052
5034
|
get sourceURL() {
|
|
5053
5035
|
if (this.element.src) {
|
|
@@ -5096,13 +5078,12 @@ class FrameController {
|
|
|
5096
5078
|
callback();
|
|
5097
5079
|
delete this.currentNavigationElement;
|
|
5098
5080
|
}
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
return element;
|
|
5081
|
+
#getFrameElementById(id) {
|
|
5082
|
+
if (id != null) {
|
|
5083
|
+
const element = id === "_parent" ? this.element.parentElement.closest("turbo-frame") : document.getElementById(id);
|
|
5084
|
+
if (element instanceof FrameElement) {
|
|
5085
|
+
return element;
|
|
5086
|
+
}
|
|
5106
5087
|
}
|
|
5107
5088
|
}
|
|
5108
5089
|
}
|
|
@@ -5126,6 +5107,7 @@ function activateElement(element, currentURL) {
|
|
|
5126
5107
|
|
|
5127
5108
|
const StreamActions = {
|
|
5128
5109
|
after() {
|
|
5110
|
+
this.removeDuplicateTargetSiblings();
|
|
5129
5111
|
this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e.nextSibling)));
|
|
5130
5112
|
},
|
|
5131
5113
|
append() {
|
|
@@ -5133,6 +5115,7 @@ const StreamActions = {
|
|
|
5133
5115
|
this.targetElements.forEach((e => e.append(this.templateContent)));
|
|
5134
5116
|
},
|
|
5135
5117
|
before() {
|
|
5118
|
+
this.removeDuplicateTargetSiblings();
|
|
5136
5119
|
this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e)));
|
|
5137
5120
|
},
|
|
5138
5121
|
prepend() {
|
|
@@ -5164,7 +5147,14 @@ const StreamActions = {
|
|
|
5164
5147
|
}));
|
|
5165
5148
|
},
|
|
5166
5149
|
refresh() {
|
|
5167
|
-
|
|
5150
|
+
const method = this.getAttribute("method");
|
|
5151
|
+
const requestId = this.requestId;
|
|
5152
|
+
const scroll = this.getAttribute("scroll");
|
|
5153
|
+
session.refresh(this.baseURI, {
|
|
5154
|
+
method: method,
|
|
5155
|
+
requestId: requestId,
|
|
5156
|
+
scroll: scroll
|
|
5157
|
+
});
|
|
5168
5158
|
}
|
|
5169
5159
|
};
|
|
5170
5160
|
|
|
@@ -5203,6 +5193,14 @@ class StreamElement extends HTMLElement {
|
|
|
5203
5193
|
const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.getAttribute("id"))).map((c => c.getAttribute("id")));
|
|
5204
5194
|
return existingChildren.filter((c => newChildrenIds.includes(c.getAttribute("id"))));
|
|
5205
5195
|
}
|
|
5196
|
+
removeDuplicateTargetSiblings() {
|
|
5197
|
+
this.duplicateSiblings.forEach((c => c.remove()));
|
|
5198
|
+
}
|
|
5199
|
+
get duplicateSiblings() {
|
|
5200
|
+
const existingChildren = this.targetElements.flatMap((e => [ ...e.parentElement.children ])).filter((c => !!c.id));
|
|
5201
|
+
const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
|
|
5202
|
+
return existingChildren.filter((c => newChildrenIds.includes(c.id)));
|
|
5203
|
+
}
|
|
5206
5204
|
get performAction() {
|
|
5207
5205
|
if (this.action) {
|
|
5208
5206
|
const actionFunction = StreamActions[this.action];
|
|
@@ -5313,10 +5311,10 @@ if (customElements.get("turbo-stream-source") === undefined) {
|
|
|
5313
5311
|
}
|
|
5314
5312
|
|
|
5315
5313
|
(() => {
|
|
5316
|
-
|
|
5317
|
-
if (!
|
|
5318
|
-
if (
|
|
5319
|
-
element =
|
|
5314
|
+
const scriptElement = document.currentScript;
|
|
5315
|
+
if (!scriptElement) return;
|
|
5316
|
+
if (scriptElement.hasAttribute("data-turbo-suppress-warning")) return;
|
|
5317
|
+
let element = scriptElement.parentElement;
|
|
5320
5318
|
while (element) {
|
|
5321
5319
|
if (element == document.body) {
|
|
5322
5320
|
return console.warn(unindent`
|
|
@@ -5328,7 +5326,7 @@ if (customElements.get("turbo-stream-source") === undefined) {
|
|
|
5328
5326
|
|
|
5329
5327
|
——
|
|
5330
5328
|
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
|
5331
|
-
`,
|
|
5329
|
+
`, scriptElement.outerHTML);
|
|
5332
5330
|
}
|
|
5333
5331
|
element = element.parentElement;
|
|
5334
5332
|
}
|
|
@@ -5356,7 +5354,6 @@ var Turbo$1 = Object.freeze({
|
|
|
5356
5354
|
StreamElement: StreamElement,
|
|
5357
5355
|
StreamSourceElement: StreamSourceElement,
|
|
5358
5356
|
cache: cache,
|
|
5359
|
-
clearCache: clearCache,
|
|
5360
5357
|
config: config,
|
|
5361
5358
|
connectStreamSource: connectStreamSource,
|
|
5362
5359
|
disconnectStreamSource: disconnectStreamSource,
|
|
@@ -5368,7 +5365,7 @@ var Turbo$1 = Object.freeze({
|
|
|
5368
5365
|
morphChildren: morphChildren,
|
|
5369
5366
|
morphElements: morphElements,
|
|
5370
5367
|
morphTurboFrameElements: morphTurboFrameElements,
|
|
5371
|
-
navigator: navigator
|
|
5368
|
+
navigator: navigator,
|
|
5372
5369
|
registerAdapter: registerAdapter,
|
|
5373
5370
|
renderStreamMessage: renderStreamMessage,
|
|
5374
5371
|
session: session,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotwired/turbo-rails",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.21",
|
|
4
4
|
"description": "The speed of a single-page web application without having to write any JavaScript",
|
|
5
5
|
"module": "app/javascript/turbo/index.js",
|
|
6
6
|
"main": "app/assets/javascripts/turbo.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"release": "yarn publish && git commit -am \"$npm_package_name v$npm_package_version\" && git push"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@hotwired/turbo": "^8.0.
|
|
22
|
+
"@hotwired/turbo": "^8.0.21",
|
|
23
23
|
"@rails/actioncable": ">=7.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|