turbo-rails 1.3.0 → 1.3.3
Sign up to get free protection for your applications and to get access to all the features.
- 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 = {
|