turbo-rails 2.0.20 → 2.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascripts/turbo.js +243 -324
- data/app/assets/javascripts/turbo.min.js +5 -5
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams/broadcasts.rb +2 -2
- data/app/helpers/turbo/drive_helper.rb +2 -2
- data/app/helpers/turbo/frames_helper.rb +4 -1
- data/app/helpers/turbo/streams/action_helper.rb +4 -1
- data/app/models/concerns/turbo/broadcastable.rb +6 -6
- data/app/models/turbo/immediate_debouncer.rb +17 -0
- data/app/models/turbo/thread_debouncer.rb +3 -1
- data/lib/turbo/broadcastable/test_helper.rb +5 -3
- data/lib/turbo/engine.rb +5 -28
- data/lib/turbo/system_test_helper.rb +2 -0
- data/lib/turbo/test_assertions.rb +74 -0
- data/lib/turbo/version.rb +1 -1
- metadata +4 -3
|
@@ -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,8 +1720,12 @@ var Idiomorph = function() {
|
|
|
1723
1720
|
}
|
|
1724
1721
|
function morphOuterHTML(ctx, oldNode, newNode) {
|
|
1725
1722
|
const oldParent = normalizeParent(oldNode);
|
|
1723
|
+
let childNodes = Array.from(oldParent.childNodes);
|
|
1724
|
+
const index = childNodes.indexOf(oldNode);
|
|
1725
|
+
const rightMargin = childNodes.length - (index + 1);
|
|
1726
1726
|
morphChildren(ctx, oldParent, newNode, oldNode, oldNode.nextSibling);
|
|
1727
|
-
|
|
1727
|
+
childNodes = Array.from(oldParent.childNodes);
|
|
1728
|
+
return childNodes.slice(index, childNodes.length - rightMargin);
|
|
1728
1729
|
}
|
|
1729
1730
|
function saveAndRestoreFocus(ctx, fn) {
|
|
1730
1731
|
if (!ctx.config.restoreFocus) return fn();
|
|
@@ -1734,8 +1735,8 @@ var Idiomorph = function() {
|
|
|
1734
1735
|
}
|
|
1735
1736
|
const {id: activeElementId, selectionStart: selectionStart, selectionEnd: selectionEnd} = activeElement;
|
|
1736
1737
|
const results = fn();
|
|
1737
|
-
if (activeElementId && activeElementId !== document.activeElement?.
|
|
1738
|
-
activeElement = ctx.target.querySelector(
|
|
1738
|
+
if (activeElementId && activeElementId !== document.activeElement?.id) {
|
|
1739
|
+
activeElement = ctx.target.querySelector(`#${activeElementId}`);
|
|
1739
1740
|
activeElement?.focus();
|
|
1740
1741
|
}
|
|
1741
1742
|
if (activeElement && !activeElement.selectionEnd && selectionEnd) {
|
|
@@ -1762,14 +1763,11 @@ var Idiomorph = function() {
|
|
|
1762
1763
|
continue;
|
|
1763
1764
|
}
|
|
1764
1765
|
}
|
|
1765
|
-
if (newChild instanceof Element) {
|
|
1766
|
-
const
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
insertionPoint = movedChild.nextSibling;
|
|
1771
|
-
continue;
|
|
1772
|
-
}
|
|
1766
|
+
if (newChild instanceof Element && ctx.persistentIds.has(newChild.id)) {
|
|
1767
|
+
const movedChild = moveBeforeById(oldParent, newChild.id, insertionPoint, ctx);
|
|
1768
|
+
morphNode(movedChild, newChild, ctx);
|
|
1769
|
+
insertionPoint = movedChild.nextSibling;
|
|
1770
|
+
continue;
|
|
1773
1771
|
}
|
|
1774
1772
|
const insertedNode = createNode(oldParent, newChild, insertionPoint, ctx);
|
|
1775
1773
|
if (insertedNode) {
|
|
@@ -1821,7 +1819,7 @@ var Idiomorph = function() {
|
|
|
1821
1819
|
softMatch = undefined;
|
|
1822
1820
|
}
|
|
1823
1821
|
}
|
|
1824
|
-
if (
|
|
1822
|
+
if (cursor.contains(document.activeElement)) break;
|
|
1825
1823
|
cursor = cursor.nextSibling;
|
|
1826
1824
|
}
|
|
1827
1825
|
return softMatch || null;
|
|
@@ -1840,7 +1838,7 @@ var Idiomorph = function() {
|
|
|
1840
1838
|
function isSoftMatch(oldNode, newNode) {
|
|
1841
1839
|
const oldElt = oldNode;
|
|
1842
1840
|
const newElt = newNode;
|
|
1843
|
-
return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && (!oldElt.
|
|
1841
|
+
return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && (!oldElt.id || oldElt.id === newElt.id);
|
|
1844
1842
|
}
|
|
1845
1843
|
return findBestMatch;
|
|
1846
1844
|
}();
|
|
@@ -1863,13 +1861,13 @@ var Idiomorph = function() {
|
|
|
1863
1861
|
return cursor;
|
|
1864
1862
|
}
|
|
1865
1863
|
function moveBeforeById(parentNode, id, after, ctx) {
|
|
1866
|
-
const target = ctx.target.
|
|
1864
|
+
const target = ctx.target.querySelector(`#${id}`) || ctx.pantry.querySelector(`#${id}`);
|
|
1867
1865
|
removeElementFromAncestorsIdMaps(target, ctx);
|
|
1868
1866
|
moveBefore(parentNode, target, after);
|
|
1869
1867
|
return target;
|
|
1870
1868
|
}
|
|
1871
1869
|
function removeElementFromAncestorsIdMaps(element, ctx) {
|
|
1872
|
-
const id = element.
|
|
1870
|
+
const id = element.id;
|
|
1873
1871
|
while (element = element.parentNode) {
|
|
1874
1872
|
let idSet = ctx.idMap.get(element);
|
|
1875
1873
|
if (idSet) {
|
|
@@ -2113,7 +2111,6 @@ var Idiomorph = function() {
|
|
|
2113
2111
|
idMap: idMap,
|
|
2114
2112
|
persistentIds: persistentIds,
|
|
2115
2113
|
pantry: createPantry(),
|
|
2116
|
-
activeElementAndParents: createActiveElementAndParents(oldNode),
|
|
2117
2114
|
callbacks: mergedConfig.callbacks,
|
|
2118
2115
|
head: mergedConfig.head
|
|
2119
2116
|
};
|
|
@@ -2131,29 +2128,16 @@ var Idiomorph = function() {
|
|
|
2131
2128
|
document.body.insertAdjacentElement("afterend", pantry);
|
|
2132
2129
|
return pantry;
|
|
2133
2130
|
}
|
|
2134
|
-
function createActiveElementAndParents(oldNode) {
|
|
2135
|
-
let activeElementAndParents = [];
|
|
2136
|
-
let elt = document.activeElement;
|
|
2137
|
-
if (elt?.tagName !== "BODY" && oldNode.contains(elt)) {
|
|
2138
|
-
while (elt) {
|
|
2139
|
-
activeElementAndParents.push(elt);
|
|
2140
|
-
if (elt === oldNode) break;
|
|
2141
|
-
elt = elt.parentElement;
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
return activeElementAndParents;
|
|
2145
|
-
}
|
|
2146
2131
|
function findIdElements(root) {
|
|
2147
2132
|
let elements = Array.from(root.querySelectorAll("[id]"));
|
|
2148
|
-
if (root.
|
|
2133
|
+
if (root.id) {
|
|
2149
2134
|
elements.push(root);
|
|
2150
2135
|
}
|
|
2151
2136
|
return elements;
|
|
2152
2137
|
}
|
|
2153
2138
|
function populateIdMapWithTree(idMap, persistentIds, root, elements) {
|
|
2154
2139
|
for (const elt of elements) {
|
|
2155
|
-
|
|
2156
|
-
if (persistentIds.has(id)) {
|
|
2140
|
+
if (persistentIds.has(elt.id)) {
|
|
2157
2141
|
let current = elt;
|
|
2158
2142
|
while (current) {
|
|
2159
2143
|
let idSet = idMap.get(current);
|
|
@@ -2161,7 +2145,7 @@ var Idiomorph = function() {
|
|
|
2161
2145
|
idSet = new Set;
|
|
2162
2146
|
idMap.set(current, idSet);
|
|
2163
2147
|
}
|
|
2164
|
-
idSet.add(id);
|
|
2148
|
+
idSet.add(elt.id);
|
|
2165
2149
|
if (current === root) break;
|
|
2166
2150
|
current = current.parentElement;
|
|
2167
2151
|
}
|
|
@@ -2224,7 +2208,7 @@ var Idiomorph = function() {
|
|
|
2224
2208
|
return newContent;
|
|
2225
2209
|
} else if (newContent instanceof Node) {
|
|
2226
2210
|
if (newContent.parentNode) {
|
|
2227
|
-
return
|
|
2211
|
+
return createDuckTypedParent(newContent);
|
|
2228
2212
|
} else {
|
|
2229
2213
|
const dummyParent = document.createElement("div");
|
|
2230
2214
|
dummyParent.append(newContent);
|
|
@@ -2238,43 +2222,19 @@ var Idiomorph = function() {
|
|
|
2238
2222
|
return dummyParent;
|
|
2239
2223
|
}
|
|
2240
2224
|
}
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
nodes.push(cursor);
|
|
2253
|
-
cursor = cursor.nextSibling;
|
|
2225
|
+
function createDuckTypedParent(newContent) {
|
|
2226
|
+
return {
|
|
2227
|
+
childNodes: [ newContent ],
|
|
2228
|
+
querySelectorAll: s => {
|
|
2229
|
+
const elements = newContent.querySelectorAll(s);
|
|
2230
|
+
return newContent.matches(s) ? [ newContent, ...elements ] : elements;
|
|
2231
|
+
},
|
|
2232
|
+
insertBefore: (n, r) => newContent.parentNode.insertBefore(n, r),
|
|
2233
|
+
moveBefore: (n, r) => newContent.parentNode.moveBefore(n, r),
|
|
2234
|
+
get __idiomorphRoot() {
|
|
2235
|
+
return newContent;
|
|
2254
2236
|
}
|
|
2255
|
-
|
|
2256
|
-
}
|
|
2257
|
-
querySelectorAll(selector) {
|
|
2258
|
-
return this.childNodes.reduce(((results, node) => {
|
|
2259
|
-
if (node instanceof Element) {
|
|
2260
|
-
if (node.matches(selector)) results.push(node);
|
|
2261
|
-
const nodeList = node.querySelectorAll(selector);
|
|
2262
|
-
for (let i = 0; i < nodeList.length; i++) {
|
|
2263
|
-
results.push(nodeList[i]);
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
return results;
|
|
2267
|
-
}), []);
|
|
2268
|
-
}
|
|
2269
|
-
insertBefore(node, referenceNode) {
|
|
2270
|
-
return this.realParentNode.insertBefore(node, referenceNode);
|
|
2271
|
-
}
|
|
2272
|
-
moveBefore(node, referenceNode) {
|
|
2273
|
-
return this.realParentNode.moveBefore(node, referenceNode);
|
|
2274
|
-
}
|
|
2275
|
-
get __idiomorphRoot() {
|
|
2276
|
-
return this.originalNode;
|
|
2277
|
-
}
|
|
2237
|
+
};
|
|
2278
2238
|
}
|
|
2279
2239
|
function parseContent(newContent) {
|
|
2280
2240
|
let parser = new DOMParser;
|
|
@@ -2629,11 +2589,17 @@ class PageSnapshot extends Snapshot {
|
|
|
2629
2589
|
for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
|
|
2630
2590
|
clonedPasswordInput.value = "";
|
|
2631
2591
|
}
|
|
2592
|
+
for (const clonedNoscriptElement of clonedElement.querySelectorAll("noscript")) {
|
|
2593
|
+
clonedNoscriptElement.remove();
|
|
2594
|
+
}
|
|
2632
2595
|
return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot);
|
|
2633
2596
|
}
|
|
2634
2597
|
get lang() {
|
|
2635
2598
|
return this.documentElement.getAttribute("lang");
|
|
2636
2599
|
}
|
|
2600
|
+
get dir() {
|
|
2601
|
+
return this.documentElement.getAttribute("dir");
|
|
2602
|
+
}
|
|
2637
2603
|
get headElement() {
|
|
2638
2604
|
return this.headSnapshot.element;
|
|
2639
2605
|
}
|
|
@@ -2657,11 +2623,11 @@ class PageSnapshot extends Snapshot {
|
|
|
2657
2623
|
const viewTransitionEnabled = this.getSetting("view-transition") === "true" || this.headSnapshot.getMetaValue("view-transition") === "same-origin";
|
|
2658
2624
|
return viewTransitionEnabled && !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
2659
2625
|
}
|
|
2660
|
-
get
|
|
2661
|
-
return this.getSetting("refresh-method")
|
|
2626
|
+
get refreshMethod() {
|
|
2627
|
+
return this.getSetting("refresh-method");
|
|
2662
2628
|
}
|
|
2663
|
-
get
|
|
2664
|
-
return this.getSetting("refresh-scroll")
|
|
2629
|
+
get refreshScroll() {
|
|
2630
|
+
return this.getSetting("refresh-scroll");
|
|
2665
2631
|
}
|
|
2666
2632
|
getSetting(name) {
|
|
2667
2633
|
return this.headSnapshot.getMetaValue(`turbo-${name}`);
|
|
@@ -2694,7 +2660,8 @@ const defaultOptions = {
|
|
|
2694
2660
|
willRender: true,
|
|
2695
2661
|
updateHistory: true,
|
|
2696
2662
|
shouldCacheSnapshot: true,
|
|
2697
|
-
acceptsStreamResponse: false
|
|
2663
|
+
acceptsStreamResponse: false,
|
|
2664
|
+
refresh: {}
|
|
2698
2665
|
};
|
|
2699
2666
|
|
|
2700
2667
|
const TimingMetric = {
|
|
@@ -2739,7 +2706,7 @@ class Visit {
|
|
|
2739
2706
|
this.delegate = delegate;
|
|
2740
2707
|
this.location = location;
|
|
2741
2708
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
|
2742
|
-
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} = {
|
|
2709
|
+
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} = {
|
|
2743
2710
|
...defaultOptions,
|
|
2744
2711
|
...options
|
|
2745
2712
|
};
|
|
@@ -2749,7 +2716,6 @@ class Visit {
|
|
|
2749
2716
|
this.snapshot = snapshot;
|
|
2750
2717
|
this.snapshotHTML = snapshotHTML;
|
|
2751
2718
|
this.response = response;
|
|
2752
|
-
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
|
2753
2719
|
this.isPageRefresh = this.view.isPageRefresh(this);
|
|
2754
2720
|
this.visitCachedSnapshot = visitCachedSnapshot;
|
|
2755
2721
|
this.willRender = willRender;
|
|
@@ -2758,6 +2724,7 @@ class Visit {
|
|
|
2758
2724
|
this.shouldCacheSnapshot = shouldCacheSnapshot;
|
|
2759
2725
|
this.acceptsStreamResponse = acceptsStreamResponse;
|
|
2760
2726
|
this.direction = direction || Direction[action];
|
|
2727
|
+
this.refresh = refresh;
|
|
2761
2728
|
}
|
|
2762
2729
|
get adapter() {
|
|
2763
2730
|
return this.delegate.adapter;
|
|
@@ -2771,9 +2738,6 @@ class Visit {
|
|
|
2771
2738
|
get restorationData() {
|
|
2772
2739
|
return this.history.getRestorationDataForIdentifier(this.restorationIdentifier);
|
|
2773
2740
|
}
|
|
2774
|
-
get silent() {
|
|
2775
|
-
return this.isSamePage;
|
|
2776
|
-
}
|
|
2777
2741
|
start() {
|
|
2778
2742
|
if (this.state == VisitState.initialized) {
|
|
2779
2743
|
this.recordTimingMetric(TimingMetric.visitStart);
|
|
@@ -2892,7 +2856,7 @@ class Visit {
|
|
|
2892
2856
|
const isPreview = this.shouldIssueRequest();
|
|
2893
2857
|
this.render((async () => {
|
|
2894
2858
|
this.cacheSnapshot();
|
|
2895
|
-
if (this.
|
|
2859
|
+
if (this.isPageRefresh) {
|
|
2896
2860
|
this.adapter.visitRendered(this);
|
|
2897
2861
|
} else {
|
|
2898
2862
|
if (this.view.renderPromise) await this.view.renderPromise;
|
|
@@ -2916,16 +2880,6 @@ class Visit {
|
|
|
2916
2880
|
this.followedRedirect = true;
|
|
2917
2881
|
}
|
|
2918
2882
|
}
|
|
2919
|
-
goToSamePageAnchor() {
|
|
2920
|
-
if (this.isSamePage) {
|
|
2921
|
-
this.render((async () => {
|
|
2922
|
-
this.cacheSnapshot();
|
|
2923
|
-
this.performScroll();
|
|
2924
|
-
this.changeHistory();
|
|
2925
|
-
this.adapter.visitRendered(this);
|
|
2926
|
-
}));
|
|
2927
|
-
}
|
|
2928
|
-
}
|
|
2929
2883
|
prepareRequest(request) {
|
|
2930
2884
|
if (this.acceptsStreamResponse) {
|
|
2931
2885
|
request.acceptResponseType(StreamMessage.contentType);
|
|
@@ -2984,9 +2938,6 @@ class Visit {
|
|
|
2984
2938
|
} else {
|
|
2985
2939
|
this.scrollToAnchor() || this.view.scrollToTop();
|
|
2986
2940
|
}
|
|
2987
|
-
if (this.isSamePage) {
|
|
2988
|
-
this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);
|
|
2989
|
-
}
|
|
2990
2941
|
this.scrolled = true;
|
|
2991
2942
|
}
|
|
2992
2943
|
}
|
|
@@ -3016,9 +2967,7 @@ class Visit {
|
|
|
3016
2967
|
return typeof this.response == "object";
|
|
3017
2968
|
}
|
|
3018
2969
|
shouldIssueRequest() {
|
|
3019
|
-
if (this.
|
|
3020
|
-
return false;
|
|
3021
|
-
} else if (this.action == "restore") {
|
|
2970
|
+
if (this.action == "restore") {
|
|
3022
2971
|
return !this.hasCachedSnapshot();
|
|
3023
2972
|
} else {
|
|
3024
2973
|
return this.willRender;
|
|
@@ -3073,7 +3022,6 @@ class BrowserAdapter {
|
|
|
3073
3022
|
this.redirectedToLocation = null;
|
|
3074
3023
|
visit.loadCachedSnapshot();
|
|
3075
3024
|
visit.issueRequest();
|
|
3076
|
-
visit.goToSamePageAnchor();
|
|
3077
3025
|
}
|
|
3078
3026
|
visitRequestStarted(visit) {
|
|
3079
3027
|
this.progressBar.setValue(0);
|
|
@@ -3167,7 +3115,6 @@ class BrowserAdapter {
|
|
|
3167
3115
|
|
|
3168
3116
|
class CacheObserver {
|
|
3169
3117
|
selector="[data-turbo-temporary]";
|
|
3170
|
-
deprecatedSelector="[data-turbo-cache=false]";
|
|
3171
3118
|
started=false;
|
|
3172
3119
|
start() {
|
|
3173
3120
|
if (!this.started) {
|
|
@@ -3187,14 +3134,7 @@ class CacheObserver {
|
|
|
3187
3134
|
}
|
|
3188
3135
|
};
|
|
3189
3136
|
get temporaryElements() {
|
|
3190
|
-
return [ ...document.querySelectorAll(this.selector)
|
|
3191
|
-
}
|
|
3192
|
-
get temporaryElementsWithDeprecation() {
|
|
3193
|
-
const elements = document.querySelectorAll(this.deprecatedSelector);
|
|
3194
|
-
if (elements.length) {
|
|
3195
|
-
console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);
|
|
3196
|
-
}
|
|
3197
|
-
return [ ...elements ];
|
|
3137
|
+
return [ ...document.querySelectorAll(this.selector) ];
|
|
3198
3138
|
}
|
|
3199
3139
|
}
|
|
3200
3140
|
|
|
@@ -3262,7 +3202,6 @@ class History {
|
|
|
3262
3202
|
restorationIdentifier=uuid();
|
|
3263
3203
|
restorationData={};
|
|
3264
3204
|
started=false;
|
|
3265
|
-
pageLoaded=false;
|
|
3266
3205
|
currentIndex=0;
|
|
3267
3206
|
constructor(delegate) {
|
|
3268
3207
|
this.delegate = delegate;
|
|
@@ -3270,7 +3209,6 @@ class History {
|
|
|
3270
3209
|
start() {
|
|
3271
3210
|
if (!this.started) {
|
|
3272
3211
|
addEventListener("popstate", this.onPopState, false);
|
|
3273
|
-
addEventListener("load", this.onPageLoad, false);
|
|
3274
3212
|
this.currentIndex = history.state?.turbo?.restorationIndex || 0;
|
|
3275
3213
|
this.started = true;
|
|
3276
3214
|
this.replace(new URL(window.location.href));
|
|
@@ -3279,7 +3217,6 @@ class History {
|
|
|
3279
3217
|
stop() {
|
|
3280
3218
|
if (this.started) {
|
|
3281
3219
|
removeEventListener("popstate", this.onPopState, false);
|
|
3282
|
-
removeEventListener("load", this.onPageLoad, false);
|
|
3283
3220
|
this.started = false;
|
|
3284
3221
|
}
|
|
3285
3222
|
}
|
|
@@ -3325,28 +3262,19 @@ class History {
|
|
|
3325
3262
|
}
|
|
3326
3263
|
}
|
|
3327
3264
|
onPopState=event => {
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3265
|
+
const {turbo: turbo} = event.state || {};
|
|
3266
|
+
this.location = new URL(window.location.href);
|
|
3267
|
+
if (turbo) {
|
|
3268
|
+
const {restorationIdentifier: restorationIdentifier, restorationIndex: restorationIndex} = turbo;
|
|
3269
|
+
this.restorationIdentifier = restorationIdentifier;
|
|
3270
|
+
const direction = restorationIndex > this.currentIndex ? "forward" : "back";
|
|
3271
|
+
this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
|
|
3272
|
+
this.currentIndex = restorationIndex;
|
|
3273
|
+
} else {
|
|
3274
|
+
this.currentIndex++;
|
|
3275
|
+
this.delegate.historyPoppedWithEmptyState(this.location);
|
|
3338
3276
|
}
|
|
3339
3277
|
};
|
|
3340
|
-
onPageLoad=async _event => {
|
|
3341
|
-
await nextMicrotask();
|
|
3342
|
-
this.pageLoaded = true;
|
|
3343
|
-
};
|
|
3344
|
-
shouldHandlePopState() {
|
|
3345
|
-
return this.pageIsLoaded();
|
|
3346
|
-
}
|
|
3347
|
-
pageIsLoaded() {
|
|
3348
|
-
return this.pageLoaded || document.readyState == "complete";
|
|
3349
|
-
}
|
|
3350
3278
|
}
|
|
3351
3279
|
|
|
3352
3280
|
class LinkPrefetchObserver {
|
|
@@ -3402,7 +3330,7 @@ class LinkPrefetchObserver {
|
|
|
3402
3330
|
this.#prefetchedLink = link;
|
|
3403
3331
|
const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, target);
|
|
3404
3332
|
fetchRequest.fetchOptions.priority = "low";
|
|
3405
|
-
prefetchCache.
|
|
3333
|
+
prefetchCache.putLater(location, fetchRequest, this.#cacheTtl);
|
|
3406
3334
|
}
|
|
3407
3335
|
}
|
|
3408
3336
|
};
|
|
@@ -3415,7 +3343,7 @@ class LinkPrefetchObserver {
|
|
|
3415
3343
|
};
|
|
3416
3344
|
#tryToUsePrefetchedRequest=event => {
|
|
3417
3345
|
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
|
|
3418
|
-
const cached = prefetchCache.get(event.detail.url
|
|
3346
|
+
const cached = prefetchCache.get(event.detail.url);
|
|
3419
3347
|
if (cached) {
|
|
3420
3348
|
event.detail.fetchRequest = cached;
|
|
3421
3349
|
}
|
|
@@ -3564,7 +3492,7 @@ class Navigator {
|
|
|
3564
3492
|
} else {
|
|
3565
3493
|
await this.view.renderPage(snapshot, false, true, this.currentVisit);
|
|
3566
3494
|
}
|
|
3567
|
-
if (
|
|
3495
|
+
if (snapshot.refreshScroll !== "preserve") {
|
|
3568
3496
|
this.view.scrollToTop();
|
|
3569
3497
|
}
|
|
3570
3498
|
this.view.clearSnapshotCache();
|
|
@@ -3592,13 +3520,7 @@ class Navigator {
|
|
|
3592
3520
|
delete this.currentVisit;
|
|
3593
3521
|
}
|
|
3594
3522
|
locationWithActionIsSamePage(location, action) {
|
|
3595
|
-
|
|
3596
|
-
const currentAnchor = getAnchor(this.view.lastRenderedLocation);
|
|
3597
|
-
const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
|
|
3598
|
-
return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
|
|
3599
|
-
}
|
|
3600
|
-
visitScrolledToSamePageLocation(oldURL, newURL) {
|
|
3601
|
-
this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
|
|
3523
|
+
return false;
|
|
3602
3524
|
}
|
|
3603
3525
|
get location() {
|
|
3604
3526
|
return this.history.location;
|
|
@@ -3929,12 +3851,17 @@ class PageRenderer extends Renderer {
|
|
|
3929
3851
|
}
|
|
3930
3852
|
#setLanguage() {
|
|
3931
3853
|
const {documentElement: documentElement} = this.currentSnapshot;
|
|
3932
|
-
const {lang: lang} = this.newSnapshot;
|
|
3854
|
+
const {dir: dir, lang: lang} = this.newSnapshot;
|
|
3933
3855
|
if (lang) {
|
|
3934
3856
|
documentElement.setAttribute("lang", lang);
|
|
3935
3857
|
} else {
|
|
3936
3858
|
documentElement.removeAttribute("lang");
|
|
3937
3859
|
}
|
|
3860
|
+
if (dir) {
|
|
3861
|
+
documentElement.setAttribute("dir", dir);
|
|
3862
|
+
} else {
|
|
3863
|
+
documentElement.removeAttribute("dir");
|
|
3864
|
+
}
|
|
3938
3865
|
}
|
|
3939
3866
|
async mergeHead() {
|
|
3940
3867
|
const mergedHeadElements = this.mergeProvisionalElements();
|
|
@@ -4014,8 +3941,14 @@ class PageRenderer extends Renderer {
|
|
|
4014
3941
|
}
|
|
4015
3942
|
activateNewBody() {
|
|
4016
3943
|
document.adoptNode(this.newElement);
|
|
3944
|
+
this.removeNoscriptElements();
|
|
4017
3945
|
this.activateNewBodyScriptElements();
|
|
4018
3946
|
}
|
|
3947
|
+
removeNoscriptElements() {
|
|
3948
|
+
for (const noscriptElement of this.newElement.querySelectorAll("noscript")) {
|
|
3949
|
+
noscriptElement.remove();
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
4019
3952
|
activateNewBodyScriptElements() {
|
|
4020
3953
|
for (const inertScriptElement of this.newBodyScriptElements) {
|
|
4021
3954
|
const activatedScriptElement = activateScriptElement(inertScriptElement);
|
|
@@ -4079,47 +4012,12 @@ class MorphingPageRenderer extends PageRenderer {
|
|
|
4079
4012
|
}
|
|
4080
4013
|
}
|
|
4081
4014
|
|
|
4082
|
-
class SnapshotCache {
|
|
4083
|
-
keys=[];
|
|
4084
|
-
snapshots={};
|
|
4015
|
+
class SnapshotCache extends LRUCache {
|
|
4085
4016
|
constructor(size) {
|
|
4086
|
-
|
|
4087
|
-
}
|
|
4088
|
-
has(location) {
|
|
4089
|
-
return toCacheKey(location) in this.snapshots;
|
|
4090
|
-
}
|
|
4091
|
-
get(location) {
|
|
4092
|
-
if (this.has(location)) {
|
|
4093
|
-
const snapshot = this.read(location);
|
|
4094
|
-
this.touch(location);
|
|
4095
|
-
return snapshot;
|
|
4096
|
-
}
|
|
4097
|
-
}
|
|
4098
|
-
put(location, snapshot) {
|
|
4099
|
-
this.write(location, snapshot);
|
|
4100
|
-
this.touch(location);
|
|
4101
|
-
return snapshot;
|
|
4102
|
-
}
|
|
4103
|
-
clear() {
|
|
4104
|
-
this.snapshots = {};
|
|
4105
|
-
}
|
|
4106
|
-
read(location) {
|
|
4107
|
-
return this.snapshots[toCacheKey(location)];
|
|
4108
|
-
}
|
|
4109
|
-
write(location, snapshot) {
|
|
4110
|
-
this.snapshots[toCacheKey(location)] = snapshot;
|
|
4111
|
-
}
|
|
4112
|
-
touch(location) {
|
|
4113
|
-
const key = toCacheKey(location);
|
|
4114
|
-
const index = this.keys.indexOf(key);
|
|
4115
|
-
if (index > -1) this.keys.splice(index, 1);
|
|
4116
|
-
this.keys.unshift(key);
|
|
4117
|
-
this.trim();
|
|
4017
|
+
super(size, toCacheKey);
|
|
4118
4018
|
}
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
delete this.snapshots[key];
|
|
4122
|
-
}
|
|
4019
|
+
get snapshots() {
|
|
4020
|
+
return this.entries;
|
|
4123
4021
|
}
|
|
4124
4022
|
}
|
|
4125
4023
|
|
|
@@ -4131,7 +4029,7 @@ class PageView extends View {
|
|
|
4131
4029
|
return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions;
|
|
4132
4030
|
}
|
|
4133
4031
|
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
|
4134
|
-
const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.
|
|
4032
|
+
const shouldMorphPage = this.isPageRefresh(visit) && (visit?.refresh?.method || this.snapshot.refreshMethod) === "morph";
|
|
4135
4033
|
const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
|
|
4136
4034
|
const renderer = new rendererClass(this.snapshot, snapshot, isPreview, willRender);
|
|
4137
4035
|
if (!renderer.shouldRender) {
|
|
@@ -4166,7 +4064,7 @@ class PageView extends View {
|
|
|
4166
4064
|
return !visit || this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === "replace";
|
|
4167
4065
|
}
|
|
4168
4066
|
shouldPreserveScrollPosition(visit) {
|
|
4169
|
-
return this.isPageRefresh(visit) && this.snapshot.
|
|
4067
|
+
return this.isPageRefresh(visit) && (visit?.refresh?.scroll || this.snapshot.refreshScroll) === "preserve";
|
|
4170
4068
|
}
|
|
4171
4069
|
get snapshot() {
|
|
4172
4070
|
return PageSnapshot.fromElement(this.element);
|
|
@@ -4319,13 +4217,21 @@ class Session {
|
|
|
4319
4217
|
this.navigator.proposeVisit(expandURL(location), options);
|
|
4320
4218
|
}
|
|
4321
4219
|
}
|
|
4322
|
-
refresh(url,
|
|
4220
|
+
refresh(url, options = {}) {
|
|
4221
|
+
options = typeof options === "string" ? {
|
|
4222
|
+
requestId: options
|
|
4223
|
+
} : options;
|
|
4224
|
+
const {method: method, requestId: requestId, scroll: scroll} = options;
|
|
4323
4225
|
const isRecentRequest = requestId && this.recentRequests.has(requestId);
|
|
4324
4226
|
const isCurrentUrl = url === document.baseURI;
|
|
4325
4227
|
if (!isRecentRequest && !this.navigator.currentVisit && isCurrentUrl) {
|
|
4326
4228
|
this.visit(url, {
|
|
4327
4229
|
action: "replace",
|
|
4328
|
-
shouldCacheSnapshot: false
|
|
4230
|
+
shouldCacheSnapshot: false,
|
|
4231
|
+
refresh: {
|
|
4232
|
+
method: method,
|
|
4233
|
+
scroll: scroll
|
|
4234
|
+
}
|
|
4329
4235
|
});
|
|
4330
4236
|
}
|
|
4331
4237
|
}
|
|
@@ -4401,6 +4307,11 @@ class Session {
|
|
|
4401
4307
|
});
|
|
4402
4308
|
}
|
|
4403
4309
|
}
|
|
4310
|
+
historyPoppedWithEmptyState(location) {
|
|
4311
|
+
this.history.replace(location);
|
|
4312
|
+
this.view.lastRenderedLocation = location;
|
|
4313
|
+
this.view.cacheSnapshot();
|
|
4314
|
+
}
|
|
4404
4315
|
scrollPositionChanged(position) {
|
|
4405
4316
|
this.history.updateRestorationData({
|
|
4406
4317
|
scrollPosition: position
|
|
@@ -4425,7 +4336,7 @@ class Session {
|
|
|
4425
4336
|
});
|
|
4426
4337
|
}
|
|
4427
4338
|
allowsVisitingLocationWithAction(location, action) {
|
|
4428
|
-
return this.
|
|
4339
|
+
return this.applicationAllowsVisitingLocation(location);
|
|
4429
4340
|
}
|
|
4430
4341
|
visitProposedToLocation(location, options) {
|
|
4431
4342
|
extendURLWithDeprecatedProperties(location);
|
|
@@ -4437,21 +4348,13 @@ class Session {
|
|
|
4437
4348
|
this.view.markVisitDirection(visit.direction);
|
|
4438
4349
|
}
|
|
4439
4350
|
extendURLWithDeprecatedProperties(visit.location);
|
|
4440
|
-
|
|
4441
|
-
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
|
|
4442
|
-
}
|
|
4351
|
+
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
|
|
4443
4352
|
}
|
|
4444
4353
|
visitCompleted(visit) {
|
|
4445
4354
|
this.view.unmarkVisitDirection();
|
|
4446
4355
|
clearBusyState(document.documentElement);
|
|
4447
4356
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
|
4448
4357
|
}
|
|
4449
|
-
locationWithActionIsSamePage(location, action) {
|
|
4450
|
-
return this.navigator.locationWithActionIsSamePage(location, action);
|
|
4451
|
-
}
|
|
4452
|
-
visitScrolledToSamePageLocation(oldURL, newURL) {
|
|
4453
|
-
this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
|
|
4454
|
-
}
|
|
4455
4358
|
willSubmitForm(form, submitter) {
|
|
4456
4359
|
const action = getAction$1(form, submitter);
|
|
4457
4360
|
return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
|
|
@@ -4473,9 +4376,7 @@ class Session {
|
|
|
4473
4376
|
this.renderStreamMessage(message);
|
|
4474
4377
|
}
|
|
4475
4378
|
viewWillCacheSnapshot() {
|
|
4476
|
-
|
|
4477
|
-
this.notifyApplicationBeforeCachingSnapshot();
|
|
4478
|
-
}
|
|
4379
|
+
this.notifyApplicationBeforeCachingSnapshot();
|
|
4479
4380
|
}
|
|
4480
4381
|
allowsImmediateRender({element: element}, options) {
|
|
4481
4382
|
const event = this.notifyApplicationBeforeRender(element, options);
|
|
@@ -4562,12 +4463,6 @@ class Session {
|
|
|
4562
4463
|
}
|
|
4563
4464
|
});
|
|
4564
4465
|
}
|
|
4565
|
-
notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
|
|
4566
|
-
dispatchEvent(new HashChangeEvent("hashchange", {
|
|
4567
|
-
oldURL: oldURL.toString(),
|
|
4568
|
-
newURL: newURL.toString()
|
|
4569
|
-
}));
|
|
4570
|
-
}
|
|
4571
4466
|
notifyApplicationAfterFrameLoad(frame) {
|
|
4572
4467
|
return dispatch("turbo:frame-load", {
|
|
4573
4468
|
target: frame
|
|
@@ -4633,7 +4528,7 @@ const deprecatedLocationPropertyDescriptors = {
|
|
|
4633
4528
|
|
|
4634
4529
|
const session = new Session(recentRequests);
|
|
4635
4530
|
|
|
4636
|
-
const {cache: cache, navigator:
|
|
4531
|
+
const {cache: cache, navigator: sessionNavigator} = session;
|
|
4637
4532
|
|
|
4638
4533
|
function start() {
|
|
4639
4534
|
session.start();
|
|
@@ -4659,11 +4554,6 @@ function renderStreamMessage(message) {
|
|
|
4659
4554
|
session.renderStreamMessage(message);
|
|
4660
4555
|
}
|
|
4661
4556
|
|
|
4662
|
-
function clearCache() {
|
|
4663
|
-
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.`");
|
|
4664
|
-
session.clearCache();
|
|
4665
|
-
}
|
|
4666
|
-
|
|
4667
4557
|
function setProgressBarDelay(delay) {
|
|
4668
4558
|
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.`");
|
|
4669
4559
|
config.drive.progressBarDelay = delay;
|
|
@@ -4689,21 +4579,20 @@ function morphTurboFrameElements(currentFrame, newFrame) {
|
|
|
4689
4579
|
|
|
4690
4580
|
var Turbo = Object.freeze({
|
|
4691
4581
|
__proto__: null,
|
|
4692
|
-
navigator: navigator$1,
|
|
4693
|
-
session: session,
|
|
4694
|
-
cache: cache,
|
|
4695
4582
|
PageRenderer: PageRenderer,
|
|
4696
4583
|
PageSnapshot: PageSnapshot,
|
|
4697
4584
|
FrameRenderer: FrameRenderer,
|
|
4698
4585
|
fetch: fetchWithTurboHeaders,
|
|
4699
4586
|
config: config,
|
|
4587
|
+
session: session,
|
|
4588
|
+
cache: cache,
|
|
4589
|
+
navigator: sessionNavigator,
|
|
4700
4590
|
start: start,
|
|
4701
4591
|
registerAdapter: registerAdapter,
|
|
4702
4592
|
visit: visit,
|
|
4703
4593
|
connectStreamSource: connectStreamSource,
|
|
4704
4594
|
disconnectStreamSource: disconnectStreamSource,
|
|
4705
4595
|
renderStreamMessage: renderStreamMessage,
|
|
4706
|
-
clearCache: clearCache,
|
|
4707
4596
|
setProgressBarDelay: setProgressBarDelay,
|
|
4708
4597
|
setConfirmMethod: setConfirmMethod,
|
|
4709
4598
|
setFormMode: setFormMode,
|
|
@@ -4753,15 +4642,23 @@ class FrameController {
|
|
|
4753
4642
|
this.formLinkClickObserver.stop();
|
|
4754
4643
|
this.linkInterceptor.stop();
|
|
4755
4644
|
this.formSubmitObserver.stop();
|
|
4645
|
+
if (!this.element.hasAttribute("recurse")) {
|
|
4646
|
+
this.#currentFetchRequest?.cancel();
|
|
4647
|
+
}
|
|
4756
4648
|
}
|
|
4757
4649
|
}
|
|
4758
4650
|
disabledChanged() {
|
|
4759
|
-
if (this.
|
|
4651
|
+
if (this.disabled) {
|
|
4652
|
+
this.#currentFetchRequest?.cancel();
|
|
4653
|
+
} else if (this.loadingStyle == FrameLoadingStyle.eager) {
|
|
4760
4654
|
this.#loadSourceURL();
|
|
4761
4655
|
}
|
|
4762
4656
|
}
|
|
4763
4657
|
sourceURLChanged() {
|
|
4764
4658
|
if (this.#isIgnoringChangesTo("src")) return;
|
|
4659
|
+
if (!this.sourceURL) {
|
|
4660
|
+
this.#currentFetchRequest?.cancel();
|
|
4661
|
+
}
|
|
4765
4662
|
if (this.element.isConnected) {
|
|
4766
4663
|
this.complete = false;
|
|
4767
4664
|
}
|
|
@@ -4839,11 +4736,12 @@ class FrameController {
|
|
|
4839
4736
|
}
|
|
4840
4737
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
|
4841
4738
|
const {fetchRequest: fetchRequest} = this.formSubmission;
|
|
4842
|
-
this
|
|
4739
|
+
const frame = this.#findFrameElement(element, submitter);
|
|
4740
|
+
this.prepareRequest(fetchRequest, frame);
|
|
4843
4741
|
this.formSubmission.start();
|
|
4844
4742
|
}
|
|
4845
|
-
prepareRequest(request) {
|
|
4846
|
-
request.headers["Turbo-Frame"] =
|
|
4743
|
+
prepareRequest(request, frame = this) {
|
|
4744
|
+
request.headers["Turbo-Frame"] = frame.id;
|
|
4847
4745
|
if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
|
|
4848
4746
|
request.acceptResponseType(StreamMessage.contentType);
|
|
4849
4747
|
}
|
|
@@ -5037,7 +4935,8 @@ class FrameController {
|
|
|
5037
4935
|
}
|
|
5038
4936
|
#findFrameElement(element, submitter) {
|
|
5039
4937
|
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
|
5040
|
-
|
|
4938
|
+
const target = this.#getFrameElementById(id);
|
|
4939
|
+
return target instanceof FrameElement ? target : this.element;
|
|
5041
4940
|
}
|
|
5042
4941
|
async extractForeignFrameElement(container) {
|
|
5043
4942
|
let element;
|
|
@@ -5071,9 +4970,11 @@ class FrameController {
|
|
|
5071
4970
|
return false;
|
|
5072
4971
|
}
|
|
5073
4972
|
if (id) {
|
|
5074
|
-
const frameElement = getFrameElementById(id);
|
|
4973
|
+
const frameElement = this.#getFrameElementById(id);
|
|
5075
4974
|
if (frameElement) {
|
|
5076
4975
|
return !frameElement.disabled;
|
|
4976
|
+
} else if (id == "_parent") {
|
|
4977
|
+
return false;
|
|
5077
4978
|
}
|
|
5078
4979
|
}
|
|
5079
4980
|
if (!session.elementIsNavigatable(element)) {
|
|
@@ -5087,8 +4988,11 @@ class FrameController {
|
|
|
5087
4988
|
get id() {
|
|
5088
4989
|
return this.element.id;
|
|
5089
4990
|
}
|
|
4991
|
+
get disabled() {
|
|
4992
|
+
return this.element.disabled;
|
|
4993
|
+
}
|
|
5090
4994
|
get enabled() {
|
|
5091
|
-
return !this.
|
|
4995
|
+
return !this.disabled;
|
|
5092
4996
|
}
|
|
5093
4997
|
get sourceURL() {
|
|
5094
4998
|
if (this.element.src) {
|
|
@@ -5137,13 +5041,12 @@ class FrameController {
|
|
|
5137
5041
|
callback();
|
|
5138
5042
|
delete this.currentNavigationElement;
|
|
5139
5043
|
}
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
return element;
|
|
5044
|
+
#getFrameElementById(id) {
|
|
5045
|
+
if (id != null) {
|
|
5046
|
+
const element = id === "_parent" ? this.element.parentElement.closest("turbo-frame") : document.getElementById(id);
|
|
5047
|
+
if (element instanceof FrameElement) {
|
|
5048
|
+
return element;
|
|
5049
|
+
}
|
|
5147
5050
|
}
|
|
5148
5051
|
}
|
|
5149
5052
|
}
|
|
@@ -5167,6 +5070,7 @@ function activateElement(element, currentURL) {
|
|
|
5167
5070
|
|
|
5168
5071
|
const StreamActions = {
|
|
5169
5072
|
after() {
|
|
5073
|
+
this.removeDuplicateTargetSiblings();
|
|
5170
5074
|
this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e.nextSibling)));
|
|
5171
5075
|
},
|
|
5172
5076
|
append() {
|
|
@@ -5174,6 +5078,7 @@ const StreamActions = {
|
|
|
5174
5078
|
this.targetElements.forEach((e => e.append(this.templateContent)));
|
|
5175
5079
|
},
|
|
5176
5080
|
before() {
|
|
5081
|
+
this.removeDuplicateTargetSiblings();
|
|
5177
5082
|
this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e)));
|
|
5178
5083
|
},
|
|
5179
5084
|
prepend() {
|
|
@@ -5205,7 +5110,14 @@ const StreamActions = {
|
|
|
5205
5110
|
}));
|
|
5206
5111
|
},
|
|
5207
5112
|
refresh() {
|
|
5208
|
-
|
|
5113
|
+
const method = this.getAttribute("method");
|
|
5114
|
+
const requestId = this.requestId;
|
|
5115
|
+
const scroll = this.getAttribute("scroll");
|
|
5116
|
+
session.refresh(this.baseURI, {
|
|
5117
|
+
method: method,
|
|
5118
|
+
requestId: requestId,
|
|
5119
|
+
scroll: scroll
|
|
5120
|
+
});
|
|
5209
5121
|
}
|
|
5210
5122
|
};
|
|
5211
5123
|
|
|
@@ -5244,6 +5156,14 @@ class StreamElement extends HTMLElement {
|
|
|
5244
5156
|
const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.getAttribute("id"))).map((c => c.getAttribute("id")));
|
|
5245
5157
|
return existingChildren.filter((c => newChildrenIds.includes(c.getAttribute("id"))));
|
|
5246
5158
|
}
|
|
5159
|
+
removeDuplicateTargetSiblings() {
|
|
5160
|
+
this.duplicateSiblings.forEach((c => c.remove()));
|
|
5161
|
+
}
|
|
5162
|
+
get duplicateSiblings() {
|
|
5163
|
+
const existingChildren = this.targetElements.flatMap((e => [ ...e.parentElement.children ])).filter((c => !!c.id));
|
|
5164
|
+
const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
|
|
5165
|
+
return existingChildren.filter((c => newChildrenIds.includes(c.id)));
|
|
5166
|
+
}
|
|
5247
5167
|
get performAction() {
|
|
5248
5168
|
if (this.action) {
|
|
5249
5169
|
const actionFunction = StreamActions[this.action];
|
|
@@ -5354,10 +5274,10 @@ if (customElements.get("turbo-stream-source") === undefined) {
|
|
|
5354
5274
|
}
|
|
5355
5275
|
|
|
5356
5276
|
(() => {
|
|
5357
|
-
|
|
5358
|
-
if (!
|
|
5359
|
-
if (
|
|
5360
|
-
element =
|
|
5277
|
+
const scriptElement = document.currentScript;
|
|
5278
|
+
if (!scriptElement) return;
|
|
5279
|
+
if (scriptElement.hasAttribute("data-turbo-suppress-warning")) return;
|
|
5280
|
+
let element = scriptElement.parentElement;
|
|
5361
5281
|
while (element) {
|
|
5362
5282
|
if (element == document.body) {
|
|
5363
5283
|
return console.warn(unindent`
|
|
@@ -5369,7 +5289,7 @@ if (customElements.get("turbo-stream-source") === undefined) {
|
|
|
5369
5289
|
|
|
5370
5290
|
——
|
|
5371
5291
|
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
|
5372
|
-
`,
|
|
5292
|
+
`, scriptElement.outerHTML);
|
|
5373
5293
|
}
|
|
5374
5294
|
element = element.parentElement;
|
|
5375
5295
|
}
|
|
@@ -5397,7 +5317,6 @@ var Turbo$1 = Object.freeze({
|
|
|
5397
5317
|
StreamElement: StreamElement,
|
|
5398
5318
|
StreamSourceElement: StreamSourceElement,
|
|
5399
5319
|
cache: cache,
|
|
5400
|
-
clearCache: clearCache,
|
|
5401
5320
|
config: config,
|
|
5402
5321
|
connectStreamSource: connectStreamSource,
|
|
5403
5322
|
disconnectStreamSource: disconnectStreamSource,
|
|
@@ -5409,7 +5328,7 @@ var Turbo$1 = Object.freeze({
|
|
|
5409
5328
|
morphChildren: morphChildren,
|
|
5410
5329
|
morphElements: morphElements,
|
|
5411
5330
|
morphTurboFrameElements: morphTurboFrameElements,
|
|
5412
|
-
navigator:
|
|
5331
|
+
navigator: sessionNavigator,
|
|
5413
5332
|
registerAdapter: registerAdapter,
|
|
5414
5333
|
renderStreamMessage: renderStreamMessage,
|
|
5415
5334
|
session: session,
|