@hotwired/turbo-rails 7.1.3 → 7.2.0-beta.1
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/app/assets/javascripts/turbo.js +552 -258
- package/package.json +2 -2
|
@@ -88,7 +88,7 @@ class FrameElement extends HTMLElement {
|
|
|
88
88
|
this.delegate = new FrameElement.delegateConstructor(this);
|
|
89
89
|
}
|
|
90
90
|
static get observedAttributes() {
|
|
91
|
-
return [ "disabled", "loading", "src" ];
|
|
91
|
+
return [ "disabled", "complete", "loading", "src" ];
|
|
92
92
|
}
|
|
93
93
|
connectedCallback() {
|
|
94
94
|
this.delegate.connect();
|
|
@@ -98,12 +98,15 @@ class FrameElement extends HTMLElement {
|
|
|
98
98
|
}
|
|
99
99
|
reload() {
|
|
100
100
|
const {src: src} = this;
|
|
101
|
+
this.removeAttribute("complete");
|
|
101
102
|
this.src = null;
|
|
102
103
|
this.src = src;
|
|
103
104
|
}
|
|
104
105
|
attributeChangedCallback(name) {
|
|
105
106
|
if (name == "loading") {
|
|
106
107
|
this.delegate.loadingStyleChanged();
|
|
108
|
+
} else if (name == "complete") {
|
|
109
|
+
this.delegate.completeChanged();
|
|
107
110
|
} else if (name == "src") {
|
|
108
111
|
this.delegate.sourceURLChanged();
|
|
109
112
|
} else {
|
|
@@ -195,7 +198,7 @@ function getExtension(url) {
|
|
|
195
198
|
}
|
|
196
199
|
|
|
197
200
|
function isHTML(url) {
|
|
198
|
-
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
|
|
201
|
+
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
function isPrefixedBy(baseURL, url) {
|
|
@@ -327,7 +330,7 @@ function interpolate(strings, values) {
|
|
|
327
330
|
}
|
|
328
331
|
|
|
329
332
|
function uuid() {
|
|
330
|
-
return Array.
|
|
333
|
+
return Array.from({
|
|
331
334
|
length: 36
|
|
332
335
|
}).map(((_, i) => {
|
|
333
336
|
if (i == 8 || i == 13 || i == 18 || i == 23) {
|
|
@@ -367,6 +370,26 @@ function clearBusyState(...elements) {
|
|
|
367
370
|
}
|
|
368
371
|
}
|
|
369
372
|
|
|
373
|
+
function getMetaElement(name) {
|
|
374
|
+
return document.querySelector(`meta[name="${name}"]`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function getMetaContent(name) {
|
|
378
|
+
const element = getMetaElement(name);
|
|
379
|
+
return element && element.content;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function setMetaContent(name, content) {
|
|
383
|
+
let element = getMetaElement(name);
|
|
384
|
+
if (!element) {
|
|
385
|
+
element = document.createElement("meta");
|
|
386
|
+
element.setAttribute("name", name);
|
|
387
|
+
document.head.appendChild(element);
|
|
388
|
+
}
|
|
389
|
+
element.setAttribute("content", content);
|
|
390
|
+
return element;
|
|
391
|
+
}
|
|
392
|
+
|
|
370
393
|
var FetchMethod;
|
|
371
394
|
|
|
372
395
|
(function(FetchMethod) {
|
|
@@ -399,7 +422,7 @@ function fetchMethodFromString(method) {
|
|
|
399
422
|
class FetchRequest {
|
|
400
423
|
constructor(delegate, method, location, body = new URLSearchParams, target = null) {
|
|
401
424
|
this.abortController = new AbortController;
|
|
402
|
-
this.resolveRequestPromise =
|
|
425
|
+
this.resolveRequestPromise = _value => {};
|
|
403
426
|
this.delegate = delegate;
|
|
404
427
|
this.method = method;
|
|
405
428
|
this.headers = this.defaultHeaders;
|
|
@@ -601,8 +624,8 @@ class FormSubmission {
|
|
|
601
624
|
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
|
|
602
625
|
this.mustRedirect = mustRedirect;
|
|
603
626
|
}
|
|
604
|
-
static confirmMethod(message,
|
|
605
|
-
return confirm(message);
|
|
627
|
+
static confirmMethod(message, _element) {
|
|
628
|
+
return Promise.resolve(confirm(message));
|
|
606
629
|
}
|
|
607
630
|
get method() {
|
|
608
631
|
var _a;
|
|
@@ -612,7 +635,11 @@ class FormSubmission {
|
|
|
612
635
|
get action() {
|
|
613
636
|
var _a;
|
|
614
637
|
const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
|
|
615
|
-
|
|
638
|
+
if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
|
|
639
|
+
return this.submitter.getAttribute("formaction") || "";
|
|
640
|
+
} else {
|
|
641
|
+
return this.formElement.getAttribute("action") || formElementAction || "";
|
|
642
|
+
}
|
|
616
643
|
}
|
|
617
644
|
get body() {
|
|
618
645
|
if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
|
|
@@ -632,7 +659,8 @@ class FormSubmission {
|
|
|
632
659
|
return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
|
|
633
660
|
}
|
|
634
661
|
get confirmationMessage() {
|
|
635
|
-
|
|
662
|
+
var _a;
|
|
663
|
+
return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-confirm")) || this.formElement.getAttribute("data-turbo-confirm");
|
|
636
664
|
}
|
|
637
665
|
get needsConfirmation() {
|
|
638
666
|
return this.confirmationMessage !== null;
|
|
@@ -640,7 +668,7 @@ class FormSubmission {
|
|
|
640
668
|
async start() {
|
|
641
669
|
const {initialized: initialized, requesting: requesting} = FormSubmissionState;
|
|
642
670
|
if (this.needsConfirmation) {
|
|
643
|
-
const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
|
|
671
|
+
const answer = await FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
|
|
644
672
|
if (!answer) {
|
|
645
673
|
return;
|
|
646
674
|
}
|
|
@@ -664,10 +692,12 @@ class FormSubmission {
|
|
|
664
692
|
if (token) {
|
|
665
693
|
headers["X-CSRF-Token"] = token;
|
|
666
694
|
}
|
|
695
|
+
}
|
|
696
|
+
if (this.requestAcceptsTurboStreamResponse(request)) {
|
|
667
697
|
headers["Accept"] = [ StreamMessage.contentType, headers["Accept"] ].join(", ");
|
|
668
698
|
}
|
|
669
699
|
}
|
|
670
|
-
requestStarted(
|
|
700
|
+
requestStarted(_request) {
|
|
671
701
|
var _a;
|
|
672
702
|
this.state = FormSubmissionState.waiting;
|
|
673
703
|
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
|
|
@@ -714,7 +744,7 @@ class FormSubmission {
|
|
|
714
744
|
};
|
|
715
745
|
this.delegate.formSubmissionErrored(this, error);
|
|
716
746
|
}
|
|
717
|
-
requestFinished(
|
|
747
|
+
requestFinished(_request) {
|
|
718
748
|
var _a;
|
|
719
749
|
this.state = FormSubmissionState.stopped;
|
|
720
750
|
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
|
|
@@ -729,6 +759,9 @@ class FormSubmission {
|
|
|
729
759
|
requestMustRedirect(request) {
|
|
730
760
|
return !request.isIdempotent && this.mustRedirect;
|
|
731
761
|
}
|
|
762
|
+
requestAcceptsTurboStreamResponse(request) {
|
|
763
|
+
return !request.isIdempotent || this.formElement.hasAttribute("data-turbo-stream");
|
|
764
|
+
}
|
|
732
765
|
}
|
|
733
766
|
|
|
734
767
|
function buildFormData(formElement, submitter) {
|
|
@@ -752,11 +785,6 @@ function getCookieValue(cookieName) {
|
|
|
752
785
|
}
|
|
753
786
|
}
|
|
754
787
|
|
|
755
|
-
function getMetaContent(name) {
|
|
756
|
-
const element = document.querySelector(`meta[name="${name}"]`);
|
|
757
|
-
return element && element.content;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
788
|
function responseSucceededWithoutRedirect(response) {
|
|
761
789
|
return response.statusCode == 200 && !response.redirected;
|
|
762
790
|
}
|
|
@@ -775,6 +803,9 @@ class Snapshot {
|
|
|
775
803
|
constructor(element) {
|
|
776
804
|
this.element = element;
|
|
777
805
|
}
|
|
806
|
+
get activeElement() {
|
|
807
|
+
return this.element.ownerDocument.activeElement;
|
|
808
|
+
}
|
|
778
809
|
get children() {
|
|
779
810
|
return [ ...this.element.children ];
|
|
780
811
|
}
|
|
@@ -836,8 +867,8 @@ class FormInterceptor {
|
|
|
836
867
|
|
|
837
868
|
class View {
|
|
838
869
|
constructor(delegate, element) {
|
|
839
|
-
this.resolveRenderPromise =
|
|
840
|
-
this.resolveInterceptionPromise =
|
|
870
|
+
this.resolveRenderPromise = _value => {};
|
|
871
|
+
this.resolveInterceptionPromise = _value => {};
|
|
841
872
|
this.delegate = delegate;
|
|
842
873
|
this.element = element;
|
|
843
874
|
}
|
|
@@ -890,10 +921,15 @@ class View {
|
|
|
890
921
|
this.renderer = renderer;
|
|
891
922
|
this.prepareToRenderSnapshot(renderer);
|
|
892
923
|
const renderInterception = new Promise((resolve => this.resolveInterceptionPromise = resolve));
|
|
893
|
-
const
|
|
924
|
+
const options = {
|
|
925
|
+
resume: this.resolveInterceptionPromise,
|
|
926
|
+
render: this.renderer.renderElement
|
|
927
|
+
};
|
|
928
|
+
const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
|
|
894
929
|
if (!immediateRender) await renderInterception;
|
|
895
930
|
await this.renderSnapshot(renderer);
|
|
896
931
|
this.delegate.viewRenderedSnapshot(snapshot, isPreview);
|
|
932
|
+
this.delegate.preloadOnLoadLinksForView(this.element);
|
|
897
933
|
this.finishRenderingSnapshot(renderer);
|
|
898
934
|
} finally {
|
|
899
935
|
delete this.renderer;
|
|
@@ -901,11 +937,11 @@ class View {
|
|
|
901
937
|
delete this.renderPromise;
|
|
902
938
|
}
|
|
903
939
|
} else {
|
|
904
|
-
this.invalidate();
|
|
940
|
+
this.invalidate(renderer.reloadReason);
|
|
905
941
|
}
|
|
906
942
|
}
|
|
907
|
-
invalidate() {
|
|
908
|
-
this.delegate.viewInvalidated();
|
|
943
|
+
invalidate(reason) {
|
|
944
|
+
this.delegate.viewInvalidated(reason);
|
|
909
945
|
}
|
|
910
946
|
prepareToRenderSnapshot(renderer) {
|
|
911
947
|
this.markAsPreview(renderer.isPreview);
|
|
@@ -954,7 +990,7 @@ class LinkInterceptor {
|
|
|
954
990
|
}
|
|
955
991
|
delete this.clickEvent;
|
|
956
992
|
};
|
|
957
|
-
this.willVisit =
|
|
993
|
+
this.willVisit = _event => {
|
|
958
994
|
delete this.clickEvent;
|
|
959
995
|
};
|
|
960
996
|
this.delegate = delegate;
|
|
@@ -976,19 +1012,55 @@ class LinkInterceptor {
|
|
|
976
1012
|
}
|
|
977
1013
|
}
|
|
978
1014
|
|
|
1015
|
+
class FormLinkInterceptor {
|
|
1016
|
+
constructor(delegate, element) {
|
|
1017
|
+
this.delegate = delegate;
|
|
1018
|
+
this.linkInterceptor = new LinkInterceptor(this, element);
|
|
1019
|
+
}
|
|
1020
|
+
start() {
|
|
1021
|
+
this.linkInterceptor.start();
|
|
1022
|
+
}
|
|
1023
|
+
stop() {
|
|
1024
|
+
this.linkInterceptor.stop();
|
|
1025
|
+
}
|
|
1026
|
+
shouldInterceptLinkClick(link) {
|
|
1027
|
+
return this.delegate.shouldInterceptFormLinkClick(link) && (link.hasAttribute("data-turbo-method") || link.hasAttribute("data-turbo-stream"));
|
|
1028
|
+
}
|
|
1029
|
+
linkClickIntercepted(link, action) {
|
|
1030
|
+
const form = document.createElement("form");
|
|
1031
|
+
form.setAttribute("data-turbo", "true");
|
|
1032
|
+
form.setAttribute("action", action);
|
|
1033
|
+
form.setAttribute("hidden", "");
|
|
1034
|
+
const method = link.getAttribute("data-turbo-method");
|
|
1035
|
+
if (method) form.setAttribute("method", method);
|
|
1036
|
+
const turboFrame = link.getAttribute("data-turbo-frame");
|
|
1037
|
+
if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
|
|
1038
|
+
const turboConfirm = link.getAttribute("data-turbo-confirm");
|
|
1039
|
+
if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
|
|
1040
|
+
const turboStream = link.hasAttribute("data-turbo-stream");
|
|
1041
|
+
if (turboStream) form.setAttribute("data-turbo-stream", "");
|
|
1042
|
+
this.delegate.formLinkClickIntercepted(link, form);
|
|
1043
|
+
document.body.appendChild(form);
|
|
1044
|
+
form.requestSubmit();
|
|
1045
|
+
form.remove();
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
979
1049
|
class Bardo {
|
|
980
|
-
constructor(permanentElementMap) {
|
|
1050
|
+
constructor(delegate, permanentElementMap) {
|
|
1051
|
+
this.delegate = delegate;
|
|
981
1052
|
this.permanentElementMap = permanentElementMap;
|
|
982
1053
|
}
|
|
983
|
-
static preservingPermanentElements(permanentElementMap, callback) {
|
|
984
|
-
const bardo = new this(permanentElementMap);
|
|
1054
|
+
static preservingPermanentElements(delegate, permanentElementMap, callback) {
|
|
1055
|
+
const bardo = new this(delegate, permanentElementMap);
|
|
985
1056
|
bardo.enter();
|
|
986
1057
|
callback();
|
|
987
1058
|
bardo.leave();
|
|
988
1059
|
}
|
|
989
1060
|
enter() {
|
|
990
1061
|
for (const id in this.permanentElementMap) {
|
|
991
|
-
const [, newPermanentElement] = this.permanentElementMap[id];
|
|
1062
|
+
const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
|
|
1063
|
+
this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
|
|
992
1064
|
this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
|
|
993
1065
|
}
|
|
994
1066
|
}
|
|
@@ -997,6 +1069,7 @@ class Bardo {
|
|
|
997
1069
|
const [currentPermanentElement] = this.permanentElementMap[id];
|
|
998
1070
|
this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
|
|
999
1071
|
this.replacePlaceholderWithPermanentElement(currentPermanentElement);
|
|
1072
|
+
this.delegate.leavingBardo(currentPermanentElement);
|
|
1000
1073
|
}
|
|
1001
1074
|
}
|
|
1002
1075
|
replaceNewPermanentElementWithPlaceholder(permanentElement) {
|
|
@@ -1027,11 +1100,13 @@ function createPlaceholderForPermanentElement(permanentElement) {
|
|
|
1027
1100
|
}
|
|
1028
1101
|
|
|
1029
1102
|
class Renderer {
|
|
1030
|
-
constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
|
|
1103
|
+
constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
|
1104
|
+
this.activeElement = null;
|
|
1031
1105
|
this.currentSnapshot = currentSnapshot;
|
|
1032
1106
|
this.newSnapshot = newSnapshot;
|
|
1033
1107
|
this.isPreview = isPreview;
|
|
1034
1108
|
this.willRender = willRender;
|
|
1109
|
+
this.renderElement = renderElement;
|
|
1035
1110
|
this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
|
|
1036
1111
|
resolve: resolve,
|
|
1037
1112
|
reject: reject
|
|
@@ -1040,6 +1115,9 @@ class Renderer {
|
|
|
1040
1115
|
get shouldRender() {
|
|
1041
1116
|
return true;
|
|
1042
1117
|
}
|
|
1118
|
+
get reloadReason() {
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1043
1121
|
prepareToRender() {
|
|
1044
1122
|
return;
|
|
1045
1123
|
}
|
|
@@ -1064,7 +1142,7 @@ class Renderer {
|
|
|
1064
1142
|
}
|
|
1065
1143
|
}
|
|
1066
1144
|
preservingPermanentElements(callback) {
|
|
1067
|
-
Bardo.preservingPermanentElements(this.permanentElementMap, callback);
|
|
1145
|
+
Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
|
|
1068
1146
|
}
|
|
1069
1147
|
focusFirstAutofocusableElement() {
|
|
1070
1148
|
const element = this.connectedSnapshot.firstAutofocusableElement;
|
|
@@ -1072,6 +1150,18 @@ class Renderer {
|
|
|
1072
1150
|
element.focus();
|
|
1073
1151
|
}
|
|
1074
1152
|
}
|
|
1153
|
+
enteringBardo(currentPermanentElement) {
|
|
1154
|
+
if (this.activeElement) return;
|
|
1155
|
+
if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
|
|
1156
|
+
this.activeElement = this.currentSnapshot.activeElement;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
leavingBardo(currentPermanentElement) {
|
|
1160
|
+
if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
|
|
1161
|
+
this.activeElement.focus();
|
|
1162
|
+
this.activeElement = null;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1075
1165
|
get connectedSnapshot() {
|
|
1076
1166
|
return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
|
|
1077
1167
|
}
|
|
@@ -1085,8 +1175,7 @@ class Renderer {
|
|
|
1085
1175
|
return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
|
|
1086
1176
|
}
|
|
1087
1177
|
get cspNonce() {
|
|
1088
|
-
|
|
1089
|
-
return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
|
|
1178
|
+
return getMetaContent("csp-nonce");
|
|
1090
1179
|
}
|
|
1091
1180
|
}
|
|
1092
1181
|
|
|
@@ -1101,6 +1190,22 @@ function elementIsFocusable(element) {
|
|
|
1101
1190
|
}
|
|
1102
1191
|
|
|
1103
1192
|
class FrameRenderer extends Renderer {
|
|
1193
|
+
constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
|
1194
|
+
super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
|
|
1195
|
+
this.delegate = delegate;
|
|
1196
|
+
}
|
|
1197
|
+
static renderElement(currentElement, newElement) {
|
|
1198
|
+
var _a;
|
|
1199
|
+
const destinationRange = document.createRange();
|
|
1200
|
+
destinationRange.selectNodeContents(currentElement);
|
|
1201
|
+
destinationRange.deleteContents();
|
|
1202
|
+
const frameElement = newElement;
|
|
1203
|
+
const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
|
|
1204
|
+
if (sourceRange) {
|
|
1205
|
+
sourceRange.selectNodeContents(frameElement);
|
|
1206
|
+
currentElement.appendChild(sourceRange.extractContents());
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1104
1209
|
get shouldRender() {
|
|
1105
1210
|
return true;
|
|
1106
1211
|
}
|
|
@@ -1116,24 +1221,18 @@ class FrameRenderer extends Renderer {
|
|
|
1116
1221
|
this.activateScriptElements();
|
|
1117
1222
|
}
|
|
1118
1223
|
loadFrameElement() {
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
destinationRange.selectNodeContents(this.currentElement);
|
|
1122
|
-
destinationRange.deleteContents();
|
|
1123
|
-
const frameElement = this.newElement;
|
|
1124
|
-
const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
|
|
1125
|
-
if (sourceRange) {
|
|
1126
|
-
sourceRange.selectNodeContents(frameElement);
|
|
1127
|
-
this.currentElement.appendChild(sourceRange.extractContents());
|
|
1128
|
-
}
|
|
1224
|
+
this.delegate.frameExtracted(this.newElement.cloneNode(true));
|
|
1225
|
+
this.renderElement(this.currentElement, this.newElement);
|
|
1129
1226
|
}
|
|
1130
1227
|
scrollFrameIntoView() {
|
|
1131
1228
|
if (this.currentElement.autoscroll || this.newElement.autoscroll) {
|
|
1132
1229
|
const element = this.currentElement.firstElementChild;
|
|
1133
1230
|
const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
|
|
1231
|
+
const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
|
|
1134
1232
|
if (element) {
|
|
1135
1233
|
element.scrollIntoView({
|
|
1136
|
-
block: block
|
|
1234
|
+
block: block,
|
|
1235
|
+
behavior: behavior
|
|
1137
1236
|
});
|
|
1138
1237
|
return true;
|
|
1139
1238
|
}
|
|
@@ -1159,6 +1258,14 @@ function readScrollLogicalPosition(value, defaultValue) {
|
|
|
1159
1258
|
}
|
|
1160
1259
|
}
|
|
1161
1260
|
|
|
1261
|
+
function readScrollBehavior(value, defaultValue) {
|
|
1262
|
+
if (value == "auto" || value == "smooth") {
|
|
1263
|
+
return value;
|
|
1264
|
+
} else {
|
|
1265
|
+
return defaultValue;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1162
1269
|
class ProgressBar {
|
|
1163
1270
|
constructor() {
|
|
1164
1271
|
this.hiding = false;
|
|
@@ -1181,7 +1288,7 @@ class ProgressBar {
|
|
|
1181
1288
|
left: 0;
|
|
1182
1289
|
height: 3px;
|
|
1183
1290
|
background: #0076ff;
|
|
1184
|
-
z-index:
|
|
1291
|
+
z-index: 2147483647;
|
|
1185
1292
|
transition:
|
|
1186
1293
|
width ${ProgressBar.animationDuration}ms ease-out,
|
|
1187
1294
|
opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
|
|
@@ -1247,6 +1354,9 @@ class ProgressBar {
|
|
|
1247
1354
|
const element = document.createElement("style");
|
|
1248
1355
|
element.type = "text/css";
|
|
1249
1356
|
element.textContent = ProgressBar.defaultCSS;
|
|
1357
|
+
if (this.cspNonce) {
|
|
1358
|
+
element.nonce = this.cspNonce;
|
|
1359
|
+
}
|
|
1250
1360
|
return element;
|
|
1251
1361
|
}
|
|
1252
1362
|
createProgressElement() {
|
|
@@ -1254,6 +1364,9 @@ class ProgressBar {
|
|
|
1254
1364
|
element.className = "turbo-progress-bar";
|
|
1255
1365
|
return element;
|
|
1256
1366
|
}
|
|
1367
|
+
get cspNonce() {
|
|
1368
|
+
return getMetaContent("csp-nonce");
|
|
1369
|
+
}
|
|
1257
1370
|
}
|
|
1258
1371
|
|
|
1259
1372
|
ProgressBar.animationDuration = 300;
|
|
@@ -1485,9 +1598,11 @@ class Visit {
|
|
|
1485
1598
|
if (this.state == VisitState.started) {
|
|
1486
1599
|
this.recordTimingMetric(TimingMetric.visitEnd);
|
|
1487
1600
|
this.state = VisitState.completed;
|
|
1488
|
-
this.adapter.visitCompleted(this);
|
|
1489
|
-
this.delegate.visitCompleted(this);
|
|
1490
1601
|
this.followRedirect();
|
|
1602
|
+
if (!this.followedRedirect) {
|
|
1603
|
+
this.adapter.visitCompleted(this);
|
|
1604
|
+
this.delegate.visitCompleted(this);
|
|
1605
|
+
}
|
|
1491
1606
|
}
|
|
1492
1607
|
}
|
|
1493
1608
|
fail() {
|
|
@@ -1546,11 +1661,11 @@ class Visit {
|
|
|
1546
1661
|
this.cacheSnapshot();
|
|
1547
1662
|
if (this.view.renderPromise) await this.view.renderPromise;
|
|
1548
1663
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
|
1549
|
-
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
|
|
1664
|
+
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
|
|
1550
1665
|
this.adapter.visitRendered(this);
|
|
1551
1666
|
this.complete();
|
|
1552
1667
|
} else {
|
|
1553
|
-
await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
|
|
1668
|
+
await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
|
|
1554
1669
|
this.adapter.visitRendered(this);
|
|
1555
1670
|
this.fail();
|
|
1556
1671
|
}
|
|
@@ -1583,7 +1698,7 @@ class Visit {
|
|
|
1583
1698
|
this.adapter.visitRendered(this);
|
|
1584
1699
|
} else {
|
|
1585
1700
|
if (this.view.renderPromise) await this.view.renderPromise;
|
|
1586
|
-
await this.view.renderPage(snapshot, isPreview, this.willRender);
|
|
1701
|
+
await this.view.renderPage(snapshot, isPreview, this.willRender, this);
|
|
1587
1702
|
this.adapter.visitRendered(this);
|
|
1588
1703
|
if (!isPreview) {
|
|
1589
1704
|
this.complete();
|
|
@@ -1597,6 +1712,7 @@ class Visit {
|
|
|
1597
1712
|
if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
|
|
1598
1713
|
this.adapter.visitProposedToLocation(this.redirectedToLocation, {
|
|
1599
1714
|
action: "replace",
|
|
1715
|
+
willRender: false,
|
|
1600
1716
|
response: this.response
|
|
1601
1717
|
});
|
|
1602
1718
|
this.followedRedirect = true;
|
|
@@ -1613,7 +1729,7 @@ class Visit {
|
|
|
1613
1729
|
requestStarted() {
|
|
1614
1730
|
this.startRequest();
|
|
1615
1731
|
}
|
|
1616
|
-
requestPreventedHandlingResponse(
|
|
1732
|
+
requestPreventedHandlingResponse(_request, _response) {}
|
|
1617
1733
|
async requestSucceededWithResponse(request, response) {
|
|
1618
1734
|
const responseHTML = await response.responseHTML;
|
|
1619
1735
|
const {redirected: redirected, statusCode: statusCode} = response;
|
|
@@ -1647,7 +1763,7 @@ class Visit {
|
|
|
1647
1763
|
});
|
|
1648
1764
|
}
|
|
1649
1765
|
}
|
|
1650
|
-
requestErrored(
|
|
1766
|
+
requestErrored(_request, _error) {
|
|
1651
1767
|
this.recordResponse({
|
|
1652
1768
|
statusCode: SystemStatusCode.networkFailure,
|
|
1653
1769
|
redirected: false
|
|
@@ -1724,7 +1840,9 @@ class Visit {
|
|
|
1724
1840
|
}));
|
|
1725
1841
|
await callback();
|
|
1726
1842
|
delete this.frame;
|
|
1727
|
-
this.
|
|
1843
|
+
if (!this.view.forceReloaded) {
|
|
1844
|
+
this.performScroll();
|
|
1845
|
+
}
|
|
1728
1846
|
}
|
|
1729
1847
|
cancelRender() {
|
|
1730
1848
|
if (this.frame) {
|
|
@@ -1750,9 +1868,9 @@ class BrowserAdapter {
|
|
|
1750
1868
|
this.navigator.startVisit(location, uuid(), options);
|
|
1751
1869
|
}
|
|
1752
1870
|
visitStarted(visit) {
|
|
1871
|
+
this.location = visit.location;
|
|
1753
1872
|
visit.loadCachedSnapshot();
|
|
1754
1873
|
visit.issueRequest();
|
|
1755
|
-
visit.changeHistory();
|
|
1756
1874
|
visit.goToSamePageAnchor();
|
|
1757
1875
|
}
|
|
1758
1876
|
visitRequestStarted(visit) {
|
|
@@ -1771,27 +1889,32 @@ class BrowserAdapter {
|
|
|
1771
1889
|
case SystemStatusCode.networkFailure:
|
|
1772
1890
|
case SystemStatusCode.timeoutFailure:
|
|
1773
1891
|
case SystemStatusCode.contentTypeMismatch:
|
|
1774
|
-
return this.reload(
|
|
1892
|
+
return this.reload({
|
|
1893
|
+
reason: "request_failed",
|
|
1894
|
+
context: {
|
|
1895
|
+
statusCode: statusCode
|
|
1896
|
+
}
|
|
1897
|
+
});
|
|
1775
1898
|
|
|
1776
1899
|
default:
|
|
1777
1900
|
return visit.loadResponse();
|
|
1778
1901
|
}
|
|
1779
1902
|
}
|
|
1780
|
-
visitRequestFinished(
|
|
1903
|
+
visitRequestFinished(_visit) {
|
|
1781
1904
|
this.progressBar.setValue(1);
|
|
1782
1905
|
this.hideVisitProgressBar();
|
|
1783
1906
|
}
|
|
1784
|
-
visitCompleted(
|
|
1785
|
-
pageInvalidated() {
|
|
1786
|
-
this.reload();
|
|
1907
|
+
visitCompleted(_visit) {}
|
|
1908
|
+
pageInvalidated(reason) {
|
|
1909
|
+
this.reload(reason);
|
|
1787
1910
|
}
|
|
1788
|
-
visitFailed(
|
|
1789
|
-
visitRendered(
|
|
1790
|
-
formSubmissionStarted(
|
|
1911
|
+
visitFailed(_visit) {}
|
|
1912
|
+
visitRendered(_visit) {}
|
|
1913
|
+
formSubmissionStarted(_formSubmission) {
|
|
1791
1914
|
this.progressBar.setValue(0);
|
|
1792
1915
|
this.showFormProgressBarAfterDelay();
|
|
1793
1916
|
}
|
|
1794
|
-
formSubmissionFinished(
|
|
1917
|
+
formSubmissionFinished(_formSubmission) {
|
|
1795
1918
|
this.progressBar.setValue(1);
|
|
1796
1919
|
this.hideFormProgressBar();
|
|
1797
1920
|
}
|
|
@@ -1817,8 +1940,12 @@ class BrowserAdapter {
|
|
|
1817
1940
|
delete this.formProgressBarTimeout;
|
|
1818
1941
|
}
|
|
1819
1942
|
}
|
|
1820
|
-
reload() {
|
|
1821
|
-
|
|
1943
|
+
reload(reason) {
|
|
1944
|
+
dispatch("turbo:reload", {
|
|
1945
|
+
detail: reason
|
|
1946
|
+
});
|
|
1947
|
+
if (!this.location) return;
|
|
1948
|
+
window.location.href = this.location.toString();
|
|
1822
1949
|
}
|
|
1823
1950
|
get navigator() {
|
|
1824
1951
|
return this.session.navigator;
|
|
@@ -1828,6 +1955,12 @@ class BrowserAdapter {
|
|
|
1828
1955
|
class CacheObserver {
|
|
1829
1956
|
constructor() {
|
|
1830
1957
|
this.started = false;
|
|
1958
|
+
this.removeStaleElements = _event => {
|
|
1959
|
+
const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
|
|
1960
|
+
for (const element of staleElements) {
|
|
1961
|
+
element.remove();
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1831
1964
|
}
|
|
1832
1965
|
start() {
|
|
1833
1966
|
if (!this.started) {
|
|
@@ -1841,12 +1974,6 @@ class CacheObserver {
|
|
|
1841
1974
|
removeEventListener("turbo:before-cache", this.removeStaleElements, false);
|
|
1842
1975
|
}
|
|
1843
1976
|
}
|
|
1844
|
-
removeStaleElements() {
|
|
1845
|
-
const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
|
|
1846
|
-
for (const element of staleElements) {
|
|
1847
|
-
element.remove();
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
1977
|
}
|
|
1851
1978
|
|
|
1852
1979
|
class FormSubmitObserver {
|
|
@@ -1860,12 +1987,9 @@ class FormSubmitObserver {
|
|
|
1860
1987
|
if (!event.defaultPrevented) {
|
|
1861
1988
|
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
|
1862
1989
|
const submitter = event.submitter || undefined;
|
|
1863
|
-
if (form) {
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
event.preventDefault();
|
|
1867
|
-
this.delegate.formSubmitted(form, submitter);
|
|
1868
|
-
}
|
|
1990
|
+
if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
|
|
1991
|
+
event.preventDefault();
|
|
1992
|
+
this.delegate.formSubmitted(form, submitter);
|
|
1869
1993
|
}
|
|
1870
1994
|
}
|
|
1871
1995
|
};
|
|
@@ -1885,6 +2009,19 @@ class FormSubmitObserver {
|
|
|
1885
2009
|
}
|
|
1886
2010
|
}
|
|
1887
2011
|
|
|
2012
|
+
function submissionDoesNotDismissDialog(form, submitter) {
|
|
2013
|
+
const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
|
|
2014
|
+
return method != "dialog";
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
function submissionDoesNotTargetIFrame(form, submitter) {
|
|
2018
|
+
const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
|
|
2019
|
+
for (const element of document.getElementsByName(target)) {
|
|
2020
|
+
if (element instanceof HTMLIFrameElement) return false;
|
|
2021
|
+
}
|
|
2022
|
+
return true;
|
|
2023
|
+
}
|
|
2024
|
+
|
|
1888
2025
|
class FrameRedirector {
|
|
1889
2026
|
constructor(element) {
|
|
1890
2027
|
this.element = element;
|
|
@@ -1899,7 +2036,7 @@ class FrameRedirector {
|
|
|
1899
2036
|
this.linkInterceptor.stop();
|
|
1900
2037
|
this.formInterceptor.stop();
|
|
1901
2038
|
}
|
|
1902
|
-
shouldInterceptLinkClick(element,
|
|
2039
|
+
shouldInterceptLinkClick(element, _url) {
|
|
1903
2040
|
return this.shouldRedirect(element);
|
|
1904
2041
|
}
|
|
1905
2042
|
linkClickIntercepted(element, url) {
|
|
@@ -1914,7 +2051,6 @@ class FrameRedirector {
|
|
|
1914
2051
|
formSubmissionIntercepted(element, submitter) {
|
|
1915
2052
|
const frame = this.findFrameElement(element, submitter);
|
|
1916
2053
|
if (frame) {
|
|
1917
|
-
frame.removeAttribute("reloadable");
|
|
1918
2054
|
frame.delegate.formSubmissionIntercepted(element, submitter);
|
|
1919
2055
|
}
|
|
1920
2056
|
}
|
|
@@ -1957,7 +2093,7 @@ class History {
|
|
|
1957
2093
|
}
|
|
1958
2094
|
}
|
|
1959
2095
|
};
|
|
1960
|
-
this.onPageLoad = async
|
|
2096
|
+
this.onPageLoad = async _event => {
|
|
1961
2097
|
await nextMicrotask();
|
|
1962
2098
|
this.pageLoaded = true;
|
|
1963
2099
|
};
|
|
@@ -2034,9 +2170,9 @@ class LinkClickObserver {
|
|
|
2034
2170
|
if (this.clickEventIsSignificant(event)) {
|
|
2035
2171
|
const target = event.composedPath && event.composedPath()[0] || event.target;
|
|
2036
2172
|
const link = this.findLinkFromClickTarget(target);
|
|
2037
|
-
if (link) {
|
|
2173
|
+
if (link && doesNotTargetIFrame(link)) {
|
|
2038
2174
|
const location = this.getLocationForLink(link);
|
|
2039
|
-
if (this.delegate.willFollowLinkToLocation(link, location)) {
|
|
2175
|
+
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
|
2040
2176
|
event.preventDefault();
|
|
2041
2177
|
this.delegate.followedLinkToLocation(link, location);
|
|
2042
2178
|
}
|
|
@@ -2070,6 +2206,13 @@ class LinkClickObserver {
|
|
|
2070
2206
|
}
|
|
2071
2207
|
}
|
|
2072
2208
|
|
|
2209
|
+
function doesNotTargetIFrame(anchor) {
|
|
2210
|
+
for (const element of document.getElementsByName(anchor.target)) {
|
|
2211
|
+
if (element instanceof HTMLIFrameElement) return false;
|
|
2212
|
+
}
|
|
2213
|
+
return true;
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2073
2216
|
function isAction(action) {
|
|
2074
2217
|
return action == "advance" || action == "replace" || action == "restore";
|
|
2075
2218
|
}
|
|
@@ -2088,6 +2231,7 @@ class Navigator {
|
|
|
2088
2231
|
}
|
|
2089
2232
|
}
|
|
2090
2233
|
startVisit(locatable, restorationIdentifier, options = {}) {
|
|
2234
|
+
this.lastVisit = this.currentVisit;
|
|
2091
2235
|
this.stop();
|
|
2092
2236
|
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
|
|
2093
2237
|
referrer: this.location
|
|
@@ -2149,9 +2293,9 @@ class Navigator {
|
|
|
2149
2293
|
if (responseHTML) {
|
|
2150
2294
|
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
|
2151
2295
|
if (fetchResponse.serverError) {
|
|
2152
|
-
await this.view.renderError(snapshot);
|
|
2296
|
+
await this.view.renderError(snapshot, this.currentVisit);
|
|
2153
2297
|
} else {
|
|
2154
|
-
await this.view.renderPage(snapshot);
|
|
2298
|
+
await this.view.renderPage(snapshot, false, true, this.currentVisit);
|
|
2155
2299
|
}
|
|
2156
2300
|
this.view.scrollToTop();
|
|
2157
2301
|
this.view.clearSnapshotCache();
|
|
@@ -2172,10 +2316,12 @@ class Navigator {
|
|
|
2172
2316
|
this.delegate.visitCompleted(visit);
|
|
2173
2317
|
}
|
|
2174
2318
|
locationWithActionIsSamePage(location, action) {
|
|
2319
|
+
var _a;
|
|
2175
2320
|
const anchor = getAnchor(location);
|
|
2176
|
-
const
|
|
2321
|
+
const lastLocation = ((_a = this.lastVisit) === null || _a === void 0 ? void 0 : _a.location) || this.view.lastRenderedLocation;
|
|
2322
|
+
const currentAnchor = getAnchor(lastLocation);
|
|
2177
2323
|
const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
|
|
2178
|
-
return action !== "replace" && getRequestURL(location) === getRequestURL(
|
|
2324
|
+
return action !== "replace" && getRequestURL(location) === getRequestURL(lastLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
|
|
2179
2325
|
}
|
|
2180
2326
|
visitScrolledToSamePageLocation(oldURL, newURL) {
|
|
2181
2327
|
this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
|
|
@@ -2354,14 +2500,18 @@ function fetchResponseIsStream(response) {
|
|
|
2354
2500
|
}
|
|
2355
2501
|
|
|
2356
2502
|
class ErrorRenderer extends Renderer {
|
|
2503
|
+
static renderElement(currentElement, newElement) {
|
|
2504
|
+
const {documentElement: documentElement, body: body} = document;
|
|
2505
|
+
documentElement.replaceChild(newElement, body);
|
|
2506
|
+
}
|
|
2357
2507
|
async render() {
|
|
2358
2508
|
this.replaceHeadAndBody();
|
|
2359
2509
|
this.activateScriptElements();
|
|
2360
2510
|
}
|
|
2361
2511
|
replaceHeadAndBody() {
|
|
2362
|
-
const {documentElement: documentElement, head: head
|
|
2512
|
+
const {documentElement: documentElement, head: head} = document;
|
|
2363
2513
|
documentElement.replaceChild(this.newHead, head);
|
|
2364
|
-
|
|
2514
|
+
this.renderElement(this.currentElement, this.newElement);
|
|
2365
2515
|
}
|
|
2366
2516
|
activateScriptElements() {
|
|
2367
2517
|
for (const replaceableElement of this.scriptElements) {
|
|
@@ -2381,9 +2531,28 @@ class ErrorRenderer extends Renderer {
|
|
|
2381
2531
|
}
|
|
2382
2532
|
|
|
2383
2533
|
class PageRenderer extends Renderer {
|
|
2534
|
+
static renderElement(currentElement, newElement) {
|
|
2535
|
+
if (document.body && newElement instanceof HTMLBodyElement) {
|
|
2536
|
+
document.body.replaceWith(newElement);
|
|
2537
|
+
} else {
|
|
2538
|
+
document.documentElement.appendChild(newElement);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2384
2541
|
get shouldRender() {
|
|
2385
2542
|
return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
|
|
2386
2543
|
}
|
|
2544
|
+
get reloadReason() {
|
|
2545
|
+
if (!this.newSnapshot.isVisitable) {
|
|
2546
|
+
return {
|
|
2547
|
+
reason: "turbo_visit_control_is_reload"
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
if (!this.trackedElementsAreIdentical) {
|
|
2551
|
+
return {
|
|
2552
|
+
reason: "tracked_element_mismatch"
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2387
2556
|
prepareToRender() {
|
|
2388
2557
|
this.mergeHead();
|
|
2389
2558
|
}
|
|
@@ -2453,11 +2622,7 @@ class PageRenderer extends Renderer {
|
|
|
2453
2622
|
}
|
|
2454
2623
|
}
|
|
2455
2624
|
assignNewBody() {
|
|
2456
|
-
|
|
2457
|
-
document.body.replaceWith(this.newElement);
|
|
2458
|
-
} else {
|
|
2459
|
-
document.documentElement.appendChild(this.newElement);
|
|
2460
|
-
}
|
|
2625
|
+
this.renderElement(this.currentElement, this.newElement);
|
|
2461
2626
|
}
|
|
2462
2627
|
get newHeadStylesheetElements() {
|
|
2463
2628
|
return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
|
|
@@ -2525,13 +2690,20 @@ class PageView extends View {
|
|
|
2525
2690
|
super(...arguments);
|
|
2526
2691
|
this.snapshotCache = new SnapshotCache(10);
|
|
2527
2692
|
this.lastRenderedLocation = new URL(location.href);
|
|
2693
|
+
this.forceReloaded = false;
|
|
2528
2694
|
}
|
|
2529
|
-
renderPage(snapshot, isPreview = false, willRender = true) {
|
|
2530
|
-
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
|
|
2695
|
+
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
|
2696
|
+
const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
|
|
2697
|
+
if (!renderer.shouldRender) {
|
|
2698
|
+
this.forceReloaded = true;
|
|
2699
|
+
} else {
|
|
2700
|
+
visit === null || visit === void 0 ? void 0 : visit.changeHistory();
|
|
2701
|
+
}
|
|
2531
2702
|
return this.render(renderer);
|
|
2532
2703
|
}
|
|
2533
|
-
renderError(snapshot) {
|
|
2534
|
-
|
|
2704
|
+
renderError(snapshot, visit) {
|
|
2705
|
+
visit === null || visit === void 0 ? void 0 : visit.changeHistory();
|
|
2706
|
+
const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
|
|
2535
2707
|
return this.render(renderer);
|
|
2536
2708
|
}
|
|
2537
2709
|
clearSnapshotCache() {
|
|
@@ -2558,10 +2730,52 @@ class PageView extends View {
|
|
|
2558
2730
|
}
|
|
2559
2731
|
}
|
|
2560
2732
|
|
|
2733
|
+
class Preloader {
|
|
2734
|
+
constructor(delegate) {
|
|
2735
|
+
this.selector = "a[data-turbo-preload]";
|
|
2736
|
+
this.delegate = delegate;
|
|
2737
|
+
}
|
|
2738
|
+
get snapshotCache() {
|
|
2739
|
+
return this.delegate.navigator.view.snapshotCache;
|
|
2740
|
+
}
|
|
2741
|
+
start() {
|
|
2742
|
+
if (document.readyState === "loading") {
|
|
2743
|
+
return document.addEventListener("DOMContentLoaded", (() => {
|
|
2744
|
+
this.preloadOnLoadLinksForView(document.body);
|
|
2745
|
+
}));
|
|
2746
|
+
} else {
|
|
2747
|
+
this.preloadOnLoadLinksForView(document.body);
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
preloadOnLoadLinksForView(element) {
|
|
2751
|
+
for (const link of element.querySelectorAll(this.selector)) {
|
|
2752
|
+
this.preloadURL(link);
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
async preloadURL(link) {
|
|
2756
|
+
const location = new URL(link.href);
|
|
2757
|
+
if (this.snapshotCache.has(location)) {
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
try {
|
|
2761
|
+
const response = await fetch(location.toString(), {
|
|
2762
|
+
headers: {
|
|
2763
|
+
"VND.PREFETCH": "true",
|
|
2764
|
+
Accept: "text/html"
|
|
2765
|
+
}
|
|
2766
|
+
});
|
|
2767
|
+
const responseText = await response.text();
|
|
2768
|
+
const snapshot = PageSnapshot.fromHTMLString(responseText);
|
|
2769
|
+
this.snapshotCache.put(location, snapshot);
|
|
2770
|
+
} catch (_) {}
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2561
2774
|
class Session {
|
|
2562
2775
|
constructor() {
|
|
2563
2776
|
this.navigator = new Navigator(this);
|
|
2564
2777
|
this.history = new History(this);
|
|
2778
|
+
this.preloader = new Preloader(this);
|
|
2565
2779
|
this.view = new PageView(this, document.documentElement);
|
|
2566
2780
|
this.adapter = new BrowserAdapter(this);
|
|
2567
2781
|
this.pageObserver = new PageObserver(this);
|
|
@@ -2570,22 +2784,26 @@ class Session {
|
|
|
2570
2784
|
this.formSubmitObserver = new FormSubmitObserver(this);
|
|
2571
2785
|
this.scrollObserver = new ScrollObserver(this);
|
|
2572
2786
|
this.streamObserver = new StreamObserver(this);
|
|
2787
|
+
this.formLinkInterceptor = new FormLinkInterceptor(this, document.documentElement);
|
|
2573
2788
|
this.frameRedirector = new FrameRedirector(document.documentElement);
|
|
2574
2789
|
this.drive = true;
|
|
2575
2790
|
this.enabled = true;
|
|
2576
2791
|
this.progressBarDelay = 500;
|
|
2577
2792
|
this.started = false;
|
|
2793
|
+
this.formMode = "on";
|
|
2578
2794
|
}
|
|
2579
2795
|
start() {
|
|
2580
2796
|
if (!this.started) {
|
|
2581
2797
|
this.pageObserver.start();
|
|
2582
2798
|
this.cacheObserver.start();
|
|
2799
|
+
this.formLinkInterceptor.start();
|
|
2583
2800
|
this.linkClickObserver.start();
|
|
2584
2801
|
this.formSubmitObserver.start();
|
|
2585
2802
|
this.scrollObserver.start();
|
|
2586
2803
|
this.streamObserver.start();
|
|
2587
2804
|
this.frameRedirector.start();
|
|
2588
2805
|
this.history.start();
|
|
2806
|
+
this.preloader.start();
|
|
2589
2807
|
this.started = true;
|
|
2590
2808
|
this.enabled = true;
|
|
2591
2809
|
}
|
|
@@ -2597,6 +2815,7 @@ class Session {
|
|
|
2597
2815
|
if (this.started) {
|
|
2598
2816
|
this.pageObserver.stop();
|
|
2599
2817
|
this.cacheObserver.stop();
|
|
2818
|
+
this.formLinkInterceptor.stop();
|
|
2600
2819
|
this.linkClickObserver.stop();
|
|
2601
2820
|
this.formSubmitObserver.stop();
|
|
2602
2821
|
this.scrollObserver.stop();
|
|
@@ -2627,6 +2846,9 @@ class Session {
|
|
|
2627
2846
|
setProgressBarDelay(delay) {
|
|
2628
2847
|
this.progressBarDelay = delay;
|
|
2629
2848
|
}
|
|
2849
|
+
setFormMode(mode) {
|
|
2850
|
+
this.formMode = mode;
|
|
2851
|
+
}
|
|
2630
2852
|
get location() {
|
|
2631
2853
|
return this.history.location;
|
|
2632
2854
|
}
|
|
@@ -2640,7 +2862,9 @@ class Session {
|
|
|
2640
2862
|
historyChanged: true
|
|
2641
2863
|
});
|
|
2642
2864
|
} else {
|
|
2643
|
-
this.adapter.pageInvalidated(
|
|
2865
|
+
this.adapter.pageInvalidated({
|
|
2866
|
+
reason: "turbo_disabled"
|
|
2867
|
+
});
|
|
2644
2868
|
}
|
|
2645
2869
|
}
|
|
2646
2870
|
scrollPositionChanged(position) {
|
|
@@ -2648,41 +2872,19 @@ class Session {
|
|
|
2648
2872
|
scrollPosition: position
|
|
2649
2873
|
});
|
|
2650
2874
|
}
|
|
2651
|
-
|
|
2652
|
-
return
|
|
2875
|
+
shouldInterceptFormLinkClick(_link) {
|
|
2876
|
+
return true;
|
|
2877
|
+
}
|
|
2878
|
+
formLinkClickIntercepted(_link, _form) {}
|
|
2879
|
+
willFollowLinkToLocation(link, location, event) {
|
|
2880
|
+
return this.elementDriveEnabled(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
|
|
2653
2881
|
}
|
|
2654
2882
|
followedLinkToLocation(link, location) {
|
|
2655
2883
|
const action = this.getActionForLink(link);
|
|
2656
|
-
this.
|
|
2884
|
+
this.visit(location.href, {
|
|
2657
2885
|
action: action
|
|
2658
2886
|
});
|
|
2659
2887
|
}
|
|
2660
|
-
convertLinkWithMethodClickToFormSubmission(link) {
|
|
2661
|
-
const linkMethod = link.getAttribute("data-turbo-method");
|
|
2662
|
-
if (linkMethod) {
|
|
2663
|
-
const form = document.createElement("form");
|
|
2664
|
-
form.method = linkMethod;
|
|
2665
|
-
form.action = link.getAttribute("href") || "undefined";
|
|
2666
|
-
form.hidden = true;
|
|
2667
|
-
if (link.hasAttribute("data-turbo-confirm")) {
|
|
2668
|
-
form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
|
|
2669
|
-
}
|
|
2670
|
-
const frame = this.getTargetFrameForLink(link);
|
|
2671
|
-
if (frame) {
|
|
2672
|
-
form.setAttribute("data-turbo-frame", frame);
|
|
2673
|
-
form.addEventListener("turbo:submit-start", (() => form.remove()));
|
|
2674
|
-
} else {
|
|
2675
|
-
form.addEventListener("submit", (() => form.remove()));
|
|
2676
|
-
}
|
|
2677
|
-
document.body.appendChild(form);
|
|
2678
|
-
return dispatch("submit", {
|
|
2679
|
-
cancelable: true,
|
|
2680
|
-
target: form
|
|
2681
|
-
});
|
|
2682
|
-
} else {
|
|
2683
|
-
return false;
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
2888
|
allowsVisitingLocationWithAction(location, action) {
|
|
2687
2889
|
return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
|
|
2688
2890
|
}
|
|
@@ -2707,7 +2909,7 @@ class Session {
|
|
|
2707
2909
|
}
|
|
2708
2910
|
willSubmitForm(form, submitter) {
|
|
2709
2911
|
const action = getAction(form, submitter);
|
|
2710
|
-
return this.elementDriveEnabled(form) && (!submitter || this.
|
|
2912
|
+
return this.elementDriveEnabled(form) && (!submitter || this.formElementDriveEnabled(submitter)) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
|
|
2711
2913
|
}
|
|
2712
2914
|
formSubmitted(form, submitter) {
|
|
2713
2915
|
this.navigator.submitForm(form, submitter);
|
|
@@ -2731,16 +2933,23 @@ class Session {
|
|
|
2731
2933
|
this.notifyApplicationBeforeCachingSnapshot();
|
|
2732
2934
|
}
|
|
2733
2935
|
}
|
|
2734
|
-
allowsImmediateRender({element: element},
|
|
2735
|
-
const event = this.notifyApplicationBeforeRender(element,
|
|
2736
|
-
|
|
2936
|
+
allowsImmediateRender({element: element}, options) {
|
|
2937
|
+
const event = this.notifyApplicationBeforeRender(element, options);
|
|
2938
|
+
const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
|
|
2939
|
+
if (this.view.renderer && render) {
|
|
2940
|
+
this.view.renderer.renderElement = render;
|
|
2941
|
+
}
|
|
2942
|
+
return !defaultPrevented;
|
|
2737
2943
|
}
|
|
2738
|
-
viewRenderedSnapshot(
|
|
2944
|
+
viewRenderedSnapshot(_snapshot, _isPreview) {
|
|
2739
2945
|
this.view.lastRenderedLocation = this.history.location;
|
|
2740
2946
|
this.notifyApplicationAfterRender();
|
|
2741
2947
|
}
|
|
2742
|
-
|
|
2743
|
-
this.
|
|
2948
|
+
preloadOnLoadLinksForView(element) {
|
|
2949
|
+
this.preloader.preloadOnLoadLinksForView(element);
|
|
2950
|
+
}
|
|
2951
|
+
viewInvalidated(reason) {
|
|
2952
|
+
this.adapter.pageInvalidated(reason);
|
|
2744
2953
|
}
|
|
2745
2954
|
frameLoaded(frame) {
|
|
2746
2955
|
this.notifyApplicationAfterFrameLoad(frame);
|
|
@@ -2748,19 +2957,20 @@ class Session {
|
|
|
2748
2957
|
frameRendered(fetchResponse, frame) {
|
|
2749
2958
|
this.notifyApplicationAfterFrameRender(fetchResponse, frame);
|
|
2750
2959
|
}
|
|
2751
|
-
applicationAllowsFollowingLinkToLocation(link, location) {
|
|
2752
|
-
const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
|
|
2960
|
+
applicationAllowsFollowingLinkToLocation(link, location, ev) {
|
|
2961
|
+
const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
|
|
2753
2962
|
return !event.defaultPrevented;
|
|
2754
2963
|
}
|
|
2755
2964
|
applicationAllowsVisitingLocation(location) {
|
|
2756
2965
|
const event = this.notifyApplicationBeforeVisitingLocation(location);
|
|
2757
2966
|
return !event.defaultPrevented;
|
|
2758
2967
|
}
|
|
2759
|
-
notifyApplicationAfterClickingLinkToLocation(link, location) {
|
|
2968
|
+
notifyApplicationAfterClickingLinkToLocation(link, location, event) {
|
|
2760
2969
|
return dispatch("turbo:click", {
|
|
2761
2970
|
target: link,
|
|
2762
2971
|
detail: {
|
|
2763
|
-
url: location.href
|
|
2972
|
+
url: location.href,
|
|
2973
|
+
originalEvent: event
|
|
2764
2974
|
},
|
|
2765
2975
|
cancelable: true
|
|
2766
2976
|
});
|
|
@@ -2785,12 +2995,11 @@ class Session {
|
|
|
2785
2995
|
notifyApplicationBeforeCachingSnapshot() {
|
|
2786
2996
|
return dispatch("turbo:before-cache");
|
|
2787
2997
|
}
|
|
2788
|
-
notifyApplicationBeforeRender(newBody,
|
|
2998
|
+
notifyApplicationBeforeRender(newBody, options) {
|
|
2789
2999
|
return dispatch("turbo:before-render", {
|
|
2790
|
-
detail: {
|
|
2791
|
-
newBody: newBody
|
|
2792
|
-
|
|
2793
|
-
},
|
|
3000
|
+
detail: Object.assign({
|
|
3001
|
+
newBody: newBody
|
|
3002
|
+
}, options),
|
|
2794
3003
|
cancelable: true
|
|
2795
3004
|
});
|
|
2796
3005
|
}
|
|
@@ -2826,6 +3035,16 @@ class Session {
|
|
|
2826
3035
|
cancelable: true
|
|
2827
3036
|
});
|
|
2828
3037
|
}
|
|
3038
|
+
formElementDriveEnabled(element) {
|
|
3039
|
+
if (this.formMode == "off") {
|
|
3040
|
+
return false;
|
|
3041
|
+
}
|
|
3042
|
+
if (this.formMode == "optin") {
|
|
3043
|
+
const form = element === null || element === void 0 ? void 0 : element.closest("form[data-turbo]");
|
|
3044
|
+
return (form === null || form === void 0 ? void 0 : form.getAttribute("data-turbo")) == "true";
|
|
3045
|
+
}
|
|
3046
|
+
return this.elementDriveEnabled(element);
|
|
3047
|
+
}
|
|
2829
3048
|
elementDriveEnabled(element) {
|
|
2830
3049
|
const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
|
|
2831
3050
|
if (this.drive) {
|
|
@@ -2846,17 +3065,6 @@ class Session {
|
|
|
2846
3065
|
const action = link.getAttribute("data-turbo-action");
|
|
2847
3066
|
return isAction(action) ? action : "advance";
|
|
2848
3067
|
}
|
|
2849
|
-
getTargetFrameForLink(link) {
|
|
2850
|
-
const frame = link.getAttribute("data-turbo-frame");
|
|
2851
|
-
if (frame) {
|
|
2852
|
-
return frame;
|
|
2853
|
-
} else {
|
|
2854
|
-
const container = link.closest("turbo-frame");
|
|
2855
|
-
if (container) {
|
|
2856
|
-
return container.id;
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2859
|
-
}
|
|
2860
3068
|
get snapshot() {
|
|
2861
3069
|
return this.view.snapshot;
|
|
2862
3070
|
}
|
|
@@ -2874,8 +3082,63 @@ const deprecatedLocationPropertyDescriptors = {
|
|
|
2874
3082
|
}
|
|
2875
3083
|
};
|
|
2876
3084
|
|
|
3085
|
+
class Cache {
|
|
3086
|
+
constructor(session) {
|
|
3087
|
+
this.session = session;
|
|
3088
|
+
}
|
|
3089
|
+
clear() {
|
|
3090
|
+
this.session.clearCache();
|
|
3091
|
+
}
|
|
3092
|
+
resetCacheControl() {
|
|
3093
|
+
this.setCacheControl("");
|
|
3094
|
+
}
|
|
3095
|
+
exemptPageFromCache() {
|
|
3096
|
+
this.setCacheControl("no-cache");
|
|
3097
|
+
}
|
|
3098
|
+
exemptPageFromPreview() {
|
|
3099
|
+
this.setCacheControl("no-preview");
|
|
3100
|
+
}
|
|
3101
|
+
setCacheControl(value) {
|
|
3102
|
+
setMetaContent("turbo-cache-control", value);
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
const StreamActions = {
|
|
3107
|
+
after() {
|
|
3108
|
+
this.targetElements.forEach((e => {
|
|
3109
|
+
var _a;
|
|
3110
|
+
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
|
|
3111
|
+
}));
|
|
3112
|
+
},
|
|
3113
|
+
append() {
|
|
3114
|
+
this.removeDuplicateTargetChildren();
|
|
3115
|
+
this.targetElements.forEach((e => e.append(this.templateContent)));
|
|
3116
|
+
},
|
|
3117
|
+
before() {
|
|
3118
|
+
this.targetElements.forEach((e => {
|
|
3119
|
+
var _a;
|
|
3120
|
+
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
|
|
3121
|
+
}));
|
|
3122
|
+
},
|
|
3123
|
+
prepend() {
|
|
3124
|
+
this.removeDuplicateTargetChildren();
|
|
3125
|
+
this.targetElements.forEach((e => e.prepend(this.templateContent)));
|
|
3126
|
+
},
|
|
3127
|
+
remove() {
|
|
3128
|
+
this.targetElements.forEach((e => e.remove()));
|
|
3129
|
+
},
|
|
3130
|
+
replace() {
|
|
3131
|
+
this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
|
|
3132
|
+
},
|
|
3133
|
+
update() {
|
|
3134
|
+
this.targetElements.forEach((e => e.replaceChildren(this.templateContent)));
|
|
3135
|
+
}
|
|
3136
|
+
};
|
|
3137
|
+
|
|
2877
3138
|
const session = new Session;
|
|
2878
3139
|
|
|
3140
|
+
const cache = new Cache(session);
|
|
3141
|
+
|
|
2879
3142
|
const {navigator: navigator$1} = session;
|
|
2880
3143
|
|
|
2881
3144
|
function start() {
|
|
@@ -2903,6 +3166,7 @@ function renderStreamMessage(message) {
|
|
|
2903
3166
|
}
|
|
2904
3167
|
|
|
2905
3168
|
function clearCache() {
|
|
3169
|
+
console.warn("Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
|
|
2906
3170
|
session.clearCache();
|
|
2907
3171
|
}
|
|
2908
3172
|
|
|
@@ -2914,12 +3178,18 @@ function setConfirmMethod(confirmMethod) {
|
|
|
2914
3178
|
FormSubmission.confirmMethod = confirmMethod;
|
|
2915
3179
|
}
|
|
2916
3180
|
|
|
3181
|
+
function setFormMode(mode) {
|
|
3182
|
+
session.setFormMode(mode);
|
|
3183
|
+
}
|
|
3184
|
+
|
|
2917
3185
|
var Turbo = Object.freeze({
|
|
2918
3186
|
__proto__: null,
|
|
2919
3187
|
navigator: navigator$1,
|
|
2920
3188
|
session: session,
|
|
3189
|
+
cache: cache,
|
|
2921
3190
|
PageRenderer: PageRenderer,
|
|
2922
3191
|
PageSnapshot: PageSnapshot,
|
|
3192
|
+
FrameRenderer: FrameRenderer,
|
|
2923
3193
|
start: start,
|
|
2924
3194
|
registerAdapter: registerAdapter,
|
|
2925
3195
|
visit: visit,
|
|
@@ -2928,39 +3198,51 @@ var Turbo = Object.freeze({
|
|
|
2928
3198
|
renderStreamMessage: renderStreamMessage,
|
|
2929
3199
|
clearCache: clearCache,
|
|
2930
3200
|
setProgressBarDelay: setProgressBarDelay,
|
|
2931
|
-
setConfirmMethod: setConfirmMethod
|
|
3201
|
+
setConfirmMethod: setConfirmMethod,
|
|
3202
|
+
setFormMode: setFormMode,
|
|
3203
|
+
StreamActions: StreamActions
|
|
2932
3204
|
});
|
|
2933
3205
|
|
|
2934
3206
|
class FrameController {
|
|
2935
3207
|
constructor(element) {
|
|
2936
|
-
this.fetchResponseLoaded =
|
|
3208
|
+
this.fetchResponseLoaded = _fetchResponse => {};
|
|
2937
3209
|
this.currentFetchRequest = null;
|
|
2938
3210
|
this.resolveVisitPromise = () => {};
|
|
2939
3211
|
this.connected = false;
|
|
2940
3212
|
this.hasBeenLoaded = false;
|
|
2941
|
-
this.
|
|
3213
|
+
this.ignoredAttributes = new Set;
|
|
3214
|
+
this.visitCachedSnapshot = ({element: element}) => {
|
|
3215
|
+
const frame = element.querySelector("#" + this.element.id);
|
|
3216
|
+
if (frame && this.previousFrameElement) {
|
|
3217
|
+
frame.replaceChildren(...this.previousFrameElement.children);
|
|
3218
|
+
}
|
|
3219
|
+
delete this.previousFrameElement;
|
|
3220
|
+
};
|
|
2942
3221
|
this.element = element;
|
|
2943
3222
|
this.view = new FrameView(this, this.element);
|
|
2944
3223
|
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
|
3224
|
+
this.formLinkInterceptor = new FormLinkInterceptor(this, this.element);
|
|
2945
3225
|
this.linkInterceptor = new LinkInterceptor(this, this.element);
|
|
2946
3226
|
this.formInterceptor = new FormInterceptor(this, this.element);
|
|
2947
3227
|
}
|
|
2948
3228
|
connect() {
|
|
2949
3229
|
if (!this.connected) {
|
|
2950
3230
|
this.connected = true;
|
|
2951
|
-
this.reloadable = false;
|
|
2952
3231
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
|
2953
3232
|
this.appearanceObserver.start();
|
|
3233
|
+
} else {
|
|
3234
|
+
this.loadSourceURL();
|
|
2954
3235
|
}
|
|
3236
|
+
this.formLinkInterceptor.start();
|
|
2955
3237
|
this.linkInterceptor.start();
|
|
2956
3238
|
this.formInterceptor.start();
|
|
2957
|
-
this.sourceURLChanged();
|
|
2958
3239
|
}
|
|
2959
3240
|
}
|
|
2960
3241
|
disconnect() {
|
|
2961
3242
|
if (this.connected) {
|
|
2962
3243
|
this.connected = false;
|
|
2963
3244
|
this.appearanceObserver.stop();
|
|
3245
|
+
this.formLinkInterceptor.stop();
|
|
2964
3246
|
this.linkInterceptor.stop();
|
|
2965
3247
|
this.formInterceptor.stop();
|
|
2966
3248
|
}
|
|
@@ -2971,10 +3253,18 @@ class FrameController {
|
|
|
2971
3253
|
}
|
|
2972
3254
|
}
|
|
2973
3255
|
sourceURLChanged() {
|
|
3256
|
+
if (this.isIgnoringChangesTo("src")) return;
|
|
3257
|
+
if (this.element.isConnected) {
|
|
3258
|
+
this.complete = false;
|
|
3259
|
+
}
|
|
2974
3260
|
if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
|
|
2975
3261
|
this.loadSourceURL();
|
|
2976
3262
|
}
|
|
2977
3263
|
}
|
|
3264
|
+
completeChanged() {
|
|
3265
|
+
if (this.isIgnoringChangesTo("complete")) return;
|
|
3266
|
+
this.loadSourceURL();
|
|
3267
|
+
}
|
|
2978
3268
|
loadingStyleChanged() {
|
|
2979
3269
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
|
2980
3270
|
this.appearanceObserver.start();
|
|
@@ -2984,20 +3274,11 @@ class FrameController {
|
|
|
2984
3274
|
}
|
|
2985
3275
|
}
|
|
2986
3276
|
async loadSourceURL() {
|
|
2987
|
-
if (
|
|
2988
|
-
|
|
2989
|
-
this.
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
this.element.loaded = this.visit(expandURL(this.sourceURL));
|
|
2993
|
-
this.appearanceObserver.stop();
|
|
2994
|
-
await this.element.loaded;
|
|
2995
|
-
this.hasBeenLoaded = true;
|
|
2996
|
-
} catch (error) {
|
|
2997
|
-
this.currentURL = previousURL;
|
|
2998
|
-
throw error;
|
|
2999
|
-
}
|
|
3000
|
-
}
|
|
3277
|
+
if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
|
|
3278
|
+
this.element.loaded = this.visit(expandURL(this.sourceURL));
|
|
3279
|
+
this.appearanceObserver.stop();
|
|
3280
|
+
await this.element.loaded;
|
|
3281
|
+
this.hasBeenLoaded = true;
|
|
3001
3282
|
}
|
|
3002
3283
|
}
|
|
3003
3284
|
async loadResponse(fetchResponse) {
|
|
@@ -3009,9 +3290,10 @@ class FrameController {
|
|
|
3009
3290
|
if (html) {
|
|
3010
3291
|
const {body: body} = parseHTMLDocument(html);
|
|
3011
3292
|
const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
|
|
3012
|
-
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
|
|
3293
|
+
const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
|
|
3013
3294
|
if (this.view.renderPromise) await this.view.renderPromise;
|
|
3014
3295
|
await this.view.render(renderer);
|
|
3296
|
+
this.complete = true;
|
|
3015
3297
|
session.frameRendered(fetchResponse, this.element);
|
|
3016
3298
|
session.frameLoaded(this.element);
|
|
3017
3299
|
this.fetchResponseLoaded(fetchResponse);
|
|
@@ -3023,18 +3305,20 @@ class FrameController {
|
|
|
3023
3305
|
this.fetchResponseLoaded = () => {};
|
|
3024
3306
|
}
|
|
3025
3307
|
}
|
|
3026
|
-
elementAppearedInViewport(
|
|
3308
|
+
elementAppearedInViewport(_element) {
|
|
3027
3309
|
this.loadSourceURL();
|
|
3028
3310
|
}
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3311
|
+
shouldInterceptFormLinkClick(link) {
|
|
3312
|
+
return this.shouldInterceptNavigation(link);
|
|
3313
|
+
}
|
|
3314
|
+
formLinkClickIntercepted(link, form) {
|
|
3315
|
+
const frame = this.findFrameElement(link);
|
|
3316
|
+
if (frame) form.setAttribute("data-turbo-frame", frame.id);
|
|
3317
|
+
}
|
|
3318
|
+
shouldInterceptLinkClick(element, _url) {
|
|
3319
|
+
return this.shouldInterceptNavigation(element);
|
|
3035
3320
|
}
|
|
3036
3321
|
linkClickIntercepted(element, url) {
|
|
3037
|
-
this.reloadable = true;
|
|
3038
3322
|
this.navigateFrame(element, url);
|
|
3039
3323
|
}
|
|
3040
3324
|
shouldInterceptFormSubmission(element, submitter) {
|
|
@@ -3044,19 +3328,18 @@ class FrameController {
|
|
|
3044
3328
|
if (this.formSubmission) {
|
|
3045
3329
|
this.formSubmission.stop();
|
|
3046
3330
|
}
|
|
3047
|
-
this.reloadable = false;
|
|
3048
3331
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
|
3049
3332
|
const {fetchRequest: fetchRequest} = this.formSubmission;
|
|
3050
3333
|
this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
|
|
3051
3334
|
this.formSubmission.start();
|
|
3052
3335
|
}
|
|
3053
|
-
prepareHeadersForRequest(headers,
|
|
3336
|
+
prepareHeadersForRequest(headers, _request) {
|
|
3054
3337
|
headers["Turbo-Frame"] = this.id;
|
|
3055
3338
|
}
|
|
3056
|
-
requestStarted(
|
|
3339
|
+
requestStarted(_request) {
|
|
3057
3340
|
markAsBusy(this.element);
|
|
3058
3341
|
}
|
|
3059
|
-
requestPreventedHandlingResponse(
|
|
3342
|
+
requestPreventedHandlingResponse(_request, _response) {
|
|
3060
3343
|
this.resolveVisitPromise();
|
|
3061
3344
|
}
|
|
3062
3345
|
async requestSucceededWithResponse(request, response) {
|
|
@@ -3071,7 +3354,7 @@ class FrameController {
|
|
|
3071
3354
|
console.error(error);
|
|
3072
3355
|
this.resolveVisitPromise();
|
|
3073
3356
|
}
|
|
3074
|
-
requestFinished(
|
|
3357
|
+
requestFinished(_request) {
|
|
3075
3358
|
clearBusyState(this.element);
|
|
3076
3359
|
}
|
|
3077
3360
|
formSubmissionStarted({formElement: formElement}) {
|
|
@@ -3091,11 +3374,28 @@ class FrameController {
|
|
|
3091
3374
|
formSubmissionFinished({formElement: formElement}) {
|
|
3092
3375
|
clearBusyState(formElement, this.findFrameElement(formElement));
|
|
3093
3376
|
}
|
|
3094
|
-
allowsImmediateRender(
|
|
3095
|
-
|
|
3377
|
+
allowsImmediateRender({element: newFrame}, options) {
|
|
3378
|
+
const event = dispatch("turbo:before-frame-render", {
|
|
3379
|
+
target: this.element,
|
|
3380
|
+
detail: Object.assign({
|
|
3381
|
+
newFrame: newFrame
|
|
3382
|
+
}, options),
|
|
3383
|
+
cancelable: true
|
|
3384
|
+
});
|
|
3385
|
+
const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
|
|
3386
|
+
if (this.view.renderer && render) {
|
|
3387
|
+
this.view.renderer.renderElement = render;
|
|
3388
|
+
}
|
|
3389
|
+
return !defaultPrevented;
|
|
3390
|
+
}
|
|
3391
|
+
viewRenderedSnapshot(_snapshot, _isPreview) {}
|
|
3392
|
+
preloadOnLoadLinksForView(element) {
|
|
3393
|
+
session.preloadOnLoadLinksForView(element);
|
|
3096
3394
|
}
|
|
3097
|
-
viewRenderedSnapshot(snapshot, isPreview) {}
|
|
3098
3395
|
viewInvalidated() {}
|
|
3396
|
+
frameExtracted(element) {
|
|
3397
|
+
this.previousFrameElement = element;
|
|
3398
|
+
}
|
|
3099
3399
|
async visit(url) {
|
|
3100
3400
|
var _a;
|
|
3101
3401
|
const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
|
|
@@ -3113,13 +3413,12 @@ class FrameController {
|
|
|
3113
3413
|
navigateFrame(element, url, submitter) {
|
|
3114
3414
|
const frame = this.findFrameElement(element, submitter);
|
|
3115
3415
|
this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
|
|
3116
|
-
frame.setAttribute("reloadable", "");
|
|
3117
3416
|
frame.src = url;
|
|
3118
3417
|
}
|
|
3119
3418
|
proposeVisitIfNavigatedWithAction(frame, element, submitter) {
|
|
3120
3419
|
const action = getAttribute("data-turbo-action", submitter, element, frame);
|
|
3121
3420
|
if (isAction(action)) {
|
|
3122
|
-
const {visitCachedSnapshot: visitCachedSnapshot} =
|
|
3421
|
+
const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
|
|
3123
3422
|
frame.delegate.fetchResponseLoaded = fetchResponse => {
|
|
3124
3423
|
if (frame.src) {
|
|
3125
3424
|
const {statusCode: statusCode, redirected: redirected} = fetchResponse;
|
|
@@ -3148,10 +3447,12 @@ class FrameController {
|
|
|
3148
3447
|
let element;
|
|
3149
3448
|
const id = CSS.escape(this.id);
|
|
3150
3449
|
try {
|
|
3151
|
-
|
|
3450
|
+
element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
|
|
3451
|
+
if (element) {
|
|
3152
3452
|
return element;
|
|
3153
3453
|
}
|
|
3154
|
-
|
|
3454
|
+
element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);
|
|
3455
|
+
if (element) {
|
|
3155
3456
|
await element.loaded;
|
|
3156
3457
|
return await this.extractForeignFrameElement(element);
|
|
3157
3458
|
}
|
|
@@ -3198,23 +3499,10 @@ class FrameController {
|
|
|
3198
3499
|
return this.element.src;
|
|
3199
3500
|
}
|
|
3200
3501
|
}
|
|
3201
|
-
get reloadable() {
|
|
3202
|
-
const frame = this.findFrameElement(this.element);
|
|
3203
|
-
return frame.hasAttribute("reloadable");
|
|
3204
|
-
}
|
|
3205
|
-
set reloadable(value) {
|
|
3206
|
-
const frame = this.findFrameElement(this.element);
|
|
3207
|
-
if (value) {
|
|
3208
|
-
frame.setAttribute("reloadable", "");
|
|
3209
|
-
} else {
|
|
3210
|
-
frame.removeAttribute("reloadable");
|
|
3211
|
-
}
|
|
3212
|
-
}
|
|
3213
3502
|
set sourceURL(sourceURL) {
|
|
3214
|
-
this.
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
this.settingSourceURL = false;
|
|
3503
|
+
this.ignoringChangesToAttribute("src", (() => {
|
|
3504
|
+
this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
|
|
3505
|
+
}));
|
|
3218
3506
|
}
|
|
3219
3507
|
get loadingStyle() {
|
|
3220
3508
|
return this.element.loading;
|
|
@@ -3222,6 +3510,18 @@ class FrameController {
|
|
|
3222
3510
|
get isLoading() {
|
|
3223
3511
|
return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
|
|
3224
3512
|
}
|
|
3513
|
+
get complete() {
|
|
3514
|
+
return this.element.hasAttribute("complete");
|
|
3515
|
+
}
|
|
3516
|
+
set complete(value) {
|
|
3517
|
+
this.ignoringChangesToAttribute("complete", (() => {
|
|
3518
|
+
if (value) {
|
|
3519
|
+
this.element.setAttribute("complete", "");
|
|
3520
|
+
} else {
|
|
3521
|
+
this.element.removeAttribute("complete");
|
|
3522
|
+
}
|
|
3523
|
+
}));
|
|
3524
|
+
}
|
|
3225
3525
|
get isActive() {
|
|
3226
3526
|
return this.element.isActive && this.connected;
|
|
3227
3527
|
}
|
|
@@ -3231,17 +3531,13 @@ class FrameController {
|
|
|
3231
3531
|
const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
|
|
3232
3532
|
return expandURL(root);
|
|
3233
3533
|
}
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
this.
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
(_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
|
|
3242
|
-
};
|
|
3243
|
-
this.clone = element.cloneNode(true);
|
|
3244
|
-
this.id = element.id;
|
|
3534
|
+
isIgnoringChangesTo(attributeName) {
|
|
3535
|
+
return this.ignoredAttributes.has(attributeName);
|
|
3536
|
+
}
|
|
3537
|
+
ignoringChangesToAttribute(attributeName, callback) {
|
|
3538
|
+
this.ignoredAttributes.add(attributeName);
|
|
3539
|
+
callback();
|
|
3540
|
+
this.ignoredAttributes.delete(attributeName);
|
|
3245
3541
|
}
|
|
3246
3542
|
}
|
|
3247
3543
|
|
|
@@ -3271,41 +3567,6 @@ function activateElement(element, currentURL) {
|
|
|
3271
3567
|
}
|
|
3272
3568
|
}
|
|
3273
3569
|
|
|
3274
|
-
const StreamActions = {
|
|
3275
|
-
after() {
|
|
3276
|
-
this.targetElements.forEach((e => {
|
|
3277
|
-
var _a;
|
|
3278
|
-
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
|
|
3279
|
-
}));
|
|
3280
|
-
},
|
|
3281
|
-
append() {
|
|
3282
|
-
this.removeDuplicateTargetChildren();
|
|
3283
|
-
this.targetElements.forEach((e => e.append(this.templateContent)));
|
|
3284
|
-
},
|
|
3285
|
-
before() {
|
|
3286
|
-
this.targetElements.forEach((e => {
|
|
3287
|
-
var _a;
|
|
3288
|
-
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
|
|
3289
|
-
}));
|
|
3290
|
-
},
|
|
3291
|
-
prepend() {
|
|
3292
|
-
this.removeDuplicateTargetChildren();
|
|
3293
|
-
this.targetElements.forEach((e => e.prepend(this.templateContent)));
|
|
3294
|
-
},
|
|
3295
|
-
remove() {
|
|
3296
|
-
this.targetElements.forEach((e => e.remove()));
|
|
3297
|
-
},
|
|
3298
|
-
replace() {
|
|
3299
|
-
this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
|
|
3300
|
-
},
|
|
3301
|
-
update() {
|
|
3302
|
-
this.targetElements.forEach((e => {
|
|
3303
|
-
e.innerHTML = "";
|
|
3304
|
-
e.append(this.templateContent);
|
|
3305
|
-
}));
|
|
3306
|
-
}
|
|
3307
|
-
};
|
|
3308
|
-
|
|
3309
3570
|
class StreamElement extends HTMLElement {
|
|
3310
3571
|
async connectedCallback() {
|
|
3311
3572
|
try {
|
|
@@ -3336,7 +3597,7 @@ class StreamElement extends HTMLElement {
|
|
|
3336
3597
|
get duplicateChildren() {
|
|
3337
3598
|
var _a;
|
|
3338
3599
|
const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
|
|
3339
|
-
const newChildrenIds = [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].filter((c => !!c.id)).map((c => c.id));
|
|
3600
|
+
const newChildrenIds = [ ...((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [] ].filter((c => !!c.id)).map((c => c.id));
|
|
3340
3601
|
return existingChildren.filter((c => newChildrenIds.includes(c.id)));
|
|
3341
3602
|
}
|
|
3342
3603
|
get performAction() {
|
|
@@ -3409,17 +3670,45 @@ class StreamElement extends HTMLElement {
|
|
|
3409
3670
|
}
|
|
3410
3671
|
}
|
|
3411
3672
|
|
|
3673
|
+
class StreamSourceElement extends HTMLElement {
|
|
3674
|
+
constructor() {
|
|
3675
|
+
super(...arguments);
|
|
3676
|
+
this.streamSource = null;
|
|
3677
|
+
}
|
|
3678
|
+
connectedCallback() {
|
|
3679
|
+
this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
|
|
3680
|
+
connectStreamSource(this.streamSource);
|
|
3681
|
+
}
|
|
3682
|
+
disconnectedCallback() {
|
|
3683
|
+
if (this.streamSource) {
|
|
3684
|
+
disconnectStreamSource(this.streamSource);
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
get src() {
|
|
3688
|
+
return this.getAttribute("src") || "";
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3412
3692
|
FrameElement.delegateConstructor = FrameController;
|
|
3413
3693
|
|
|
3414
|
-
customElements.
|
|
3694
|
+
if (customElements.get("turbo-frame") === undefined) {
|
|
3695
|
+
customElements.define("turbo-frame", FrameElement);
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3698
|
+
if (customElements.get("turbo-stream") === undefined) {
|
|
3699
|
+
customElements.define("turbo-stream", StreamElement);
|
|
3700
|
+
}
|
|
3415
3701
|
|
|
3416
|
-
customElements.
|
|
3702
|
+
if (customElements.get("turbo-stream-source") === undefined) {
|
|
3703
|
+
customElements.define("turbo-stream-source", StreamSourceElement);
|
|
3704
|
+
}
|
|
3417
3705
|
|
|
3418
3706
|
(() => {
|
|
3419
3707
|
let element = document.currentScript;
|
|
3420
3708
|
if (!element) return;
|
|
3421
3709
|
if (element.hasAttribute("data-turbo-suppress-warning")) return;
|
|
3422
|
-
|
|
3710
|
+
element = element.parentElement;
|
|
3711
|
+
while (element) {
|
|
3423
3712
|
if (element == document.body) {
|
|
3424
3713
|
return console.warn(unindent`
|
|
3425
3714
|
You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
|
|
@@ -3432,6 +3721,7 @@ customElements.define("turbo-stream", StreamElement);
|
|
|
3432
3721
|
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
|
3433
3722
|
`, element.outerHTML);
|
|
3434
3723
|
}
|
|
3724
|
+
element = element.parentElement;
|
|
3435
3725
|
}
|
|
3436
3726
|
})();
|
|
3437
3727
|
|
|
@@ -3441,8 +3731,11 @@ start();
|
|
|
3441
3731
|
|
|
3442
3732
|
var turbo_es2017Esm = Object.freeze({
|
|
3443
3733
|
__proto__: null,
|
|
3734
|
+
FrameRenderer: FrameRenderer,
|
|
3444
3735
|
PageRenderer: PageRenderer,
|
|
3445
3736
|
PageSnapshot: PageSnapshot,
|
|
3737
|
+
StreamActions: StreamActions,
|
|
3738
|
+
cache: cache,
|
|
3446
3739
|
clearCache: clearCache,
|
|
3447
3740
|
connectStreamSource: connectStreamSource,
|
|
3448
3741
|
disconnectStreamSource: disconnectStreamSource,
|
|
@@ -3451,6 +3744,7 @@ var turbo_es2017Esm = Object.freeze({
|
|
|
3451
3744
|
renderStreamMessage: renderStreamMessage,
|
|
3452
3745
|
session: session,
|
|
3453
3746
|
setConfirmMethod: setConfirmMethod,
|
|
3747
|
+
setFormMode: setFormMode,
|
|
3454
3748
|
setProgressBarDelay: setProgressBarDelay,
|
|
3455
3749
|
start: start,
|
|
3456
3750
|
visit: visit
|