@hotwired/turbo 7.2.4 → 7.3.0
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 +238 -128
- package/dist/turbo.es2017-umd.js +238 -128
- package/dist/types/core/bardo.d.ts +1 -1
- package/dist/types/core/drive/form_submission.d.ts +10 -6
- package/dist/types/core/drive/head_snapshot.d.ts +3 -3
- package/dist/types/core/drive/history.d.ts +3 -3
- package/dist/types/core/drive/navigator.d.ts +2 -2
- package/dist/types/core/drive/page_renderer.d.ts +4 -2
- package/dist/types/core/drive/page_view.d.ts +2 -2
- package/dist/types/core/drive/visit.d.ts +5 -5
- package/dist/types/core/errors.d.ts +2 -0
- package/dist/types/core/frames/frame_controller.d.ts +11 -9
- package/dist/types/core/frames/frame_view.d.ts +2 -2
- package/dist/types/core/index.d.ts +5 -4
- package/dist/types/core/native/browser_adapter.d.ts +1 -1
- package/dist/types/core/renderer.d.ts +2 -2
- package/dist/types/core/session.d.ts +12 -12
- package/dist/types/core/snapshot.d.ts +1 -1
- package/dist/types/core/streams/stream_actions.d.ts +2 -2
- package/dist/types/core/types.d.ts +3 -4
- package/dist/types/core/url.d.ts +1 -1
- package/dist/types/core/view.d.ts +1 -1
- package/dist/types/elements/frame_element.d.ts +1 -1
- package/dist/types/elements/stream_element.d.ts +2 -2
- package/dist/types/http/fetch_request.d.ts +8 -8
- package/dist/types/http/index.d.ts +1 -1
- package/dist/types/observers/appearance_observer.d.ts +6 -6
- package/dist/types/observers/cache_observer.d.ts +5 -1
- package/dist/types/observers/form_link_click_observer.d.ts +1 -1
- package/dist/types/observers/link_click_observer.d.ts +1 -1
- package/dist/types/polyfills/custom-elements-native-shim.d.ts +1 -0
- package/dist/types/tests/functional/frame_tests.d.ts +7 -0
- package/dist/types/tests/helpers/dom_test_case.d.ts +1 -2
- package/dist/types/tests/helpers/page.d.ts +15 -8
- package/dist/types/tests/unit/deprecated_adapter_support_tests.d.ts +1 -0
- package/dist/types/tests/unit/export_tests.d.ts +1 -5
- package/dist/types/tests/unit/stream_element_tests.d.ts +0 -10
- package/dist/types/util.d.ts +3 -1
- package/package.json +17 -11
- package/dist/types/tests/helpers/intern_test_case.d.ts +0 -20
- package/dist/types/tests/unit/deprecated_adapter_support_test.d.ts +0 -24
- package/dist/types/tests/unit/index.d.ts +0 -3
- /package/dist/types/tests/{functional → integration}/ujs_tests.d.ts +0 -0
package/dist/turbo.es2017-esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Turbo 7.
|
|
3
|
-
Copyright ©
|
|
2
|
+
Turbo 7.3.0
|
|
3
|
+
Copyright © 2023 37signals LLC
|
|
4
4
|
*/
|
|
5
5
|
(function () {
|
|
6
6
|
if (window.Reflect === undefined ||
|
|
@@ -87,16 +87,13 @@ function clickCaptured(event) {
|
|
|
87
87
|
(function () {
|
|
88
88
|
if ("submitter" in Event.prototype)
|
|
89
89
|
return;
|
|
90
|
-
let prototype;
|
|
90
|
+
let prototype = window.Event.prototype;
|
|
91
91
|
if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
|
|
92
92
|
prototype = window.SubmitEvent.prototype;
|
|
93
93
|
}
|
|
94
94
|
else if ("SubmitEvent" in window) {
|
|
95
95
|
return;
|
|
96
96
|
}
|
|
97
|
-
else {
|
|
98
|
-
prototype = window.Event.prototype;
|
|
99
|
-
}
|
|
100
97
|
addEventListener("click", clickCaptured, true);
|
|
101
98
|
Object.defineProperty(prototype, "submitter", {
|
|
102
99
|
get() {
|
|
@@ -113,14 +110,14 @@ var FrameLoadingStyle;
|
|
|
113
110
|
FrameLoadingStyle["lazy"] = "lazy";
|
|
114
111
|
})(FrameLoadingStyle || (FrameLoadingStyle = {}));
|
|
115
112
|
class FrameElement extends HTMLElement {
|
|
113
|
+
static get observedAttributes() {
|
|
114
|
+
return ["disabled", "complete", "loading", "src"];
|
|
115
|
+
}
|
|
116
116
|
constructor() {
|
|
117
117
|
super();
|
|
118
118
|
this.loaded = Promise.resolve();
|
|
119
119
|
this.delegate = new FrameElement.delegateConstructor(this);
|
|
120
120
|
}
|
|
121
|
-
static get observedAttributes() {
|
|
122
|
-
return ["disabled", "complete", "loading", "src"];
|
|
123
|
-
}
|
|
124
121
|
connectedCallback() {
|
|
125
122
|
this.delegate.connect();
|
|
126
123
|
}
|
|
@@ -307,10 +304,6 @@ class FetchResponse {
|
|
|
307
304
|
}
|
|
308
305
|
}
|
|
309
306
|
|
|
310
|
-
function isAction(action) {
|
|
311
|
-
return action == "advance" || action == "replace" || action == "restore";
|
|
312
|
-
}
|
|
313
|
-
|
|
314
307
|
function activateScriptElement(element) {
|
|
315
308
|
if (element.getAttribute("data-turbo-eval") == "false") {
|
|
316
309
|
return element;
|
|
@@ -341,6 +334,7 @@ function dispatch(eventName, { target, cancelable, detail } = {}) {
|
|
|
341
334
|
const event = new CustomEvent(eventName, {
|
|
342
335
|
cancelable,
|
|
343
336
|
bubbles: true,
|
|
337
|
+
composed: true,
|
|
344
338
|
detail,
|
|
345
339
|
});
|
|
346
340
|
if (target && target.isConnected) {
|
|
@@ -440,6 +434,9 @@ function getHistoryMethodForAction(action) {
|
|
|
440
434
|
return history.pushState;
|
|
441
435
|
}
|
|
442
436
|
}
|
|
437
|
+
function isAction(action) {
|
|
438
|
+
return action == "advance" || action == "replace" || action == "restore";
|
|
439
|
+
}
|
|
443
440
|
function getVisitAction(...elements) {
|
|
444
441
|
const action = getAttribute("data-turbo-action", ...elements);
|
|
445
442
|
return isAction(action) ? action : null;
|
|
@@ -461,6 +458,13 @@ function setMetaContent(name, content) {
|
|
|
461
458
|
element.setAttribute("content", content);
|
|
462
459
|
return element;
|
|
463
460
|
}
|
|
461
|
+
function findClosestRecursively(element, selector) {
|
|
462
|
+
var _a;
|
|
463
|
+
if (element instanceof Element) {
|
|
464
|
+
return (element.closest(selector) ||
|
|
465
|
+
findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
464
468
|
|
|
465
469
|
var FetchMethod;
|
|
466
470
|
(function (FetchMethod) {
|
|
@@ -508,9 +512,8 @@ class FetchRequest {
|
|
|
508
512
|
this.abortController.abort();
|
|
509
513
|
}
|
|
510
514
|
async perform() {
|
|
511
|
-
var _a, _b;
|
|
512
515
|
const { fetchOptions } = this;
|
|
513
|
-
|
|
516
|
+
this.delegate.prepareRequest(this);
|
|
514
517
|
await this.allowRequestToBeIntercepted(fetchOptions);
|
|
515
518
|
try {
|
|
516
519
|
this.delegate.requestStarted(this);
|
|
@@ -554,7 +557,7 @@ class FetchRequest {
|
|
|
554
557
|
credentials: "same-origin",
|
|
555
558
|
headers: this.headers,
|
|
556
559
|
redirect: "follow",
|
|
557
|
-
body: this.
|
|
560
|
+
body: this.isSafe ? null : this.body,
|
|
558
561
|
signal: this.abortSignal,
|
|
559
562
|
referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href,
|
|
560
563
|
};
|
|
@@ -564,8 +567,8 @@ class FetchRequest {
|
|
|
564
567
|
Accept: "text/html, application/xhtml+xml",
|
|
565
568
|
};
|
|
566
569
|
}
|
|
567
|
-
get
|
|
568
|
-
return this.method
|
|
570
|
+
get isSafe() {
|
|
571
|
+
return this.method === FetchMethod.get;
|
|
569
572
|
}
|
|
570
573
|
get abortSignal() {
|
|
571
574
|
return this.abortController.signal;
|
|
@@ -625,9 +628,6 @@ class AppearanceObserver {
|
|
|
625
628
|
}
|
|
626
629
|
|
|
627
630
|
class StreamMessage {
|
|
628
|
-
constructor(fragment) {
|
|
629
|
-
this.fragment = importStreamElements(fragment);
|
|
630
|
-
}
|
|
631
631
|
static wrap(message) {
|
|
632
632
|
if (typeof message == "string") {
|
|
633
633
|
return new this(createDocumentFragment(message));
|
|
@@ -636,6 +636,9 @@ class StreamMessage {
|
|
|
636
636
|
return message;
|
|
637
637
|
}
|
|
638
638
|
}
|
|
639
|
+
constructor(fragment) {
|
|
640
|
+
this.fragment = importStreamElements(fragment);
|
|
641
|
+
}
|
|
639
642
|
}
|
|
640
643
|
StreamMessage.contentType = "text/vnd.turbo-stream.html";
|
|
641
644
|
function importStreamElements(fragment) {
|
|
@@ -675,6 +678,9 @@ function formEnctypeFromString(encoding) {
|
|
|
675
678
|
}
|
|
676
679
|
}
|
|
677
680
|
class FormSubmission {
|
|
681
|
+
static confirmMethod(message, _element, _submitter) {
|
|
682
|
+
return Promise.resolve(confirm(message));
|
|
683
|
+
}
|
|
678
684
|
constructor(delegate, formElement, submitter, mustRedirect = false) {
|
|
679
685
|
this.state = FormSubmissionState.initialized;
|
|
680
686
|
this.delegate = delegate;
|
|
@@ -688,9 +694,6 @@ class FormSubmission {
|
|
|
688
694
|
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
|
|
689
695
|
this.mustRedirect = mustRedirect;
|
|
690
696
|
}
|
|
691
|
-
static confirmMethod(message, _element, _submitter) {
|
|
692
|
-
return Promise.resolve(confirm(message));
|
|
693
|
-
}
|
|
694
697
|
get method() {
|
|
695
698
|
var _a;
|
|
696
699
|
const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
|
|
@@ -718,8 +721,8 @@ class FormSubmission {
|
|
|
718
721
|
var _a;
|
|
719
722
|
return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
|
|
720
723
|
}
|
|
721
|
-
get
|
|
722
|
-
return this.fetchRequest.
|
|
724
|
+
get isSafe() {
|
|
725
|
+
return this.fetchRequest.isSafe;
|
|
723
726
|
}
|
|
724
727
|
get stringFormData() {
|
|
725
728
|
return [...this.formData].reduce((entries, [name, value]) => {
|
|
@@ -748,11 +751,11 @@ class FormSubmission {
|
|
|
748
751
|
return true;
|
|
749
752
|
}
|
|
750
753
|
}
|
|
751
|
-
|
|
752
|
-
if (!request.
|
|
754
|
+
prepareRequest(request) {
|
|
755
|
+
if (!request.isSafe) {
|
|
753
756
|
const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
|
|
754
757
|
if (token) {
|
|
755
|
-
headers["X-CSRF-Token"] = token;
|
|
758
|
+
request.headers["X-CSRF-Token"] = token;
|
|
756
759
|
}
|
|
757
760
|
}
|
|
758
761
|
if (this.requestAcceptsTurboStreamResponse(request)) {
|
|
@@ -763,6 +766,7 @@ class FormSubmission {
|
|
|
763
766
|
var _a;
|
|
764
767
|
this.state = FormSubmissionState.waiting;
|
|
765
768
|
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
|
|
769
|
+
this.setSubmitsWith();
|
|
766
770
|
dispatch("turbo:submit-start", {
|
|
767
771
|
target: this.formElement,
|
|
768
772
|
detail: { formSubmission: this },
|
|
@@ -798,17 +802,46 @@ class FormSubmission {
|
|
|
798
802
|
var _a;
|
|
799
803
|
this.state = FormSubmissionState.stopped;
|
|
800
804
|
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
|
|
805
|
+
this.resetSubmitterText();
|
|
801
806
|
dispatch("turbo:submit-end", {
|
|
802
807
|
target: this.formElement,
|
|
803
808
|
detail: Object.assign({ formSubmission: this }, this.result),
|
|
804
809
|
});
|
|
805
810
|
this.delegate.formSubmissionFinished(this);
|
|
806
811
|
}
|
|
812
|
+
setSubmitsWith() {
|
|
813
|
+
if (!this.submitter || !this.submitsWith)
|
|
814
|
+
return;
|
|
815
|
+
if (this.submitter.matches("button")) {
|
|
816
|
+
this.originalSubmitText = this.submitter.innerHTML;
|
|
817
|
+
this.submitter.innerHTML = this.submitsWith;
|
|
818
|
+
}
|
|
819
|
+
else if (this.submitter.matches("input")) {
|
|
820
|
+
const input = this.submitter;
|
|
821
|
+
this.originalSubmitText = input.value;
|
|
822
|
+
input.value = this.submitsWith;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
resetSubmitterText() {
|
|
826
|
+
if (!this.submitter || !this.originalSubmitText)
|
|
827
|
+
return;
|
|
828
|
+
if (this.submitter.matches("button")) {
|
|
829
|
+
this.submitter.innerHTML = this.originalSubmitText;
|
|
830
|
+
}
|
|
831
|
+
else if (this.submitter.matches("input")) {
|
|
832
|
+
const input = this.submitter;
|
|
833
|
+
input.value = this.originalSubmitText;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
807
836
|
requestMustRedirect(request) {
|
|
808
|
-
return !request.
|
|
837
|
+
return !request.isSafe && this.mustRedirect;
|
|
809
838
|
}
|
|
810
839
|
requestAcceptsTurboStreamResponse(request) {
|
|
811
|
-
return !request.
|
|
840
|
+
return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
|
|
841
|
+
}
|
|
842
|
+
get submitsWith() {
|
|
843
|
+
var _a;
|
|
844
|
+
return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with");
|
|
812
845
|
}
|
|
813
846
|
}
|
|
814
847
|
function buildFormData(formElement, submitter) {
|
|
@@ -940,12 +973,17 @@ function submissionDoesNotDismissDialog(form, submitter) {
|
|
|
940
973
|
return method != "dialog";
|
|
941
974
|
}
|
|
942
975
|
function submissionDoesNotTargetIFrame(form, submitter) {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
976
|
+
if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
|
|
977
|
+
const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
|
|
978
|
+
for (const element of document.getElementsByName(target)) {
|
|
979
|
+
if (element instanceof HTMLIFrameElement)
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
return true;
|
|
947
986
|
}
|
|
948
|
-
return true;
|
|
949
987
|
}
|
|
950
988
|
|
|
951
989
|
class View {
|
|
@@ -1043,8 +1081,8 @@ class View {
|
|
|
1043
1081
|
}
|
|
1044
1082
|
|
|
1045
1083
|
class FrameView extends View {
|
|
1046
|
-
|
|
1047
|
-
this.element.innerHTML = ""
|
|
1084
|
+
missing() {
|
|
1085
|
+
this.element.innerHTML = `<strong class="turbo-frame-error">Content missing</strong>`;
|
|
1048
1086
|
}
|
|
1049
1087
|
get snapshot() {
|
|
1050
1088
|
return new Snapshot(this.element);
|
|
@@ -1138,20 +1176,23 @@ class LinkClickObserver {
|
|
|
1138
1176
|
event.shiftKey);
|
|
1139
1177
|
}
|
|
1140
1178
|
findLinkFromClickTarget(target) {
|
|
1141
|
-
|
|
1142
|
-
return target.closest("a[href]:not([target^=_]):not([download])");
|
|
1143
|
-
}
|
|
1179
|
+
return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
|
|
1144
1180
|
}
|
|
1145
1181
|
getLocationForLink(link) {
|
|
1146
1182
|
return expandURL(link.getAttribute("href") || "");
|
|
1147
1183
|
}
|
|
1148
1184
|
}
|
|
1149
1185
|
function doesNotTargetIFrame(anchor) {
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1186
|
+
if (anchor.hasAttribute("target")) {
|
|
1187
|
+
for (const element of document.getElementsByName(anchor.target)) {
|
|
1188
|
+
if (element instanceof HTMLIFrameElement)
|
|
1189
|
+
return false;
|
|
1190
|
+
}
|
|
1191
|
+
return true;
|
|
1192
|
+
}
|
|
1193
|
+
else {
|
|
1194
|
+
return true;
|
|
1153
1195
|
}
|
|
1154
|
-
return true;
|
|
1155
1196
|
}
|
|
1156
1197
|
|
|
1157
1198
|
class FormLinkClickObserver {
|
|
@@ -1170,10 +1211,14 @@ class FormLinkClickObserver {
|
|
|
1170
1211
|
link.hasAttribute("data-turbo-method"));
|
|
1171
1212
|
}
|
|
1172
1213
|
followedLinkToLocation(link, location) {
|
|
1173
|
-
const action = location.href;
|
|
1174
1214
|
const form = document.createElement("form");
|
|
1215
|
+
const type = "hidden";
|
|
1216
|
+
for (const [name, value] of location.searchParams) {
|
|
1217
|
+
form.append(Object.assign(document.createElement("input"), { type, name, value }));
|
|
1218
|
+
}
|
|
1219
|
+
const action = Object.assign(location, { search: "" });
|
|
1175
1220
|
form.setAttribute("data-turbo", "true");
|
|
1176
|
-
form.setAttribute("action", action);
|
|
1221
|
+
form.setAttribute("action", action.href);
|
|
1177
1222
|
form.setAttribute("hidden", "");
|
|
1178
1223
|
const method = link.getAttribute("data-turbo-method");
|
|
1179
1224
|
if (method)
|
|
@@ -1181,7 +1226,7 @@ class FormLinkClickObserver {
|
|
|
1181
1226
|
const turboFrame = link.getAttribute("data-turbo-frame");
|
|
1182
1227
|
if (turboFrame)
|
|
1183
1228
|
form.setAttribute("data-turbo-frame", turboFrame);
|
|
1184
|
-
const turboAction = link
|
|
1229
|
+
const turboAction = getVisitAction(link);
|
|
1185
1230
|
if (turboAction)
|
|
1186
1231
|
form.setAttribute("data-turbo-action", turboAction);
|
|
1187
1232
|
const turboConfirm = link.getAttribute("data-turbo-confirm");
|
|
@@ -1198,16 +1243,16 @@ class FormLinkClickObserver {
|
|
|
1198
1243
|
}
|
|
1199
1244
|
|
|
1200
1245
|
class Bardo {
|
|
1201
|
-
|
|
1202
|
-
this.delegate = delegate;
|
|
1203
|
-
this.permanentElementMap = permanentElementMap;
|
|
1204
|
-
}
|
|
1205
|
-
static preservingPermanentElements(delegate, permanentElementMap, callback) {
|
|
1246
|
+
static async preservingPermanentElements(delegate, permanentElementMap, callback) {
|
|
1206
1247
|
const bardo = new this(delegate, permanentElementMap);
|
|
1207
1248
|
bardo.enter();
|
|
1208
|
-
callback();
|
|
1249
|
+
await callback();
|
|
1209
1250
|
bardo.leave();
|
|
1210
1251
|
}
|
|
1252
|
+
constructor(delegate, permanentElementMap) {
|
|
1253
|
+
this.delegate = delegate;
|
|
1254
|
+
this.permanentElementMap = permanentElementMap;
|
|
1255
|
+
}
|
|
1211
1256
|
enter() {
|
|
1212
1257
|
for (const id in this.permanentElementMap) {
|
|
1213
1258
|
const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
|
|
@@ -1274,8 +1319,8 @@ class Renderer {
|
|
|
1274
1319
|
delete this.resolvingFunctions;
|
|
1275
1320
|
}
|
|
1276
1321
|
}
|
|
1277
|
-
preservingPermanentElements(callback) {
|
|
1278
|
-
Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
|
|
1322
|
+
async preservingPermanentElements(callback) {
|
|
1323
|
+
await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
|
|
1279
1324
|
}
|
|
1280
1325
|
focusFirstAutofocusableElement() {
|
|
1281
1326
|
const element = this.connectedSnapshot.firstAutofocusableElement;
|
|
@@ -1314,10 +1359,6 @@ function elementIsFocusable(element) {
|
|
|
1314
1359
|
}
|
|
1315
1360
|
|
|
1316
1361
|
class FrameRenderer extends Renderer {
|
|
1317
|
-
constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
|
1318
|
-
super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
|
|
1319
|
-
this.delegate = delegate;
|
|
1320
|
-
}
|
|
1321
1362
|
static renderElement(currentElement, newElement) {
|
|
1322
1363
|
var _a;
|
|
1323
1364
|
const destinationRange = document.createRange();
|
|
@@ -1330,6 +1371,10 @@ class FrameRenderer extends Renderer {
|
|
|
1330
1371
|
currentElement.appendChild(sourceRange.extractContents());
|
|
1331
1372
|
}
|
|
1332
1373
|
}
|
|
1374
|
+
constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
|
1375
|
+
super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
|
|
1376
|
+
this.delegate = delegate;
|
|
1377
|
+
}
|
|
1333
1378
|
get shouldRender() {
|
|
1334
1379
|
return true;
|
|
1335
1380
|
}
|
|
@@ -1388,18 +1433,6 @@ function readScrollBehavior(value, defaultValue) {
|
|
|
1388
1433
|
}
|
|
1389
1434
|
|
|
1390
1435
|
class ProgressBar {
|
|
1391
|
-
constructor() {
|
|
1392
|
-
this.hiding = false;
|
|
1393
|
-
this.value = 0;
|
|
1394
|
-
this.visible = false;
|
|
1395
|
-
this.trickle = () => {
|
|
1396
|
-
this.setValue(this.value + Math.random() / 100);
|
|
1397
|
-
};
|
|
1398
|
-
this.stylesheetElement = this.createStylesheetElement();
|
|
1399
|
-
this.progressElement = this.createProgressElement();
|
|
1400
|
-
this.installStylesheetElement();
|
|
1401
|
-
this.setValue(0);
|
|
1402
|
-
}
|
|
1403
1436
|
static get defaultCSS() {
|
|
1404
1437
|
return unindent `
|
|
1405
1438
|
.turbo-progress-bar {
|
|
@@ -1417,6 +1450,18 @@ class ProgressBar {
|
|
|
1417
1450
|
}
|
|
1418
1451
|
`;
|
|
1419
1452
|
}
|
|
1453
|
+
constructor() {
|
|
1454
|
+
this.hiding = false;
|
|
1455
|
+
this.value = 0;
|
|
1456
|
+
this.visible = false;
|
|
1457
|
+
this.trickle = () => {
|
|
1458
|
+
this.setValue(this.value + Math.random() / 100);
|
|
1459
|
+
};
|
|
1460
|
+
this.stylesheetElement = this.createStylesheetElement();
|
|
1461
|
+
this.progressElement = this.createProgressElement();
|
|
1462
|
+
this.installStylesheetElement();
|
|
1463
|
+
this.setValue(0);
|
|
1464
|
+
}
|
|
1420
1465
|
show() {
|
|
1421
1466
|
if (!this.visible) {
|
|
1422
1467
|
this.visible = true;
|
|
@@ -1587,10 +1632,6 @@ function elementWithoutNonce(element) {
|
|
|
1587
1632
|
}
|
|
1588
1633
|
|
|
1589
1634
|
class PageSnapshot extends Snapshot {
|
|
1590
|
-
constructor(element, headSnapshot) {
|
|
1591
|
-
super(element);
|
|
1592
|
-
this.headSnapshot = headSnapshot;
|
|
1593
|
-
}
|
|
1594
1635
|
static fromHTMLString(html = "") {
|
|
1595
1636
|
return this.fromDocument(parseHTMLDocument(html));
|
|
1596
1637
|
}
|
|
@@ -1600,6 +1641,10 @@ class PageSnapshot extends Snapshot {
|
|
|
1600
1641
|
static fromDocument({ head, body }) {
|
|
1601
1642
|
return new this(body, new HeadSnapshot(head));
|
|
1602
1643
|
}
|
|
1644
|
+
constructor(element, headSnapshot) {
|
|
1645
|
+
super(element);
|
|
1646
|
+
this.headSnapshot = headSnapshot;
|
|
1647
|
+
}
|
|
1603
1648
|
clone() {
|
|
1604
1649
|
const clonedElement = this.element.cloneNode(true);
|
|
1605
1650
|
const selectElements = this.element.querySelectorAll("select");
|
|
@@ -1860,6 +1905,8 @@ class Visit {
|
|
|
1860
1905
|
this.adapter.visitProposedToLocation(this.redirectedToLocation, {
|
|
1861
1906
|
action: "replace",
|
|
1862
1907
|
response: this.response,
|
|
1908
|
+
shouldCacheSnapshot: false,
|
|
1909
|
+
willRender: false,
|
|
1863
1910
|
});
|
|
1864
1911
|
this.followedRedirect = true;
|
|
1865
1912
|
}
|
|
@@ -1874,7 +1921,7 @@ class Visit {
|
|
|
1874
1921
|
});
|
|
1875
1922
|
}
|
|
1876
1923
|
}
|
|
1877
|
-
|
|
1924
|
+
prepareRequest(request) {
|
|
1878
1925
|
if (this.acceptsStreamResponse) {
|
|
1879
1926
|
request.acceptResponseType(StreamMessage.contentType);
|
|
1880
1927
|
}
|
|
@@ -2097,10 +2144,11 @@ class BrowserAdapter {
|
|
|
2097
2144
|
|
|
2098
2145
|
class CacheObserver {
|
|
2099
2146
|
constructor() {
|
|
2147
|
+
this.selector = "[data-turbo-temporary]";
|
|
2148
|
+
this.deprecatedSelector = "[data-turbo-cache=false]";
|
|
2100
2149
|
this.started = false;
|
|
2101
|
-
this.
|
|
2102
|
-
const
|
|
2103
|
-
for (const element of staleElements) {
|
|
2150
|
+
this.removeTemporaryElements = ((_event) => {
|
|
2151
|
+
for (const element of this.temporaryElements) {
|
|
2104
2152
|
element.remove();
|
|
2105
2153
|
}
|
|
2106
2154
|
});
|
|
@@ -2108,15 +2156,25 @@ class CacheObserver {
|
|
|
2108
2156
|
start() {
|
|
2109
2157
|
if (!this.started) {
|
|
2110
2158
|
this.started = true;
|
|
2111
|
-
addEventListener("turbo:before-cache", this.
|
|
2159
|
+
addEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
|
2112
2160
|
}
|
|
2113
2161
|
}
|
|
2114
2162
|
stop() {
|
|
2115
2163
|
if (this.started) {
|
|
2116
2164
|
this.started = false;
|
|
2117
|
-
removeEventListener("turbo:before-cache", this.
|
|
2165
|
+
removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
|
2118
2166
|
}
|
|
2119
2167
|
}
|
|
2168
|
+
get temporaryElements() {
|
|
2169
|
+
return [...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation];
|
|
2170
|
+
}
|
|
2171
|
+
get temporaryElementsWithDeprecation() {
|
|
2172
|
+
const elements = document.querySelectorAll(this.deprecatedSelector);
|
|
2173
|
+
if (elements.length) {
|
|
2174
|
+
console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);
|
|
2175
|
+
}
|
|
2176
|
+
return [...elements];
|
|
2177
|
+
}
|
|
2120
2178
|
}
|
|
2121
2179
|
|
|
2122
2180
|
class FrameRedirector {
|
|
@@ -2315,7 +2373,7 @@ class Navigator {
|
|
|
2315
2373
|
if (formSubmission == this.formSubmission) {
|
|
2316
2374
|
const responseHTML = await fetchResponse.responseHTML;
|
|
2317
2375
|
if (responseHTML) {
|
|
2318
|
-
const shouldCacheSnapshot = formSubmission.
|
|
2376
|
+
const shouldCacheSnapshot = formSubmission.isSafe;
|
|
2319
2377
|
if (!shouldCacheSnapshot) {
|
|
2320
2378
|
this.view.clearSnapshotCache();
|
|
2321
2379
|
}
|
|
@@ -2375,10 +2433,8 @@ class Navigator {
|
|
|
2375
2433
|
get restorationIdentifier() {
|
|
2376
2434
|
return this.history.restorationIdentifier;
|
|
2377
2435
|
}
|
|
2378
|
-
getActionForFormSubmission(
|
|
2379
|
-
|
|
2380
|
-
const action = getAttribute("data-turbo-action", submitter, formElement);
|
|
2381
|
-
return isAction(action) ? action : "advance";
|
|
2436
|
+
getActionForFormSubmission({ submitter, formElement }) {
|
|
2437
|
+
return getVisitAction(submitter, formElement) || "advance";
|
|
2382
2438
|
}
|
|
2383
2439
|
}
|
|
2384
2440
|
|
|
@@ -2620,7 +2676,7 @@ class PageRenderer extends Renderer {
|
|
|
2620
2676
|
}
|
|
2621
2677
|
async render() {
|
|
2622
2678
|
if (this.willRender) {
|
|
2623
|
-
this.replaceBody();
|
|
2679
|
+
await this.replaceBody();
|
|
2624
2680
|
}
|
|
2625
2681
|
}
|
|
2626
2682
|
finishRendering() {
|
|
@@ -2639,16 +2695,16 @@ class PageRenderer extends Renderer {
|
|
|
2639
2695
|
return this.newSnapshot.element;
|
|
2640
2696
|
}
|
|
2641
2697
|
async mergeHead() {
|
|
2698
|
+
const mergedHeadElements = this.mergeProvisionalElements();
|
|
2642
2699
|
const newStylesheetElements = this.copyNewHeadStylesheetElements();
|
|
2643
2700
|
this.copyNewHeadScriptElements();
|
|
2644
|
-
|
|
2645
|
-
this.copyNewHeadProvisionalElements();
|
|
2701
|
+
await mergedHeadElements;
|
|
2646
2702
|
await newStylesheetElements;
|
|
2647
2703
|
}
|
|
2648
|
-
replaceBody() {
|
|
2649
|
-
this.preservingPermanentElements(() => {
|
|
2704
|
+
async replaceBody() {
|
|
2705
|
+
await this.preservingPermanentElements(async () => {
|
|
2650
2706
|
this.activateNewBody();
|
|
2651
|
-
this.assignNewBody();
|
|
2707
|
+
await this.assignNewBody();
|
|
2652
2708
|
});
|
|
2653
2709
|
}
|
|
2654
2710
|
get trackedElementsAreIdentical() {
|
|
@@ -2667,6 +2723,35 @@ class PageRenderer extends Renderer {
|
|
|
2667
2723
|
document.head.appendChild(activateScriptElement(element));
|
|
2668
2724
|
}
|
|
2669
2725
|
}
|
|
2726
|
+
async mergeProvisionalElements() {
|
|
2727
|
+
const newHeadElements = [...this.newHeadProvisionalElements];
|
|
2728
|
+
for (const element of this.currentHeadProvisionalElements) {
|
|
2729
|
+
if (!this.isCurrentElementInElementList(element, newHeadElements)) {
|
|
2730
|
+
document.head.removeChild(element);
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
for (const element of newHeadElements) {
|
|
2734
|
+
document.head.appendChild(element);
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
isCurrentElementInElementList(element, elementList) {
|
|
2738
|
+
for (const [index, newElement] of elementList.entries()) {
|
|
2739
|
+
if (element.tagName == "TITLE") {
|
|
2740
|
+
if (newElement.tagName != "TITLE") {
|
|
2741
|
+
continue;
|
|
2742
|
+
}
|
|
2743
|
+
if (element.innerHTML == newElement.innerHTML) {
|
|
2744
|
+
elementList.splice(index, 1);
|
|
2745
|
+
return true;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
if (newElement.isEqualNode(element)) {
|
|
2749
|
+
elementList.splice(index, 1);
|
|
2750
|
+
return true;
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
return false;
|
|
2754
|
+
}
|
|
2670
2755
|
removeCurrentHeadProvisionalElements() {
|
|
2671
2756
|
for (const element of this.currentHeadProvisionalElements) {
|
|
2672
2757
|
document.head.removeChild(element);
|
|
@@ -2687,8 +2772,8 @@ class PageRenderer extends Renderer {
|
|
|
2687
2772
|
inertScriptElement.replaceWith(activatedScriptElement);
|
|
2688
2773
|
}
|
|
2689
2774
|
}
|
|
2690
|
-
assignNewBody() {
|
|
2691
|
-
this.renderElement(this.currentElement, this.newElement);
|
|
2775
|
+
async assignNewBody() {
|
|
2776
|
+
await this.renderElement(this.currentElement, this.newElement);
|
|
2692
2777
|
}
|
|
2693
2778
|
get newHeadStylesheetElements() {
|
|
2694
2779
|
return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
|
|
@@ -3105,8 +3190,8 @@ class Session {
|
|
|
3105
3190
|
}
|
|
3106
3191
|
}
|
|
3107
3192
|
elementIsNavigatable(element) {
|
|
3108
|
-
const container = element
|
|
3109
|
-
const withinFrame = element
|
|
3193
|
+
const container = findClosestRecursively(element, "[data-turbo]");
|
|
3194
|
+
const withinFrame = findClosestRecursively(element, "turbo-frame");
|
|
3110
3195
|
if (this.drive || withinFrame) {
|
|
3111
3196
|
if (container) {
|
|
3112
3197
|
return container.getAttribute("data-turbo") != "false";
|
|
@@ -3125,8 +3210,7 @@ class Session {
|
|
|
3125
3210
|
}
|
|
3126
3211
|
}
|
|
3127
3212
|
getActionForLink(link) {
|
|
3128
|
-
|
|
3129
|
-
return isAction(action) ? action : "advance";
|
|
3213
|
+
return getVisitAction(link) || "advance";
|
|
3130
3214
|
}
|
|
3131
3215
|
get snapshot() {
|
|
3132
3216
|
return this.view.snapshot;
|
|
@@ -3186,7 +3270,10 @@ const StreamActions = {
|
|
|
3186
3270
|
this.targetElements.forEach((e) => e.replaceWith(this.templateContent));
|
|
3187
3271
|
},
|
|
3188
3272
|
update() {
|
|
3189
|
-
this.targetElements.forEach((
|
|
3273
|
+
this.targetElements.forEach((targetElement) => {
|
|
3274
|
+
targetElement.innerHTML = "";
|
|
3275
|
+
targetElement.append(this.templateContent);
|
|
3276
|
+
});
|
|
3190
3277
|
},
|
|
3191
3278
|
};
|
|
3192
3279
|
|
|
@@ -3246,6 +3333,9 @@ var Turbo = /*#__PURE__*/Object.freeze({
|
|
|
3246
3333
|
StreamActions: StreamActions
|
|
3247
3334
|
});
|
|
3248
3335
|
|
|
3336
|
+
class TurboFrameMissingError extends Error {
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3249
3339
|
class FrameController {
|
|
3250
3340
|
constructor(element) {
|
|
3251
3341
|
this.fetchResponseLoaded = (_fetchResponse) => { };
|
|
@@ -3346,35 +3436,22 @@ class FrameController {
|
|
|
3346
3436
|
try {
|
|
3347
3437
|
const html = await fetchResponse.responseHTML;
|
|
3348
3438
|
if (html) {
|
|
3349
|
-
const
|
|
3350
|
-
const
|
|
3351
|
-
if (
|
|
3352
|
-
|
|
3353
|
-
const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
|
|
3354
|
-
if (this.view.renderPromise)
|
|
3355
|
-
await this.view.renderPromise;
|
|
3356
|
-
this.changeHistory();
|
|
3357
|
-
await this.view.render(renderer);
|
|
3358
|
-
this.complete = true;
|
|
3359
|
-
session.frameRendered(fetchResponse, this.element);
|
|
3360
|
-
session.frameLoaded(this.element);
|
|
3361
|
-
this.fetchResponseLoaded(fetchResponse);
|
|
3439
|
+
const document = parseHTMLDocument(html);
|
|
3440
|
+
const pageSnapshot = PageSnapshot.fromDocument(document);
|
|
3441
|
+
if (pageSnapshot.isVisitable) {
|
|
3442
|
+
await this.loadFrameResponse(fetchResponse, document);
|
|
3362
3443
|
}
|
|
3363
|
-
else
|
|
3364
|
-
|
|
3365
|
-
this.visitResponse(fetchResponse.response);
|
|
3444
|
+
else {
|
|
3445
|
+
await this.handleUnvisitableFrameResponse(fetchResponse);
|
|
3366
3446
|
}
|
|
3367
3447
|
}
|
|
3368
3448
|
}
|
|
3369
|
-
catch (error) {
|
|
3370
|
-
console.error(error);
|
|
3371
|
-
this.view.invalidate();
|
|
3372
|
-
}
|
|
3373
3449
|
finally {
|
|
3374
3450
|
this.fetchResponseLoaded = () => { };
|
|
3375
3451
|
}
|
|
3376
3452
|
}
|
|
3377
|
-
elementAppearedInViewport(
|
|
3453
|
+
elementAppearedInViewport(element) {
|
|
3454
|
+
this.proposeVisitIfNavigatedWithAction(element, element);
|
|
3378
3455
|
this.loadSourceURL();
|
|
3379
3456
|
}
|
|
3380
3457
|
willSubmitFormLinkToLocation(link) {
|
|
@@ -3400,12 +3477,12 @@ class FrameController {
|
|
|
3400
3477
|
}
|
|
3401
3478
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
|
3402
3479
|
const { fetchRequest } = this.formSubmission;
|
|
3403
|
-
this.
|
|
3480
|
+
this.prepareRequest(fetchRequest);
|
|
3404
3481
|
this.formSubmission.start();
|
|
3405
3482
|
}
|
|
3406
|
-
|
|
3483
|
+
prepareRequest(request) {
|
|
3407
3484
|
var _a;
|
|
3408
|
-
headers["Turbo-Frame"] = this.id;
|
|
3485
|
+
request.headers["Turbo-Frame"] = this.id;
|
|
3409
3486
|
if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
|
|
3410
3487
|
request.acceptResponseType(StreamMessage.contentType);
|
|
3411
3488
|
}
|
|
@@ -3421,7 +3498,6 @@ class FrameController {
|
|
|
3421
3498
|
this.resolveVisitPromise();
|
|
3422
3499
|
}
|
|
3423
3500
|
async requestFailedWithResponse(request, response) {
|
|
3424
|
-
console.error(response);
|
|
3425
3501
|
await this.loadResponse(response);
|
|
3426
3502
|
this.resolveVisitPromise();
|
|
3427
3503
|
}
|
|
@@ -3439,9 +3515,13 @@ class FrameController {
|
|
|
3439
3515
|
const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
|
|
3440
3516
|
frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
|
|
3441
3517
|
frame.delegate.loadResponse(response);
|
|
3518
|
+
if (!formSubmission.isSafe) {
|
|
3519
|
+
session.clearCache();
|
|
3520
|
+
}
|
|
3442
3521
|
}
|
|
3443
3522
|
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
|
3444
3523
|
this.element.delegate.loadResponse(fetchResponse);
|
|
3524
|
+
session.clearCache();
|
|
3445
3525
|
}
|
|
3446
3526
|
formSubmissionErrored(formSubmission, error) {
|
|
3447
3527
|
console.error(error);
|
|
@@ -3469,6 +3549,24 @@ class FrameController {
|
|
|
3469
3549
|
willRenderFrame(currentElement, _newElement) {
|
|
3470
3550
|
this.previousFrameElement = currentElement.cloneNode(true);
|
|
3471
3551
|
}
|
|
3552
|
+
async loadFrameResponse(fetchResponse, document) {
|
|
3553
|
+
const newFrameElement = await this.extractForeignFrameElement(document.body);
|
|
3554
|
+
if (newFrameElement) {
|
|
3555
|
+
const snapshot = new Snapshot(newFrameElement);
|
|
3556
|
+
const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
|
|
3557
|
+
if (this.view.renderPromise)
|
|
3558
|
+
await this.view.renderPromise;
|
|
3559
|
+
this.changeHistory();
|
|
3560
|
+
await this.view.render(renderer);
|
|
3561
|
+
this.complete = true;
|
|
3562
|
+
session.frameRendered(fetchResponse, this.element);
|
|
3563
|
+
session.frameLoaded(this.element);
|
|
3564
|
+
this.fetchResponseLoaded(fetchResponse);
|
|
3565
|
+
}
|
|
3566
|
+
else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
|
|
3567
|
+
this.handleFrameMissingFromResponse(fetchResponse);
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3472
3570
|
async visit(url) {
|
|
3473
3571
|
var _a;
|
|
3474
3572
|
const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);
|
|
@@ -3485,7 +3583,6 @@ class FrameController {
|
|
|
3485
3583
|
}
|
|
3486
3584
|
navigateFrame(element, url, submitter) {
|
|
3487
3585
|
const frame = this.findFrameElement(element, submitter);
|
|
3488
|
-
this.pageSnapshot = PageSnapshot.fromElement(frame).clone();
|
|
3489
3586
|
frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
|
|
3490
3587
|
this.withCurrentNavigationElement(element, () => {
|
|
3491
3588
|
frame.src = url;
|
|
@@ -3493,7 +3590,8 @@ class FrameController {
|
|
|
3493
3590
|
}
|
|
3494
3591
|
proposeVisitIfNavigatedWithAction(frame, element, submitter) {
|
|
3495
3592
|
this.action = getVisitAction(submitter, element, frame);
|
|
3496
|
-
if (
|
|
3593
|
+
if (this.action) {
|
|
3594
|
+
const pageSnapshot = PageSnapshot.fromElement(frame).clone();
|
|
3497
3595
|
const { visitCachedSnapshot } = frame.delegate;
|
|
3498
3596
|
frame.delegate.fetchResponseLoaded = (fetchResponse) => {
|
|
3499
3597
|
if (frame.src) {
|
|
@@ -3506,7 +3604,7 @@ class FrameController {
|
|
|
3506
3604
|
willRender: false,
|
|
3507
3605
|
updateHistory: false,
|
|
3508
3606
|
restorationIdentifier: this.restorationIdentifier,
|
|
3509
|
-
snapshot:
|
|
3607
|
+
snapshot: pageSnapshot,
|
|
3510
3608
|
};
|
|
3511
3609
|
if (this.action)
|
|
3512
3610
|
options.action = this.action;
|
|
@@ -3521,6 +3619,10 @@ class FrameController {
|
|
|
3521
3619
|
session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
|
|
3522
3620
|
}
|
|
3523
3621
|
}
|
|
3622
|
+
async handleUnvisitableFrameResponse(fetchResponse) {
|
|
3623
|
+
console.warn(`The response (${fetchResponse.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.`);
|
|
3624
|
+
await this.visitResponse(fetchResponse.response);
|
|
3625
|
+
}
|
|
3524
3626
|
willHandleFrameMissingFromResponse(fetchResponse) {
|
|
3525
3627
|
this.element.setAttribute("complete", "");
|
|
3526
3628
|
const response = fetchResponse.response;
|
|
@@ -3539,6 +3641,14 @@ class FrameController {
|
|
|
3539
3641
|
});
|
|
3540
3642
|
return !event.defaultPrevented;
|
|
3541
3643
|
}
|
|
3644
|
+
handleFrameMissingFromResponse(fetchResponse) {
|
|
3645
|
+
this.view.missing();
|
|
3646
|
+
this.throwFrameMissingError(fetchResponse);
|
|
3647
|
+
}
|
|
3648
|
+
throwFrameMissingError(fetchResponse) {
|
|
3649
|
+
const message = `The response (${fetchResponse.statusCode}) did not contain the expected <turbo-frame id="${this.element.id}"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;
|
|
3650
|
+
throw new TurboFrameMissingError(message);
|
|
3651
|
+
}
|
|
3542
3652
|
async visitResponse(response) {
|
|
3543
3653
|
const wrapped = new FetchResponse(response);
|
|
3544
3654
|
const responseHTML = await wrapped.responseHTML;
|