@hotwired/turbo 8.0.20 → 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/dist/turbo.es2017-esm.js +242 -326
- package/dist/turbo.es2017-umd.js +242 -327
- package/package.json +2 -3
package/dist/turbo.es2017-umd.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
Turbo 8.0.
|
|
3
|
-
Copyright ©
|
|
2
|
+
Turbo 8.0.21
|
|
3
|
+
Copyright © 2026 37signals LLC
|
|
4
4
|
*/
|
|
5
5
|
(function (global, factory) {
|
|
6
6
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
@@ -8,103 +8,6 @@ Copyright © 2025 37signals LLC
|
|
|
8
8
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Turbo = {}));
|
|
9
9
|
})(this, (function (exports) { 'use strict';
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
* The MIT License (MIT)
|
|
13
|
-
*
|
|
14
|
-
* Copyright (c) 2019 Javan Makhmali
|
|
15
|
-
*
|
|
16
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
17
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
18
|
-
* in the Software without restriction, including without limitation the rights
|
|
19
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
20
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
21
|
-
* furnished to do so, subject to the following conditions:
|
|
22
|
-
*
|
|
23
|
-
* The above copyright notice and this permission notice shall be included in
|
|
24
|
-
* all copies or substantial portions of the Software.
|
|
25
|
-
*
|
|
26
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
27
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
28
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
29
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
30
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
31
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
32
|
-
* THE SOFTWARE.
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
(function (prototype) {
|
|
36
|
-
if (typeof prototype.requestSubmit == "function") return
|
|
37
|
-
|
|
38
|
-
prototype.requestSubmit = function (submitter) {
|
|
39
|
-
if (submitter) {
|
|
40
|
-
validateSubmitter(submitter, this);
|
|
41
|
-
submitter.click();
|
|
42
|
-
} else {
|
|
43
|
-
submitter = document.createElement("input");
|
|
44
|
-
submitter.type = "submit";
|
|
45
|
-
submitter.hidden = true;
|
|
46
|
-
this.appendChild(submitter);
|
|
47
|
-
submitter.click();
|
|
48
|
-
this.removeChild(submitter);
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function validateSubmitter(submitter, form) {
|
|
53
|
-
submitter instanceof HTMLElement || raise(TypeError, "parameter 1 is not of type 'HTMLElement'");
|
|
54
|
-
submitter.type == "submit" || raise(TypeError, "The specified element is not a submit button");
|
|
55
|
-
submitter.form == form ||
|
|
56
|
-
raise(DOMException, "The specified element is not owned by this form element", "NotFoundError");
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function raise(errorConstructor, message, name) {
|
|
60
|
-
throw new errorConstructor("Failed to execute 'requestSubmit' on 'HTMLFormElement': " + message + ".", name)
|
|
61
|
-
}
|
|
62
|
-
})(HTMLFormElement.prototype);
|
|
63
|
-
|
|
64
|
-
const submittersByForm = new WeakMap();
|
|
65
|
-
|
|
66
|
-
function findSubmitterFromClickTarget(target) {
|
|
67
|
-
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
|
68
|
-
const candidate = element ? element.closest("input, button") : null;
|
|
69
|
-
return candidate?.type == "submit" ? candidate : null
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function clickCaptured(event) {
|
|
73
|
-
const submitter = findSubmitterFromClickTarget(event.target);
|
|
74
|
-
|
|
75
|
-
if (submitter && submitter.form) {
|
|
76
|
-
submittersByForm.set(submitter.form, submitter);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
(function () {
|
|
81
|
-
if ("submitter" in Event.prototype) return
|
|
82
|
-
|
|
83
|
-
let prototype = window.Event.prototype;
|
|
84
|
-
// Certain versions of Safari 15 have a bug where they won't
|
|
85
|
-
// populate the submitter. This hurts TurboDrive's enable/disable detection.
|
|
86
|
-
// See https://bugs.webkit.org/show_bug.cgi?id=229660
|
|
87
|
-
if ("SubmitEvent" in window) {
|
|
88
|
-
const prototypeOfSubmitEvent = window.SubmitEvent.prototype;
|
|
89
|
-
|
|
90
|
-
if (/Apple Computer/.test(navigator.vendor) && !("submitter" in prototypeOfSubmitEvent)) {
|
|
91
|
-
prototype = prototypeOfSubmitEvent;
|
|
92
|
-
} else {
|
|
93
|
-
return // polyfill not needed
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
addEventListener("click", clickCaptured, true);
|
|
98
|
-
|
|
99
|
-
Object.defineProperty(prototype, "submitter", {
|
|
100
|
-
get() {
|
|
101
|
-
if (this.type == "submit" && this.target instanceof HTMLFormElement) {
|
|
102
|
-
return submittersByForm.get(this.target)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
})();
|
|
107
|
-
|
|
108
11
|
const FrameLoadingStyle = {
|
|
109
12
|
eager: "eager",
|
|
110
13
|
lazy: "lazy"
|
|
@@ -380,10 +283,6 @@ Copyright © 2025 37signals LLC
|
|
|
380
283
|
return new Promise((resolve) => setTimeout(() => resolve(), 0))
|
|
381
284
|
}
|
|
382
285
|
|
|
383
|
-
function nextMicrotask() {
|
|
384
|
-
return Promise.resolve()
|
|
385
|
-
}
|
|
386
|
-
|
|
387
286
|
function parseHTMLDocument(html = "") {
|
|
388
287
|
return new DOMParser().parseFromString(html, "text/html")
|
|
389
288
|
}
|
|
@@ -412,7 +311,7 @@ Copyright © 2025 37signals LLC
|
|
|
412
311
|
} else if (i == 19) {
|
|
413
312
|
return (Math.floor(Math.random() * 4) + 8).toString(16)
|
|
414
313
|
} else {
|
|
415
|
-
return Math.floor(Math.random() *
|
|
314
|
+
return Math.floor(Math.random() * 16).toString(16)
|
|
416
315
|
}
|
|
417
316
|
})
|
|
418
317
|
.join("")
|
|
@@ -564,14 +463,13 @@ Copyright © 2025 37signals LLC
|
|
|
564
463
|
const link = findClosestRecursively(target, "a[href], a[xlink\\:href]");
|
|
565
464
|
|
|
566
465
|
if (!link) return null
|
|
466
|
+
if (link.href.startsWith("#")) return null
|
|
567
467
|
if (link.hasAttribute("download")) return null
|
|
568
|
-
if (link.hasAttribute("target") && link.target !== "_self") return null
|
|
569
468
|
|
|
570
|
-
|
|
571
|
-
|
|
469
|
+
const linkTarget = link.getAttribute("target");
|
|
470
|
+
if (linkTarget && linkTarget !== "_self") return null
|
|
572
471
|
|
|
573
|
-
|
|
574
|
-
return expandURL(link.getAttribute("href") || "")
|
|
472
|
+
return link
|
|
575
473
|
}
|
|
576
474
|
|
|
577
475
|
function debounce(fn, delay) {
|
|
@@ -662,6 +560,10 @@ Copyright © 2025 37signals LLC
|
|
|
662
560
|
return isPrefixedBy(location, rootLocation) && !config.drive.unvisitableExtensions.has(getExtension(location))
|
|
663
561
|
}
|
|
664
562
|
|
|
563
|
+
function getLocationForLink(link) {
|
|
564
|
+
return expandURL(link.getAttribute("href") || "")
|
|
565
|
+
}
|
|
566
|
+
|
|
665
567
|
function getRequestURL(url) {
|
|
666
568
|
const anchor = getAnchor(url);
|
|
667
569
|
return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href
|
|
@@ -1077,35 +979,113 @@ Copyright © 2025 37signals LLC
|
|
|
1077
979
|
return fragment
|
|
1078
980
|
}
|
|
1079
981
|
|
|
1080
|
-
const
|
|
982
|
+
const identity = key => key;
|
|
1081
983
|
|
|
1082
|
-
class
|
|
1083
|
-
|
|
1084
|
-
|
|
984
|
+
class LRUCache {
|
|
985
|
+
keys = []
|
|
986
|
+
entries = {}
|
|
987
|
+
#toCacheKey
|
|
988
|
+
|
|
989
|
+
constructor(size, toCacheKey = identity) {
|
|
990
|
+
this.size = size;
|
|
991
|
+
this.#toCacheKey = toCacheKey;
|
|
992
|
+
}
|
|
1085
993
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
994
|
+
has(key) {
|
|
995
|
+
return this.#toCacheKey(key) in this.entries
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
get(key) {
|
|
999
|
+
if (this.has(key)) {
|
|
1000
|
+
const entry = this.read(key);
|
|
1001
|
+
this.touch(key);
|
|
1002
|
+
return entry
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
put(key, entry) {
|
|
1007
|
+
this.write(key, entry);
|
|
1008
|
+
this.touch(key);
|
|
1009
|
+
return entry
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
clear() {
|
|
1013
|
+
for (const key of Object.keys(this.entries)) {
|
|
1014
|
+
this.evict(key);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// Private
|
|
1019
|
+
|
|
1020
|
+
read(key) {
|
|
1021
|
+
return this.entries[this.#toCacheKey(key)]
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
write(key, entry) {
|
|
1025
|
+
this.entries[this.#toCacheKey(key)] = entry;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
touch(key) {
|
|
1029
|
+
key = this.#toCacheKey(key);
|
|
1030
|
+
const index = this.keys.indexOf(key);
|
|
1031
|
+
if (index > -1) this.keys.splice(index, 1);
|
|
1032
|
+
this.keys.unshift(key);
|
|
1033
|
+
this.trim();
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
trim() {
|
|
1037
|
+
for (const key of this.keys.splice(this.size)) {
|
|
1038
|
+
this.evict(key);
|
|
1089
1039
|
}
|
|
1090
1040
|
}
|
|
1091
1041
|
|
|
1092
|
-
|
|
1093
|
-
this.
|
|
1042
|
+
evict(key) {
|
|
1043
|
+
delete this.entries[key];
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
const PREFETCH_DELAY = 100;
|
|
1048
|
+
|
|
1049
|
+
class PrefetchCache extends LRUCache {
|
|
1050
|
+
#prefetchTimeout = null
|
|
1051
|
+
#maxAges = {}
|
|
1052
|
+
|
|
1053
|
+
constructor(size = 1, prefetchDelay = PREFETCH_DELAY) {
|
|
1054
|
+
super(size, toCacheKey);
|
|
1055
|
+
this.prefetchDelay = prefetchDelay;
|
|
1056
|
+
}
|
|
1094
1057
|
|
|
1058
|
+
putLater(url, request, ttl) {
|
|
1095
1059
|
this.#prefetchTimeout = setTimeout(() => {
|
|
1096
1060
|
request.perform();
|
|
1097
|
-
this.
|
|
1061
|
+
this.put(url, request, ttl);
|
|
1098
1062
|
this.#prefetchTimeout = null;
|
|
1099
|
-
},
|
|
1063
|
+
}, this.prefetchDelay);
|
|
1100
1064
|
}
|
|
1101
1065
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1066
|
+
put(url, request, ttl = cacheTtl) {
|
|
1067
|
+
super.put(url, request);
|
|
1068
|
+
this.#maxAges[toCacheKey(url)] = new Date(new Date().getTime() + ttl);
|
|
1104
1069
|
}
|
|
1105
1070
|
|
|
1106
1071
|
clear() {
|
|
1072
|
+
super.clear();
|
|
1107
1073
|
if (this.#prefetchTimeout) clearTimeout(this.#prefetchTimeout);
|
|
1108
|
-
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
evict(key) {
|
|
1077
|
+
super.evict(key);
|
|
1078
|
+
delete this.#maxAges[key];
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
has(key) {
|
|
1082
|
+
if (super.has(key)) {
|
|
1083
|
+
const maxAge = this.#maxAges[toCacheKey(key)];
|
|
1084
|
+
|
|
1085
|
+
return maxAge && maxAge > Date.now()
|
|
1086
|
+
} else {
|
|
1087
|
+
return false
|
|
1088
|
+
}
|
|
1109
1089
|
}
|
|
1110
1090
|
}
|
|
1111
1091
|
|
|
@@ -3808,6 +3788,10 @@ Copyright © 2025 37signals LLC
|
|
|
3808
3788
|
clonedPasswordInput.value = "";
|
|
3809
3789
|
}
|
|
3810
3790
|
|
|
3791
|
+
for (const clonedNoscriptElement of clonedElement.querySelectorAll("noscript")) {
|
|
3792
|
+
clonedNoscriptElement.remove();
|
|
3793
|
+
}
|
|
3794
|
+
|
|
3811
3795
|
return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot)
|
|
3812
3796
|
}
|
|
3813
3797
|
|
|
@@ -3815,6 +3799,10 @@ Copyright © 2025 37signals LLC
|
|
|
3815
3799
|
return this.documentElement.getAttribute("lang")
|
|
3816
3800
|
}
|
|
3817
3801
|
|
|
3802
|
+
get dir() {
|
|
3803
|
+
return this.documentElement.getAttribute("dir")
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3818
3806
|
get headElement() {
|
|
3819
3807
|
return this.headSnapshot.element
|
|
3820
3808
|
}
|
|
@@ -3845,12 +3833,12 @@ Copyright © 2025 37signals LLC
|
|
|
3845
3833
|
return viewTransitionEnabled && !window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
3846
3834
|
}
|
|
3847
3835
|
|
|
3848
|
-
get
|
|
3849
|
-
return this.getSetting("refresh-method")
|
|
3836
|
+
get refreshMethod() {
|
|
3837
|
+
return this.getSetting("refresh-method")
|
|
3850
3838
|
}
|
|
3851
3839
|
|
|
3852
|
-
get
|
|
3853
|
-
return this.getSetting("refresh-scroll")
|
|
3840
|
+
get refreshScroll() {
|
|
3841
|
+
return this.getSetting("refresh-scroll")
|
|
3854
3842
|
}
|
|
3855
3843
|
|
|
3856
3844
|
// Private
|
|
@@ -3889,7 +3877,8 @@ Copyright © 2025 37signals LLC
|
|
|
3889
3877
|
willRender: true,
|
|
3890
3878
|
updateHistory: true,
|
|
3891
3879
|
shouldCacheSnapshot: true,
|
|
3892
|
-
acceptsStreamResponse: false
|
|
3880
|
+
acceptsStreamResponse: false,
|
|
3881
|
+
refresh: {}
|
|
3893
3882
|
};
|
|
3894
3883
|
|
|
3895
3884
|
const TimingMetric = {
|
|
@@ -3949,7 +3938,8 @@ Copyright © 2025 37signals LLC
|
|
|
3949
3938
|
updateHistory,
|
|
3950
3939
|
shouldCacheSnapshot,
|
|
3951
3940
|
acceptsStreamResponse,
|
|
3952
|
-
direction
|
|
3941
|
+
direction,
|
|
3942
|
+
refresh
|
|
3953
3943
|
} = {
|
|
3954
3944
|
...defaultOptions,
|
|
3955
3945
|
...options
|
|
@@ -3960,7 +3950,6 @@ Copyright © 2025 37signals LLC
|
|
|
3960
3950
|
this.snapshot = snapshot;
|
|
3961
3951
|
this.snapshotHTML = snapshotHTML;
|
|
3962
3952
|
this.response = response;
|
|
3963
|
-
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
|
3964
3953
|
this.isPageRefresh = this.view.isPageRefresh(this);
|
|
3965
3954
|
this.visitCachedSnapshot = visitCachedSnapshot;
|
|
3966
3955
|
this.willRender = willRender;
|
|
@@ -3969,6 +3958,7 @@ Copyright © 2025 37signals LLC
|
|
|
3969
3958
|
this.shouldCacheSnapshot = shouldCacheSnapshot;
|
|
3970
3959
|
this.acceptsStreamResponse = acceptsStreamResponse;
|
|
3971
3960
|
this.direction = direction || Direction[action];
|
|
3961
|
+
this.refresh = refresh;
|
|
3972
3962
|
}
|
|
3973
3963
|
|
|
3974
3964
|
get adapter() {
|
|
@@ -3987,10 +3977,6 @@ Copyright © 2025 37signals LLC
|
|
|
3987
3977
|
return this.history.getRestorationDataForIdentifier(this.restorationIdentifier)
|
|
3988
3978
|
}
|
|
3989
3979
|
|
|
3990
|
-
get silent() {
|
|
3991
|
-
return this.isSamePage
|
|
3992
|
-
}
|
|
3993
|
-
|
|
3994
3980
|
start() {
|
|
3995
3981
|
if (this.state == VisitState.initialized) {
|
|
3996
3982
|
this.recordTimingMetric(TimingMetric.visitStart);
|
|
@@ -4127,7 +4113,7 @@ Copyright © 2025 37signals LLC
|
|
|
4127
4113
|
const isPreview = this.shouldIssueRequest();
|
|
4128
4114
|
this.render(async () => {
|
|
4129
4115
|
this.cacheSnapshot();
|
|
4130
|
-
if (this.
|
|
4116
|
+
if (this.isPageRefresh) {
|
|
4131
4117
|
this.adapter.visitRendered(this);
|
|
4132
4118
|
} else {
|
|
4133
4119
|
if (this.view.renderPromise) await this.view.renderPromise;
|
|
@@ -4155,17 +4141,6 @@ Copyright © 2025 37signals LLC
|
|
|
4155
4141
|
}
|
|
4156
4142
|
}
|
|
4157
4143
|
|
|
4158
|
-
goToSamePageAnchor() {
|
|
4159
|
-
if (this.isSamePage) {
|
|
4160
|
-
this.render(async () => {
|
|
4161
|
-
this.cacheSnapshot();
|
|
4162
|
-
this.performScroll();
|
|
4163
|
-
this.changeHistory();
|
|
4164
|
-
this.adapter.visitRendered(this);
|
|
4165
|
-
});
|
|
4166
|
-
}
|
|
4167
|
-
}
|
|
4168
|
-
|
|
4169
4144
|
// Fetch request delegate
|
|
4170
4145
|
|
|
4171
4146
|
prepareRequest(request) {
|
|
@@ -4227,9 +4202,6 @@ Copyright © 2025 37signals LLC
|
|
|
4227
4202
|
} else {
|
|
4228
4203
|
this.scrollToAnchor() || this.view.scrollToTop();
|
|
4229
4204
|
}
|
|
4230
|
-
if (this.isSamePage) {
|
|
4231
|
-
this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);
|
|
4232
|
-
}
|
|
4233
4205
|
|
|
4234
4206
|
this.scrolled = true;
|
|
4235
4207
|
}
|
|
@@ -4268,9 +4240,7 @@ Copyright © 2025 37signals LLC
|
|
|
4268
4240
|
}
|
|
4269
4241
|
|
|
4270
4242
|
shouldIssueRequest() {
|
|
4271
|
-
if (this.
|
|
4272
|
-
return false
|
|
4273
|
-
} else if (this.action == "restore") {
|
|
4243
|
+
if (this.action == "restore") {
|
|
4274
4244
|
return !this.hasCachedSnapshot()
|
|
4275
4245
|
} else {
|
|
4276
4246
|
return this.willRender
|
|
@@ -4334,7 +4304,6 @@ Copyright © 2025 37signals LLC
|
|
|
4334
4304
|
|
|
4335
4305
|
visit.loadCachedSnapshot();
|
|
4336
4306
|
visit.issueRequest();
|
|
4337
|
-
visit.goToSamePageAnchor();
|
|
4338
4307
|
}
|
|
4339
4308
|
|
|
4340
4309
|
visitRequestStarted(visit) {
|
|
@@ -4451,7 +4420,6 @@ Copyright © 2025 37signals LLC
|
|
|
4451
4420
|
|
|
4452
4421
|
class CacheObserver {
|
|
4453
4422
|
selector = "[data-turbo-temporary]"
|
|
4454
|
-
deprecatedSelector = "[data-turbo-cache=false]"
|
|
4455
4423
|
|
|
4456
4424
|
started = false
|
|
4457
4425
|
|
|
@@ -4476,19 +4444,7 @@ Copyright © 2025 37signals LLC
|
|
|
4476
4444
|
}
|
|
4477
4445
|
|
|
4478
4446
|
get temporaryElements() {
|
|
4479
|
-
return [...document.querySelectorAll(this.selector)
|
|
4480
|
-
}
|
|
4481
|
-
|
|
4482
|
-
get temporaryElementsWithDeprecation() {
|
|
4483
|
-
const elements = document.querySelectorAll(this.deprecatedSelector);
|
|
4484
|
-
|
|
4485
|
-
if (elements.length) {
|
|
4486
|
-
console.warn(
|
|
4487
|
-
`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`
|
|
4488
|
-
);
|
|
4489
|
-
}
|
|
4490
|
-
|
|
4491
|
-
return [...elements]
|
|
4447
|
+
return [...document.querySelectorAll(this.selector)]
|
|
4492
4448
|
}
|
|
4493
4449
|
}
|
|
4494
4450
|
|
|
@@ -4578,7 +4534,6 @@ Copyright © 2025 37signals LLC
|
|
|
4578
4534
|
restorationIdentifier = uuid()
|
|
4579
4535
|
restorationData = {}
|
|
4580
4536
|
started = false
|
|
4581
|
-
pageLoaded = false
|
|
4582
4537
|
currentIndex = 0
|
|
4583
4538
|
|
|
4584
4539
|
constructor(delegate) {
|
|
@@ -4588,7 +4543,6 @@ Copyright © 2025 37signals LLC
|
|
|
4588
4543
|
start() {
|
|
4589
4544
|
if (!this.started) {
|
|
4590
4545
|
addEventListener("popstate", this.onPopState, false);
|
|
4591
|
-
addEventListener("load", this.onPageLoad, false);
|
|
4592
4546
|
this.currentIndex = history.state?.turbo?.restorationIndex || 0;
|
|
4593
4547
|
this.started = true;
|
|
4594
4548
|
this.replace(new URL(window.location.href));
|
|
@@ -4598,7 +4552,6 @@ Copyright © 2025 37signals LLC
|
|
|
4598
4552
|
stop() {
|
|
4599
4553
|
if (this.started) {
|
|
4600
4554
|
removeEventListener("popstate", this.onPopState, false);
|
|
4601
|
-
removeEventListener("load", this.onPageLoad, false);
|
|
4602
4555
|
this.started = false;
|
|
4603
4556
|
}
|
|
4604
4557
|
}
|
|
@@ -4654,34 +4607,20 @@ Copyright © 2025 37signals LLC
|
|
|
4654
4607
|
// Event handlers
|
|
4655
4608
|
|
|
4656
4609
|
onPopState = (event) => {
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4610
|
+
const { turbo } = event.state || {};
|
|
4611
|
+
this.location = new URL(window.location.href);
|
|
4612
|
+
|
|
4613
|
+
if (turbo) {
|
|
4614
|
+
const { restorationIdentifier, restorationIndex } = turbo;
|
|
4615
|
+
this.restorationIdentifier = restorationIdentifier;
|
|
4616
|
+
const direction = restorationIndex > this.currentIndex ? "forward" : "back";
|
|
4617
|
+
this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
|
|
4618
|
+
this.currentIndex = restorationIndex;
|
|
4619
|
+
} else {
|
|
4620
|
+
this.currentIndex++;
|
|
4621
|
+
this.delegate.historyPoppedWithEmptyState(this.location);
|
|
4667
4622
|
}
|
|
4668
4623
|
}
|
|
4669
|
-
|
|
4670
|
-
onPageLoad = async (_event) => {
|
|
4671
|
-
await nextMicrotask();
|
|
4672
|
-
this.pageLoaded = true;
|
|
4673
|
-
}
|
|
4674
|
-
|
|
4675
|
-
// Private
|
|
4676
|
-
|
|
4677
|
-
shouldHandlePopState() {
|
|
4678
|
-
// Safari dispatches a popstate event after window's load event, ignore it
|
|
4679
|
-
return this.pageIsLoaded()
|
|
4680
|
-
}
|
|
4681
|
-
|
|
4682
|
-
pageIsLoaded() {
|
|
4683
|
-
return this.pageLoaded || document.readyState == "complete"
|
|
4684
|
-
}
|
|
4685
4624
|
}
|
|
4686
4625
|
|
|
4687
4626
|
class LinkPrefetchObserver {
|
|
@@ -4756,7 +4695,7 @@ Copyright © 2025 37signals LLC
|
|
|
4756
4695
|
|
|
4757
4696
|
fetchRequest.fetchOptions.priority = "low";
|
|
4758
4697
|
|
|
4759
|
-
prefetchCache.
|
|
4698
|
+
prefetchCache.putLater(location, fetchRequest, this.#cacheTtl);
|
|
4760
4699
|
}
|
|
4761
4700
|
}
|
|
4762
4701
|
}
|
|
@@ -4772,7 +4711,7 @@ Copyright © 2025 37signals LLC
|
|
|
4772
4711
|
|
|
4773
4712
|
#tryToUsePrefetchedRequest = (event) => {
|
|
4774
4713
|
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
|
|
4775
|
-
const cached = prefetchCache.get(event.detail.url
|
|
4714
|
+
const cached = prefetchCache.get(event.detail.url);
|
|
4776
4715
|
|
|
4777
4716
|
if (cached) {
|
|
4778
4717
|
// User clicked link, use cache response
|
|
@@ -4962,7 +4901,7 @@ Copyright © 2025 37signals LLC
|
|
|
4962
4901
|
} else {
|
|
4963
4902
|
await this.view.renderPage(snapshot, false, true, this.currentVisit);
|
|
4964
4903
|
}
|
|
4965
|
-
if(
|
|
4904
|
+
if (snapshot.refreshScroll !== "preserve") {
|
|
4966
4905
|
this.view.scrollToTop();
|
|
4967
4906
|
}
|
|
4968
4907
|
this.view.clearSnapshotCache();
|
|
@@ -5002,20 +4941,10 @@ Copyright © 2025 37signals LLC
|
|
|
5002
4941
|
delete this.currentVisit;
|
|
5003
4942
|
}
|
|
5004
4943
|
|
|
4944
|
+
// Same-page links are no longer handled with a Visit.
|
|
4945
|
+
// This method is still needed for Turbo Native adapters.
|
|
5005
4946
|
locationWithActionIsSamePage(location, action) {
|
|
5006
|
-
|
|
5007
|
-
const currentAnchor = getAnchor(this.view.lastRenderedLocation);
|
|
5008
|
-
const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
|
|
5009
|
-
|
|
5010
|
-
return (
|
|
5011
|
-
action !== "replace" &&
|
|
5012
|
-
getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&
|
|
5013
|
-
(isRestorationToTop || (anchor != null && anchor !== currentAnchor))
|
|
5014
|
-
)
|
|
5015
|
-
}
|
|
5016
|
-
|
|
5017
|
-
visitScrolledToSamePageLocation(oldURL, newURL) {
|
|
5018
|
-
this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
|
|
4947
|
+
return false
|
|
5019
4948
|
}
|
|
5020
4949
|
|
|
5021
4950
|
// Visits
|
|
@@ -5408,13 +5337,18 @@ Copyright © 2025 37signals LLC
|
|
|
5408
5337
|
|
|
5409
5338
|
#setLanguage() {
|
|
5410
5339
|
const { documentElement } = this.currentSnapshot;
|
|
5411
|
-
const { lang } = this.newSnapshot;
|
|
5340
|
+
const { dir, lang } = this.newSnapshot;
|
|
5412
5341
|
|
|
5413
5342
|
if (lang) {
|
|
5414
5343
|
documentElement.setAttribute("lang", lang);
|
|
5415
5344
|
} else {
|
|
5416
5345
|
documentElement.removeAttribute("lang");
|
|
5417
5346
|
}
|
|
5347
|
+
if (dir) {
|
|
5348
|
+
documentElement.setAttribute("dir", dir);
|
|
5349
|
+
} else {
|
|
5350
|
+
documentElement.removeAttribute("dir");
|
|
5351
|
+
}
|
|
5418
5352
|
}
|
|
5419
5353
|
|
|
5420
5354
|
async mergeHead() {
|
|
@@ -5516,9 +5450,16 @@ Copyright © 2025 37signals LLC
|
|
|
5516
5450
|
|
|
5517
5451
|
activateNewBody() {
|
|
5518
5452
|
document.adoptNode(this.newElement);
|
|
5453
|
+
this.removeNoscriptElements();
|
|
5519
5454
|
this.activateNewBodyScriptElements();
|
|
5520
5455
|
}
|
|
5521
5456
|
|
|
5457
|
+
removeNoscriptElements() {
|
|
5458
|
+
for (const noscriptElement of this.newElement.querySelectorAll("noscript")) {
|
|
5459
|
+
noscriptElement.remove();
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
5462
|
+
|
|
5522
5463
|
activateNewBodyScriptElements() {
|
|
5523
5464
|
for (const inertScriptElement of this.newBodyScriptElements) {
|
|
5524
5465
|
const activatedScriptElement = activateScriptElement(inertScriptElement);
|
|
@@ -5594,58 +5535,13 @@ Copyright © 2025 37signals LLC
|
|
|
5594
5535
|
}
|
|
5595
5536
|
}
|
|
5596
5537
|
|
|
5597
|
-
class SnapshotCache {
|
|
5598
|
-
keys = []
|
|
5599
|
-
snapshots = {}
|
|
5600
|
-
|
|
5538
|
+
class SnapshotCache extends LRUCache {
|
|
5601
5539
|
constructor(size) {
|
|
5602
|
-
|
|
5603
|
-
}
|
|
5604
|
-
|
|
5605
|
-
has(location) {
|
|
5606
|
-
return toCacheKey(location) in this.snapshots
|
|
5607
|
-
}
|
|
5608
|
-
|
|
5609
|
-
get(location) {
|
|
5610
|
-
if (this.has(location)) {
|
|
5611
|
-
const snapshot = this.read(location);
|
|
5612
|
-
this.touch(location);
|
|
5613
|
-
return snapshot
|
|
5614
|
-
}
|
|
5615
|
-
}
|
|
5616
|
-
|
|
5617
|
-
put(location, snapshot) {
|
|
5618
|
-
this.write(location, snapshot);
|
|
5619
|
-
this.touch(location);
|
|
5620
|
-
return snapshot
|
|
5540
|
+
super(size, toCacheKey);
|
|
5621
5541
|
}
|
|
5622
5542
|
|
|
5623
|
-
|
|
5624
|
-
this.
|
|
5625
|
-
}
|
|
5626
|
-
|
|
5627
|
-
// Private
|
|
5628
|
-
|
|
5629
|
-
read(location) {
|
|
5630
|
-
return this.snapshots[toCacheKey(location)]
|
|
5631
|
-
}
|
|
5632
|
-
|
|
5633
|
-
write(location, snapshot) {
|
|
5634
|
-
this.snapshots[toCacheKey(location)] = snapshot;
|
|
5635
|
-
}
|
|
5636
|
-
|
|
5637
|
-
touch(location) {
|
|
5638
|
-
const key = toCacheKey(location);
|
|
5639
|
-
const index = this.keys.indexOf(key);
|
|
5640
|
-
if (index > -1) this.keys.splice(index, 1);
|
|
5641
|
-
this.keys.unshift(key);
|
|
5642
|
-
this.trim();
|
|
5643
|
-
}
|
|
5644
|
-
|
|
5645
|
-
trim() {
|
|
5646
|
-
for (const key of this.keys.splice(this.size)) {
|
|
5647
|
-
delete this.snapshots[key];
|
|
5648
|
-
}
|
|
5543
|
+
get snapshots() {
|
|
5544
|
+
return this.entries
|
|
5649
5545
|
}
|
|
5650
5546
|
}
|
|
5651
5547
|
|
|
@@ -5659,7 +5555,7 @@ Copyright © 2025 37signals LLC
|
|
|
5659
5555
|
}
|
|
5660
5556
|
|
|
5661
5557
|
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
|
5662
|
-
const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.
|
|
5558
|
+
const shouldMorphPage = this.isPageRefresh(visit) && (visit?.refresh?.method || this.snapshot.refreshMethod) === "morph";
|
|
5663
5559
|
const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
|
|
5664
5560
|
|
|
5665
5561
|
const renderer = new rendererClass(this.snapshot, snapshot, isPreview, willRender);
|
|
@@ -5703,7 +5599,7 @@ Copyright © 2025 37signals LLC
|
|
|
5703
5599
|
}
|
|
5704
5600
|
|
|
5705
5601
|
shouldPreserveScrollPosition(visit) {
|
|
5706
|
-
return this.isPageRefresh(visit) && this.snapshot.
|
|
5602
|
+
return this.isPageRefresh(visit) && (visit?.refresh?.scroll || this.snapshot.refreshScroll) === "preserve"
|
|
5707
5603
|
}
|
|
5708
5604
|
|
|
5709
5605
|
get snapshot() {
|
|
@@ -5893,11 +5789,14 @@ Copyright © 2025 37signals LLC
|
|
|
5893
5789
|
}
|
|
5894
5790
|
}
|
|
5895
5791
|
|
|
5896
|
-
refresh(url,
|
|
5792
|
+
refresh(url, options = {}) {
|
|
5793
|
+
options = typeof options === "string" ? { requestId: options } : options;
|
|
5794
|
+
|
|
5795
|
+
const { method, requestId, scroll } = options;
|
|
5897
5796
|
const isRecentRequest = requestId && this.recentRequests.has(requestId);
|
|
5898
5797
|
const isCurrentUrl = url === document.baseURI;
|
|
5899
5798
|
if (!isRecentRequest && !this.navigator.currentVisit && isCurrentUrl) {
|
|
5900
|
-
this.visit(url, { action: "replace", shouldCacheSnapshot: false });
|
|
5799
|
+
this.visit(url, { action: "replace", shouldCacheSnapshot: false, refresh: { method, scroll } });
|
|
5901
5800
|
}
|
|
5902
5801
|
}
|
|
5903
5802
|
|
|
@@ -6001,6 +5900,12 @@ Copyright © 2025 37signals LLC
|
|
|
6001
5900
|
}
|
|
6002
5901
|
}
|
|
6003
5902
|
|
|
5903
|
+
historyPoppedWithEmptyState(location) {
|
|
5904
|
+
this.history.replace(location);
|
|
5905
|
+
this.view.lastRenderedLocation = location;
|
|
5906
|
+
this.view.cacheSnapshot();
|
|
5907
|
+
}
|
|
5908
|
+
|
|
6004
5909
|
// Scroll observer delegate
|
|
6005
5910
|
|
|
6006
5911
|
scrollPositionChanged(position) {
|
|
@@ -6045,7 +5950,7 @@ Copyright © 2025 37signals LLC
|
|
|
6045
5950
|
// Navigator delegate
|
|
6046
5951
|
|
|
6047
5952
|
allowsVisitingLocationWithAction(location, action) {
|
|
6048
|
-
return this.
|
|
5953
|
+
return this.applicationAllowsVisitingLocation(location)
|
|
6049
5954
|
}
|
|
6050
5955
|
|
|
6051
5956
|
visitProposedToLocation(location, options) {
|
|
@@ -6061,9 +5966,7 @@ Copyright © 2025 37signals LLC
|
|
|
6061
5966
|
this.view.markVisitDirection(visit.direction);
|
|
6062
5967
|
}
|
|
6063
5968
|
extendURLWithDeprecatedProperties(visit.location);
|
|
6064
|
-
|
|
6065
|
-
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
|
|
6066
|
-
}
|
|
5969
|
+
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
|
|
6067
5970
|
}
|
|
6068
5971
|
|
|
6069
5972
|
visitCompleted(visit) {
|
|
@@ -6072,14 +5975,6 @@ Copyright © 2025 37signals LLC
|
|
|
6072
5975
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
|
6073
5976
|
}
|
|
6074
5977
|
|
|
6075
|
-
locationWithActionIsSamePage(location, action) {
|
|
6076
|
-
return this.navigator.locationWithActionIsSamePage(location, action)
|
|
6077
|
-
}
|
|
6078
|
-
|
|
6079
|
-
visitScrolledToSamePageLocation(oldURL, newURL) {
|
|
6080
|
-
this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
|
|
6081
|
-
}
|
|
6082
|
-
|
|
6083
5978
|
// Form submit observer delegate
|
|
6084
5979
|
|
|
6085
5980
|
willSubmitForm(form, submitter) {
|
|
@@ -6119,9 +6014,7 @@ Copyright © 2025 37signals LLC
|
|
|
6119
6014
|
// Page view delegate
|
|
6120
6015
|
|
|
6121
6016
|
viewWillCacheSnapshot() {
|
|
6122
|
-
|
|
6123
|
-
this.notifyApplicationBeforeCachingSnapshot();
|
|
6124
|
-
}
|
|
6017
|
+
this.notifyApplicationBeforeCachingSnapshot();
|
|
6125
6018
|
}
|
|
6126
6019
|
|
|
6127
6020
|
allowsImmediateRender({ element }, options) {
|
|
@@ -6213,15 +6106,6 @@ Copyright © 2025 37signals LLC
|
|
|
6213
6106
|
})
|
|
6214
6107
|
}
|
|
6215
6108
|
|
|
6216
|
-
notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
|
|
6217
|
-
dispatchEvent(
|
|
6218
|
-
new HashChangeEvent("hashchange", {
|
|
6219
|
-
oldURL: oldURL.toString(),
|
|
6220
|
-
newURL: newURL.toString()
|
|
6221
|
-
})
|
|
6222
|
-
);
|
|
6223
|
-
}
|
|
6224
|
-
|
|
6225
6109
|
notifyApplicationAfterFrameLoad(frame) {
|
|
6226
6110
|
return dispatch("turbo:frame-load", { target: frame })
|
|
6227
6111
|
}
|
|
@@ -6307,7 +6191,7 @@ Copyright © 2025 37signals LLC
|
|
|
6307
6191
|
};
|
|
6308
6192
|
|
|
6309
6193
|
const session = new Session(recentRequests);
|
|
6310
|
-
const { cache, navigator
|
|
6194
|
+
const { cache, navigator } = session;
|
|
6311
6195
|
|
|
6312
6196
|
/**
|
|
6313
6197
|
* Starts the main session.
|
|
@@ -6373,19 +6257,6 @@ Copyright © 2025 37signals LLC
|
|
|
6373
6257
|
session.renderStreamMessage(message);
|
|
6374
6258
|
}
|
|
6375
6259
|
|
|
6376
|
-
/**
|
|
6377
|
-
* Removes all entries from the Turbo Drive page cache.
|
|
6378
|
-
* Call this when state has changed on the server that may affect cached pages.
|
|
6379
|
-
*
|
|
6380
|
-
* @deprecated since version 7.2.0 in favor of `Turbo.cache.clear()`
|
|
6381
|
-
*/
|
|
6382
|
-
function clearCache() {
|
|
6383
|
-
console.warn(
|
|
6384
|
-
"Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`"
|
|
6385
|
-
);
|
|
6386
|
-
session.clearCache();
|
|
6387
|
-
}
|
|
6388
|
-
|
|
6389
6260
|
/**
|
|
6390
6261
|
* Sets the delay after which the progress bar will appear during navigation.
|
|
6391
6262
|
*
|
|
@@ -6445,7 +6316,7 @@ Copyright © 2025 37signals LLC
|
|
|
6445
6316
|
|
|
6446
6317
|
var Turbo = /*#__PURE__*/Object.freeze({
|
|
6447
6318
|
__proto__: null,
|
|
6448
|
-
navigator: navigator
|
|
6319
|
+
navigator: navigator,
|
|
6449
6320
|
session: session,
|
|
6450
6321
|
cache: cache,
|
|
6451
6322
|
PageRenderer: PageRenderer,
|
|
@@ -6459,7 +6330,6 @@ Copyright © 2025 37signals LLC
|
|
|
6459
6330
|
connectStreamSource: connectStreamSource,
|
|
6460
6331
|
disconnectStreamSource: disconnectStreamSource,
|
|
6461
6332
|
renderStreamMessage: renderStreamMessage,
|
|
6462
|
-
clearCache: clearCache,
|
|
6463
6333
|
setProgressBarDelay: setProgressBarDelay,
|
|
6464
6334
|
setConfirmMethod: setConfirmMethod,
|
|
6465
6335
|
setFormMode: setFormMode,
|
|
@@ -6514,11 +6384,17 @@ Copyright © 2025 37signals LLC
|
|
|
6514
6384
|
this.formLinkClickObserver.stop();
|
|
6515
6385
|
this.linkInterceptor.stop();
|
|
6516
6386
|
this.formSubmitObserver.stop();
|
|
6387
|
+
|
|
6388
|
+
if (!this.element.hasAttribute("recurse")) {
|
|
6389
|
+
this.#currentFetchRequest?.cancel();
|
|
6390
|
+
}
|
|
6517
6391
|
}
|
|
6518
6392
|
}
|
|
6519
6393
|
|
|
6520
6394
|
disabledChanged() {
|
|
6521
|
-
if (this.
|
|
6395
|
+
if (this.disabled) {
|
|
6396
|
+
this.#currentFetchRequest?.cancel();
|
|
6397
|
+
} else if (this.loadingStyle == FrameLoadingStyle.eager) {
|
|
6522
6398
|
this.#loadSourceURL();
|
|
6523
6399
|
}
|
|
6524
6400
|
}
|
|
@@ -6526,6 +6402,10 @@ Copyright © 2025 37signals LLC
|
|
|
6526
6402
|
sourceURLChanged() {
|
|
6527
6403
|
if (this.#isIgnoringChangesTo("src")) return
|
|
6528
6404
|
|
|
6405
|
+
if (!this.sourceURL) {
|
|
6406
|
+
this.#currentFetchRequest?.cancel();
|
|
6407
|
+
}
|
|
6408
|
+
|
|
6529
6409
|
if (this.element.isConnected) {
|
|
6530
6410
|
this.complete = false;
|
|
6531
6411
|
}
|
|
@@ -6627,15 +6507,18 @@ Copyright © 2025 37signals LLC
|
|
|
6627
6507
|
}
|
|
6628
6508
|
|
|
6629
6509
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
|
6510
|
+
|
|
6630
6511
|
const { fetchRequest } = this.formSubmission;
|
|
6631
|
-
this
|
|
6512
|
+
const frame = this.#findFrameElement(element, submitter);
|
|
6513
|
+
|
|
6514
|
+
this.prepareRequest(fetchRequest, frame);
|
|
6632
6515
|
this.formSubmission.start();
|
|
6633
6516
|
}
|
|
6634
6517
|
|
|
6635
6518
|
// Fetch request delegate
|
|
6636
6519
|
|
|
6637
|
-
prepareRequest(request) {
|
|
6638
|
-
request.headers["Turbo-Frame"] =
|
|
6520
|
+
prepareRequest(request, frame = this) {
|
|
6521
|
+
request.headers["Turbo-Frame"] = frame.id;
|
|
6639
6522
|
|
|
6640
6523
|
if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
|
|
6641
6524
|
request.acceptResponseType(StreamMessage.contentType);
|
|
@@ -6877,7 +6760,9 @@ Copyright © 2025 37signals LLC
|
|
|
6877
6760
|
|
|
6878
6761
|
#findFrameElement(element, submitter) {
|
|
6879
6762
|
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
|
6880
|
-
|
|
6763
|
+
const target = this.#getFrameElementById(id);
|
|
6764
|
+
|
|
6765
|
+
return target instanceof FrameElement ? target : this.element
|
|
6881
6766
|
}
|
|
6882
6767
|
|
|
6883
6768
|
async extractForeignFrameElement(container) {
|
|
@@ -6921,9 +6806,11 @@ Copyright © 2025 37signals LLC
|
|
|
6921
6806
|
}
|
|
6922
6807
|
|
|
6923
6808
|
if (id) {
|
|
6924
|
-
const frameElement = getFrameElementById(id);
|
|
6809
|
+
const frameElement = this.#getFrameElementById(id);
|
|
6925
6810
|
if (frameElement) {
|
|
6926
6811
|
return !frameElement.disabled
|
|
6812
|
+
} else if (id == "_parent") {
|
|
6813
|
+
return false
|
|
6927
6814
|
}
|
|
6928
6815
|
}
|
|
6929
6816
|
|
|
@@ -6944,8 +6831,12 @@ Copyright © 2025 37signals LLC
|
|
|
6944
6831
|
return this.element.id
|
|
6945
6832
|
}
|
|
6946
6833
|
|
|
6834
|
+
get disabled() {
|
|
6835
|
+
return this.element.disabled
|
|
6836
|
+
}
|
|
6837
|
+
|
|
6947
6838
|
get enabled() {
|
|
6948
|
-
return !this.
|
|
6839
|
+
return !this.disabled
|
|
6949
6840
|
}
|
|
6950
6841
|
|
|
6951
6842
|
get sourceURL() {
|
|
@@ -7005,13 +6896,15 @@ Copyright © 2025 37signals LLC
|
|
|
7005
6896
|
callback();
|
|
7006
6897
|
delete this.currentNavigationElement;
|
|
7007
6898
|
}
|
|
7008
|
-
}
|
|
7009
6899
|
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
6900
|
+
#getFrameElementById(id) {
|
|
6901
|
+
if (id != null) {
|
|
6902
|
+
const element = id === "_parent" ?
|
|
6903
|
+
this.element.parentElement.closest("turbo-frame") :
|
|
6904
|
+
document.getElementById(id);
|
|
6905
|
+
if (element instanceof FrameElement) {
|
|
6906
|
+
return element
|
|
6907
|
+
}
|
|
7015
6908
|
}
|
|
7016
6909
|
}
|
|
7017
6910
|
}
|
|
@@ -7036,6 +6929,7 @@ Copyright © 2025 37signals LLC
|
|
|
7036
6929
|
|
|
7037
6930
|
const StreamActions = {
|
|
7038
6931
|
after() {
|
|
6932
|
+
this.removeDuplicateTargetSiblings();
|
|
7039
6933
|
this.targetElements.forEach((e) => e.parentElement?.insertBefore(this.templateContent, e.nextSibling));
|
|
7040
6934
|
},
|
|
7041
6935
|
|
|
@@ -7045,6 +6939,7 @@ Copyright © 2025 37signals LLC
|
|
|
7045
6939
|
},
|
|
7046
6940
|
|
|
7047
6941
|
before() {
|
|
6942
|
+
this.removeDuplicateTargetSiblings();
|
|
7048
6943
|
this.targetElements.forEach((e) => e.parentElement?.insertBefore(this.templateContent, e));
|
|
7049
6944
|
},
|
|
7050
6945
|
|
|
@@ -7083,7 +6978,11 @@ Copyright © 2025 37signals LLC
|
|
|
7083
6978
|
},
|
|
7084
6979
|
|
|
7085
6980
|
refresh() {
|
|
7086
|
-
|
|
6981
|
+
const method = this.getAttribute("method");
|
|
6982
|
+
const requestId = this.requestId;
|
|
6983
|
+
const scroll = this.getAttribute("scroll");
|
|
6984
|
+
|
|
6985
|
+
session.refresh(this.baseURI, { method, requestId, scroll });
|
|
7087
6986
|
}
|
|
7088
6987
|
};
|
|
7089
6988
|
|
|
@@ -7161,6 +7060,23 @@ Copyright © 2025 37signals LLC
|
|
|
7161
7060
|
return existingChildren.filter((c) => newChildrenIds.includes(c.getAttribute("id")))
|
|
7162
7061
|
}
|
|
7163
7062
|
|
|
7063
|
+
/**
|
|
7064
|
+
* Removes duplicate siblings (by ID)
|
|
7065
|
+
*/
|
|
7066
|
+
removeDuplicateTargetSiblings() {
|
|
7067
|
+
this.duplicateSiblings.forEach((c) => c.remove());
|
|
7068
|
+
}
|
|
7069
|
+
|
|
7070
|
+
/**
|
|
7071
|
+
* Gets the list of duplicate siblings (i.e. those with the same ID)
|
|
7072
|
+
*/
|
|
7073
|
+
get duplicateSiblings() {
|
|
7074
|
+
const existingChildren = this.targetElements.flatMap((e) => [...e.parentElement.children]).filter((c) => !!c.id);
|
|
7075
|
+
const newChildrenIds = [...(this.templateContent?.children || [])].filter((c) => !!c.id).map((c) => c.id);
|
|
7076
|
+
|
|
7077
|
+
return existingChildren.filter((c) => newChildrenIds.includes(c.id))
|
|
7078
|
+
}
|
|
7079
|
+
|
|
7164
7080
|
/**
|
|
7165
7081
|
* Gets the action function to be performed.
|
|
7166
7082
|
*/
|
|
@@ -7312,11 +7228,11 @@ Copyright © 2025 37signals LLC
|
|
|
7312
7228
|
}
|
|
7313
7229
|
|
|
7314
7230
|
(() => {
|
|
7315
|
-
|
|
7316
|
-
if (!
|
|
7317
|
-
if (
|
|
7231
|
+
const scriptElement = document.currentScript;
|
|
7232
|
+
if (!scriptElement) return
|
|
7233
|
+
if (scriptElement.hasAttribute("data-turbo-suppress-warning")) return
|
|
7318
7234
|
|
|
7319
|
-
element =
|
|
7235
|
+
let element = scriptElement.parentElement;
|
|
7320
7236
|
while (element) {
|
|
7321
7237
|
if (element == document.body) {
|
|
7322
7238
|
return console.warn(
|
|
@@ -7330,7 +7246,7 @@ Copyright © 2025 37signals LLC
|
|
|
7330
7246
|
——
|
|
7331
7247
|
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
|
7332
7248
|
`,
|
|
7333
|
-
|
|
7249
|
+
scriptElement.outerHTML
|
|
7334
7250
|
)
|
|
7335
7251
|
}
|
|
7336
7252
|
|
|
@@ -7354,7 +7270,6 @@ Copyright © 2025 37signals LLC
|
|
|
7354
7270
|
exports.StreamElement = StreamElement;
|
|
7355
7271
|
exports.StreamSourceElement = StreamSourceElement;
|
|
7356
7272
|
exports.cache = cache;
|
|
7357
|
-
exports.clearCache = clearCache;
|
|
7358
7273
|
exports.config = config;
|
|
7359
7274
|
exports.connectStreamSource = connectStreamSource;
|
|
7360
7275
|
exports.disconnectStreamSource = disconnectStreamSource;
|
|
@@ -7366,7 +7281,7 @@ Copyright © 2025 37signals LLC
|
|
|
7366
7281
|
exports.morphChildren = morphChildren;
|
|
7367
7282
|
exports.morphElements = morphElements;
|
|
7368
7283
|
exports.morphTurboFrameElements = morphTurboFrameElements;
|
|
7369
|
-
exports.navigator = navigator
|
|
7284
|
+
exports.navigator = navigator;
|
|
7370
7285
|
exports.registerAdapter = registerAdapter;
|
|
7371
7286
|
exports.renderStreamMessage = renderStreamMessage;
|
|
7372
7287
|
exports.session = session;
|