@hotwired/turbo 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/dist/turbo.es2017-esm.js +385 -392
- package/dist/turbo.es2017-umd.js +385 -393
- 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
|
+
}
|
|
993
|
+
|
|
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
|
+
}
|
|
1085
1027
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
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;
|
|
1094
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
|
+
}
|
|
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
|
|
|
@@ -2145,6 +2125,7 @@ Copyright © 2025 37signals LLC
|
|
|
2145
2125
|
* @property {ConfigInternal['callbacks']} callbacks
|
|
2146
2126
|
* @property {ConfigInternal['head']} head
|
|
2147
2127
|
* @property {HTMLDivElement} pantry
|
|
2128
|
+
* @property {Element[]} activeElementAndParents
|
|
2148
2129
|
*/
|
|
2149
2130
|
|
|
2150
2131
|
//=============================================================================
|
|
@@ -2220,14 +2201,6 @@ Copyright © 2025 37signals LLC
|
|
|
2220
2201
|
*/
|
|
2221
2202
|
function morphOuterHTML(ctx, oldNode, newNode) {
|
|
2222
2203
|
const oldParent = normalizeParent(oldNode);
|
|
2223
|
-
|
|
2224
|
-
// basis for calulating which nodes were morphed
|
|
2225
|
-
// since there may be unmorphed sibling nodes
|
|
2226
|
-
let childNodes = Array.from(oldParent.childNodes);
|
|
2227
|
-
const index = childNodes.indexOf(oldNode);
|
|
2228
|
-
// how many elements are to the right of the oldNode
|
|
2229
|
-
const rightMargin = childNodes.length - (index + 1);
|
|
2230
|
-
|
|
2231
2204
|
morphChildren(
|
|
2232
2205
|
ctx,
|
|
2233
2206
|
oldParent,
|
|
@@ -2236,10 +2209,8 @@ Copyright © 2025 37signals LLC
|
|
|
2236
2209
|
oldNode, // start point for iteration
|
|
2237
2210
|
oldNode.nextSibling, // end point for iteration
|
|
2238
2211
|
);
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
childNodes = Array.from(oldParent.childNodes);
|
|
2242
|
-
return childNodes.slice(index, childNodes.length - rightMargin);
|
|
2212
|
+
// this is safe even with siblings, because normalizeParent returns a SlicedParentNode if needed.
|
|
2213
|
+
return Array.from(oldParent.childNodes);
|
|
2243
2214
|
}
|
|
2244
2215
|
|
|
2245
2216
|
/**
|
|
@@ -2268,8 +2239,11 @@ Copyright © 2025 37signals LLC
|
|
|
2268
2239
|
|
|
2269
2240
|
const results = fn();
|
|
2270
2241
|
|
|
2271
|
-
if (
|
|
2272
|
-
|
|
2242
|
+
if (
|
|
2243
|
+
activeElementId &&
|
|
2244
|
+
activeElementId !== document.activeElement?.getAttribute("id")
|
|
2245
|
+
) {
|
|
2246
|
+
activeElement = ctx.target.querySelector(`[id="${activeElementId}"]`);
|
|
2273
2247
|
activeElement?.focus();
|
|
2274
2248
|
}
|
|
2275
2249
|
if (activeElement && !activeElement.selectionEnd && selectionEnd) {
|
|
@@ -2347,17 +2321,23 @@ Copyright © 2025 37signals LLC
|
|
|
2347
2321
|
}
|
|
2348
2322
|
|
|
2349
2323
|
// if the matching node is elsewhere in the original content
|
|
2350
|
-
if (newChild instanceof Element
|
|
2351
|
-
//
|
|
2352
|
-
const
|
|
2353
|
-
|
|
2354
|
-
newChild.id,
|
|
2355
|
-
insertionPoint,
|
|
2356
|
-
ctx,
|
|
2324
|
+
if (newChild instanceof Element) {
|
|
2325
|
+
// we can pretend the id is non-null because the next `.has` line will reject it if not
|
|
2326
|
+
const newChildId = /** @type {String} */ (
|
|
2327
|
+
newChild.getAttribute("id")
|
|
2357
2328
|
);
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2329
|
+
if (ctx.persistentIds.has(newChildId)) {
|
|
2330
|
+
// move it and all its children here and morph
|
|
2331
|
+
const movedChild = moveBeforeById(
|
|
2332
|
+
oldParent,
|
|
2333
|
+
newChildId,
|
|
2334
|
+
insertionPoint,
|
|
2335
|
+
ctx,
|
|
2336
|
+
);
|
|
2337
|
+
morphNode(movedChild, newChild, ctx);
|
|
2338
|
+
insertionPoint = movedChild.nextSibling;
|
|
2339
|
+
continue;
|
|
2340
|
+
}
|
|
2361
2341
|
}
|
|
2362
2342
|
|
|
2363
2343
|
// last resort: insert the new node from scratch
|
|
@@ -2467,7 +2447,8 @@ Copyright © 2025 37signals LLC
|
|
|
2467
2447
|
|
|
2468
2448
|
// if the current node contains active element, stop looking for better future matches,
|
|
2469
2449
|
// because if one is found, this node will be moved to the pantry, reparenting it and thus losing focus
|
|
2470
|
-
|
|
2450
|
+
// @ts-ignore pretend cursor is Element rather than Node, we're just testing for array inclusion
|
|
2451
|
+
if (ctx.activeElementAndParents.includes(cursor)) break;
|
|
2471
2452
|
|
|
2472
2453
|
cursor = cursor.nextSibling;
|
|
2473
2454
|
}
|
|
@@ -2517,7 +2498,9 @@ Copyright © 2025 37signals LLC
|
|
|
2517
2498
|
// If oldElt has an `id` with possible state and it doesn't match newElt.id then avoid morphing.
|
|
2518
2499
|
// We'll still match an anonymous node with an IDed newElt, though, because if it got this far,
|
|
2519
2500
|
// its not persistent, and new nodes can't have any hidden state.
|
|
2520
|
-
|
|
2501
|
+
// We can't use .id because of form input shadowing, and we can't count on .getAttribute's presence because it could be a document-fragment
|
|
2502
|
+
(!oldElt.getAttribute?.("id") ||
|
|
2503
|
+
oldElt.getAttribute?.("id") === newElt.getAttribute?.("id"))
|
|
2521
2504
|
);
|
|
2522
2505
|
}
|
|
2523
2506
|
|
|
@@ -2581,8 +2564,11 @@ Copyright © 2025 37signals LLC
|
|
|
2581
2564
|
const target =
|
|
2582
2565
|
/** @type {Element} - will always be found */
|
|
2583
2566
|
(
|
|
2584
|
-
ctx.target.
|
|
2585
|
-
|
|
2567
|
+
// ctx.target.id unsafe because of form input shadowing
|
|
2568
|
+
// ctx.target could be a document fragment which doesn't have `getAttribute`
|
|
2569
|
+
(ctx.target.getAttribute?.("id") === id && ctx.target) ||
|
|
2570
|
+
ctx.target.querySelector(`[id="${id}"]`) ||
|
|
2571
|
+
ctx.pantry.querySelector(`[id="${id}"]`)
|
|
2586
2572
|
);
|
|
2587
2573
|
removeElementFromAncestorsIdMaps(target, ctx);
|
|
2588
2574
|
moveBefore(parentNode, target, after);
|
|
@@ -2598,7 +2584,8 @@ Copyright © 2025 37signals LLC
|
|
|
2598
2584
|
* @param {MorphContext} ctx
|
|
2599
2585
|
*/
|
|
2600
2586
|
function removeElementFromAncestorsIdMaps(element, ctx) {
|
|
2601
|
-
|
|
2587
|
+
// we know id is non-null String, because this function is only called on elements with ids
|
|
2588
|
+
const id = /** @type {String} */ (element.getAttribute("id"));
|
|
2602
2589
|
/** @ts-ignore - safe to loop in this way **/
|
|
2603
2590
|
while ((element = element.parentNode)) {
|
|
2604
2591
|
let idSet = ctx.idMap.get(element);
|
|
@@ -3036,6 +3023,7 @@ Copyright © 2025 37signals LLC
|
|
|
3036
3023
|
idMap: idMap,
|
|
3037
3024
|
persistentIds: persistentIds,
|
|
3038
3025
|
pantry: createPantry(),
|
|
3026
|
+
activeElementAndParents: createActiveElementAndParents(oldNode),
|
|
3039
3027
|
callbacks: mergedConfig.callbacks,
|
|
3040
3028
|
head: mergedConfig.head,
|
|
3041
3029
|
};
|
|
@@ -3076,6 +3064,24 @@ Copyright © 2025 37signals LLC
|
|
|
3076
3064
|
return pantry;
|
|
3077
3065
|
}
|
|
3078
3066
|
|
|
3067
|
+
/**
|
|
3068
|
+
* @param {Element} oldNode
|
|
3069
|
+
* @returns {Element[]}
|
|
3070
|
+
*/
|
|
3071
|
+
function createActiveElementAndParents(oldNode) {
|
|
3072
|
+
/** @type {Element[]} */
|
|
3073
|
+
let activeElementAndParents = [];
|
|
3074
|
+
let elt = document.activeElement;
|
|
3075
|
+
if (elt?.tagName !== "BODY" && oldNode.contains(elt)) {
|
|
3076
|
+
while (elt) {
|
|
3077
|
+
activeElementAndParents.push(elt);
|
|
3078
|
+
if (elt === oldNode) break;
|
|
3079
|
+
elt = elt.parentElement;
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
return activeElementAndParents;
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3079
3085
|
/**
|
|
3080
3086
|
* Returns all elements with an ID contained within the root element and its descendants
|
|
3081
3087
|
*
|
|
@@ -3084,7 +3090,8 @@ Copyright © 2025 37signals LLC
|
|
|
3084
3090
|
*/
|
|
3085
3091
|
function findIdElements(root) {
|
|
3086
3092
|
let elements = Array.from(root.querySelectorAll("[id]"));
|
|
3087
|
-
|
|
3093
|
+
// root could be a document fragment which doesn't have `getAttribute`
|
|
3094
|
+
if (root.getAttribute?.("id")) {
|
|
3088
3095
|
elements.push(root);
|
|
3089
3096
|
}
|
|
3090
3097
|
return elements;
|
|
@@ -3103,7 +3110,9 @@ Copyright © 2025 37signals LLC
|
|
|
3103
3110
|
*/
|
|
3104
3111
|
function populateIdMapWithTree(idMap, persistentIds, root, elements) {
|
|
3105
3112
|
for (const elt of elements) {
|
|
3106
|
-
|
|
3113
|
+
// we can pretend id is non-null String, because the .has line will reject it immediately if not
|
|
3114
|
+
const id = /** @type {String} */ (elt.getAttribute("id"));
|
|
3115
|
+
if (persistentIds.has(id)) {
|
|
3107
3116
|
/** @type {Element|null} */
|
|
3108
3117
|
let current = elt;
|
|
3109
3118
|
// walk up the parent hierarchy of that element, adding the id
|
|
@@ -3115,7 +3124,7 @@ Copyright © 2025 37signals LLC
|
|
|
3115
3124
|
idSet = new Set();
|
|
3116
3125
|
idMap.set(current, idSet);
|
|
3117
3126
|
}
|
|
3118
|
-
idSet.add(
|
|
3127
|
+
idSet.add(id);
|
|
3119
3128
|
|
|
3120
3129
|
if (current === root) break;
|
|
3121
3130
|
current = current.parentElement;
|
|
@@ -3229,8 +3238,9 @@ Copyright © 2025 37signals LLC
|
|
|
3229
3238
|
if (newContent.parentNode) {
|
|
3230
3239
|
// we can't use the parent directly because newContent may have siblings
|
|
3231
3240
|
// that we don't want in the morph, and reparenting might be expensive (TODO is it?),
|
|
3232
|
-
// so we create a
|
|
3233
|
-
|
|
3241
|
+
// so instead we create a fake parent node that only sees a slice of its children.
|
|
3242
|
+
/** @type {Element} */
|
|
3243
|
+
return /** @type {any} */ (new SlicedParentNode(newContent));
|
|
3234
3244
|
} else {
|
|
3235
3245
|
// a single node is added as a child to a dummy parent
|
|
3236
3246
|
const dummyParent = document.createElement("div");
|
|
@@ -3249,33 +3259,78 @@ Copyright © 2025 37signals LLC
|
|
|
3249
3259
|
}
|
|
3250
3260
|
|
|
3251
3261
|
/**
|
|
3252
|
-
*
|
|
3262
|
+
* A fake duck-typed parent element to wrap a single node, without actually reparenting it.
|
|
3263
|
+
* This is useful because the node may have siblings that we don't want in the morph, and it may also be moved
|
|
3264
|
+
* or replaced with one or more elements during the morph. This class effectively allows us a window into
|
|
3265
|
+
* a slice of a node's children.
|
|
3253
3266
|
* "If it walks like a duck, and quacks like a duck, then it must be a duck!" -- James Whitcomb Riley (1849–1916)
|
|
3254
|
-
*
|
|
3255
|
-
* @param {Node} newContent
|
|
3256
|
-
* @returns {Element}
|
|
3257
3267
|
*/
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
}
|
|
3278
|
-
|
|
3268
|
+
class SlicedParentNode {
|
|
3269
|
+
/** @param {Node} node */
|
|
3270
|
+
constructor(node) {
|
|
3271
|
+
this.originalNode = node;
|
|
3272
|
+
this.realParentNode = /** @type {Element} */ (node.parentNode);
|
|
3273
|
+
this.previousSibling = node.previousSibling;
|
|
3274
|
+
this.nextSibling = node.nextSibling;
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
/** @returns {Node[]} */
|
|
3278
|
+
get childNodes() {
|
|
3279
|
+
// return slice of realParent's current childNodes, based on previousSibling and nextSibling
|
|
3280
|
+
const nodes = [];
|
|
3281
|
+
let cursor = this.previousSibling
|
|
3282
|
+
? this.previousSibling.nextSibling
|
|
3283
|
+
: this.realParentNode.firstChild;
|
|
3284
|
+
while (cursor && cursor != this.nextSibling) {
|
|
3285
|
+
nodes.push(cursor);
|
|
3286
|
+
cursor = cursor.nextSibling;
|
|
3287
|
+
}
|
|
3288
|
+
return nodes;
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
/**
|
|
3292
|
+
* @param {string} selector
|
|
3293
|
+
* @returns {Element[]}
|
|
3294
|
+
*/
|
|
3295
|
+
querySelectorAll(selector) {
|
|
3296
|
+
return this.childNodes.reduce((results, node) => {
|
|
3297
|
+
if (node instanceof Element) {
|
|
3298
|
+
if (node.matches(selector)) results.push(node);
|
|
3299
|
+
const nodeList = node.querySelectorAll(selector);
|
|
3300
|
+
for (let i = 0; i < nodeList.length; i++) {
|
|
3301
|
+
results.push(nodeList[i]);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
return results;
|
|
3305
|
+
}, /** @type {Element[]} */ ([]));
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
/**
|
|
3309
|
+
* @param {Node} node
|
|
3310
|
+
* @param {Node} referenceNode
|
|
3311
|
+
* @returns {Node}
|
|
3312
|
+
*/
|
|
3313
|
+
insertBefore(node, referenceNode) {
|
|
3314
|
+
return this.realParentNode.insertBefore(node, referenceNode);
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
/**
|
|
3318
|
+
* @param {Node} node
|
|
3319
|
+
* @param {Node} referenceNode
|
|
3320
|
+
* @returns {Node}
|
|
3321
|
+
*/
|
|
3322
|
+
moveBefore(node, referenceNode) {
|
|
3323
|
+
// @ts-ignore - use new moveBefore feature
|
|
3324
|
+
return this.realParentNode.moveBefore(node, referenceNode);
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
/**
|
|
3328
|
+
* for later use with populateIdMapWithTree to halt upwards iteration
|
|
3329
|
+
* @returns {Node}
|
|
3330
|
+
*/
|
|
3331
|
+
get __idiomorphRoot() {
|
|
3332
|
+
return this.originalNode;
|
|
3333
|
+
}
|
|
3279
3334
|
}
|
|
3280
3335
|
|
|
3281
3336
|
/**
|
|
@@ -3370,16 +3425,18 @@ Copyright © 2025 37signals LLC
|
|
|
3370
3425
|
|
|
3371
3426
|
function shouldRefreshFrameWithMorphing(currentFrame, newFrame) {
|
|
3372
3427
|
return currentFrame instanceof FrameElement &&
|
|
3373
|
-
|
|
3374
|
-
// elements don't get initialized until they're attached to the DOM, so
|
|
3375
|
-
// test its Element#nodeName instead
|
|
3376
|
-
newFrame instanceof Element && newFrame.nodeName === "TURBO-FRAME" &&
|
|
3377
|
-
currentFrame.shouldReloadWithMorph &&
|
|
3378
|
-
currentFrame.id === newFrame.id &&
|
|
3379
|
-
(!newFrame.getAttribute("src") || urlsAreEqual(currentFrame.src, newFrame.getAttribute("src"))) &&
|
|
3428
|
+
currentFrame.shouldReloadWithMorph && (!newFrame || areFramesCompatibleForRefreshing(currentFrame, newFrame)) &&
|
|
3380
3429
|
!currentFrame.closest("[data-turbo-permanent]")
|
|
3381
3430
|
}
|
|
3382
3431
|
|
|
3432
|
+
function areFramesCompatibleForRefreshing(currentFrame, newFrame) {
|
|
3433
|
+
// newFrame cannot yet be an instance of FrameElement because custom
|
|
3434
|
+
// elements don't get initialized until they're attached to the DOM, so
|
|
3435
|
+
// test its Element#nodeName instead
|
|
3436
|
+
return newFrame instanceof Element && newFrame.nodeName === "TURBO-FRAME" && currentFrame.id === newFrame.id &&
|
|
3437
|
+
(!newFrame.getAttribute("src") || urlsAreEqual(currentFrame.src, newFrame.getAttribute("src")))
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3383
3440
|
function closestFrameReloadableWithMorphing(node) {
|
|
3384
3441
|
return node.parentElement.closest("turbo-frame[src][refresh=morph]")
|
|
3385
3442
|
}
|
|
@@ -3731,6 +3788,10 @@ Copyright © 2025 37signals LLC
|
|
|
3731
3788
|
clonedPasswordInput.value = "";
|
|
3732
3789
|
}
|
|
3733
3790
|
|
|
3791
|
+
for (const clonedNoscriptElement of clonedElement.querySelectorAll("noscript")) {
|
|
3792
|
+
clonedNoscriptElement.remove();
|
|
3793
|
+
}
|
|
3794
|
+
|
|
3734
3795
|
return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot)
|
|
3735
3796
|
}
|
|
3736
3797
|
|
|
@@ -3738,6 +3799,10 @@ Copyright © 2025 37signals LLC
|
|
|
3738
3799
|
return this.documentElement.getAttribute("lang")
|
|
3739
3800
|
}
|
|
3740
3801
|
|
|
3802
|
+
get dir() {
|
|
3803
|
+
return this.documentElement.getAttribute("dir")
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3741
3806
|
get headElement() {
|
|
3742
3807
|
return this.headSnapshot.element
|
|
3743
3808
|
}
|
|
@@ -3768,12 +3833,12 @@ Copyright © 2025 37signals LLC
|
|
|
3768
3833
|
return viewTransitionEnabled && !window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
3769
3834
|
}
|
|
3770
3835
|
|
|
3771
|
-
get
|
|
3772
|
-
return this.getSetting("refresh-method")
|
|
3836
|
+
get refreshMethod() {
|
|
3837
|
+
return this.getSetting("refresh-method")
|
|
3773
3838
|
}
|
|
3774
3839
|
|
|
3775
|
-
get
|
|
3776
|
-
return this.getSetting("refresh-scroll")
|
|
3840
|
+
get refreshScroll() {
|
|
3841
|
+
return this.getSetting("refresh-scroll")
|
|
3777
3842
|
}
|
|
3778
3843
|
|
|
3779
3844
|
// Private
|
|
@@ -3812,7 +3877,8 @@ Copyright © 2025 37signals LLC
|
|
|
3812
3877
|
willRender: true,
|
|
3813
3878
|
updateHistory: true,
|
|
3814
3879
|
shouldCacheSnapshot: true,
|
|
3815
|
-
acceptsStreamResponse: false
|
|
3880
|
+
acceptsStreamResponse: false,
|
|
3881
|
+
refresh: {}
|
|
3816
3882
|
};
|
|
3817
3883
|
|
|
3818
3884
|
const TimingMetric = {
|
|
@@ -3872,7 +3938,8 @@ Copyright © 2025 37signals LLC
|
|
|
3872
3938
|
updateHistory,
|
|
3873
3939
|
shouldCacheSnapshot,
|
|
3874
3940
|
acceptsStreamResponse,
|
|
3875
|
-
direction
|
|
3941
|
+
direction,
|
|
3942
|
+
refresh
|
|
3876
3943
|
} = {
|
|
3877
3944
|
...defaultOptions,
|
|
3878
3945
|
...options
|
|
@@ -3883,7 +3950,6 @@ Copyright © 2025 37signals LLC
|
|
|
3883
3950
|
this.snapshot = snapshot;
|
|
3884
3951
|
this.snapshotHTML = snapshotHTML;
|
|
3885
3952
|
this.response = response;
|
|
3886
|
-
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
|
3887
3953
|
this.isPageRefresh = this.view.isPageRefresh(this);
|
|
3888
3954
|
this.visitCachedSnapshot = visitCachedSnapshot;
|
|
3889
3955
|
this.willRender = willRender;
|
|
@@ -3892,6 +3958,7 @@ Copyright © 2025 37signals LLC
|
|
|
3892
3958
|
this.shouldCacheSnapshot = shouldCacheSnapshot;
|
|
3893
3959
|
this.acceptsStreamResponse = acceptsStreamResponse;
|
|
3894
3960
|
this.direction = direction || Direction[action];
|
|
3961
|
+
this.refresh = refresh;
|
|
3895
3962
|
}
|
|
3896
3963
|
|
|
3897
3964
|
get adapter() {
|
|
@@ -3910,10 +3977,6 @@ Copyright © 2025 37signals LLC
|
|
|
3910
3977
|
return this.history.getRestorationDataForIdentifier(this.restorationIdentifier)
|
|
3911
3978
|
}
|
|
3912
3979
|
|
|
3913
|
-
get silent() {
|
|
3914
|
-
return this.isSamePage
|
|
3915
|
-
}
|
|
3916
|
-
|
|
3917
3980
|
start() {
|
|
3918
3981
|
if (this.state == VisitState.initialized) {
|
|
3919
3982
|
this.recordTimingMetric(TimingMetric.visitStart);
|
|
@@ -4050,7 +4113,7 @@ Copyright © 2025 37signals LLC
|
|
|
4050
4113
|
const isPreview = this.shouldIssueRequest();
|
|
4051
4114
|
this.render(async () => {
|
|
4052
4115
|
this.cacheSnapshot();
|
|
4053
|
-
if (this.
|
|
4116
|
+
if (this.isPageRefresh) {
|
|
4054
4117
|
this.adapter.visitRendered(this);
|
|
4055
4118
|
} else {
|
|
4056
4119
|
if (this.view.renderPromise) await this.view.renderPromise;
|
|
@@ -4078,17 +4141,6 @@ Copyright © 2025 37signals LLC
|
|
|
4078
4141
|
}
|
|
4079
4142
|
}
|
|
4080
4143
|
|
|
4081
|
-
goToSamePageAnchor() {
|
|
4082
|
-
if (this.isSamePage) {
|
|
4083
|
-
this.render(async () => {
|
|
4084
|
-
this.cacheSnapshot();
|
|
4085
|
-
this.performScroll();
|
|
4086
|
-
this.changeHistory();
|
|
4087
|
-
this.adapter.visitRendered(this);
|
|
4088
|
-
});
|
|
4089
|
-
}
|
|
4090
|
-
}
|
|
4091
|
-
|
|
4092
4144
|
// Fetch request delegate
|
|
4093
4145
|
|
|
4094
4146
|
prepareRequest(request) {
|
|
@@ -4150,9 +4202,6 @@ Copyright © 2025 37signals LLC
|
|
|
4150
4202
|
} else {
|
|
4151
4203
|
this.scrollToAnchor() || this.view.scrollToTop();
|
|
4152
4204
|
}
|
|
4153
|
-
if (this.isSamePage) {
|
|
4154
|
-
this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);
|
|
4155
|
-
}
|
|
4156
4205
|
|
|
4157
4206
|
this.scrolled = true;
|
|
4158
4207
|
}
|
|
@@ -4191,9 +4240,7 @@ Copyright © 2025 37signals LLC
|
|
|
4191
4240
|
}
|
|
4192
4241
|
|
|
4193
4242
|
shouldIssueRequest() {
|
|
4194
|
-
if (this.
|
|
4195
|
-
return false
|
|
4196
|
-
} else if (this.action == "restore") {
|
|
4243
|
+
if (this.action == "restore") {
|
|
4197
4244
|
return !this.hasCachedSnapshot()
|
|
4198
4245
|
} else {
|
|
4199
4246
|
return this.willRender
|
|
@@ -4257,7 +4304,6 @@ Copyright © 2025 37signals LLC
|
|
|
4257
4304
|
|
|
4258
4305
|
visit.loadCachedSnapshot();
|
|
4259
4306
|
visit.issueRequest();
|
|
4260
|
-
visit.goToSamePageAnchor();
|
|
4261
4307
|
}
|
|
4262
4308
|
|
|
4263
4309
|
visitRequestStarted(visit) {
|
|
@@ -4374,7 +4420,6 @@ Copyright © 2025 37signals LLC
|
|
|
4374
4420
|
|
|
4375
4421
|
class CacheObserver {
|
|
4376
4422
|
selector = "[data-turbo-temporary]"
|
|
4377
|
-
deprecatedSelector = "[data-turbo-cache=false]"
|
|
4378
4423
|
|
|
4379
4424
|
started = false
|
|
4380
4425
|
|
|
@@ -4399,19 +4444,7 @@ Copyright © 2025 37signals LLC
|
|
|
4399
4444
|
}
|
|
4400
4445
|
|
|
4401
4446
|
get temporaryElements() {
|
|
4402
|
-
return [...document.querySelectorAll(this.selector)
|
|
4403
|
-
}
|
|
4404
|
-
|
|
4405
|
-
get temporaryElementsWithDeprecation() {
|
|
4406
|
-
const elements = document.querySelectorAll(this.deprecatedSelector);
|
|
4407
|
-
|
|
4408
|
-
if (elements.length) {
|
|
4409
|
-
console.warn(
|
|
4410
|
-
`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`
|
|
4411
|
-
);
|
|
4412
|
-
}
|
|
4413
|
-
|
|
4414
|
-
return [...elements]
|
|
4447
|
+
return [...document.querySelectorAll(this.selector)]
|
|
4415
4448
|
}
|
|
4416
4449
|
}
|
|
4417
4450
|
|
|
@@ -4501,7 +4534,6 @@ Copyright © 2025 37signals LLC
|
|
|
4501
4534
|
restorationIdentifier = uuid()
|
|
4502
4535
|
restorationData = {}
|
|
4503
4536
|
started = false
|
|
4504
|
-
pageLoaded = false
|
|
4505
4537
|
currentIndex = 0
|
|
4506
4538
|
|
|
4507
4539
|
constructor(delegate) {
|
|
@@ -4511,7 +4543,6 @@ Copyright © 2025 37signals LLC
|
|
|
4511
4543
|
start() {
|
|
4512
4544
|
if (!this.started) {
|
|
4513
4545
|
addEventListener("popstate", this.onPopState, false);
|
|
4514
|
-
addEventListener("load", this.onPageLoad, false);
|
|
4515
4546
|
this.currentIndex = history.state?.turbo?.restorationIndex || 0;
|
|
4516
4547
|
this.started = true;
|
|
4517
4548
|
this.replace(new URL(window.location.href));
|
|
@@ -4521,7 +4552,6 @@ Copyright © 2025 37signals LLC
|
|
|
4521
4552
|
stop() {
|
|
4522
4553
|
if (this.started) {
|
|
4523
4554
|
removeEventListener("popstate", this.onPopState, false);
|
|
4524
|
-
removeEventListener("load", this.onPageLoad, false);
|
|
4525
4555
|
this.started = false;
|
|
4526
4556
|
}
|
|
4527
4557
|
}
|
|
@@ -4577,34 +4607,20 @@ Copyright © 2025 37signals LLC
|
|
|
4577
4607
|
// Event handlers
|
|
4578
4608
|
|
|
4579
4609
|
onPopState = (event) => {
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
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);
|
|
4590
4622
|
}
|
|
4591
4623
|
}
|
|
4592
|
-
|
|
4593
|
-
onPageLoad = async (_event) => {
|
|
4594
|
-
await nextMicrotask();
|
|
4595
|
-
this.pageLoaded = true;
|
|
4596
|
-
}
|
|
4597
|
-
|
|
4598
|
-
// Private
|
|
4599
|
-
|
|
4600
|
-
shouldHandlePopState() {
|
|
4601
|
-
// Safari dispatches a popstate event after window's load event, ignore it
|
|
4602
|
-
return this.pageIsLoaded()
|
|
4603
|
-
}
|
|
4604
|
-
|
|
4605
|
-
pageIsLoaded() {
|
|
4606
|
-
return this.pageLoaded || document.readyState == "complete"
|
|
4607
|
-
}
|
|
4608
4624
|
}
|
|
4609
4625
|
|
|
4610
4626
|
class LinkPrefetchObserver {
|
|
@@ -4679,7 +4695,7 @@ Copyright © 2025 37signals LLC
|
|
|
4679
4695
|
|
|
4680
4696
|
fetchRequest.fetchOptions.priority = "low";
|
|
4681
4697
|
|
|
4682
|
-
prefetchCache.
|
|
4698
|
+
prefetchCache.putLater(location, fetchRequest, this.#cacheTtl);
|
|
4683
4699
|
}
|
|
4684
4700
|
}
|
|
4685
4701
|
}
|
|
@@ -4695,7 +4711,7 @@ Copyright © 2025 37signals LLC
|
|
|
4695
4711
|
|
|
4696
4712
|
#tryToUsePrefetchedRequest = (event) => {
|
|
4697
4713
|
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
|
|
4698
|
-
const cached = prefetchCache.get(event.detail.url
|
|
4714
|
+
const cached = prefetchCache.get(event.detail.url);
|
|
4699
4715
|
|
|
4700
4716
|
if (cached) {
|
|
4701
4717
|
// User clicked link, use cache response
|
|
@@ -4885,7 +4901,7 @@ Copyright © 2025 37signals LLC
|
|
|
4885
4901
|
} else {
|
|
4886
4902
|
await this.view.renderPage(snapshot, false, true, this.currentVisit);
|
|
4887
4903
|
}
|
|
4888
|
-
if(
|
|
4904
|
+
if (snapshot.refreshScroll !== "preserve") {
|
|
4889
4905
|
this.view.scrollToTop();
|
|
4890
4906
|
}
|
|
4891
4907
|
this.view.clearSnapshotCache();
|
|
@@ -4925,20 +4941,10 @@ Copyright © 2025 37signals LLC
|
|
|
4925
4941
|
delete this.currentVisit;
|
|
4926
4942
|
}
|
|
4927
4943
|
|
|
4944
|
+
// Same-page links are no longer handled with a Visit.
|
|
4945
|
+
// This method is still needed for Turbo Native adapters.
|
|
4928
4946
|
locationWithActionIsSamePage(location, action) {
|
|
4929
|
-
|
|
4930
|
-
const currentAnchor = getAnchor(this.view.lastRenderedLocation);
|
|
4931
|
-
const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
|
|
4932
|
-
|
|
4933
|
-
return (
|
|
4934
|
-
action !== "replace" &&
|
|
4935
|
-
getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&
|
|
4936
|
-
(isRestorationToTop || (anchor != null && anchor !== currentAnchor))
|
|
4937
|
-
)
|
|
4938
|
-
}
|
|
4939
|
-
|
|
4940
|
-
visitScrolledToSamePageLocation(oldURL, newURL) {
|
|
4941
|
-
this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
|
|
4947
|
+
return false
|
|
4942
4948
|
}
|
|
4943
4949
|
|
|
4944
4950
|
// Visits
|
|
@@ -5331,13 +5337,18 @@ Copyright © 2025 37signals LLC
|
|
|
5331
5337
|
|
|
5332
5338
|
#setLanguage() {
|
|
5333
5339
|
const { documentElement } = this.currentSnapshot;
|
|
5334
|
-
const { lang } = this.newSnapshot;
|
|
5340
|
+
const { dir, lang } = this.newSnapshot;
|
|
5335
5341
|
|
|
5336
5342
|
if (lang) {
|
|
5337
5343
|
documentElement.setAttribute("lang", lang);
|
|
5338
5344
|
} else {
|
|
5339
5345
|
documentElement.removeAttribute("lang");
|
|
5340
5346
|
}
|
|
5347
|
+
if (dir) {
|
|
5348
|
+
documentElement.setAttribute("dir", dir);
|
|
5349
|
+
} else {
|
|
5350
|
+
documentElement.removeAttribute("dir");
|
|
5351
|
+
}
|
|
5341
5352
|
}
|
|
5342
5353
|
|
|
5343
5354
|
async mergeHead() {
|
|
@@ -5439,9 +5450,16 @@ Copyright © 2025 37signals LLC
|
|
|
5439
5450
|
|
|
5440
5451
|
activateNewBody() {
|
|
5441
5452
|
document.adoptNode(this.newElement);
|
|
5453
|
+
this.removeNoscriptElements();
|
|
5442
5454
|
this.activateNewBodyScriptElements();
|
|
5443
5455
|
}
|
|
5444
5456
|
|
|
5457
|
+
removeNoscriptElements() {
|
|
5458
|
+
for (const noscriptElement of this.newElement.querySelectorAll("noscript")) {
|
|
5459
|
+
noscriptElement.remove();
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
5462
|
+
|
|
5445
5463
|
activateNewBodyScriptElements() {
|
|
5446
5464
|
for (const inertScriptElement of this.newBodyScriptElements) {
|
|
5447
5465
|
const activatedScriptElement = activateScriptElement(inertScriptElement);
|
|
@@ -5517,58 +5535,13 @@ Copyright © 2025 37signals LLC
|
|
|
5517
5535
|
}
|
|
5518
5536
|
}
|
|
5519
5537
|
|
|
5520
|
-
class SnapshotCache {
|
|
5521
|
-
keys = []
|
|
5522
|
-
snapshots = {}
|
|
5523
|
-
|
|
5538
|
+
class SnapshotCache extends LRUCache {
|
|
5524
5539
|
constructor(size) {
|
|
5525
|
-
|
|
5540
|
+
super(size, toCacheKey);
|
|
5526
5541
|
}
|
|
5527
5542
|
|
|
5528
|
-
|
|
5529
|
-
return
|
|
5530
|
-
}
|
|
5531
|
-
|
|
5532
|
-
get(location) {
|
|
5533
|
-
if (this.has(location)) {
|
|
5534
|
-
const snapshot = this.read(location);
|
|
5535
|
-
this.touch(location);
|
|
5536
|
-
return snapshot
|
|
5537
|
-
}
|
|
5538
|
-
}
|
|
5539
|
-
|
|
5540
|
-
put(location, snapshot) {
|
|
5541
|
-
this.write(location, snapshot);
|
|
5542
|
-
this.touch(location);
|
|
5543
|
-
return snapshot
|
|
5544
|
-
}
|
|
5545
|
-
|
|
5546
|
-
clear() {
|
|
5547
|
-
this.snapshots = {};
|
|
5548
|
-
}
|
|
5549
|
-
|
|
5550
|
-
// Private
|
|
5551
|
-
|
|
5552
|
-
read(location) {
|
|
5553
|
-
return this.snapshots[toCacheKey(location)]
|
|
5554
|
-
}
|
|
5555
|
-
|
|
5556
|
-
write(location, snapshot) {
|
|
5557
|
-
this.snapshots[toCacheKey(location)] = snapshot;
|
|
5558
|
-
}
|
|
5559
|
-
|
|
5560
|
-
touch(location) {
|
|
5561
|
-
const key = toCacheKey(location);
|
|
5562
|
-
const index = this.keys.indexOf(key);
|
|
5563
|
-
if (index > -1) this.keys.splice(index, 1);
|
|
5564
|
-
this.keys.unshift(key);
|
|
5565
|
-
this.trim();
|
|
5566
|
-
}
|
|
5567
|
-
|
|
5568
|
-
trim() {
|
|
5569
|
-
for (const key of this.keys.splice(this.size)) {
|
|
5570
|
-
delete this.snapshots[key];
|
|
5571
|
-
}
|
|
5543
|
+
get snapshots() {
|
|
5544
|
+
return this.entries
|
|
5572
5545
|
}
|
|
5573
5546
|
}
|
|
5574
5547
|
|
|
@@ -5582,7 +5555,7 @@ Copyright © 2025 37signals LLC
|
|
|
5582
5555
|
}
|
|
5583
5556
|
|
|
5584
5557
|
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
|
5585
|
-
const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.
|
|
5558
|
+
const shouldMorphPage = this.isPageRefresh(visit) && (visit?.refresh?.method || this.snapshot.refreshMethod) === "morph";
|
|
5586
5559
|
const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
|
|
5587
5560
|
|
|
5588
5561
|
const renderer = new rendererClass(this.snapshot, snapshot, isPreview, willRender);
|
|
@@ -5626,7 +5599,7 @@ Copyright © 2025 37signals LLC
|
|
|
5626
5599
|
}
|
|
5627
5600
|
|
|
5628
5601
|
shouldPreserveScrollPosition(visit) {
|
|
5629
|
-
return this.isPageRefresh(visit) && this.snapshot.
|
|
5602
|
+
return this.isPageRefresh(visit) && (visit?.refresh?.scroll || this.snapshot.refreshScroll) === "preserve"
|
|
5630
5603
|
}
|
|
5631
5604
|
|
|
5632
5605
|
get snapshot() {
|
|
@@ -5816,11 +5789,14 @@ Copyright © 2025 37signals LLC
|
|
|
5816
5789
|
}
|
|
5817
5790
|
}
|
|
5818
5791
|
|
|
5819
|
-
refresh(url,
|
|
5792
|
+
refresh(url, options = {}) {
|
|
5793
|
+
options = typeof options === "string" ? { requestId: options } : options;
|
|
5794
|
+
|
|
5795
|
+
const { method, requestId, scroll } = options;
|
|
5820
5796
|
const isRecentRequest = requestId && this.recentRequests.has(requestId);
|
|
5821
5797
|
const isCurrentUrl = url === document.baseURI;
|
|
5822
5798
|
if (!isRecentRequest && !this.navigator.currentVisit && isCurrentUrl) {
|
|
5823
|
-
this.visit(url, { action: "replace", shouldCacheSnapshot: false });
|
|
5799
|
+
this.visit(url, { action: "replace", shouldCacheSnapshot: false, refresh: { method, scroll } });
|
|
5824
5800
|
}
|
|
5825
5801
|
}
|
|
5826
5802
|
|
|
@@ -5924,6 +5900,12 @@ Copyright © 2025 37signals LLC
|
|
|
5924
5900
|
}
|
|
5925
5901
|
}
|
|
5926
5902
|
|
|
5903
|
+
historyPoppedWithEmptyState(location) {
|
|
5904
|
+
this.history.replace(location);
|
|
5905
|
+
this.view.lastRenderedLocation = location;
|
|
5906
|
+
this.view.cacheSnapshot();
|
|
5907
|
+
}
|
|
5908
|
+
|
|
5927
5909
|
// Scroll observer delegate
|
|
5928
5910
|
|
|
5929
5911
|
scrollPositionChanged(position) {
|
|
@@ -5968,7 +5950,7 @@ Copyright © 2025 37signals LLC
|
|
|
5968
5950
|
// Navigator delegate
|
|
5969
5951
|
|
|
5970
5952
|
allowsVisitingLocationWithAction(location, action) {
|
|
5971
|
-
return this.
|
|
5953
|
+
return this.applicationAllowsVisitingLocation(location)
|
|
5972
5954
|
}
|
|
5973
5955
|
|
|
5974
5956
|
visitProposedToLocation(location, options) {
|
|
@@ -5984,9 +5966,7 @@ Copyright © 2025 37signals LLC
|
|
|
5984
5966
|
this.view.markVisitDirection(visit.direction);
|
|
5985
5967
|
}
|
|
5986
5968
|
extendURLWithDeprecatedProperties(visit.location);
|
|
5987
|
-
|
|
5988
|
-
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
|
|
5989
|
-
}
|
|
5969
|
+
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
|
|
5990
5970
|
}
|
|
5991
5971
|
|
|
5992
5972
|
visitCompleted(visit) {
|
|
@@ -5995,14 +5975,6 @@ Copyright © 2025 37signals LLC
|
|
|
5995
5975
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
|
5996
5976
|
}
|
|
5997
5977
|
|
|
5998
|
-
locationWithActionIsSamePage(location, action) {
|
|
5999
|
-
return this.navigator.locationWithActionIsSamePage(location, action)
|
|
6000
|
-
}
|
|
6001
|
-
|
|
6002
|
-
visitScrolledToSamePageLocation(oldURL, newURL) {
|
|
6003
|
-
this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
|
|
6004
|
-
}
|
|
6005
|
-
|
|
6006
5978
|
// Form submit observer delegate
|
|
6007
5979
|
|
|
6008
5980
|
willSubmitForm(form, submitter) {
|
|
@@ -6042,9 +6014,7 @@ Copyright © 2025 37signals LLC
|
|
|
6042
6014
|
// Page view delegate
|
|
6043
6015
|
|
|
6044
6016
|
viewWillCacheSnapshot() {
|
|
6045
|
-
|
|
6046
|
-
this.notifyApplicationBeforeCachingSnapshot();
|
|
6047
|
-
}
|
|
6017
|
+
this.notifyApplicationBeforeCachingSnapshot();
|
|
6048
6018
|
}
|
|
6049
6019
|
|
|
6050
6020
|
allowsImmediateRender({ element }, options) {
|
|
@@ -6136,15 +6106,6 @@ Copyright © 2025 37signals LLC
|
|
|
6136
6106
|
})
|
|
6137
6107
|
}
|
|
6138
6108
|
|
|
6139
|
-
notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
|
|
6140
|
-
dispatchEvent(
|
|
6141
|
-
new HashChangeEvent("hashchange", {
|
|
6142
|
-
oldURL: oldURL.toString(),
|
|
6143
|
-
newURL: newURL.toString()
|
|
6144
|
-
})
|
|
6145
|
-
);
|
|
6146
|
-
}
|
|
6147
|
-
|
|
6148
6109
|
notifyApplicationAfterFrameLoad(frame) {
|
|
6149
6110
|
return dispatch("turbo:frame-load", { target: frame })
|
|
6150
6111
|
}
|
|
@@ -6230,7 +6191,7 @@ Copyright © 2025 37signals LLC
|
|
|
6230
6191
|
};
|
|
6231
6192
|
|
|
6232
6193
|
const session = new Session(recentRequests);
|
|
6233
|
-
const { cache, navigator
|
|
6194
|
+
const { cache, navigator } = session;
|
|
6234
6195
|
|
|
6235
6196
|
/**
|
|
6236
6197
|
* Starts the main session.
|
|
@@ -6296,19 +6257,6 @@ Copyright © 2025 37signals LLC
|
|
|
6296
6257
|
session.renderStreamMessage(message);
|
|
6297
6258
|
}
|
|
6298
6259
|
|
|
6299
|
-
/**
|
|
6300
|
-
* Removes all entries from the Turbo Drive page cache.
|
|
6301
|
-
* Call this when state has changed on the server that may affect cached pages.
|
|
6302
|
-
*
|
|
6303
|
-
* @deprecated since version 7.2.0 in favor of `Turbo.cache.clear()`
|
|
6304
|
-
*/
|
|
6305
|
-
function clearCache() {
|
|
6306
|
-
console.warn(
|
|
6307
|
-
"Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`"
|
|
6308
|
-
);
|
|
6309
|
-
session.clearCache();
|
|
6310
|
-
}
|
|
6311
|
-
|
|
6312
6260
|
/**
|
|
6313
6261
|
* Sets the delay after which the progress bar will appear during navigation.
|
|
6314
6262
|
*
|
|
@@ -6368,7 +6316,7 @@ Copyright © 2025 37signals LLC
|
|
|
6368
6316
|
|
|
6369
6317
|
var Turbo = /*#__PURE__*/Object.freeze({
|
|
6370
6318
|
__proto__: null,
|
|
6371
|
-
navigator: navigator
|
|
6319
|
+
navigator: navigator,
|
|
6372
6320
|
session: session,
|
|
6373
6321
|
cache: cache,
|
|
6374
6322
|
PageRenderer: PageRenderer,
|
|
@@ -6382,7 +6330,6 @@ Copyright © 2025 37signals LLC
|
|
|
6382
6330
|
connectStreamSource: connectStreamSource,
|
|
6383
6331
|
disconnectStreamSource: disconnectStreamSource,
|
|
6384
6332
|
renderStreamMessage: renderStreamMessage,
|
|
6385
|
-
clearCache: clearCache,
|
|
6386
6333
|
setProgressBarDelay: setProgressBarDelay,
|
|
6387
6334
|
setConfirmMethod: setConfirmMethod,
|
|
6388
6335
|
setFormMode: setFormMode,
|
|
@@ -6437,11 +6384,17 @@ Copyright © 2025 37signals LLC
|
|
|
6437
6384
|
this.formLinkClickObserver.stop();
|
|
6438
6385
|
this.linkInterceptor.stop();
|
|
6439
6386
|
this.formSubmitObserver.stop();
|
|
6387
|
+
|
|
6388
|
+
if (!this.element.hasAttribute("recurse")) {
|
|
6389
|
+
this.#currentFetchRequest?.cancel();
|
|
6390
|
+
}
|
|
6440
6391
|
}
|
|
6441
6392
|
}
|
|
6442
6393
|
|
|
6443
6394
|
disabledChanged() {
|
|
6444
|
-
if (this.
|
|
6395
|
+
if (this.disabled) {
|
|
6396
|
+
this.#currentFetchRequest?.cancel();
|
|
6397
|
+
} else if (this.loadingStyle == FrameLoadingStyle.eager) {
|
|
6445
6398
|
this.#loadSourceURL();
|
|
6446
6399
|
}
|
|
6447
6400
|
}
|
|
@@ -6449,6 +6402,10 @@ Copyright © 2025 37signals LLC
|
|
|
6449
6402
|
sourceURLChanged() {
|
|
6450
6403
|
if (this.#isIgnoringChangesTo("src")) return
|
|
6451
6404
|
|
|
6405
|
+
if (!this.sourceURL) {
|
|
6406
|
+
this.#currentFetchRequest?.cancel();
|
|
6407
|
+
}
|
|
6408
|
+
|
|
6452
6409
|
if (this.element.isConnected) {
|
|
6453
6410
|
this.complete = false;
|
|
6454
6411
|
}
|
|
@@ -6550,15 +6507,18 @@ Copyright © 2025 37signals LLC
|
|
|
6550
6507
|
}
|
|
6551
6508
|
|
|
6552
6509
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
|
6510
|
+
|
|
6553
6511
|
const { fetchRequest } = this.formSubmission;
|
|
6554
|
-
this
|
|
6512
|
+
const frame = this.#findFrameElement(element, submitter);
|
|
6513
|
+
|
|
6514
|
+
this.prepareRequest(fetchRequest, frame);
|
|
6555
6515
|
this.formSubmission.start();
|
|
6556
6516
|
}
|
|
6557
6517
|
|
|
6558
6518
|
// Fetch request delegate
|
|
6559
6519
|
|
|
6560
|
-
prepareRequest(request) {
|
|
6561
|
-
request.headers["Turbo-Frame"] =
|
|
6520
|
+
prepareRequest(request, frame = this) {
|
|
6521
|
+
request.headers["Turbo-Frame"] = frame.id;
|
|
6562
6522
|
|
|
6563
6523
|
if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
|
|
6564
6524
|
request.acceptResponseType(StreamMessage.contentType);
|
|
@@ -6800,7 +6760,9 @@ Copyright © 2025 37signals LLC
|
|
|
6800
6760
|
|
|
6801
6761
|
#findFrameElement(element, submitter) {
|
|
6802
6762
|
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
|
6803
|
-
|
|
6763
|
+
const target = this.#getFrameElementById(id);
|
|
6764
|
+
|
|
6765
|
+
return target instanceof FrameElement ? target : this.element
|
|
6804
6766
|
}
|
|
6805
6767
|
|
|
6806
6768
|
async extractForeignFrameElement(container) {
|
|
@@ -6844,9 +6806,11 @@ Copyright © 2025 37signals LLC
|
|
|
6844
6806
|
}
|
|
6845
6807
|
|
|
6846
6808
|
if (id) {
|
|
6847
|
-
const frameElement = getFrameElementById(id);
|
|
6809
|
+
const frameElement = this.#getFrameElementById(id);
|
|
6848
6810
|
if (frameElement) {
|
|
6849
6811
|
return !frameElement.disabled
|
|
6812
|
+
} else if (id == "_parent") {
|
|
6813
|
+
return false
|
|
6850
6814
|
}
|
|
6851
6815
|
}
|
|
6852
6816
|
|
|
@@ -6867,8 +6831,12 @@ Copyright © 2025 37signals LLC
|
|
|
6867
6831
|
return this.element.id
|
|
6868
6832
|
}
|
|
6869
6833
|
|
|
6834
|
+
get disabled() {
|
|
6835
|
+
return this.element.disabled
|
|
6836
|
+
}
|
|
6837
|
+
|
|
6870
6838
|
get enabled() {
|
|
6871
|
-
return !this.
|
|
6839
|
+
return !this.disabled
|
|
6872
6840
|
}
|
|
6873
6841
|
|
|
6874
6842
|
get sourceURL() {
|
|
@@ -6928,13 +6896,15 @@ Copyright © 2025 37signals LLC
|
|
|
6928
6896
|
callback();
|
|
6929
6897
|
delete this.currentNavigationElement;
|
|
6930
6898
|
}
|
|
6931
|
-
}
|
|
6932
6899
|
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
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
|
+
}
|
|
6938
6908
|
}
|
|
6939
6909
|
}
|
|
6940
6910
|
}
|
|
@@ -6959,6 +6929,7 @@ Copyright © 2025 37signals LLC
|
|
|
6959
6929
|
|
|
6960
6930
|
const StreamActions = {
|
|
6961
6931
|
after() {
|
|
6932
|
+
this.removeDuplicateTargetSiblings();
|
|
6962
6933
|
this.targetElements.forEach((e) => e.parentElement?.insertBefore(this.templateContent, e.nextSibling));
|
|
6963
6934
|
},
|
|
6964
6935
|
|
|
@@ -6968,6 +6939,7 @@ Copyright © 2025 37signals LLC
|
|
|
6968
6939
|
},
|
|
6969
6940
|
|
|
6970
6941
|
before() {
|
|
6942
|
+
this.removeDuplicateTargetSiblings();
|
|
6971
6943
|
this.targetElements.forEach((e) => e.parentElement?.insertBefore(this.templateContent, e));
|
|
6972
6944
|
},
|
|
6973
6945
|
|
|
@@ -7006,7 +6978,11 @@ Copyright © 2025 37signals LLC
|
|
|
7006
6978
|
},
|
|
7007
6979
|
|
|
7008
6980
|
refresh() {
|
|
7009
|
-
|
|
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 });
|
|
7010
6986
|
}
|
|
7011
6987
|
};
|
|
7012
6988
|
|
|
@@ -7084,6 +7060,23 @@ Copyright © 2025 37signals LLC
|
|
|
7084
7060
|
return existingChildren.filter((c) => newChildrenIds.includes(c.getAttribute("id")))
|
|
7085
7061
|
}
|
|
7086
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
|
+
|
|
7087
7080
|
/**
|
|
7088
7081
|
* Gets the action function to be performed.
|
|
7089
7082
|
*/
|
|
@@ -7235,11 +7228,11 @@ Copyright © 2025 37signals LLC
|
|
|
7235
7228
|
}
|
|
7236
7229
|
|
|
7237
7230
|
(() => {
|
|
7238
|
-
|
|
7239
|
-
if (!
|
|
7240
|
-
if (
|
|
7231
|
+
const scriptElement = document.currentScript;
|
|
7232
|
+
if (!scriptElement) return
|
|
7233
|
+
if (scriptElement.hasAttribute("data-turbo-suppress-warning")) return
|
|
7241
7234
|
|
|
7242
|
-
element =
|
|
7235
|
+
let element = scriptElement.parentElement;
|
|
7243
7236
|
while (element) {
|
|
7244
7237
|
if (element == document.body) {
|
|
7245
7238
|
return console.warn(
|
|
@@ -7253,7 +7246,7 @@ Copyright © 2025 37signals LLC
|
|
|
7253
7246
|
——
|
|
7254
7247
|
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
|
7255
7248
|
`,
|
|
7256
|
-
|
|
7249
|
+
scriptElement.outerHTML
|
|
7257
7250
|
)
|
|
7258
7251
|
}
|
|
7259
7252
|
|
|
@@ -7277,7 +7270,6 @@ Copyright © 2025 37signals LLC
|
|
|
7277
7270
|
exports.StreamElement = StreamElement;
|
|
7278
7271
|
exports.StreamSourceElement = StreamSourceElement;
|
|
7279
7272
|
exports.cache = cache;
|
|
7280
|
-
exports.clearCache = clearCache;
|
|
7281
7273
|
exports.config = config;
|
|
7282
7274
|
exports.connectStreamSource = connectStreamSource;
|
|
7283
7275
|
exports.disconnectStreamSource = disconnectStreamSource;
|
|
@@ -7289,7 +7281,7 @@ Copyright © 2025 37signals LLC
|
|
|
7289
7281
|
exports.morphChildren = morphChildren;
|
|
7290
7282
|
exports.morphElements = morphElements;
|
|
7291
7283
|
exports.morphTurboFrameElements = morphTurboFrameElements;
|
|
7292
|
-
exports.navigator = navigator
|
|
7284
|
+
exports.navigator = navigator;
|
|
7293
7285
|
exports.registerAdapter = registerAdapter;
|
|
7294
7286
|
exports.renderStreamMessage = renderStreamMessage;
|
|
7295
7287
|
exports.session = session;
|