turbo-rails 1.3.0 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/assets/javascripts/turbo.js +228 -119
- data/app/assets/javascripts/turbo.min.js +5 -5
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/controllers/turbo/native/navigation.rb +3 -1
- data/app/helpers/turbo/streams/action_helper.rb +4 -4
- data/app/javascript/turbo/cable_stream_source_element.js +4 -1
- data/app/javascript/turbo/fetch_requests.js +34 -3
- data/app/models/concerns/turbo/broadcastable.rb +7 -7
- data/lib/turbo/version.rb +1 -1
- metadata +3 -3
@@ -97,11 +97,7 @@ class FrameElement extends HTMLElement {
|
|
97
97
|
this.delegate.disconnect();
|
98
98
|
}
|
99
99
|
reload() {
|
100
|
-
|
101
|
-
this.removeAttribute("complete");
|
102
|
-
this.src = null;
|
103
|
-
this.src = src;
|
104
|
-
return this.loaded;
|
100
|
+
return this.delegate.sourceURLReloaded();
|
105
101
|
}
|
106
102
|
attributeChangedCallback(name) {
|
107
103
|
if (name == "loading") {
|
@@ -286,10 +282,6 @@ class FetchResponse {
|
|
286
282
|
}
|
287
283
|
}
|
288
284
|
|
289
|
-
function isAction(action) {
|
290
|
-
return action == "advance" || action == "replace" || action == "restore";
|
291
|
-
}
|
292
|
-
|
293
285
|
function activateScriptElement(element) {
|
294
286
|
if (element.getAttribute("data-turbo-eval") == "false") {
|
295
287
|
return element;
|
@@ -322,6 +314,7 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
|
|
322
314
|
const event = new CustomEvent(eventName, {
|
323
315
|
cancelable: cancelable,
|
324
316
|
bubbles: true,
|
317
|
+
composed: true,
|
325
318
|
detail: detail
|
326
319
|
});
|
327
320
|
if (target && target.isConnected) {
|
@@ -435,6 +428,10 @@ function getHistoryMethodForAction(action) {
|
|
435
428
|
}
|
436
429
|
}
|
437
430
|
|
431
|
+
function isAction(action) {
|
432
|
+
return action == "advance" || action == "replace" || action == "restore";
|
433
|
+
}
|
434
|
+
|
438
435
|
function getVisitAction(...elements) {
|
439
436
|
const action = getAttribute("data-turbo-action", ...elements);
|
440
437
|
return isAction(action) ? action : null;
|
@@ -460,6 +457,13 @@ function setMetaContent(name, content) {
|
|
460
457
|
return element;
|
461
458
|
}
|
462
459
|
|
460
|
+
function findClosestRecursively(element, selector) {
|
461
|
+
var _a;
|
462
|
+
if (element instanceof Element) {
|
463
|
+
return element.closest(selector) || findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector);
|
464
|
+
}
|
465
|
+
}
|
466
|
+
|
463
467
|
var FetchMethod;
|
464
468
|
|
465
469
|
(function(FetchMethod) {
|
@@ -513,9 +517,8 @@ class FetchRequest {
|
|
513
517
|
this.abortController.abort();
|
514
518
|
}
|
515
519
|
async perform() {
|
516
|
-
var _a, _b;
|
517
520
|
const {fetchOptions: fetchOptions} = this;
|
518
|
-
|
521
|
+
this.delegate.prepareRequest(this);
|
519
522
|
await this.allowRequestToBeIntercepted(fetchOptions);
|
520
523
|
try {
|
521
524
|
this.delegate.requestStarted(this);
|
@@ -757,11 +760,11 @@ class FormSubmission {
|
|
757
760
|
return true;
|
758
761
|
}
|
759
762
|
}
|
760
|
-
|
763
|
+
prepareRequest(request) {
|
761
764
|
if (!request.isIdempotent) {
|
762
765
|
const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
|
763
766
|
if (token) {
|
764
|
-
headers["X-CSRF-Token"] = token;
|
767
|
+
request.headers["X-CSRF-Token"] = token;
|
765
768
|
}
|
766
769
|
}
|
767
770
|
if (this.requestAcceptsTurboStreamResponse(request)) {
|
@@ -936,6 +939,7 @@ class FormSubmitObserver {
|
|
936
939
|
const submitter = event.submitter || undefined;
|
937
940
|
if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
|
938
941
|
event.preventDefault();
|
942
|
+
event.stopImmediatePropagation();
|
939
943
|
this.delegate.formSubmitted(form, submitter);
|
940
944
|
}
|
941
945
|
}
|
@@ -963,11 +967,15 @@ function submissionDoesNotDismissDialog(form, submitter) {
|
|
963
967
|
}
|
964
968
|
|
965
969
|
function submissionDoesNotTargetIFrame(form, submitter) {
|
966
|
-
|
967
|
-
|
968
|
-
|
970
|
+
if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
|
971
|
+
const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
|
972
|
+
for (const element of document.getElementsByName(target)) {
|
973
|
+
if (element instanceof HTMLIFrameElement) return false;
|
974
|
+
}
|
975
|
+
return true;
|
976
|
+
} else {
|
977
|
+
return true;
|
969
978
|
}
|
970
|
-
return true;
|
971
979
|
}
|
972
980
|
|
973
981
|
class View {
|
@@ -1076,6 +1084,47 @@ class FrameView extends View {
|
|
1076
1084
|
}
|
1077
1085
|
}
|
1078
1086
|
|
1087
|
+
class LinkInterceptor {
|
1088
|
+
constructor(delegate, element) {
|
1089
|
+
this.clickBubbled = event => {
|
1090
|
+
if (this.respondsToEventTarget(event.target)) {
|
1091
|
+
this.clickEvent = event;
|
1092
|
+
} else {
|
1093
|
+
delete this.clickEvent;
|
1094
|
+
}
|
1095
|
+
};
|
1096
|
+
this.linkClicked = event => {
|
1097
|
+
if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
|
1098
|
+
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
1099
|
+
this.clickEvent.preventDefault();
|
1100
|
+
event.preventDefault();
|
1101
|
+
this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
|
1102
|
+
}
|
1103
|
+
}
|
1104
|
+
delete this.clickEvent;
|
1105
|
+
};
|
1106
|
+
this.willVisit = _event => {
|
1107
|
+
delete this.clickEvent;
|
1108
|
+
};
|
1109
|
+
this.delegate = delegate;
|
1110
|
+
this.element = element;
|
1111
|
+
}
|
1112
|
+
start() {
|
1113
|
+
this.element.addEventListener("click", this.clickBubbled);
|
1114
|
+
document.addEventListener("turbo:click", this.linkClicked);
|
1115
|
+
document.addEventListener("turbo:before-visit", this.willVisit);
|
1116
|
+
}
|
1117
|
+
stop() {
|
1118
|
+
this.element.removeEventListener("click", this.clickBubbled);
|
1119
|
+
document.removeEventListener("turbo:click", this.linkClicked);
|
1120
|
+
document.removeEventListener("turbo:before-visit", this.willVisit);
|
1121
|
+
}
|
1122
|
+
respondsToEventTarget(target) {
|
1123
|
+
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
1124
|
+
return element && element.closest("turbo-frame, html") == this.element;
|
1125
|
+
}
|
1126
|
+
}
|
1127
|
+
|
1079
1128
|
class LinkClickObserver {
|
1080
1129
|
constructor(delegate, eventTarget) {
|
1081
1130
|
this.started = false;
|
@@ -1115,9 +1164,7 @@ class LinkClickObserver {
|
|
1115
1164
|
return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
|
1116
1165
|
}
|
1117
1166
|
findLinkFromClickTarget(target) {
|
1118
|
-
|
1119
|
-
return target.closest("a[href]:not([target^=_]):not([download])");
|
1120
|
-
}
|
1167
|
+
return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
|
1121
1168
|
}
|
1122
1169
|
getLocationForLink(link) {
|
1123
1170
|
return expandURL(link.getAttribute("href") || "");
|
@@ -1125,37 +1172,51 @@ class LinkClickObserver {
|
|
1125
1172
|
}
|
1126
1173
|
|
1127
1174
|
function doesNotTargetIFrame(anchor) {
|
1128
|
-
|
1129
|
-
|
1175
|
+
if (anchor.hasAttribute("target")) {
|
1176
|
+
for (const element of document.getElementsByName(anchor.target)) {
|
1177
|
+
if (element instanceof HTMLIFrameElement) return false;
|
1178
|
+
}
|
1179
|
+
return true;
|
1180
|
+
} else {
|
1181
|
+
return true;
|
1130
1182
|
}
|
1131
|
-
return true;
|
1132
1183
|
}
|
1133
1184
|
|
1134
1185
|
class FormLinkClickObserver {
|
1135
1186
|
constructor(delegate, element) {
|
1136
1187
|
this.delegate = delegate;
|
1137
|
-
this.
|
1188
|
+
this.linkInterceptor = new LinkClickObserver(this, element);
|
1138
1189
|
}
|
1139
1190
|
start() {
|
1140
|
-
this.
|
1191
|
+
this.linkInterceptor.start();
|
1141
1192
|
}
|
1142
1193
|
stop() {
|
1143
|
-
this.
|
1194
|
+
this.linkInterceptor.stop();
|
1144
1195
|
}
|
1145
1196
|
willFollowLinkToLocation(link, location, originalEvent) {
|
1146
1197
|
return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
|
1147
1198
|
}
|
1148
1199
|
followedLinkToLocation(link, location) {
|
1149
|
-
const action = location.href;
|
1150
1200
|
const form = document.createElement("form");
|
1201
|
+
const type = "hidden";
|
1202
|
+
for (const [name, value] of location.searchParams) {
|
1203
|
+
form.append(Object.assign(document.createElement("input"), {
|
1204
|
+
type: type,
|
1205
|
+
name: name,
|
1206
|
+
value: value
|
1207
|
+
}));
|
1208
|
+
}
|
1209
|
+
const action = Object.assign(location, {
|
1210
|
+
search: ""
|
1211
|
+
});
|
1151
1212
|
form.setAttribute("data-turbo", "true");
|
1152
|
-
form.setAttribute("action", action);
|
1213
|
+
form.setAttribute("action", action.href);
|
1153
1214
|
form.setAttribute("hidden", "");
|
1154
1215
|
const method = link.getAttribute("data-turbo-method");
|
1155
1216
|
if (method) form.setAttribute("method", method);
|
1156
1217
|
const turboFrame = link.getAttribute("data-turbo-frame");
|
1157
1218
|
if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
|
1158
|
-
const turboAction = link
|
1219
|
+
const turboAction = getVisitAction(link);
|
1159
1220
|
if (turboAction) form.setAttribute("data-turbo-action", turboAction);
|
1160
1221
|
const turboConfirm = link.getAttribute("data-turbo-confirm");
|
1161
1222
|
if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
|
@@ -1175,10 +1236,10 @@ class Bardo {
|
|
1175
1236
|
this.delegate = delegate;
|
1176
1237
|
this.permanentElementMap = permanentElementMap;
|
1177
1238
|
}
|
1178
|
-
static preservingPermanentElements(delegate, permanentElementMap, callback) {
|
1239
|
+
static async preservingPermanentElements(delegate, permanentElementMap, callback) {
|
1179
1240
|
const bardo = new this(delegate, permanentElementMap);
|
1180
1241
|
bardo.enter();
|
1181
|
-
callback();
|
1242
|
+
await callback();
|
1182
1243
|
bardo.leave();
|
1183
1244
|
}
|
1184
1245
|
enter() {
|
@@ -1251,8 +1312,8 @@ class Renderer {
|
|
1251
1312
|
delete this.resolvingFunctions;
|
1252
1313
|
}
|
1253
1314
|
}
|
1254
|
-
preservingPermanentElements(callback) {
|
1255
|
-
Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
|
1315
|
+
async preservingPermanentElements(callback) {
|
1316
|
+
await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
|
1256
1317
|
}
|
1257
1318
|
focusFirstAutofocusableElement() {
|
1258
1319
|
const element = this.connectedSnapshot.firstAutofocusableElement;
|
@@ -1668,10 +1729,11 @@ class Visit {
|
|
1668
1729
|
this.delegate = delegate;
|
1669
1730
|
this.location = location;
|
1670
1731
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
1671
|
-
const {action: action, historyChanged: historyChanged, referrer: referrer, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = Object.assign(Object.assign({}, defaultOptions), options);
|
1732
|
+
const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = Object.assign(Object.assign({}, defaultOptions), options);
|
1672
1733
|
this.action = action;
|
1673
1734
|
this.historyChanged = historyChanged;
|
1674
1735
|
this.referrer = referrer;
|
1736
|
+
this.snapshot = snapshot;
|
1675
1737
|
this.snapshotHTML = snapshotHTML;
|
1676
1738
|
this.response = response;
|
1677
1739
|
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
@@ -1834,7 +1896,9 @@ class Visit {
|
|
1834
1896
|
if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
|
1835
1897
|
this.adapter.visitProposedToLocation(this.redirectedToLocation, {
|
1836
1898
|
action: "replace",
|
1837
|
-
response: this.response
|
1899
|
+
response: this.response,
|
1900
|
+
shouldCacheSnapshot: false,
|
1901
|
+
willRender: false
|
1838
1902
|
});
|
1839
1903
|
this.followedRedirect = true;
|
1840
1904
|
}
|
@@ -1844,11 +1908,12 @@ class Visit {
|
|
1844
1908
|
this.render((async () => {
|
1845
1909
|
this.cacheSnapshot();
|
1846
1910
|
this.performScroll();
|
1911
|
+
this.changeHistory();
|
1847
1912
|
this.adapter.visitRendered(this);
|
1848
1913
|
}));
|
1849
1914
|
}
|
1850
1915
|
}
|
1851
|
-
|
1916
|
+
prepareRequest(request) {
|
1852
1917
|
if (this.acceptsStreamResponse) {
|
1853
1918
|
request.acceptResponseType(StreamMessage.contentType);
|
1854
1919
|
}
|
@@ -1956,7 +2021,7 @@ class Visit {
|
|
1956
2021
|
}
|
1957
2022
|
cacheSnapshot() {
|
1958
2023
|
if (!this.snapshotCached) {
|
1959
|
-
this.view.cacheSnapshot().then((snapshot => snapshot && this.visitCachedSnapshot(snapshot)));
|
2024
|
+
this.view.cacheSnapshot(this.snapshot).then((snapshot => snapshot && this.visitCachedSnapshot(snapshot)));
|
1960
2025
|
this.snapshotCached = true;
|
1961
2026
|
}
|
1962
2027
|
}
|
@@ -2065,11 +2130,11 @@ class BrowserAdapter {
|
|
2065
2130
|
}
|
2066
2131
|
}
|
2067
2132
|
reload(reason) {
|
2133
|
+
var _a;
|
2068
2134
|
dispatch("turbo:reload", {
|
2069
2135
|
detail: reason
|
2070
2136
|
});
|
2071
|
-
|
2072
|
-
window.location.href = this.location.toString();
|
2137
|
+
window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;
|
2073
2138
|
}
|
2074
2139
|
get navigator() {
|
2075
2140
|
return this.session.navigator;
|
@@ -2104,24 +2169,24 @@ class FrameRedirector {
|
|
2104
2169
|
constructor(session, element) {
|
2105
2170
|
this.session = session;
|
2106
2171
|
this.element = element;
|
2107
|
-
this.
|
2172
|
+
this.linkInterceptor = new LinkInterceptor(this, element);
|
2108
2173
|
this.formSubmitObserver = new FormSubmitObserver(this, element);
|
2109
2174
|
}
|
2110
2175
|
start() {
|
2111
|
-
this.
|
2176
|
+
this.linkInterceptor.start();
|
2112
2177
|
this.formSubmitObserver.start();
|
2113
2178
|
}
|
2114
2179
|
stop() {
|
2115
|
-
this.
|
2180
|
+
this.linkInterceptor.stop();
|
2116
2181
|
this.formSubmitObserver.stop();
|
2117
2182
|
}
|
2118
|
-
|
2119
|
-
return this.shouldRedirect(element)
|
2183
|
+
shouldInterceptLinkClick(element, _location, _event) {
|
2184
|
+
return this.shouldRedirect(element);
|
2120
2185
|
}
|
2121
|
-
|
2186
|
+
linkClickIntercepted(element, url, event) {
|
2122
2187
|
const frame = this.findFrameElement(element);
|
2123
2188
|
if (frame) {
|
2124
|
-
frame.delegate.
|
2189
|
+
frame.delegate.linkClickIntercepted(element, url, event);
|
2125
2190
|
}
|
2126
2191
|
}
|
2127
2192
|
willSubmitForm(element, submitter) {
|
@@ -2133,17 +2198,6 @@ class FrameRedirector {
|
|
2133
2198
|
frame.delegate.formSubmitted(element, submitter);
|
2134
2199
|
}
|
2135
2200
|
}
|
2136
|
-
frameAllowsVisitingLocation(target, {href: url}, originalEvent) {
|
2137
|
-
const event = dispatch("turbo:click", {
|
2138
|
-
target: target,
|
2139
|
-
detail: {
|
2140
|
-
url: url,
|
2141
|
-
originalEvent: originalEvent
|
2142
|
-
},
|
2143
|
-
cancelable: true
|
2144
|
-
});
|
2145
|
-
return !event.defaultPrevented;
|
2146
|
-
}
|
2147
2201
|
shouldSubmit(form, submitter) {
|
2148
2202
|
var _a;
|
2149
2203
|
const action = getAction(form, submitter);
|
@@ -2268,7 +2322,6 @@ class Navigator {
|
|
2268
2322
|
}
|
2269
2323
|
}
|
2270
2324
|
startVisit(locatable, restorationIdentifier, options = {}) {
|
2271
|
-
this.lastVisit = this.currentVisit;
|
2272
2325
|
this.stop();
|
2273
2326
|
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
|
2274
2327
|
referrer: this.location
|
@@ -2355,12 +2408,10 @@ class Navigator {
|
|
2355
2408
|
this.delegate.visitCompleted(visit);
|
2356
2409
|
}
|
2357
2410
|
locationWithActionIsSamePage(location, action) {
|
2358
|
-
var _a;
|
2359
2411
|
const anchor = getAnchor(location);
|
2360
|
-
const
|
2361
|
-
const currentAnchor = getAnchor(lastLocation);
|
2412
|
+
const currentAnchor = getAnchor(this.view.lastRenderedLocation);
|
2362
2413
|
const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
|
2363
|
-
return action !== "replace" && getRequestURL(location) === getRequestURL(
|
2414
|
+
return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
|
2364
2415
|
}
|
2365
2416
|
visitScrolledToSamePageLocation(oldURL, newURL) {
|
2366
2417
|
this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
|
@@ -2371,10 +2422,8 @@ class Navigator {
|
|
2371
2422
|
get restorationIdentifier() {
|
2372
2423
|
return this.history.restorationIdentifier;
|
2373
2424
|
}
|
2374
|
-
getActionForFormSubmission(
|
2375
|
-
|
2376
|
-
const action = getAttribute("data-turbo-action", submitter, formElement);
|
2377
|
-
return isAction(action) ? action : "advance";
|
2425
|
+
getActionForFormSubmission({submitter: submitter, formElement: formElement}) {
|
2426
|
+
return getVisitAction(submitter, formElement) || "advance";
|
2378
2427
|
}
|
2379
2428
|
}
|
2380
2429
|
|
@@ -2622,7 +2671,7 @@ class PageRenderer extends Renderer {
|
|
2622
2671
|
}
|
2623
2672
|
async render() {
|
2624
2673
|
if (this.willRender) {
|
2625
|
-
this.replaceBody();
|
2674
|
+
await this.replaceBody();
|
2626
2675
|
}
|
2627
2676
|
}
|
2628
2677
|
finishRendering() {
|
@@ -2641,16 +2690,16 @@ class PageRenderer extends Renderer {
|
|
2641
2690
|
return this.newSnapshot.element;
|
2642
2691
|
}
|
2643
2692
|
async mergeHead() {
|
2693
|
+
const mergedHeadElements = this.mergeProvisionalElements();
|
2644
2694
|
const newStylesheetElements = this.copyNewHeadStylesheetElements();
|
2645
2695
|
this.copyNewHeadScriptElements();
|
2646
|
-
|
2647
|
-
this.copyNewHeadProvisionalElements();
|
2696
|
+
await mergedHeadElements;
|
2648
2697
|
await newStylesheetElements;
|
2649
2698
|
}
|
2650
|
-
replaceBody() {
|
2651
|
-
this.preservingPermanentElements((() => {
|
2699
|
+
async replaceBody() {
|
2700
|
+
await this.preservingPermanentElements((async () => {
|
2652
2701
|
this.activateNewBody();
|
2653
|
-
this.assignNewBody();
|
2702
|
+
await this.assignNewBody();
|
2654
2703
|
}));
|
2655
2704
|
}
|
2656
2705
|
get trackedElementsAreIdentical() {
|
@@ -2669,6 +2718,35 @@ class PageRenderer extends Renderer {
|
|
2669
2718
|
document.head.appendChild(activateScriptElement(element));
|
2670
2719
|
}
|
2671
2720
|
}
|
2721
|
+
async mergeProvisionalElements() {
|
2722
|
+
const newHeadElements = [ ...this.newHeadProvisionalElements ];
|
2723
|
+
for (const element of this.currentHeadProvisionalElements) {
|
2724
|
+
if (!this.isCurrentElementInElementList(element, newHeadElements)) {
|
2725
|
+
document.head.removeChild(element);
|
2726
|
+
}
|
2727
|
+
}
|
2728
|
+
for (const element of newHeadElements) {
|
2729
|
+
document.head.appendChild(element);
|
2730
|
+
}
|
2731
|
+
}
|
2732
|
+
isCurrentElementInElementList(element, elementList) {
|
2733
|
+
for (const [index, newElement] of elementList.entries()) {
|
2734
|
+
if (element.tagName == "TITLE") {
|
2735
|
+
if (newElement.tagName != "TITLE") {
|
2736
|
+
continue;
|
2737
|
+
}
|
2738
|
+
if (element.innerHTML == newElement.innerHTML) {
|
2739
|
+
elementList.splice(index, 1);
|
2740
|
+
return true;
|
2741
|
+
}
|
2742
|
+
}
|
2743
|
+
if (newElement.isEqualNode(element)) {
|
2744
|
+
elementList.splice(index, 1);
|
2745
|
+
return true;
|
2746
|
+
}
|
2747
|
+
}
|
2748
|
+
return false;
|
2749
|
+
}
|
2672
2750
|
removeCurrentHeadProvisionalElements() {
|
2673
2751
|
for (const element of this.currentHeadProvisionalElements) {
|
2674
2752
|
document.head.removeChild(element);
|
@@ -2689,8 +2767,8 @@ class PageRenderer extends Renderer {
|
|
2689
2767
|
inertScriptElement.replaceWith(activatedScriptElement);
|
2690
2768
|
}
|
2691
2769
|
}
|
2692
|
-
assignNewBody() {
|
2693
|
-
this.renderElement(this.currentElement, this.newElement);
|
2770
|
+
async assignNewBody() {
|
2771
|
+
await this.renderElement(this.currentElement, this.newElement);
|
2694
2772
|
}
|
2695
2773
|
get newHeadStylesheetElements() {
|
2696
2774
|
return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
|
@@ -2777,10 +2855,10 @@ class PageView extends View {
|
|
2777
2855
|
clearSnapshotCache() {
|
2778
2856
|
this.snapshotCache.clear();
|
2779
2857
|
}
|
2780
|
-
async cacheSnapshot() {
|
2781
|
-
if (
|
2858
|
+
async cacheSnapshot(snapshot = this.snapshot) {
|
2859
|
+
if (snapshot.isCacheable) {
|
2782
2860
|
this.delegate.viewWillCacheSnapshot();
|
2783
|
-
const {
|
2861
|
+
const {lastRenderedLocation: location} = this;
|
2784
2862
|
await nextEventLoopTick();
|
2785
2863
|
const cachedSnapshot = snapshot.clone();
|
2786
2864
|
this.snapshotCache.put(location, cachedSnapshot);
|
@@ -2793,9 +2871,6 @@ class PageView extends View {
|
|
2793
2871
|
get snapshot() {
|
2794
2872
|
return PageSnapshot.fromElement(this.element);
|
2795
2873
|
}
|
2796
|
-
get shouldCacheSnapshot() {
|
2797
|
-
return this.snapshot.isCacheable;
|
2798
|
-
}
|
2799
2874
|
}
|
2800
2875
|
|
2801
2876
|
class Preloader {
|
@@ -3127,8 +3202,8 @@ class Session {
|
|
3127
3202
|
}
|
3128
3203
|
}
|
3129
3204
|
elementIsNavigatable(element) {
|
3130
|
-
const container = element
|
3131
|
-
const withinFrame = element
|
3205
|
+
const container = findClosestRecursively(element, "[data-turbo]");
|
3206
|
+
const withinFrame = findClosestRecursively(element, "turbo-frame");
|
3132
3207
|
if (this.drive || withinFrame) {
|
3133
3208
|
if (container) {
|
3134
3209
|
return container.getAttribute("data-turbo") != "false";
|
@@ -3144,8 +3219,7 @@ class Session {
|
|
3144
3219
|
}
|
3145
3220
|
}
|
3146
3221
|
getActionForLink(link) {
|
3147
|
-
|
3148
|
-
return isAction(action) ? action : "advance";
|
3222
|
+
return getVisitAction(link) || "advance";
|
3149
3223
|
}
|
3150
3224
|
get snapshot() {
|
3151
3225
|
return this.view.snapshot;
|
@@ -3213,7 +3287,10 @@ const StreamActions = {
|
|
3213
3287
|
this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
|
3214
3288
|
},
|
3215
3289
|
update() {
|
3216
|
-
this.targetElements.forEach((
|
3290
|
+
this.targetElements.forEach((targetElement => {
|
3291
|
+
targetElement.innerHTML = "";
|
3292
|
+
targetElement.append(this.templateContent);
|
3293
|
+
}));
|
3217
3294
|
}
|
3218
3295
|
};
|
3219
3296
|
|
@@ -3305,7 +3382,7 @@ class FrameController {
|
|
3305
3382
|
this.view = new FrameView(this, this.element);
|
3306
3383
|
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
3307
3384
|
this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);
|
3308
|
-
this.
|
3385
|
+
this.linkInterceptor = new LinkInterceptor(this, this.element);
|
3309
3386
|
this.restorationIdentifier = uuid();
|
3310
3387
|
this.formSubmitObserver = new FormSubmitObserver(this, this.element);
|
3311
3388
|
}
|
@@ -3318,7 +3395,7 @@ class FrameController {
|
|
3318
3395
|
this.loadSourceURL();
|
3319
3396
|
}
|
3320
3397
|
this.formLinkClickObserver.start();
|
3321
|
-
this.
|
3398
|
+
this.linkInterceptor.start();
|
3322
3399
|
this.formSubmitObserver.start();
|
3323
3400
|
}
|
3324
3401
|
}
|
@@ -3327,7 +3404,7 @@ class FrameController {
|
|
3327
3404
|
this.connected = false;
|
3328
3405
|
this.appearanceObserver.stop();
|
3329
3406
|
this.formLinkClickObserver.stop();
|
3330
|
-
this.
|
3407
|
+
this.linkInterceptor.stop();
|
3331
3408
|
this.formSubmitObserver.stop();
|
3332
3409
|
}
|
3333
3410
|
}
|
@@ -3345,6 +3422,15 @@ class FrameController {
|
|
3345
3422
|
this.loadSourceURL();
|
3346
3423
|
}
|
3347
3424
|
}
|
3425
|
+
sourceURLReloaded() {
|
3426
|
+
const {src: src} = this.element;
|
3427
|
+
this.ignoringChangesToAttribute("complete", (() => {
|
3428
|
+
this.element.removeAttribute("complete");
|
3429
|
+
}));
|
3430
|
+
this.element.src = null;
|
3431
|
+
this.element.src = src;
|
3432
|
+
return this.element.loaded;
|
3433
|
+
}
|
3348
3434
|
completeChanged() {
|
3349
3435
|
if (this.isIgnoringChangesTo("complete")) return;
|
3350
3436
|
this.loadSourceURL();
|
@@ -3396,21 +3482,22 @@ class FrameController {
|
|
3396
3482
|
this.fetchResponseLoaded = () => {};
|
3397
3483
|
}
|
3398
3484
|
}
|
3399
|
-
elementAppearedInViewport(
|
3485
|
+
elementAppearedInViewport(element) {
|
3486
|
+
this.proposeVisitIfNavigatedWithAction(element, element);
|
3400
3487
|
this.loadSourceURL();
|
3401
3488
|
}
|
3402
3489
|
willSubmitFormLinkToLocation(link) {
|
3403
|
-
return
|
3490
|
+
return this.shouldInterceptNavigation(link);
|
3404
3491
|
}
|
3405
3492
|
submittedFormLinkToLocation(link, _location, form) {
|
3406
3493
|
const frame = this.findFrameElement(link);
|
3407
3494
|
if (frame) form.setAttribute("data-turbo-frame", frame.id);
|
3408
3495
|
}
|
3409
|
-
|
3410
|
-
return this.shouldInterceptNavigation(element)
|
3496
|
+
shouldInterceptLinkClick(element, _location, _event) {
|
3497
|
+
return this.shouldInterceptNavigation(element);
|
3411
3498
|
}
|
3412
|
-
|
3413
|
-
this.navigateFrame(element, location
|
3499
|
+
linkClickIntercepted(element, location) {
|
3500
|
+
this.navigateFrame(element, location);
|
3414
3501
|
}
|
3415
3502
|
willSubmitForm(element, submitter) {
|
3416
3503
|
return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
|
@@ -3421,12 +3508,12 @@ class FrameController {
|
|
3421
3508
|
}
|
3422
3509
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
3423
3510
|
const {fetchRequest: fetchRequest} = this.formSubmission;
|
3424
|
-
this.
|
3511
|
+
this.prepareRequest(fetchRequest);
|
3425
3512
|
this.formSubmission.start();
|
3426
3513
|
}
|
3427
|
-
|
3514
|
+
prepareRequest(request) {
|
3428
3515
|
var _a;
|
3429
|
-
headers["Turbo-Frame"] = this.id;
|
3516
|
+
request.headers["Turbo-Frame"] = this.id;
|
3430
3517
|
if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
|
3431
3518
|
request.acceptResponseType(StreamMessage.contentType);
|
3432
3519
|
}
|
@@ -3458,7 +3545,7 @@ class FrameController {
|
|
3458
3545
|
}
|
3459
3546
|
formSubmissionSucceededWithResponse(formSubmission, response) {
|
3460
3547
|
const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
|
3461
|
-
|
3548
|
+
frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
|
3462
3549
|
frame.delegate.loadResponse(response);
|
3463
3550
|
}
|
3464
3551
|
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
@@ -3508,15 +3595,15 @@ class FrameController {
|
|
3508
3595
|
}
|
3509
3596
|
navigateFrame(element, url, submitter) {
|
3510
3597
|
const frame = this.findFrameElement(element, submitter);
|
3511
|
-
|
3598
|
+
frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
|
3512
3599
|
this.withCurrentNavigationElement(element, (() => {
|
3513
3600
|
frame.src = url;
|
3514
3601
|
}));
|
3515
3602
|
}
|
3516
3603
|
proposeVisitIfNavigatedWithAction(frame, element, submitter) {
|
3517
3604
|
this.action = getVisitAction(submitter, element, frame);
|
3518
|
-
this.
|
3519
|
-
|
3605
|
+
if (this.action) {
|
3606
|
+
const pageSnapshot = PageSnapshot.fromElement(frame).clone();
|
3520
3607
|
const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
|
3521
3608
|
frame.delegate.fetchResponseLoaded = fetchResponse => {
|
3522
3609
|
if (frame.src) {
|
@@ -3532,7 +3619,8 @@ class FrameController {
|
|
3532
3619
|
visitCachedSnapshot: visitCachedSnapshot,
|
3533
3620
|
willRender: false,
|
3534
3621
|
updateHistory: false,
|
3535
|
-
restorationIdentifier: this.restorationIdentifier
|
3622
|
+
restorationIdentifier: this.restorationIdentifier,
|
3623
|
+
snapshot: pageSnapshot
|
3536
3624
|
};
|
3537
3625
|
if (this.action) options.action = this.action;
|
3538
3626
|
session.visit(frame.src, options);
|
@@ -3541,9 +3629,9 @@ class FrameController {
|
|
3541
3629
|
}
|
3542
3630
|
}
|
3543
3631
|
changeHistory() {
|
3544
|
-
if (this.action
|
3632
|
+
if (this.action) {
|
3545
3633
|
const method = getHistoryMethodForAction(this.action);
|
3546
|
-
session.history.update(method, expandURL(this.
|
3634
|
+
session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
|
3547
3635
|
}
|
3548
3636
|
}
|
3549
3637
|
willHandleFrameMissingFromResponse(fetchResponse) {
|
@@ -3671,17 +3759,6 @@ class FrameController {
|
|
3671
3759
|
const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
|
3672
3760
|
return expandURL(root);
|
3673
3761
|
}
|
3674
|
-
frameAllowsVisitingLocation(target, {href: url}, originalEvent) {
|
3675
|
-
const event = dispatch("turbo:click", {
|
3676
|
-
target: target,
|
3677
|
-
detail: {
|
3678
|
-
url: url,
|
3679
|
-
originalEvent: originalEvent
|
3680
|
-
},
|
3681
|
-
cancelable: true
|
3682
|
-
});
|
3683
|
-
return !event.defaultPrevented;
|
3684
|
-
}
|
3685
3762
|
isIgnoringChangesTo(attributeName) {
|
3686
3763
|
return this.ignoredAttributes.has(attributeName);
|
3687
3764
|
}
|
@@ -3997,18 +4074,21 @@ class TurboCableStreamSourceElement extends HTMLElement {
|
|
3997
4074
|
}
|
3998
4075
|
}
|
3999
4076
|
|
4000
|
-
customElements.
|
4077
|
+
if (customElements.get("turbo-cable-stream-source") === undefined) {
|
4078
|
+
customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement);
|
4079
|
+
}
|
4001
4080
|
|
4002
4081
|
function encodeMethodIntoRequestBody(event) {
|
4003
4082
|
if (event.target instanceof HTMLFormElement) {
|
4004
4083
|
const {target: form, detail: {fetchOptions: fetchOptions}} = event;
|
4005
4084
|
form.addEventListener("turbo:submit-start", (({detail: {formSubmission: {submitter: submitter}}}) => {
|
4006
|
-
const
|
4085
|
+
const body = isBodyInit(fetchOptions.body) ? fetchOptions.body : new URLSearchParams;
|
4086
|
+
const method = determineFetchMethod(submitter, body, form);
|
4007
4087
|
if (!/get/i.test(method)) {
|
4008
4088
|
if (/post/i.test(method)) {
|
4009
|
-
|
4089
|
+
body.delete("_method");
|
4010
4090
|
} else {
|
4011
|
-
|
4091
|
+
body.set("_method", method);
|
4012
4092
|
}
|
4013
4093
|
fetchOptions.method = "post";
|
4014
4094
|
}
|
@@ -4018,6 +4098,35 @@ function encodeMethodIntoRequestBody(event) {
|
|
4018
4098
|
}
|
4019
4099
|
}
|
4020
4100
|
|
4101
|
+
function determineFetchMethod(submitter, body, form) {
|
4102
|
+
const formMethod = determineFormMethod(submitter);
|
4103
|
+
const overrideMethod = body.get("_method");
|
4104
|
+
const method = form.getAttribute("method") || "get";
|
4105
|
+
if (typeof formMethod == "string") {
|
4106
|
+
return formMethod;
|
4107
|
+
} else if (typeof overrideMethod == "string") {
|
4108
|
+
return overrideMethod;
|
4109
|
+
} else {
|
4110
|
+
return method;
|
4111
|
+
}
|
4112
|
+
}
|
4113
|
+
|
4114
|
+
function determineFormMethod(submitter) {
|
4115
|
+
if (submitter instanceof HTMLButtonElement || submitter instanceof HTMLInputElement) {
|
4116
|
+
if (submitter.hasAttribute("formmethod")) {
|
4117
|
+
return submitter.formMethod;
|
4118
|
+
} else {
|
4119
|
+
return null;
|
4120
|
+
}
|
4121
|
+
} else {
|
4122
|
+
return null;
|
4123
|
+
}
|
4124
|
+
}
|
4125
|
+
|
4126
|
+
function isBodyInit(body) {
|
4127
|
+
return body instanceof FormData || body instanceof URLSearchParams;
|
4128
|
+
}
|
4129
|
+
|
4021
4130
|
addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
|
4022
4131
|
|
4023
4132
|
var adapters = {
|