turbo-rails 0.5.9 → 0.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +4 -0
- data/app/assets/javascripts/turbo.js +878 -722
- data/app/channels/turbo/streams/broadcasts.rb +16 -0
- data/app/helpers/turbo/frames_helper.rb +4 -0
- data/app/helpers/turbo/streams_helper.rb +5 -3
- data/app/javascript/turbo/cable.js +6 -3
- data/app/jobs/turbo/streams/action_broadcast_job.rb +2 -0
- data/app/jobs/turbo/streams/broadcast_job.rb +2 -0
- data/app/models/concerns/turbo/broadcastable.rb +39 -7
- data/app/models/turbo/streams/tag_builder.rb +26 -0
- data/lib/install/turbo_with_asset_pipeline.rb +1 -6
- data/lib/install/turbo_with_webpacker.rb +5 -4
- data/lib/turbo/engine.rb +5 -1
- data/lib/turbo/test_assertions.rb +2 -3
- data/lib/turbo/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1abdc38a6d5a735bf6bbd2ff6fb8db49ea74d14e3452afa5c2bda93b23ffb073
|
4
|
+
data.tar.gz: 0bf468488fd8232fec62b975fb9f0ef5449325cf57b932858e03af876d0548e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79272181e9d15ab14434ba684ac33a2fa186df4fa15462d2ad96a77caaeb8caaf111beebd9bf70f67b7fee3eae0e35c5af5f6e86a4e2c970f07c7b1c58e983bf
|
7
|
+
data.tar.gz: ed1f850a041f5d66ae7bafa8264ac3ee5679db81eb5f91869e4e8624ef0cfadb92f345e9f1de192ee917a568deb866e1231db248820ecdb0afcafc96f8296dc4
|
data/Rakefile
CHANGED
@@ -2,6 +2,10 @@ require "bundler/setup"
|
|
2
2
|
require "bundler/gem_tasks"
|
3
3
|
require "rake/testtask"
|
4
4
|
|
5
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
6
|
+
load "rails/tasks/engine.rake"
|
7
|
+
load "rails/tasks/statistics.rake"
|
8
|
+
|
5
9
|
Rake::TestTask.new do |test|
|
6
10
|
test.libs << "test"
|
7
11
|
test.test_files = FileList["test/**/*_test.rb"]
|
@@ -55,7 +55,7 @@ class FrameElement extends HTMLElement {
|
|
55
55
|
this.delegate = new FrameElement.delegateConstructor(this);
|
56
56
|
}
|
57
57
|
static get observedAttributes() {
|
58
|
-
return [ "loading", "src" ];
|
58
|
+
return [ "disabled", "loading", "src" ];
|
59
59
|
}
|
60
60
|
connectedCallback() {
|
61
61
|
this.delegate.connect();
|
@@ -68,6 +68,8 @@ class FrameElement extends HTMLElement {
|
|
68
68
|
this.delegate.loadingStyleChanged();
|
69
69
|
} else if (name == "src") {
|
70
70
|
this.delegate.sourceURLChanged();
|
71
|
+
} else {
|
72
|
+
this.delegate.disabledChanged();
|
71
73
|
}
|
72
74
|
}
|
73
75
|
get src() {
|
@@ -133,9 +135,7 @@ function frameLoadingStyleFromString(style) {
|
|
133
135
|
}
|
134
136
|
|
135
137
|
function expandURL(locatable) {
|
136
|
-
|
137
|
-
anchor.href = locatable.toString();
|
138
|
-
return new URL(anchor.href);
|
138
|
+
return new URL(locatable.toString(), document.baseURI);
|
139
139
|
}
|
140
140
|
|
141
141
|
function getAnchor(url) {
|
@@ -171,6 +171,10 @@ function toCacheKey(url) {
|
|
171
171
|
}
|
172
172
|
}
|
173
173
|
|
174
|
+
function urlsAreEqual(left, right) {
|
175
|
+
return expandURL(left).href == expandURL(right).href;
|
176
|
+
}
|
177
|
+
|
174
178
|
function getPathComponents(url) {
|
175
179
|
return url.pathname.split("/").slice(1);
|
176
180
|
}
|
@@ -323,6 +327,7 @@ class FetchRequest {
|
|
323
327
|
this.abortController = new AbortController;
|
324
328
|
this.delegate = delegate;
|
325
329
|
this.method = method;
|
330
|
+
this.headers = this.defaultHeaders;
|
326
331
|
if (this.isIdempotent) {
|
327
332
|
this.url = mergeFormDataEntries(location, [ ...body.entries() ]);
|
328
333
|
} else {
|
@@ -343,7 +348,9 @@ class FetchRequest {
|
|
343
348
|
this.abortController.abort();
|
344
349
|
}
|
345
350
|
async perform() {
|
351
|
+
var _a, _b;
|
346
352
|
const {fetchOptions: fetchOptions} = this;
|
353
|
+
(_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
|
347
354
|
dispatch("turbo:before-fetch-request", {
|
348
355
|
detail: {
|
349
356
|
fetchOptions: fetchOptions
|
@@ -387,24 +394,17 @@ class FetchRequest {
|
|
387
394
|
signal: this.abortSignal
|
388
395
|
};
|
389
396
|
}
|
397
|
+
get defaultHeaders() {
|
398
|
+
return {
|
399
|
+
Accept: "text/html, application/xhtml+xml"
|
400
|
+
};
|
401
|
+
}
|
390
402
|
get isIdempotent() {
|
391
403
|
return this.method == FetchMethod.get;
|
392
404
|
}
|
393
|
-
get headers() {
|
394
|
-
const headers = Object.assign({}, this.defaultHeaders);
|
395
|
-
if (typeof this.delegate.prepareHeadersForRequest == "function") {
|
396
|
-
this.delegate.prepareHeadersForRequest(headers, this);
|
397
|
-
}
|
398
|
-
return headers;
|
399
|
-
}
|
400
405
|
get abortSignal() {
|
401
406
|
return this.abortController.signal;
|
402
407
|
}
|
403
|
-
get defaultHeaders() {
|
404
|
-
return {
|
405
|
-
Accept: "text/html, application/xhtml+xml"
|
406
|
-
};
|
407
|
-
}
|
408
408
|
}
|
409
409
|
|
410
410
|
function mergeFormDataEntries(url, entries) {
|
@@ -548,6 +548,9 @@ class FormSubmission {
|
|
548
548
|
var _a;
|
549
549
|
return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
|
550
550
|
}
|
551
|
+
get isIdempotent() {
|
552
|
+
return this.fetchRequest.isIdempotent;
|
553
|
+
}
|
551
554
|
get stringFormData() {
|
552
555
|
return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
|
553
556
|
}
|
@@ -639,8 +642,8 @@ function buildFormData(formElement, submitter) {
|
|
639
642
|
const formData = new FormData(formElement);
|
640
643
|
const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
|
641
644
|
const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
|
642
|
-
if (name && formData.get(name) != value) {
|
643
|
-
formData.append(name, value
|
645
|
+
if (name && value != null && formData.get(name) != value) {
|
646
|
+
formData.append(name, value);
|
644
647
|
}
|
645
648
|
return formData;
|
646
649
|
}
|
@@ -682,6 +685,9 @@ class Snapshot {
|
|
682
685
|
return null;
|
683
686
|
}
|
684
687
|
}
|
688
|
+
get isConnected() {
|
689
|
+
return this.element.isConnected;
|
690
|
+
}
|
685
691
|
get firstAutofocusableElement() {
|
686
692
|
return this.element.querySelector("[autofocus]");
|
687
693
|
}
|
@@ -691,8 +697,16 @@ class Snapshot {
|
|
691
697
|
getPermanentElementById(id) {
|
692
698
|
return this.element.querySelector(`#${id}[data-turbo-permanent]`);
|
693
699
|
}
|
694
|
-
|
695
|
-
|
700
|
+
getPermanentElementMapForSnapshot(snapshot) {
|
701
|
+
const permanentElementMap = {};
|
702
|
+
for (const currentPermanentElement of this.permanentElements) {
|
703
|
+
const {id: id} = currentPermanentElement;
|
704
|
+
const newPermanentElement = snapshot.getPermanentElementById(id);
|
705
|
+
if (newPermanentElement) {
|
706
|
+
permanentElementMap[id] = [ currentPermanentElement, newPermanentElement ];
|
707
|
+
}
|
708
|
+
}
|
709
|
+
return permanentElementMap;
|
696
710
|
}
|
697
711
|
}
|
698
712
|
|
@@ -810,7 +824,7 @@ class LinkInterceptor {
|
|
810
824
|
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) {
|
811
825
|
this.clickEvent.preventDefault();
|
812
826
|
event.preventDefault();
|
813
|
-
this.delegate.linkClickIntercepted(event.target, event.detail.url);
|
827
|
+
this.convertLinkWithMethodClickToFormSubmission(event.target) || this.delegate.linkClickIntercepted(event.target, event.detail.url);
|
814
828
|
}
|
815
829
|
}
|
816
830
|
delete this.clickEvent;
|
@@ -831,12 +845,77 @@ class LinkInterceptor {
|
|
831
845
|
document.removeEventListener("turbo:click", this.linkClicked);
|
832
846
|
document.removeEventListener("turbo:before-visit", this.willVisit);
|
833
847
|
}
|
848
|
+
convertLinkWithMethodClickToFormSubmission(link) {
|
849
|
+
var _a;
|
850
|
+
const linkMethod = link.getAttribute("data-turbo-method") || link.getAttribute("data-method");
|
851
|
+
if (linkMethod) {
|
852
|
+
const form = document.createElement("form");
|
853
|
+
form.method = linkMethod;
|
854
|
+
form.action = link.getAttribute("href") || "undefined";
|
855
|
+
(_a = link.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(form, link);
|
856
|
+
return dispatch("submit", {
|
857
|
+
target: form
|
858
|
+
});
|
859
|
+
} else {
|
860
|
+
return false;
|
861
|
+
}
|
862
|
+
}
|
834
863
|
respondsToEventTarget(target) {
|
835
864
|
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
836
865
|
return element && element.closest("turbo-frame, html") == this.element;
|
837
866
|
}
|
838
867
|
}
|
839
868
|
|
869
|
+
class Bardo {
|
870
|
+
constructor(permanentElementMap) {
|
871
|
+
this.permanentElementMap = permanentElementMap;
|
872
|
+
}
|
873
|
+
static preservingPermanentElements(permanentElementMap, callback) {
|
874
|
+
const bardo = new this(permanentElementMap);
|
875
|
+
bardo.enter();
|
876
|
+
callback();
|
877
|
+
bardo.leave();
|
878
|
+
}
|
879
|
+
enter() {
|
880
|
+
for (const id in this.permanentElementMap) {
|
881
|
+
const [, newPermanentElement] = this.permanentElementMap[id];
|
882
|
+
this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
|
883
|
+
}
|
884
|
+
}
|
885
|
+
leave() {
|
886
|
+
for (const id in this.permanentElementMap) {
|
887
|
+
const [currentPermanentElement] = this.permanentElementMap[id];
|
888
|
+
this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
|
889
|
+
this.replacePlaceholderWithPermanentElement(currentPermanentElement);
|
890
|
+
}
|
891
|
+
}
|
892
|
+
replaceNewPermanentElementWithPlaceholder(permanentElement) {
|
893
|
+
const placeholder = createPlaceholderForPermanentElement(permanentElement);
|
894
|
+
permanentElement.replaceWith(placeholder);
|
895
|
+
}
|
896
|
+
replaceCurrentPermanentElementWithClone(permanentElement) {
|
897
|
+
const clone = permanentElement.cloneNode(true);
|
898
|
+
permanentElement.replaceWith(clone);
|
899
|
+
}
|
900
|
+
replacePlaceholderWithPermanentElement(permanentElement) {
|
901
|
+
const placeholder = this.getPlaceholderById(permanentElement.id);
|
902
|
+
placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
|
903
|
+
}
|
904
|
+
getPlaceholderById(id) {
|
905
|
+
return this.placeholders.find((element => element.content == id));
|
906
|
+
}
|
907
|
+
get placeholders() {
|
908
|
+
return [ ...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]") ];
|
909
|
+
}
|
910
|
+
}
|
911
|
+
|
912
|
+
function createPlaceholderForPermanentElement(permanentElement) {
|
913
|
+
const element = document.createElement("meta");
|
914
|
+
element.setAttribute("name", "turbo-permanent-placeholder");
|
915
|
+
element.setAttribute("content", permanentElement.id);
|
916
|
+
return element;
|
917
|
+
}
|
918
|
+
|
840
919
|
class Renderer {
|
841
920
|
constructor(currentSnapshot, newSnapshot, isPreview) {
|
842
921
|
this.currentSnapshot = currentSnapshot;
|
@@ -871,28 +950,25 @@ class Renderer {
|
|
871
950
|
}
|
872
951
|
}
|
873
952
|
preservingPermanentElements(callback) {
|
874
|
-
|
875
|
-
callback();
|
876
|
-
replacePlaceholderElementsWithClonedPermanentElements(placeholders);
|
953
|
+
Bardo.preservingPermanentElements(this.permanentElementMap, callback);
|
877
954
|
}
|
878
955
|
focusFirstAutofocusableElement() {
|
879
|
-
const element = this.
|
956
|
+
const element = this.connectedSnapshot.firstAutofocusableElement;
|
880
957
|
if (elementIsFocusable(element)) {
|
881
958
|
element.focus();
|
882
959
|
}
|
883
960
|
}
|
961
|
+
get connectedSnapshot() {
|
962
|
+
return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
|
963
|
+
}
|
884
964
|
get currentElement() {
|
885
965
|
return this.currentSnapshot.element;
|
886
966
|
}
|
887
967
|
get newElement() {
|
888
968
|
return this.newSnapshot.element;
|
889
969
|
}
|
890
|
-
|
891
|
-
|
892
|
-
function replaceElementWithElement(fromElement, toElement) {
|
893
|
-
const parentElement = fromElement.parentElement;
|
894
|
-
if (parentElement) {
|
895
|
-
return parentElement.replaceChild(toElement, fromElement);
|
970
|
+
get permanentElementMap() {
|
971
|
+
return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
|
896
972
|
}
|
897
973
|
}
|
898
974
|
|
@@ -902,37 +978,6 @@ function copyElementAttributes(destinationElement, sourceElement) {
|
|
902
978
|
}
|
903
979
|
}
|
904
980
|
|
905
|
-
function createPlaceholderForPermanentElement(permanentElement) {
|
906
|
-
const element = document.createElement("meta");
|
907
|
-
element.setAttribute("name", "turbo-permanent-placeholder");
|
908
|
-
element.setAttribute("content", permanentElement.id);
|
909
|
-
return {
|
910
|
-
element: element,
|
911
|
-
permanentElement: permanentElement
|
912
|
-
};
|
913
|
-
}
|
914
|
-
|
915
|
-
function replacePlaceholderElementsWithClonedPermanentElements(placeholders) {
|
916
|
-
for (const {element: element, permanentElement: permanentElement} of placeholders) {
|
917
|
-
const clonedElement = permanentElement.cloneNode(true);
|
918
|
-
replaceElementWithElement(element, clonedElement);
|
919
|
-
}
|
920
|
-
}
|
921
|
-
|
922
|
-
function relocatePermanentElements(currentSnapshot, newSnapshot) {
|
923
|
-
return currentSnapshot.getPermanentElementsPresentInSnapshot(newSnapshot).reduce(((placeholders, permanentElement) => {
|
924
|
-
const newElement = newSnapshot.getPermanentElementById(permanentElement.id);
|
925
|
-
if (newElement) {
|
926
|
-
const placeholder = createPlaceholderForPermanentElement(permanentElement);
|
927
|
-
replaceElementWithElement(permanentElement, placeholder.element);
|
928
|
-
replaceElementWithElement(newElement, permanentElement);
|
929
|
-
return [ ...placeholders, placeholder ];
|
930
|
-
} else {
|
931
|
-
return placeholders;
|
932
|
-
}
|
933
|
-
}), []);
|
934
|
-
}
|
935
|
-
|
936
981
|
function elementIsFocusable(element) {
|
937
982
|
return element && typeof element.focus == "function";
|
938
983
|
}
|
@@ -985,458 +1030,133 @@ function readScrollLogicalPosition(value, defaultValue) {
|
|
985
1030
|
}
|
986
1031
|
}
|
987
1032
|
|
988
|
-
class
|
989
|
-
constructor(
|
990
|
-
this.
|
991
|
-
this.
|
992
|
-
this.
|
993
|
-
this.
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
}
|
1001
|
-
this.linkInterceptor.start();
|
1002
|
-
this.formInterceptor.start();
|
1003
|
-
}
|
1004
|
-
disconnect() {
|
1005
|
-
this.appearanceObserver.stop();
|
1006
|
-
this.linkInterceptor.stop();
|
1007
|
-
this.formInterceptor.stop();
|
1008
|
-
}
|
1009
|
-
sourceURLChanged() {
|
1010
|
-
if (this.loadingStyle == FrameLoadingStyle.eager) {
|
1011
|
-
this.loadSourceURL();
|
1012
|
-
}
|
1013
|
-
}
|
1014
|
-
loadingStyleChanged() {
|
1015
|
-
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
1016
|
-
this.appearanceObserver.start();
|
1017
|
-
} else {
|
1018
|
-
this.appearanceObserver.stop();
|
1019
|
-
this.loadSourceURL();
|
1020
|
-
}
|
1033
|
+
class ProgressBar {
|
1034
|
+
constructor() {
|
1035
|
+
this.hiding = false;
|
1036
|
+
this.value = 0;
|
1037
|
+
this.visible = false;
|
1038
|
+
this.trickle = () => {
|
1039
|
+
this.setValue(this.value + Math.random() / 100);
|
1040
|
+
};
|
1041
|
+
this.stylesheetElement = this.createStylesheetElement();
|
1042
|
+
this.progressElement = this.createProgressElement();
|
1043
|
+
this.installStylesheetElement();
|
1044
|
+
this.setValue(0);
|
1021
1045
|
}
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1046
|
+
static get defaultCSS() {
|
1047
|
+
return unindent`
|
1048
|
+
.turbo-progress-bar {
|
1049
|
+
position: fixed;
|
1050
|
+
display: block;
|
1051
|
+
top: 0;
|
1052
|
+
left: 0;
|
1053
|
+
height: 3px;
|
1054
|
+
background: #0076ff;
|
1055
|
+
z-index: 9999;
|
1056
|
+
transition:
|
1057
|
+
width ${ProgressBar.animationDuration}ms ease-out,
|
1058
|
+
opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
|
1059
|
+
transform: translate3d(0, 0, 0);
|
1031
1060
|
}
|
1061
|
+
`;
|
1062
|
+
}
|
1063
|
+
show() {
|
1064
|
+
if (!this.visible) {
|
1065
|
+
this.visible = true;
|
1066
|
+
this.installProgressElement();
|
1067
|
+
this.startTrickling();
|
1032
1068
|
}
|
1033
1069
|
}
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
}
|
1043
|
-
} catch (error) {
|
1044
|
-
console.error(error);
|
1045
|
-
this.view.invalidate();
|
1070
|
+
hide() {
|
1071
|
+
if (this.visible && !this.hiding) {
|
1072
|
+
this.hiding = true;
|
1073
|
+
this.fadeProgressElement((() => {
|
1074
|
+
this.uninstallProgressElement();
|
1075
|
+
this.stopTrickling();
|
1076
|
+
this.visible = false;
|
1077
|
+
this.hiding = false;
|
1078
|
+
}));
|
1046
1079
|
}
|
1047
1080
|
}
|
1048
|
-
|
1049
|
-
this.
|
1081
|
+
setValue(value) {
|
1082
|
+
this.value = value;
|
1083
|
+
this.refresh();
|
1050
1084
|
}
|
1051
|
-
|
1052
|
-
|
1085
|
+
installStylesheetElement() {
|
1086
|
+
document.head.insertBefore(this.stylesheetElement, document.head.firstChild);
|
1053
1087
|
}
|
1054
|
-
|
1055
|
-
this.
|
1088
|
+
installProgressElement() {
|
1089
|
+
this.progressElement.style.width = "0";
|
1090
|
+
this.progressElement.style.opacity = "1";
|
1091
|
+
document.documentElement.insertBefore(this.progressElement, document.body);
|
1092
|
+
this.refresh();
|
1056
1093
|
}
|
1057
|
-
|
1058
|
-
|
1094
|
+
fadeProgressElement(callback) {
|
1095
|
+
this.progressElement.style.opacity = "0";
|
1096
|
+
setTimeout(callback, ProgressBar.animationDuration * 1.5);
|
1059
1097
|
}
|
1060
|
-
|
1061
|
-
if (this.
|
1062
|
-
|
1098
|
+
uninstallProgressElement() {
|
1099
|
+
if (this.progressElement.parentNode) {
|
1100
|
+
document.documentElement.removeChild(this.progressElement);
|
1063
1101
|
}
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
this.formSubmission.start();
|
1102
|
+
}
|
1103
|
+
startTrickling() {
|
1104
|
+
if (!this.trickleInterval) {
|
1105
|
+
this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration);
|
1069
1106
|
}
|
1070
1107
|
}
|
1071
|
-
|
1072
|
-
|
1108
|
+
stopTrickling() {
|
1109
|
+
window.clearInterval(this.trickleInterval);
|
1110
|
+
delete this.trickleInterval;
|
1073
1111
|
}
|
1074
|
-
|
1075
|
-
|
1112
|
+
refresh() {
|
1113
|
+
requestAnimationFrame((() => {
|
1114
|
+
this.progressElement.style.width = `${10 + this.value * 90}%`;
|
1115
|
+
}));
|
1076
1116
|
}
|
1077
|
-
|
1078
|
-
|
1117
|
+
createStylesheetElement() {
|
1118
|
+
const element = document.createElement("style");
|
1119
|
+
element.type = "text/css";
|
1120
|
+
element.textContent = ProgressBar.defaultCSS;
|
1121
|
+
return element;
|
1079
1122
|
}
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1123
|
+
createProgressElement() {
|
1124
|
+
const element = document.createElement("div");
|
1125
|
+
element.className = "turbo-progress-bar";
|
1126
|
+
return element;
|
1083
1127
|
}
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1128
|
+
}
|
1129
|
+
|
1130
|
+
ProgressBar.animationDuration = 300;
|
1131
|
+
|
1132
|
+
class HeadSnapshot extends Snapshot {
|
1133
|
+
constructor() {
|
1134
|
+
super(...arguments);
|
1135
|
+
this.detailsByOuterHTML = this.children.reduce(((result, element) => {
|
1136
|
+
const {outerHTML: outerHTML} = element;
|
1137
|
+
const details = outerHTML in result ? result[outerHTML] : {
|
1138
|
+
type: elementType(element),
|
1139
|
+
tracked: elementIsTracked(element),
|
1140
|
+
elements: []
|
1141
|
+
};
|
1142
|
+
return Object.assign(Object.assign({}, result), {
|
1143
|
+
[outerHTML]: Object.assign(Object.assign({}, details), {
|
1144
|
+
elements: [ ...details.elements, element ]
|
1145
|
+
})
|
1146
|
+
});
|
1147
|
+
}), {});
|
1087
1148
|
}
|
1088
|
-
|
1089
|
-
|
1090
|
-
this.resolveVisitPromise();
|
1149
|
+
get trackedElementSignature() {
|
1150
|
+
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
|
1091
1151
|
}
|
1092
|
-
|
1093
|
-
this.
|
1152
|
+
getScriptElementsNotInSnapshot(snapshot) {
|
1153
|
+
return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
|
1094
1154
|
}
|
1095
|
-
|
1096
|
-
|
1097
|
-
const frame = this.findFrameElement(formSubmission.formElement);
|
1098
|
-
frame.delegate.loadResponse(response);
|
1155
|
+
getStylesheetElementsNotInSnapshot(snapshot) {
|
1156
|
+
return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
|
1099
1157
|
}
|
1100
|
-
|
1101
|
-
this.
|
1102
|
-
}
|
1103
|
-
formSubmissionErrored(formSubmission, error) {}
|
1104
|
-
formSubmissionFinished(formSubmission) {}
|
1105
|
-
viewWillRenderSnapshot(snapshot, isPreview) {}
|
1106
|
-
viewRenderedSnapshot(snapshot, isPreview) {}
|
1107
|
-
viewInvalidated() {}
|
1108
|
-
async visit(url) {
|
1109
|
-
const request = new FetchRequest(this, FetchMethod.get, expandURL(url));
|
1110
|
-
return new Promise((resolve => {
|
1111
|
-
this.resolveVisitPromise = () => {
|
1112
|
-
this.resolveVisitPromise = () => {};
|
1113
|
-
resolve();
|
1114
|
-
};
|
1115
|
-
request.perform();
|
1116
|
-
}));
|
1117
|
-
}
|
1118
|
-
navigateFrame(element, url) {
|
1119
|
-
const frame = this.findFrameElement(element);
|
1120
|
-
frame.src = url;
|
1121
|
-
}
|
1122
|
-
findFrameElement(element) {
|
1123
|
-
var _a;
|
1124
|
-
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
1125
|
-
return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
|
1126
|
-
}
|
1127
|
-
async extractForeignFrameElement(container) {
|
1128
|
-
let element;
|
1129
|
-
const id = CSS.escape(this.id);
|
1130
|
-
if (element = activateElement(container.querySelector(`turbo-frame#${id}`))) {
|
1131
|
-
return element;
|
1132
|
-
}
|
1133
|
-
if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`))) {
|
1134
|
-
await element.loaded;
|
1135
|
-
return await this.extractForeignFrameElement(element);
|
1136
|
-
}
|
1137
|
-
console.error(`Response has no matching <turbo-frame id="${id}"> element`);
|
1138
|
-
return new FrameElement;
|
1139
|
-
}
|
1140
|
-
shouldInterceptNavigation(element) {
|
1141
|
-
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
1142
|
-
if (!this.enabled || id == "_top") {
|
1143
|
-
return false;
|
1144
|
-
}
|
1145
|
-
if (id) {
|
1146
|
-
const frameElement = getFrameElementById(id);
|
1147
|
-
if (frameElement) {
|
1148
|
-
return !frameElement.disabled;
|
1149
|
-
}
|
1150
|
-
}
|
1151
|
-
return true;
|
1152
|
-
}
|
1153
|
-
get id() {
|
1154
|
-
return this.element.id;
|
1155
|
-
}
|
1156
|
-
get enabled() {
|
1157
|
-
return !this.element.disabled;
|
1158
|
-
}
|
1159
|
-
get sourceURL() {
|
1160
|
-
return this.element.src;
|
1161
|
-
}
|
1162
|
-
get loadingStyle() {
|
1163
|
-
return this.element.loading;
|
1164
|
-
}
|
1165
|
-
get isLoading() {
|
1166
|
-
return this.formSubmission !== undefined || this.loadingURL !== undefined;
|
1167
|
-
}
|
1168
|
-
get isActive() {
|
1169
|
-
return this.element.isActive;
|
1170
|
-
}
|
1171
|
-
}
|
1172
|
-
|
1173
|
-
function getFrameElementById(id) {
|
1174
|
-
if (id != null) {
|
1175
|
-
const element = document.getElementById(id);
|
1176
|
-
if (element instanceof FrameElement) {
|
1177
|
-
return element;
|
1178
|
-
}
|
1179
|
-
}
|
1180
|
-
}
|
1181
|
-
|
1182
|
-
function activateElement(element) {
|
1183
|
-
if (element && element.ownerDocument !== document) {
|
1184
|
-
element = document.importNode(element, true);
|
1185
|
-
}
|
1186
|
-
if (element instanceof FrameElement) {
|
1187
|
-
return element;
|
1188
|
-
}
|
1189
|
-
}
|
1190
|
-
|
1191
|
-
const StreamActions = {
|
1192
|
-
append() {
|
1193
|
-
var _a;
|
1194
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.append(this.templateContent);
|
1195
|
-
},
|
1196
|
-
prepend() {
|
1197
|
-
var _a;
|
1198
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.prepend(this.templateContent);
|
1199
|
-
},
|
1200
|
-
remove() {
|
1201
|
-
var _a;
|
1202
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.remove();
|
1203
|
-
},
|
1204
|
-
replace() {
|
1205
|
-
var _a;
|
1206
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.replaceWith(this.templateContent);
|
1207
|
-
},
|
1208
|
-
update() {
|
1209
|
-
if (this.targetElement) {
|
1210
|
-
this.targetElement.innerHTML = "";
|
1211
|
-
this.targetElement.append(this.templateContent);
|
1212
|
-
}
|
1213
|
-
}
|
1214
|
-
};
|
1215
|
-
|
1216
|
-
class StreamElement extends HTMLElement {
|
1217
|
-
async connectedCallback() {
|
1218
|
-
try {
|
1219
|
-
await this.render();
|
1220
|
-
} catch (error) {
|
1221
|
-
console.error(error);
|
1222
|
-
} finally {
|
1223
|
-
this.disconnect();
|
1224
|
-
}
|
1225
|
-
}
|
1226
|
-
async render() {
|
1227
|
-
var _a;
|
1228
|
-
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
|
1229
|
-
if (this.dispatchEvent(this.beforeRenderEvent)) {
|
1230
|
-
await nextAnimationFrame();
|
1231
|
-
this.performAction();
|
1232
|
-
}
|
1233
|
-
})();
|
1234
|
-
}
|
1235
|
-
disconnect() {
|
1236
|
-
try {
|
1237
|
-
this.remove();
|
1238
|
-
} catch (_a) {}
|
1239
|
-
}
|
1240
|
-
get performAction() {
|
1241
|
-
if (this.action) {
|
1242
|
-
const actionFunction = StreamActions[this.action];
|
1243
|
-
if (actionFunction) {
|
1244
|
-
return actionFunction;
|
1245
|
-
}
|
1246
|
-
this.raise("unknown action");
|
1247
|
-
}
|
1248
|
-
this.raise("action attribute is missing");
|
1249
|
-
}
|
1250
|
-
get targetElement() {
|
1251
|
-
var _a;
|
1252
|
-
if (this.target) {
|
1253
|
-
return (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
|
1254
|
-
}
|
1255
|
-
this.raise("target attribute is missing");
|
1256
|
-
}
|
1257
|
-
get templateContent() {
|
1258
|
-
return this.templateElement.content;
|
1259
|
-
}
|
1260
|
-
get templateElement() {
|
1261
|
-
if (this.firstElementChild instanceof HTMLTemplateElement) {
|
1262
|
-
return this.firstElementChild;
|
1263
|
-
}
|
1264
|
-
this.raise("first child element must be a <template> element");
|
1265
|
-
}
|
1266
|
-
get action() {
|
1267
|
-
return this.getAttribute("action");
|
1268
|
-
}
|
1269
|
-
get target() {
|
1270
|
-
return this.getAttribute("target");
|
1271
|
-
}
|
1272
|
-
raise(message) {
|
1273
|
-
throw new Error(`${this.description}: ${message}`);
|
1274
|
-
}
|
1275
|
-
get description() {
|
1276
|
-
var _a, _b;
|
1277
|
-
return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
|
1278
|
-
}
|
1279
|
-
get beforeRenderEvent() {
|
1280
|
-
return new CustomEvent("turbo:before-stream-render", {
|
1281
|
-
bubbles: true,
|
1282
|
-
cancelable: true
|
1283
|
-
});
|
1284
|
-
}
|
1285
|
-
}
|
1286
|
-
|
1287
|
-
FrameElement.delegateConstructor = FrameController;
|
1288
|
-
|
1289
|
-
customElements.define("turbo-frame", FrameElement);
|
1290
|
-
|
1291
|
-
customElements.define("turbo-stream", StreamElement);
|
1292
|
-
|
1293
|
-
(() => {
|
1294
|
-
let element = document.currentScript;
|
1295
|
-
if (!element) return;
|
1296
|
-
if (element.hasAttribute("data-turbo-suppress-warning")) return;
|
1297
|
-
while (element = element.parentElement) {
|
1298
|
-
if (element == document.body) {
|
1299
|
-
return console.warn(unindent`
|
1300
|
-
You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
|
1301
|
-
|
1302
|
-
Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
|
1303
|
-
|
1304
|
-
For more information, see: https://turbo.hotwire.dev/handbook/building#working-with-script-elements
|
1305
|
-
|
1306
|
-
——
|
1307
|
-
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
1308
|
-
`, element.outerHTML);
|
1309
|
-
}
|
1310
|
-
}
|
1311
|
-
})();
|
1312
|
-
|
1313
|
-
class ProgressBar {
|
1314
|
-
constructor() {
|
1315
|
-
this.hiding = false;
|
1316
|
-
this.value = 0;
|
1317
|
-
this.visible = false;
|
1318
|
-
this.trickle = () => {
|
1319
|
-
this.setValue(this.value + Math.random() / 100);
|
1320
|
-
};
|
1321
|
-
this.stylesheetElement = this.createStylesheetElement();
|
1322
|
-
this.progressElement = this.createProgressElement();
|
1323
|
-
this.installStylesheetElement();
|
1324
|
-
this.setValue(0);
|
1325
|
-
}
|
1326
|
-
static get defaultCSS() {
|
1327
|
-
return unindent`
|
1328
|
-
.turbo-progress-bar {
|
1329
|
-
position: fixed;
|
1330
|
-
display: block;
|
1331
|
-
top: 0;
|
1332
|
-
left: 0;
|
1333
|
-
height: 3px;
|
1334
|
-
background: #0076ff;
|
1335
|
-
z-index: 9999;
|
1336
|
-
transition:
|
1337
|
-
width ${ProgressBar.animationDuration}ms ease-out,
|
1338
|
-
opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
|
1339
|
-
transform: translate3d(0, 0, 0);
|
1340
|
-
}
|
1341
|
-
`;
|
1342
|
-
}
|
1343
|
-
show() {
|
1344
|
-
if (!this.visible) {
|
1345
|
-
this.visible = true;
|
1346
|
-
this.installProgressElement();
|
1347
|
-
this.startTrickling();
|
1348
|
-
}
|
1349
|
-
}
|
1350
|
-
hide() {
|
1351
|
-
if (this.visible && !this.hiding) {
|
1352
|
-
this.hiding = true;
|
1353
|
-
this.fadeProgressElement((() => {
|
1354
|
-
this.uninstallProgressElement();
|
1355
|
-
this.stopTrickling();
|
1356
|
-
this.visible = false;
|
1357
|
-
this.hiding = false;
|
1358
|
-
}));
|
1359
|
-
}
|
1360
|
-
}
|
1361
|
-
setValue(value) {
|
1362
|
-
this.value = value;
|
1363
|
-
this.refresh();
|
1364
|
-
}
|
1365
|
-
installStylesheetElement() {
|
1366
|
-
document.head.insertBefore(this.stylesheetElement, document.head.firstChild);
|
1367
|
-
}
|
1368
|
-
installProgressElement() {
|
1369
|
-
this.progressElement.style.width = "0";
|
1370
|
-
this.progressElement.style.opacity = "1";
|
1371
|
-
document.documentElement.insertBefore(this.progressElement, document.body);
|
1372
|
-
this.refresh();
|
1373
|
-
}
|
1374
|
-
fadeProgressElement(callback) {
|
1375
|
-
this.progressElement.style.opacity = "0";
|
1376
|
-
setTimeout(callback, ProgressBar.animationDuration * 1.5);
|
1377
|
-
}
|
1378
|
-
uninstallProgressElement() {
|
1379
|
-
if (this.progressElement.parentNode) {
|
1380
|
-
document.documentElement.removeChild(this.progressElement);
|
1381
|
-
}
|
1382
|
-
}
|
1383
|
-
startTrickling() {
|
1384
|
-
if (!this.trickleInterval) {
|
1385
|
-
this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration);
|
1386
|
-
}
|
1387
|
-
}
|
1388
|
-
stopTrickling() {
|
1389
|
-
window.clearInterval(this.trickleInterval);
|
1390
|
-
delete this.trickleInterval;
|
1391
|
-
}
|
1392
|
-
refresh() {
|
1393
|
-
requestAnimationFrame((() => {
|
1394
|
-
this.progressElement.style.width = `${10 + this.value * 90}%`;
|
1395
|
-
}));
|
1396
|
-
}
|
1397
|
-
createStylesheetElement() {
|
1398
|
-
const element = document.createElement("style");
|
1399
|
-
element.type = "text/css";
|
1400
|
-
element.textContent = ProgressBar.defaultCSS;
|
1401
|
-
return element;
|
1402
|
-
}
|
1403
|
-
createProgressElement() {
|
1404
|
-
const element = document.createElement("div");
|
1405
|
-
element.className = "turbo-progress-bar";
|
1406
|
-
return element;
|
1407
|
-
}
|
1408
|
-
}
|
1409
|
-
|
1410
|
-
ProgressBar.animationDuration = 300;
|
1411
|
-
|
1412
|
-
class HeadSnapshot extends Snapshot {
|
1413
|
-
constructor() {
|
1414
|
-
super(...arguments);
|
1415
|
-
this.detailsByOuterHTML = this.children.reduce(((result, element) => {
|
1416
|
-
const {outerHTML: outerHTML} = element;
|
1417
|
-
const details = outerHTML in result ? result[outerHTML] : {
|
1418
|
-
type: elementType(element),
|
1419
|
-
tracked: elementIsTracked(element),
|
1420
|
-
elements: []
|
1421
|
-
};
|
1422
|
-
return Object.assign(Object.assign({}, result), {
|
1423
|
-
[outerHTML]: Object.assign(Object.assign({}, details), {
|
1424
|
-
elements: [ ...details.elements, element ]
|
1425
|
-
})
|
1426
|
-
});
|
1427
|
-
}), {});
|
1428
|
-
}
|
1429
|
-
get trackedElementSignature() {
|
1430
|
-
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
|
1431
|
-
}
|
1432
|
-
getScriptElementsNotInSnapshot(snapshot) {
|
1433
|
-
return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
|
1434
|
-
}
|
1435
|
-
getStylesheetElementsNotInSnapshot(snapshot) {
|
1436
|
-
return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
|
1437
|
-
}
|
1438
|
-
getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
|
1439
|
-
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))).map((outerHTML => this.detailsByOuterHTML[outerHTML])).filter((({type: type}) => type == matchedType)).map((({elements: [element]}) => element));
|
1158
|
+
getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
|
1159
|
+
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))).map((outerHTML => this.detailsByOuterHTML[outerHTML])).filter((({type: type}) => type == matchedType)).map((({elements: [element]}) => element));
|
1440
1160
|
}
|
1441
1161
|
get provisionalElements() {
|
1442
1162
|
return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
|
@@ -1707,7 +1427,7 @@ class Visit {
|
|
1707
1427
|
const isPreview = this.shouldIssueRequest();
|
1708
1428
|
this.render((async () => {
|
1709
1429
|
this.cacheSnapshot();
|
1710
|
-
await this.view.renderPage(snapshot);
|
1430
|
+
await this.view.renderPage(snapshot, isPreview);
|
1711
1431
|
this.adapter.visitRendered(this);
|
1712
1432
|
if (!isPreview) {
|
1713
1433
|
this.complete();
|
@@ -1779,7 +1499,7 @@ class Visit {
|
|
1779
1499
|
}
|
1780
1500
|
}
|
1781
1501
|
scrollToAnchor() {
|
1782
|
-
if (getAnchor(this.location)
|
1502
|
+
if (getAnchor(this.location)) {
|
1783
1503
|
this.view.scrollToAnchor(getAnchor(this.location));
|
1784
1504
|
return true;
|
1785
1505
|
}
|
@@ -2119,6 +1839,10 @@ class LinkClickObserver {
|
|
2119
1839
|
}
|
2120
1840
|
}
|
2121
1841
|
|
1842
|
+
function isAction(action) {
|
1843
|
+
return action == "advance" || action == "replace" || action == "restore";
|
1844
|
+
}
|
1845
|
+
|
2122
1846
|
class Navigator {
|
2123
1847
|
constructor(delegate) {
|
2124
1848
|
this.delegate = delegate;
|
@@ -2138,8 +1862,10 @@ class Navigator {
|
|
2138
1862
|
submitForm(form, submitter) {
|
2139
1863
|
this.stop();
|
2140
1864
|
this.formSubmission = new FormSubmission(this, form, submitter, true);
|
2141
|
-
if (this.formSubmission.
|
2142
|
-
this.proposeVisit(this.formSubmission.fetchRequest.url
|
1865
|
+
if (this.formSubmission.isIdempotent) {
|
1866
|
+
this.proposeVisit(this.formSubmission.fetchRequest.url, {
|
1867
|
+
action: this.getActionForFormSubmission(this.formSubmission)
|
1868
|
+
});
|
2143
1869
|
} else {
|
2144
1870
|
this.formSubmission.start();
|
2145
1871
|
}
|
@@ -2190,7 +1916,9 @@ class Navigator {
|
|
2190
1916
|
this.view.clearSnapshotCache();
|
2191
1917
|
}
|
2192
1918
|
}
|
2193
|
-
formSubmissionErrored(formSubmission, error) {
|
1919
|
+
formSubmissionErrored(formSubmission, error) {
|
1920
|
+
console.error(error);
|
1921
|
+
}
|
2194
1922
|
formSubmissionFinished(formSubmission) {}
|
2195
1923
|
visitStarted(visit) {
|
2196
1924
|
this.delegate.visitStarted(visit);
|
@@ -2204,6 +1932,11 @@ class Navigator {
|
|
2204
1932
|
get restorationIdentifier() {
|
2205
1933
|
return this.history.restorationIdentifier;
|
2206
1934
|
}
|
1935
|
+
getActionForFormSubmission(formSubmission) {
|
1936
|
+
const {formElement: formElement, submitter: submitter} = formSubmission;
|
1937
|
+
const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-action")) || formElement.getAttribute("data-turbo-action");
|
1938
|
+
return isAction(action) ? action : "advance";
|
1939
|
+
}
|
2207
1940
|
}
|
2208
1941
|
|
2209
1942
|
var PageStage;
|
@@ -2366,10 +2099,6 @@ function fetchResponseIsStream(response) {
|
|
2366
2099
|
return contentType.startsWith(StreamMessage.contentType);
|
2367
2100
|
}
|
2368
2101
|
|
2369
|
-
function isAction(action) {
|
2370
|
-
return action == "advance" || action == "replace" || action == "restore";
|
2371
|
-
}
|
2372
|
-
|
2373
2102
|
class ErrorRenderer extends Renderer {
|
2374
2103
|
async render() {
|
2375
2104
|
this.replaceHeadAndBody();
|
@@ -2409,7 +2138,7 @@ class PageRenderer extends Renderer {
|
|
2409
2138
|
}
|
2410
2139
|
finishRendering() {
|
2411
2140
|
super.finishRendering();
|
2412
|
-
if (this.isPreview) {
|
2141
|
+
if (!this.isPreview) {
|
2413
2142
|
this.focusFirstAutofocusableElement();
|
2414
2143
|
}
|
2415
2144
|
}
|
@@ -2464,12 +2193,12 @@ class PageRenderer extends Renderer {
|
|
2464
2193
|
activateNewBodyScriptElements() {
|
2465
2194
|
for (const inertScriptElement of this.newBodyScriptElements) {
|
2466
2195
|
const activatedScriptElement = this.createScriptElement(inertScriptElement);
|
2467
|
-
|
2196
|
+
inertScriptElement.replaceWith(activatedScriptElement);
|
2468
2197
|
}
|
2469
2198
|
}
|
2470
2199
|
assignNewBody() {
|
2471
2200
|
if (document.body && this.newElement instanceof HTMLBodyElement) {
|
2472
|
-
|
2201
|
+
document.body.replaceWith(this.newElement);
|
2473
2202
|
} else {
|
2474
2203
|
document.documentElement.appendChild(this.newElement);
|
2475
2204
|
}
|
@@ -2521,281 +2250,689 @@ class SnapshotCache {
|
|
2521
2250
|
write(location, snapshot) {
|
2522
2251
|
this.snapshots[toCacheKey(location)] = snapshot;
|
2523
2252
|
}
|
2524
|
-
touch(location) {
|
2525
|
-
const key = toCacheKey(location);
|
2526
|
-
const index = this.keys.indexOf(key);
|
2527
|
-
if (index > -1) this.keys.splice(index, 1);
|
2528
|
-
this.keys.unshift(key);
|
2529
|
-
this.trim();
|
2253
|
+
touch(location) {
|
2254
|
+
const key = toCacheKey(location);
|
2255
|
+
const index = this.keys.indexOf(key);
|
2256
|
+
if (index > -1) this.keys.splice(index, 1);
|
2257
|
+
this.keys.unshift(key);
|
2258
|
+
this.trim();
|
2259
|
+
}
|
2260
|
+
trim() {
|
2261
|
+
for (const key of this.keys.splice(this.size)) {
|
2262
|
+
delete this.snapshots[key];
|
2263
|
+
}
|
2264
|
+
}
|
2265
|
+
}
|
2266
|
+
|
2267
|
+
class PageView extends View {
|
2268
|
+
constructor() {
|
2269
|
+
super(...arguments);
|
2270
|
+
this.snapshotCache = new SnapshotCache(10);
|
2271
|
+
this.lastRenderedLocation = new URL(location.href);
|
2272
|
+
}
|
2273
|
+
renderPage(snapshot, isPreview = false) {
|
2274
|
+
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
|
2275
|
+
return this.render(renderer);
|
2276
|
+
}
|
2277
|
+
renderError(snapshot) {
|
2278
|
+
const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
|
2279
|
+
this.render(renderer);
|
2280
|
+
}
|
2281
|
+
clearSnapshotCache() {
|
2282
|
+
this.snapshotCache.clear();
|
2283
|
+
}
|
2284
|
+
async cacheSnapshot() {
|
2285
|
+
if (this.shouldCacheSnapshot) {
|
2286
|
+
this.delegate.viewWillCacheSnapshot();
|
2287
|
+
const {snapshot: snapshot, lastRenderedLocation: location} = this;
|
2288
|
+
await nextEventLoopTick();
|
2289
|
+
this.snapshotCache.put(location, snapshot.clone());
|
2290
|
+
}
|
2291
|
+
}
|
2292
|
+
getCachedSnapshotForLocation(location) {
|
2293
|
+
return this.snapshotCache.get(location);
|
2294
|
+
}
|
2295
|
+
get snapshot() {
|
2296
|
+
return PageSnapshot.fromElement(this.element);
|
2297
|
+
}
|
2298
|
+
get shouldCacheSnapshot() {
|
2299
|
+
return this.snapshot.isCacheable;
|
2300
|
+
}
|
2301
|
+
}
|
2302
|
+
|
2303
|
+
class Session {
|
2304
|
+
constructor() {
|
2305
|
+
this.navigator = new Navigator(this);
|
2306
|
+
this.history = new History(this);
|
2307
|
+
this.view = new PageView(this, document.documentElement);
|
2308
|
+
this.adapter = new BrowserAdapter(this);
|
2309
|
+
this.pageObserver = new PageObserver(this);
|
2310
|
+
this.linkClickObserver = new LinkClickObserver(this);
|
2311
|
+
this.formSubmitObserver = new FormSubmitObserver(this);
|
2312
|
+
this.scrollObserver = new ScrollObserver(this);
|
2313
|
+
this.streamObserver = new StreamObserver(this);
|
2314
|
+
this.frameRedirector = new FrameRedirector(document.documentElement);
|
2315
|
+
this.enabled = true;
|
2316
|
+
this.progressBarDelay = 500;
|
2317
|
+
this.started = false;
|
2318
|
+
}
|
2319
|
+
start() {
|
2320
|
+
if (!this.started) {
|
2321
|
+
this.pageObserver.start();
|
2322
|
+
this.linkClickObserver.start();
|
2323
|
+
this.formSubmitObserver.start();
|
2324
|
+
this.scrollObserver.start();
|
2325
|
+
this.streamObserver.start();
|
2326
|
+
this.frameRedirector.start();
|
2327
|
+
this.history.start();
|
2328
|
+
this.started = true;
|
2329
|
+
this.enabled = true;
|
2330
|
+
}
|
2331
|
+
}
|
2332
|
+
disable() {
|
2333
|
+
this.enabled = false;
|
2334
|
+
}
|
2335
|
+
stop() {
|
2336
|
+
if (this.started) {
|
2337
|
+
this.pageObserver.stop();
|
2338
|
+
this.linkClickObserver.stop();
|
2339
|
+
this.formSubmitObserver.stop();
|
2340
|
+
this.scrollObserver.stop();
|
2341
|
+
this.streamObserver.stop();
|
2342
|
+
this.frameRedirector.stop();
|
2343
|
+
this.history.stop();
|
2344
|
+
this.started = false;
|
2345
|
+
}
|
2346
|
+
}
|
2347
|
+
registerAdapter(adapter) {
|
2348
|
+
this.adapter = adapter;
|
2349
|
+
}
|
2350
|
+
visit(location, options = {}) {
|
2351
|
+
this.navigator.proposeVisit(expandURL(location), options);
|
2352
|
+
}
|
2353
|
+
connectStreamSource(source) {
|
2354
|
+
this.streamObserver.connectStreamSource(source);
|
2355
|
+
}
|
2356
|
+
disconnectStreamSource(source) {
|
2357
|
+
this.streamObserver.disconnectStreamSource(source);
|
2358
|
+
}
|
2359
|
+
renderStreamMessage(message) {
|
2360
|
+
document.documentElement.appendChild(StreamMessage.wrap(message).fragment);
|
2361
|
+
}
|
2362
|
+
clearCache() {
|
2363
|
+
this.view.clearSnapshotCache();
|
2364
|
+
}
|
2365
|
+
setProgressBarDelay(delay) {
|
2366
|
+
this.progressBarDelay = delay;
|
2367
|
+
}
|
2368
|
+
get location() {
|
2369
|
+
return this.history.location;
|
2370
|
+
}
|
2371
|
+
get restorationIdentifier() {
|
2372
|
+
return this.history.restorationIdentifier;
|
2373
|
+
}
|
2374
|
+
historyPoppedToLocationWithRestorationIdentifier(location) {
|
2375
|
+
if (this.enabled) {
|
2376
|
+
this.navigator.proposeVisit(location, {
|
2377
|
+
action: "restore",
|
2378
|
+
historyChanged: true
|
2379
|
+
});
|
2380
|
+
} else {
|
2381
|
+
this.adapter.pageInvalidated();
|
2382
|
+
}
|
2383
|
+
}
|
2384
|
+
scrollPositionChanged(position) {
|
2385
|
+
this.history.updateRestorationData({
|
2386
|
+
scrollPosition: position
|
2387
|
+
});
|
2388
|
+
}
|
2389
|
+
willFollowLinkToLocation(link, location) {
|
2390
|
+
return elementIsNavigable(link) && this.locationIsVisitable(location) && this.applicationAllowsFollowingLinkToLocation(link, location);
|
2391
|
+
}
|
2392
|
+
followedLinkToLocation(link, location) {
|
2393
|
+
const action = this.getActionForLink(link);
|
2394
|
+
this.visit(location.href, {
|
2395
|
+
action: action
|
2396
|
+
});
|
2397
|
+
}
|
2398
|
+
allowsVisitingLocation(location) {
|
2399
|
+
return this.applicationAllowsVisitingLocation(location);
|
2400
|
+
}
|
2401
|
+
visitProposedToLocation(location, options) {
|
2402
|
+
extendURLWithDeprecatedProperties(location);
|
2403
|
+
this.adapter.visitProposedToLocation(location, options);
|
2404
|
+
}
|
2405
|
+
visitStarted(visit) {
|
2406
|
+
extendURLWithDeprecatedProperties(visit.location);
|
2407
|
+
this.notifyApplicationAfterVisitingLocation(visit.location);
|
2408
|
+
}
|
2409
|
+
visitCompleted(visit) {
|
2410
|
+
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
2411
|
+
}
|
2412
|
+
willSubmitForm(form, submitter) {
|
2413
|
+
return elementIsNavigable(form) && elementIsNavigable(submitter);
|
2414
|
+
}
|
2415
|
+
formSubmitted(form, submitter) {
|
2416
|
+
this.navigator.submitForm(form, submitter);
|
2417
|
+
}
|
2418
|
+
pageBecameInteractive() {
|
2419
|
+
this.view.lastRenderedLocation = this.location;
|
2420
|
+
this.notifyApplicationAfterPageLoad();
|
2421
|
+
}
|
2422
|
+
pageLoaded() {
|
2423
|
+
this.history.assumeControlOfScrollRestoration();
|
2424
|
+
}
|
2425
|
+
pageWillUnload() {
|
2426
|
+
this.history.relinquishControlOfScrollRestoration();
|
2427
|
+
}
|
2428
|
+
receivedMessageFromStream(message) {
|
2429
|
+
this.renderStreamMessage(message);
|
2430
|
+
}
|
2431
|
+
viewWillCacheSnapshot() {
|
2432
|
+
this.notifyApplicationBeforeCachingSnapshot();
|
2433
|
+
}
|
2434
|
+
viewWillRenderSnapshot({element: element}, isPreview) {
|
2435
|
+
this.notifyApplicationBeforeRender(element);
|
2436
|
+
}
|
2437
|
+
viewRenderedSnapshot(snapshot, isPreview) {
|
2438
|
+
this.view.lastRenderedLocation = this.history.location;
|
2439
|
+
this.notifyApplicationAfterRender();
|
2440
|
+
}
|
2441
|
+
viewInvalidated() {
|
2442
|
+
this.adapter.pageInvalidated();
|
2443
|
+
}
|
2444
|
+
applicationAllowsFollowingLinkToLocation(link, location) {
|
2445
|
+
const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
|
2446
|
+
return !event.defaultPrevented;
|
2447
|
+
}
|
2448
|
+
applicationAllowsVisitingLocation(location) {
|
2449
|
+
const event = this.notifyApplicationBeforeVisitingLocation(location);
|
2450
|
+
return !event.defaultPrevented;
|
2451
|
+
}
|
2452
|
+
notifyApplicationAfterClickingLinkToLocation(link, location) {
|
2453
|
+
return dispatch("turbo:click", {
|
2454
|
+
target: link,
|
2455
|
+
detail: {
|
2456
|
+
url: location.href
|
2457
|
+
},
|
2458
|
+
cancelable: true
|
2459
|
+
});
|
2460
|
+
}
|
2461
|
+
notifyApplicationBeforeVisitingLocation(location) {
|
2462
|
+
return dispatch("turbo:before-visit", {
|
2463
|
+
detail: {
|
2464
|
+
url: location.href
|
2465
|
+
},
|
2466
|
+
cancelable: true
|
2467
|
+
});
|
2468
|
+
}
|
2469
|
+
notifyApplicationAfterVisitingLocation(location) {
|
2470
|
+
return dispatch("turbo:visit", {
|
2471
|
+
detail: {
|
2472
|
+
url: location.href
|
2473
|
+
}
|
2474
|
+
});
|
2475
|
+
}
|
2476
|
+
notifyApplicationBeforeCachingSnapshot() {
|
2477
|
+
return dispatch("turbo:before-cache");
|
2478
|
+
}
|
2479
|
+
notifyApplicationBeforeRender(newBody) {
|
2480
|
+
return dispatch("turbo:before-render", {
|
2481
|
+
detail: {
|
2482
|
+
newBody: newBody
|
2483
|
+
}
|
2484
|
+
});
|
2485
|
+
}
|
2486
|
+
notifyApplicationAfterRender() {
|
2487
|
+
return dispatch("turbo:render");
|
2488
|
+
}
|
2489
|
+
notifyApplicationAfterPageLoad(timing = {}) {
|
2490
|
+
return dispatch("turbo:load", {
|
2491
|
+
detail: {
|
2492
|
+
url: this.location.href,
|
2493
|
+
timing: timing
|
2494
|
+
}
|
2495
|
+
});
|
2496
|
+
}
|
2497
|
+
getActionForLink(link) {
|
2498
|
+
const action = link.getAttribute("data-turbo-action");
|
2499
|
+
return isAction(action) ? action : "advance";
|
2500
|
+
}
|
2501
|
+
locationIsVisitable(location) {
|
2502
|
+
return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
|
2530
2503
|
}
|
2531
|
-
|
2532
|
-
|
2533
|
-
delete this.snapshots[key];
|
2534
|
-
}
|
2504
|
+
get snapshot() {
|
2505
|
+
return this.view.snapshot;
|
2535
2506
|
}
|
2536
2507
|
}
|
2537
2508
|
|
2538
|
-
|
2539
|
-
|
2540
|
-
|
2541
|
-
|
2542
|
-
|
2509
|
+
function elementIsNavigable(element) {
|
2510
|
+
const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
|
2511
|
+
if (container) {
|
2512
|
+
return container.getAttribute("data-turbo") != "false";
|
2513
|
+
} else {
|
2514
|
+
return true;
|
2543
2515
|
}
|
2544
|
-
|
2545
|
-
|
2546
|
-
|
2516
|
+
}
|
2517
|
+
|
2518
|
+
function extendURLWithDeprecatedProperties(url) {
|
2519
|
+
Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
|
2520
|
+
}
|
2521
|
+
|
2522
|
+
const deprecatedLocationPropertyDescriptors = {
|
2523
|
+
absoluteURL: {
|
2524
|
+
get() {
|
2525
|
+
return this.toString();
|
2526
|
+
}
|
2547
2527
|
}
|
2548
|
-
|
2549
|
-
|
2550
|
-
|
2528
|
+
};
|
2529
|
+
|
2530
|
+
class FrameController {
|
2531
|
+
constructor(element) {
|
2532
|
+
this.resolveVisitPromise = () => {};
|
2533
|
+
this.connected = false;
|
2534
|
+
this.hasBeenLoaded = false;
|
2535
|
+
this.settingSourceURL = false;
|
2536
|
+
this.element = element;
|
2537
|
+
this.view = new FrameView(this, this.element);
|
2538
|
+
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
2539
|
+
this.linkInterceptor = new LinkInterceptor(this, this.element);
|
2540
|
+
this.formInterceptor = new FormInterceptor(this, this.element);
|
2551
2541
|
}
|
2552
|
-
|
2553
|
-
this.
|
2542
|
+
connect() {
|
2543
|
+
if (!this.connected) {
|
2544
|
+
this.connected = true;
|
2545
|
+
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
2546
|
+
this.appearanceObserver.start();
|
2547
|
+
}
|
2548
|
+
this.linkInterceptor.start();
|
2549
|
+
this.formInterceptor.start();
|
2550
|
+
this.sourceURLChanged();
|
2551
|
+
}
|
2554
2552
|
}
|
2555
|
-
|
2556
|
-
if (this.
|
2557
|
-
this.
|
2558
|
-
|
2559
|
-
|
2560
|
-
this.
|
2553
|
+
disconnect() {
|
2554
|
+
if (this.connected) {
|
2555
|
+
this.connected = false;
|
2556
|
+
this.appearanceObserver.stop();
|
2557
|
+
this.linkInterceptor.stop();
|
2558
|
+
this.formInterceptor.stop();
|
2561
2559
|
}
|
2562
2560
|
}
|
2563
|
-
|
2564
|
-
|
2561
|
+
disabledChanged() {
|
2562
|
+
if (this.loadingStyle == FrameLoadingStyle.eager) {
|
2563
|
+
this.loadSourceURL();
|
2564
|
+
}
|
2565
2565
|
}
|
2566
|
-
|
2567
|
-
|
2566
|
+
sourceURLChanged() {
|
2567
|
+
if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
|
2568
|
+
this.loadSourceURL();
|
2569
|
+
}
|
2568
2570
|
}
|
2569
|
-
|
2570
|
-
|
2571
|
+
loadingStyleChanged() {
|
2572
|
+
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
2573
|
+
this.appearanceObserver.start();
|
2574
|
+
} else {
|
2575
|
+
this.appearanceObserver.stop();
|
2576
|
+
this.loadSourceURL();
|
2577
|
+
}
|
2571
2578
|
}
|
2572
|
-
|
2573
|
-
|
2574
|
-
|
2575
|
-
|
2576
|
-
|
2577
|
-
|
2578
|
-
|
2579
|
-
|
2580
|
-
|
2581
|
-
|
2582
|
-
|
2583
|
-
|
2584
|
-
|
2585
|
-
|
2586
|
-
|
2587
|
-
|
2588
|
-
this.started = false;
|
2579
|
+
async loadSourceURL() {
|
2580
|
+
if (!this.settingSourceURL && this.enabled && this.isActive && this.sourceURL != this.currentURL) {
|
2581
|
+
const previousURL = this.currentURL;
|
2582
|
+
this.currentURL = this.sourceURL;
|
2583
|
+
if (this.sourceURL) {
|
2584
|
+
try {
|
2585
|
+
this.element.loaded = this.visit(this.sourceURL);
|
2586
|
+
this.appearanceObserver.stop();
|
2587
|
+
await this.element.loaded;
|
2588
|
+
this.hasBeenLoaded = true;
|
2589
|
+
} catch (error) {
|
2590
|
+
this.currentURL = previousURL;
|
2591
|
+
throw error;
|
2592
|
+
}
|
2593
|
+
}
|
2594
|
+
}
|
2589
2595
|
}
|
2590
|
-
|
2591
|
-
if (
|
2592
|
-
this.
|
2593
|
-
|
2594
|
-
|
2595
|
-
|
2596
|
-
|
2597
|
-
|
2598
|
-
|
2599
|
-
|
2600
|
-
|
2596
|
+
async loadResponse(fetchResponse) {
|
2597
|
+
if (fetchResponse.redirected) {
|
2598
|
+
this.sourceURL = fetchResponse.response.url;
|
2599
|
+
}
|
2600
|
+
try {
|
2601
|
+
const html = await fetchResponse.responseHTML;
|
2602
|
+
if (html) {
|
2603
|
+
const {body: body} = parseHTMLDocument(html);
|
2604
|
+
const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
|
2605
|
+
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
|
2606
|
+
await this.view.render(renderer);
|
2607
|
+
}
|
2608
|
+
} catch (error) {
|
2609
|
+
console.error(error);
|
2610
|
+
this.view.invalidate();
|
2601
2611
|
}
|
2602
2612
|
}
|
2603
|
-
|
2604
|
-
this.
|
2613
|
+
elementAppearedInViewport(element) {
|
2614
|
+
this.loadSourceURL();
|
2605
2615
|
}
|
2606
|
-
|
2607
|
-
|
2608
|
-
|
2609
|
-
|
2610
|
-
|
2611
|
-
|
2612
|
-
|
2613
|
-
|
2614
|
-
|
2615
|
-
|
2616
|
+
shouldInterceptLinkClick(element, url) {
|
2617
|
+
return this.shouldInterceptNavigation(element);
|
2618
|
+
}
|
2619
|
+
linkClickIntercepted(element, url) {
|
2620
|
+
this.navigateFrame(element, url);
|
2621
|
+
}
|
2622
|
+
shouldInterceptFormSubmission(element, submitter) {
|
2623
|
+
return this.shouldInterceptNavigation(element, submitter);
|
2624
|
+
}
|
2625
|
+
formSubmissionIntercepted(element, submitter) {
|
2626
|
+
if (this.formSubmission) {
|
2627
|
+
this.formSubmission.stop();
|
2628
|
+
}
|
2629
|
+
this.formSubmission = new FormSubmission(this, element, submitter);
|
2630
|
+
if (this.formSubmission.fetchRequest.isIdempotent) {
|
2631
|
+
this.navigateFrame(element, this.formSubmission.fetchRequest.url.href);
|
2632
|
+
} else {
|
2633
|
+
const {fetchRequest: fetchRequest} = this.formSubmission;
|
2634
|
+
this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
|
2635
|
+
this.formSubmission.start();
|
2616
2636
|
}
|
2617
2637
|
}
|
2618
|
-
|
2619
|
-
|
2638
|
+
prepareHeadersForRequest(headers, request) {
|
2639
|
+
headers["Turbo-Frame"] = this.id;
|
2620
2640
|
}
|
2621
|
-
|
2622
|
-
this.
|
2641
|
+
requestStarted(request) {
|
2642
|
+
this.element.setAttribute("busy", "");
|
2623
2643
|
}
|
2624
|
-
|
2625
|
-
this.
|
2644
|
+
requestPreventedHandlingResponse(request, response) {
|
2645
|
+
this.resolveVisitPromise();
|
2626
2646
|
}
|
2627
|
-
|
2628
|
-
this.
|
2647
|
+
async requestSucceededWithResponse(request, response) {
|
2648
|
+
await this.loadResponse(response);
|
2649
|
+
this.resolveVisitPromise();
|
2629
2650
|
}
|
2630
|
-
|
2631
|
-
|
2651
|
+
requestFailedWithResponse(request, response) {
|
2652
|
+
console.error(response);
|
2653
|
+
this.resolveVisitPromise();
|
2632
2654
|
}
|
2633
|
-
|
2634
|
-
|
2655
|
+
requestErrored(request, error) {
|
2656
|
+
console.error(error);
|
2657
|
+
this.resolveVisitPromise();
|
2635
2658
|
}
|
2636
|
-
|
2637
|
-
this.
|
2659
|
+
requestFinished(request) {
|
2660
|
+
this.element.removeAttribute("busy");
|
2638
2661
|
}
|
2639
|
-
|
2640
|
-
|
2662
|
+
formSubmissionStarted(formSubmission) {
|
2663
|
+
const frame = this.findFrameElement(formSubmission.formElement);
|
2664
|
+
frame.setAttribute("busy", "");
|
2641
2665
|
}
|
2642
|
-
|
2643
|
-
|
2666
|
+
formSubmissionSucceededWithResponse(formSubmission, response) {
|
2667
|
+
const frame = this.findFrameElement(formSubmission.formElement);
|
2668
|
+
frame.delegate.loadResponse(response);
|
2644
2669
|
}
|
2645
|
-
|
2646
|
-
|
2647
|
-
this.navigator.proposeVisit(location, {
|
2648
|
-
action: "restore",
|
2649
|
-
historyChanged: true
|
2650
|
-
});
|
2651
|
-
} else {
|
2652
|
-
this.adapter.pageInvalidated();
|
2653
|
-
}
|
2670
|
+
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
2671
|
+
this.element.delegate.loadResponse(fetchResponse);
|
2654
2672
|
}
|
2655
|
-
|
2656
|
-
|
2657
|
-
scrollPosition: position
|
2658
|
-
});
|
2673
|
+
formSubmissionErrored(formSubmission, error) {
|
2674
|
+
console.error(error);
|
2659
2675
|
}
|
2660
|
-
|
2661
|
-
|
2676
|
+
formSubmissionFinished(formSubmission) {
|
2677
|
+
const frame = this.findFrameElement(formSubmission.formElement);
|
2678
|
+
frame.removeAttribute("busy");
|
2662
2679
|
}
|
2663
|
-
|
2664
|
-
|
2665
|
-
|
2666
|
-
|
2667
|
-
|
2680
|
+
viewWillRenderSnapshot(snapshot, isPreview) {}
|
2681
|
+
viewRenderedSnapshot(snapshot, isPreview) {}
|
2682
|
+
viewInvalidated() {}
|
2683
|
+
async visit(url) {
|
2684
|
+
const request = new FetchRequest(this, FetchMethod.get, expandURL(url));
|
2685
|
+
return new Promise((resolve => {
|
2686
|
+
this.resolveVisitPromise = () => {
|
2687
|
+
this.resolveVisitPromise = () => {};
|
2688
|
+
resolve();
|
2689
|
+
};
|
2690
|
+
request.perform();
|
2691
|
+
}));
|
2668
2692
|
}
|
2669
|
-
|
2670
|
-
|
2693
|
+
navigateFrame(element, url) {
|
2694
|
+
const frame = this.findFrameElement(element);
|
2695
|
+
frame.src = url;
|
2671
2696
|
}
|
2672
|
-
|
2673
|
-
|
2674
|
-
this.
|
2697
|
+
findFrameElement(element) {
|
2698
|
+
var _a;
|
2699
|
+
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
2700
|
+
return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
|
2675
2701
|
}
|
2676
|
-
|
2677
|
-
|
2678
|
-
|
2702
|
+
async extractForeignFrameElement(container) {
|
2703
|
+
let element;
|
2704
|
+
const id = CSS.escape(this.id);
|
2705
|
+
try {
|
2706
|
+
if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
|
2707
|
+
return element;
|
2708
|
+
}
|
2709
|
+
if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
|
2710
|
+
await element.loaded;
|
2711
|
+
return await this.extractForeignFrameElement(element);
|
2712
|
+
}
|
2713
|
+
console.error(`Response has no matching <turbo-frame id="${id}"> element`);
|
2714
|
+
} catch (error) {
|
2715
|
+
console.error(error);
|
2716
|
+
}
|
2717
|
+
return new FrameElement;
|
2718
|
+
}
|
2719
|
+
shouldInterceptNavigation(element, submitter) {
|
2720
|
+
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
2721
|
+
if (!this.enabled || id == "_top") {
|
2722
|
+
return false;
|
2723
|
+
}
|
2724
|
+
if (id) {
|
2725
|
+
const frameElement = getFrameElementById(id);
|
2726
|
+
if (frameElement) {
|
2727
|
+
return !frameElement.disabled;
|
2728
|
+
}
|
2729
|
+
}
|
2730
|
+
if (!elementIsNavigable(element)) {
|
2731
|
+
return false;
|
2732
|
+
}
|
2733
|
+
if (submitter && !elementIsNavigable(submitter)) {
|
2734
|
+
return false;
|
2735
|
+
}
|
2736
|
+
return true;
|
2679
2737
|
}
|
2680
|
-
|
2681
|
-
this.
|
2738
|
+
get id() {
|
2739
|
+
return this.element.id;
|
2682
2740
|
}
|
2683
|
-
|
2684
|
-
return this.
|
2741
|
+
get enabled() {
|
2742
|
+
return !this.element.disabled;
|
2685
2743
|
}
|
2686
|
-
|
2687
|
-
this.
|
2744
|
+
get sourceURL() {
|
2745
|
+
if (this.element.src) {
|
2746
|
+
return this.element.src;
|
2747
|
+
}
|
2688
2748
|
}
|
2689
|
-
|
2690
|
-
this.
|
2691
|
-
this.
|
2749
|
+
set sourceURL(sourceURL) {
|
2750
|
+
this.settingSourceURL = true;
|
2751
|
+
this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
|
2752
|
+
this.settingSourceURL = false;
|
2692
2753
|
}
|
2693
|
-
|
2694
|
-
this.
|
2754
|
+
get loadingStyle() {
|
2755
|
+
return this.element.loading;
|
2695
2756
|
}
|
2696
|
-
|
2697
|
-
this.
|
2757
|
+
get isLoading() {
|
2758
|
+
return this.formSubmission !== undefined || this.resolveVisitPromise !== undefined;
|
2698
2759
|
}
|
2699
|
-
|
2700
|
-
this.
|
2760
|
+
get isActive() {
|
2761
|
+
return this.element.isActive && this.connected;
|
2701
2762
|
}
|
2702
|
-
|
2703
|
-
|
2763
|
+
}
|
2764
|
+
|
2765
|
+
function getFrameElementById(id) {
|
2766
|
+
if (id != null) {
|
2767
|
+
const element = document.getElementById(id);
|
2768
|
+
if (element instanceof FrameElement) {
|
2769
|
+
return element;
|
2770
|
+
}
|
2704
2771
|
}
|
2705
|
-
|
2706
|
-
|
2772
|
+
}
|
2773
|
+
|
2774
|
+
function activateElement(element, currentURL) {
|
2775
|
+
if (element) {
|
2776
|
+
const src = element.getAttribute("src");
|
2777
|
+
if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {
|
2778
|
+
throw new Error(`Matching <turbo-frame id="${element.id}"> element has a source URL which references itself`);
|
2779
|
+
}
|
2780
|
+
if (element.ownerDocument !== document) {
|
2781
|
+
element = document.importNode(element, true);
|
2782
|
+
}
|
2783
|
+
if (element instanceof FrameElement) {
|
2784
|
+
element.connectedCallback();
|
2785
|
+
return element;
|
2786
|
+
}
|
2707
2787
|
}
|
2708
|
-
|
2709
|
-
|
2710
|
-
|
2788
|
+
}
|
2789
|
+
|
2790
|
+
const StreamActions = {
|
2791
|
+
after() {
|
2792
|
+
var _a, _b;
|
2793
|
+
(_b = (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.insertBefore(this.templateContent, this.targetElement.nextSibling);
|
2794
|
+
},
|
2795
|
+
append() {
|
2796
|
+
var _a;
|
2797
|
+
this.removeDuplicateTargetChildren();
|
2798
|
+
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.append(this.templateContent);
|
2799
|
+
},
|
2800
|
+
before() {
|
2801
|
+
var _a, _b;
|
2802
|
+
(_b = (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.insertBefore(this.templateContent, this.targetElement);
|
2803
|
+
},
|
2804
|
+
prepend() {
|
2805
|
+
var _a;
|
2806
|
+
this.removeDuplicateTargetChildren();
|
2807
|
+
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.prepend(this.templateContent);
|
2808
|
+
},
|
2809
|
+
remove() {
|
2810
|
+
var _a;
|
2811
|
+
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.remove();
|
2812
|
+
},
|
2813
|
+
replace() {
|
2814
|
+
var _a;
|
2815
|
+
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.replaceWith(this.templateContent);
|
2816
|
+
},
|
2817
|
+
update() {
|
2818
|
+
if (this.targetElement) {
|
2819
|
+
this.targetElement.innerHTML = "";
|
2820
|
+
this.targetElement.append(this.templateContent);
|
2821
|
+
}
|
2711
2822
|
}
|
2712
|
-
|
2713
|
-
|
2823
|
+
};
|
2824
|
+
|
2825
|
+
class StreamElement extends HTMLElement {
|
2826
|
+
async connectedCallback() {
|
2827
|
+
try {
|
2828
|
+
await this.render();
|
2829
|
+
} catch (error) {
|
2830
|
+
console.error(error);
|
2831
|
+
} finally {
|
2832
|
+
this.disconnect();
|
2833
|
+
}
|
2714
2834
|
}
|
2715
|
-
|
2716
|
-
|
2717
|
-
return
|
2835
|
+
async render() {
|
2836
|
+
var _a;
|
2837
|
+
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
|
2838
|
+
if (this.dispatchEvent(this.beforeRenderEvent)) {
|
2839
|
+
await nextAnimationFrame();
|
2840
|
+
this.performAction();
|
2841
|
+
}
|
2842
|
+
})();
|
2718
2843
|
}
|
2719
|
-
|
2720
|
-
|
2721
|
-
|
2844
|
+
disconnect() {
|
2845
|
+
try {
|
2846
|
+
this.remove();
|
2847
|
+
} catch (_a) {}
|
2722
2848
|
}
|
2723
|
-
|
2724
|
-
|
2725
|
-
|
2726
|
-
|
2727
|
-
url: location.href
|
2728
|
-
},
|
2729
|
-
cancelable: true
|
2730
|
-
});
|
2849
|
+
removeDuplicateTargetChildren() {
|
2850
|
+
this.duplicateChildren.forEach((({targetChild: targetChild}) => {
|
2851
|
+
targetChild.remove();
|
2852
|
+
}));
|
2731
2853
|
}
|
2732
|
-
|
2733
|
-
|
2734
|
-
|
2735
|
-
|
2736
|
-
|
2737
|
-
|
2738
|
-
|
2854
|
+
get duplicateChildren() {
|
2855
|
+
var _a;
|
2856
|
+
return [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].map((templateChild => {
|
2857
|
+
let targetChild = [ ...this.targetElement.children ].filter((c => c.id === templateChild.id))[0];
|
2858
|
+
return {
|
2859
|
+
targetChild: targetChild,
|
2860
|
+
templateChild: templateChild
|
2861
|
+
};
|
2862
|
+
})).filter((({targetChild: targetChild}) => targetChild));
|
2739
2863
|
}
|
2740
|
-
|
2741
|
-
|
2742
|
-
|
2743
|
-
|
2864
|
+
get performAction() {
|
2865
|
+
if (this.action) {
|
2866
|
+
const actionFunction = StreamActions[this.action];
|
2867
|
+
if (actionFunction) {
|
2868
|
+
return actionFunction;
|
2744
2869
|
}
|
2745
|
-
|
2870
|
+
this.raise("unknown action");
|
2871
|
+
}
|
2872
|
+
this.raise("action attribute is missing");
|
2746
2873
|
}
|
2747
|
-
|
2748
|
-
|
2874
|
+
get targetElement() {
|
2875
|
+
var _a;
|
2876
|
+
if (this.target) {
|
2877
|
+
return (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
|
2878
|
+
}
|
2879
|
+
this.raise("target attribute is missing");
|
2749
2880
|
}
|
2750
|
-
|
2751
|
-
return
|
2752
|
-
detail: {
|
2753
|
-
newBody: newBody
|
2754
|
-
}
|
2755
|
-
});
|
2881
|
+
get templateContent() {
|
2882
|
+
return this.templateElement.content;
|
2756
2883
|
}
|
2757
|
-
|
2758
|
-
|
2884
|
+
get templateElement() {
|
2885
|
+
if (this.firstElementChild instanceof HTMLTemplateElement) {
|
2886
|
+
return this.firstElementChild;
|
2887
|
+
}
|
2888
|
+
this.raise("first child element must be a <template> element");
|
2759
2889
|
}
|
2760
|
-
|
2761
|
-
return
|
2762
|
-
detail: {
|
2763
|
-
url: this.location.href,
|
2764
|
-
timing: timing
|
2765
|
-
}
|
2766
|
-
});
|
2890
|
+
get action() {
|
2891
|
+
return this.getAttribute("action");
|
2767
2892
|
}
|
2768
|
-
|
2769
|
-
|
2770
|
-
return isAction(action) ? action : "advance";
|
2893
|
+
get target() {
|
2894
|
+
return this.getAttribute("target");
|
2771
2895
|
}
|
2772
|
-
|
2773
|
-
|
2774
|
-
if (container) {
|
2775
|
-
return container.getAttribute("data-turbo") != "false";
|
2776
|
-
} else {
|
2777
|
-
return true;
|
2778
|
-
}
|
2896
|
+
raise(message) {
|
2897
|
+
throw new Error(`${this.description}: ${message}`);
|
2779
2898
|
}
|
2780
|
-
|
2781
|
-
|
2899
|
+
get description() {
|
2900
|
+
var _a, _b;
|
2901
|
+
return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
|
2782
2902
|
}
|
2783
|
-
get
|
2784
|
-
return
|
2903
|
+
get beforeRenderEvent() {
|
2904
|
+
return new CustomEvent("turbo:before-stream-render", {
|
2905
|
+
bubbles: true,
|
2906
|
+
cancelable: true
|
2907
|
+
});
|
2785
2908
|
}
|
2786
2909
|
}
|
2787
2910
|
|
2788
|
-
|
2789
|
-
Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
|
2790
|
-
}
|
2911
|
+
FrameElement.delegateConstructor = FrameController;
|
2791
2912
|
|
2792
|
-
|
2793
|
-
|
2794
|
-
|
2795
|
-
|
2913
|
+
customElements.define("turbo-frame", FrameElement);
|
2914
|
+
|
2915
|
+
customElements.define("turbo-stream", StreamElement);
|
2916
|
+
|
2917
|
+
(() => {
|
2918
|
+
let element = document.currentScript;
|
2919
|
+
if (!element) return;
|
2920
|
+
if (element.hasAttribute("data-turbo-suppress-warning")) return;
|
2921
|
+
while (element = element.parentElement) {
|
2922
|
+
if (element == document.body) {
|
2923
|
+
return console.warn(unindent`
|
2924
|
+
You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
|
2925
|
+
|
2926
|
+
Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
|
2927
|
+
|
2928
|
+
For more information, see: https://turbo.hotwire.dev/handbook/building#working-with-script-elements
|
2929
|
+
|
2930
|
+
——
|
2931
|
+
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
2932
|
+
`, element.outerHTML);
|
2796
2933
|
}
|
2797
2934
|
}
|
2798
|
-
};
|
2935
|
+
})();
|
2799
2936
|
|
2800
2937
|
const session = new Session;
|
2801
2938
|
|
@@ -2833,6 +2970,21 @@ function setProgressBarDelay(delay) {
|
|
2833
2970
|
session.setProgressBarDelay(delay);
|
2834
2971
|
}
|
2835
2972
|
|
2973
|
+
var Turbo = Object.freeze({
|
2974
|
+
__proto__: null,
|
2975
|
+
navigator: navigator,
|
2976
|
+
start: start,
|
2977
|
+
registerAdapter: registerAdapter,
|
2978
|
+
visit: visit,
|
2979
|
+
connectStreamSource: connectStreamSource,
|
2980
|
+
disconnectStreamSource: disconnectStreamSource,
|
2981
|
+
renderStreamMessage: renderStreamMessage,
|
2982
|
+
clearCache: clearCache,
|
2983
|
+
setProgressBarDelay: setProgressBarDelay
|
2984
|
+
});
|
2985
|
+
|
2986
|
+
window.Turbo = Turbo;
|
2987
|
+
|
2836
2988
|
start();
|
2837
2989
|
|
2838
2990
|
var turbo_es2017Esm = Object.freeze({
|
@@ -2851,17 +3003,20 @@ var turbo_es2017Esm = Object.freeze({
|
|
2851
3003
|
let consumer;
|
2852
3004
|
|
2853
3005
|
async function getConsumer() {
|
2854
|
-
|
2855
|
-
const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
|
2856
|
-
return index;
|
2857
|
-
}));
|
2858
|
-
return setConsumer(createConsumer());
|
3006
|
+
return consumer || setConsumer(createConsumer().then(setConsumer));
|
2859
3007
|
}
|
2860
3008
|
|
2861
3009
|
function setConsumer(newConsumer) {
|
2862
3010
|
return consumer = newConsumer;
|
2863
3011
|
}
|
2864
3012
|
|
3013
|
+
async function createConsumer() {
|
3014
|
+
const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
|
3015
|
+
return index;
|
3016
|
+
}));
|
3017
|
+
return createConsumer();
|
3018
|
+
}
|
3019
|
+
|
2865
3020
|
async function subscribeTo(channel, mixin) {
|
2866
3021
|
const {subscriptions: subscriptions} = await getConsumer();
|
2867
3022
|
return subscriptions.create(channel, mixin);
|
@@ -2871,6 +3026,7 @@ var cable = Object.freeze({
|
|
2871
3026
|
__proto__: null,
|
2872
3027
|
getConsumer: getConsumer,
|
2873
3028
|
setConsumer: setConsumer,
|
3029
|
+
createConsumer: createConsumer,
|
2874
3030
|
subscribeTo: subscribeTo
|
2875
3031
|
});
|
2876
3032
|
|
@@ -3334,7 +3490,7 @@ function createWebSocketURL(url) {
|
|
3334
3490
|
}
|
3335
3491
|
}
|
3336
3492
|
|
3337
|
-
function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
|
3493
|
+
function createConsumer$1(url = getConfig("url") || INTERNAL.default_mount_path) {
|
3338
3494
|
return new Consumer(url);
|
3339
3495
|
}
|
3340
3496
|
|
@@ -3356,7 +3512,7 @@ var index = Object.freeze({
|
|
3356
3512
|
adapters: adapters,
|
3357
3513
|
createWebSocketURL: createWebSocketURL,
|
3358
3514
|
logger: logger,
|
3359
|
-
createConsumer: createConsumer,
|
3515
|
+
createConsumer: createConsumer$1,
|
3360
3516
|
getConfig: getConfig
|
3361
3517
|
});
|
3362
3518
|
|