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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 025ba92afa27783a938ec698ff465685559d71698ab3023b091a54d14f0d6dd1
4
- data.tar.gz: ae377ccfdb57c9f9f2f862dc81e5863a5bc614002bbdfe39547cb1ede2aa797a
3
+ metadata.gz: 1abdc38a6d5a735bf6bbd2ff6fb8db49ea74d14e3452afa5c2bda93b23ffb073
4
+ data.tar.gz: 0bf468488fd8232fec62b975fb9f0ef5449325cf57b932858e03af876d0548e6
5
5
  SHA512:
6
- metadata.gz: a7f03c2b76b07d65f64b3170b9f9ed6c1a9c4429c9f7eee3abf8bffbdbed8a320806f239df725c5aad37c826d2171ec2abc0c7787d42f929e2741584fc4d84e3
7
- data.tar.gz: b2566bd6203b982d5b3cad1c852517a41b3a11ca8c8f097cac3cf7ff45f8d83db3eab3337807a80299cf61fc21f58990275f2e5dc92af75b03b167bf04f05ecc
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
- const anchor = document.createElement("a");
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
- getPermanentElementsPresentInSnapshot(snapshot) {
695
- return this.permanentElements.filter((({id: id}) => snapshot.getPermanentElementById(id)));
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
- const placeholders = relocatePermanentElements(this.currentSnapshot, this.newSnapshot);
875
- callback();
876
- replacePlaceholderElementsWithClonedPermanentElements(placeholders);
953
+ Bardo.preservingPermanentElements(this.permanentElementMap, callback);
877
954
  }
878
955
  focusFirstAutofocusableElement() {
879
- const element = this.newSnapshot.firstAutofocusableElement;
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 FrameController {
989
- constructor(element) {
990
- this.resolveVisitPromise = () => {};
991
- this.element = element;
992
- this.view = new FrameView(this, this.element);
993
- this.appearanceObserver = new AppearanceObserver(this, this.element);
994
- this.linkInterceptor = new LinkInterceptor(this, this.element);
995
- this.formInterceptor = new FormInterceptor(this, this.element);
996
- }
997
- connect() {
998
- if (this.loadingStyle == FrameLoadingStyle.lazy) {
999
- this.appearanceObserver.start();
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
- async loadSourceURL() {
1023
- if (this.isActive && this.sourceURL && this.sourceURL != this.loadingURL) {
1024
- try {
1025
- this.loadingURL = this.sourceURL;
1026
- this.element.loaded = this.visit(this.sourceURL);
1027
- this.appearanceObserver.stop();
1028
- await this.element.loaded;
1029
- } finally {
1030
- delete this.loadingURL;
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
- async loadResponse(response) {
1035
- try {
1036
- const html = await response.responseHTML;
1037
- if (html) {
1038
- const {body: body} = parseHTMLDocument(html);
1039
- const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
1040
- const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
1041
- await this.view.render(renderer);
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
- elementAppearedInViewport(element) {
1049
- this.loadSourceURL();
1081
+ setValue(value) {
1082
+ this.value = value;
1083
+ this.refresh();
1050
1084
  }
1051
- shouldInterceptLinkClick(element, url) {
1052
- return this.shouldInterceptNavigation(element);
1085
+ installStylesheetElement() {
1086
+ document.head.insertBefore(this.stylesheetElement, document.head.firstChild);
1053
1087
  }
1054
- linkClickIntercepted(element, url) {
1055
- this.navigateFrame(element, url);
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
- shouldInterceptFormSubmission(element) {
1058
- return this.shouldInterceptNavigation(element);
1094
+ fadeProgressElement(callback) {
1095
+ this.progressElement.style.opacity = "0";
1096
+ setTimeout(callback, ProgressBar.animationDuration * 1.5);
1059
1097
  }
1060
- formSubmissionIntercepted(element, submitter) {
1061
- if (this.formSubmission) {
1062
- this.formSubmission.stop();
1098
+ uninstallProgressElement() {
1099
+ if (this.progressElement.parentNode) {
1100
+ document.documentElement.removeChild(this.progressElement);
1063
1101
  }
1064
- this.formSubmission = new FormSubmission(this, element, submitter);
1065
- if (this.formSubmission.fetchRequest.isIdempotent) {
1066
- this.navigateFrame(element, this.formSubmission.fetchRequest.url.href);
1067
- } else {
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
- prepareHeadersForRequest(headers, request) {
1072
- headers["Turbo-Frame"] = this.id;
1108
+ stopTrickling() {
1109
+ window.clearInterval(this.trickleInterval);
1110
+ delete this.trickleInterval;
1073
1111
  }
1074
- requestStarted(request) {
1075
- this.element.setAttribute("busy", "");
1112
+ refresh() {
1113
+ requestAnimationFrame((() => {
1114
+ this.progressElement.style.width = `${10 + this.value * 90}%`;
1115
+ }));
1076
1116
  }
1077
- requestPreventedHandlingResponse(request, response) {
1078
- this.resolveVisitPromise();
1117
+ createStylesheetElement() {
1118
+ const element = document.createElement("style");
1119
+ element.type = "text/css";
1120
+ element.textContent = ProgressBar.defaultCSS;
1121
+ return element;
1079
1122
  }
1080
- async requestSucceededWithResponse(request, response) {
1081
- await this.loadResponse(response);
1082
- this.resolveVisitPromise();
1123
+ createProgressElement() {
1124
+ const element = document.createElement("div");
1125
+ element.className = "turbo-progress-bar";
1126
+ return element;
1083
1127
  }
1084
- requestFailedWithResponse(request, response) {
1085
- console.error(response);
1086
- this.resolveVisitPromise();
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
- requestErrored(request, error) {
1089
- console.error(error);
1090
- this.resolveVisitPromise();
1149
+ get trackedElementSignature() {
1150
+ return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
1091
1151
  }
1092
- requestFinished(request) {
1093
- this.element.removeAttribute("busy");
1152
+ getScriptElementsNotInSnapshot(snapshot) {
1153
+ return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
1094
1154
  }
1095
- formSubmissionStarted(formSubmission) {}
1096
- formSubmissionSucceededWithResponse(formSubmission, response) {
1097
- const frame = this.findFrameElement(formSubmission.formElement);
1098
- frame.delegate.loadResponse(response);
1155
+ getStylesheetElementsNotInSnapshot(snapshot) {
1156
+ return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
1099
1157
  }
1100
- formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
1101
- this.element.delegate.loadResponse(fetchResponse);
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) != null) {
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.fetchRequest.isIdempotent) {
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
- replaceElementWithElement(inertScriptElement, activatedScriptElement);
2196
+ inertScriptElement.replaceWith(activatedScriptElement);
2468
2197
  }
2469
2198
  }
2470
2199
  assignNewBody() {
2471
2200
  if (document.body && this.newElement instanceof HTMLBodyElement) {
2472
- replaceElementWithElement(document.body, this.newElement);
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
- trim() {
2532
- for (const key of this.keys.splice(this.size)) {
2533
- delete this.snapshots[key];
2534
- }
2504
+ get snapshot() {
2505
+ return this.view.snapshot;
2535
2506
  }
2536
2507
  }
2537
2508
 
2538
- class PageView extends View {
2539
- constructor() {
2540
- super(...arguments);
2541
- this.snapshotCache = new SnapshotCache(10);
2542
- this.lastRenderedLocation = new URL(location.href);
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
- renderPage(snapshot, isPreview = false) {
2545
- const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
2546
- return this.render(renderer);
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
- renderError(snapshot) {
2549
- const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2550
- this.render(renderer);
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
- clearSnapshotCache() {
2553
- this.snapshotCache.clear();
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
- async cacheSnapshot() {
2556
- if (this.shouldCacheSnapshot) {
2557
- this.delegate.viewWillCacheSnapshot();
2558
- const {snapshot: snapshot, lastRenderedLocation: location} = this;
2559
- await nextEventLoopTick();
2560
- this.snapshotCache.put(location, snapshot.clone());
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
- getCachedSnapshotForLocation(location) {
2564
- return this.snapshotCache.get(location);
2561
+ disabledChanged() {
2562
+ if (this.loadingStyle == FrameLoadingStyle.eager) {
2563
+ this.loadSourceURL();
2564
+ }
2565
2565
  }
2566
- get snapshot() {
2567
- return PageSnapshot.fromElement(this.element);
2566
+ sourceURLChanged() {
2567
+ if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
2568
+ this.loadSourceURL();
2569
+ }
2568
2570
  }
2569
- get shouldCacheSnapshot() {
2570
- return this.snapshot.isCacheable;
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
- class Session {
2575
- constructor() {
2576
- this.navigator = new Navigator(this);
2577
- this.history = new History(this);
2578
- this.view = new PageView(this, document.documentElement);
2579
- this.adapter = new BrowserAdapter(this);
2580
- this.pageObserver = new PageObserver(this);
2581
- this.linkClickObserver = new LinkClickObserver(this);
2582
- this.formSubmitObserver = new FormSubmitObserver(this);
2583
- this.scrollObserver = new ScrollObserver(this);
2584
- this.streamObserver = new StreamObserver(this);
2585
- this.frameRedirector = new FrameRedirector(document.documentElement);
2586
- this.enabled = true;
2587
- this.progressBarDelay = 500;
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
- start() {
2591
- if (!this.started) {
2592
- this.pageObserver.start();
2593
- this.linkClickObserver.start();
2594
- this.formSubmitObserver.start();
2595
- this.scrollObserver.start();
2596
- this.streamObserver.start();
2597
- this.frameRedirector.start();
2598
- this.history.start();
2599
- this.started = true;
2600
- this.enabled = true;
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
- disable() {
2604
- this.enabled = false;
2613
+ elementAppearedInViewport(element) {
2614
+ this.loadSourceURL();
2605
2615
  }
2606
- stop() {
2607
- if (this.started) {
2608
- this.pageObserver.stop();
2609
- this.linkClickObserver.stop();
2610
- this.formSubmitObserver.stop();
2611
- this.scrollObserver.stop();
2612
- this.streamObserver.stop();
2613
- this.frameRedirector.stop();
2614
- this.history.stop();
2615
- this.started = false;
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
- registerAdapter(adapter) {
2619
- this.adapter = adapter;
2638
+ prepareHeadersForRequest(headers, request) {
2639
+ headers["Turbo-Frame"] = this.id;
2620
2640
  }
2621
- visit(location, options = {}) {
2622
- this.navigator.proposeVisit(expandURL(location), options);
2641
+ requestStarted(request) {
2642
+ this.element.setAttribute("busy", "");
2623
2643
  }
2624
- connectStreamSource(source) {
2625
- this.streamObserver.connectStreamSource(source);
2644
+ requestPreventedHandlingResponse(request, response) {
2645
+ this.resolveVisitPromise();
2626
2646
  }
2627
- disconnectStreamSource(source) {
2628
- this.streamObserver.disconnectStreamSource(source);
2647
+ async requestSucceededWithResponse(request, response) {
2648
+ await this.loadResponse(response);
2649
+ this.resolveVisitPromise();
2629
2650
  }
2630
- renderStreamMessage(message) {
2631
- document.documentElement.appendChild(StreamMessage.wrap(message).fragment);
2651
+ requestFailedWithResponse(request, response) {
2652
+ console.error(response);
2653
+ this.resolveVisitPromise();
2632
2654
  }
2633
- clearCache() {
2634
- this.view.clearSnapshotCache();
2655
+ requestErrored(request, error) {
2656
+ console.error(error);
2657
+ this.resolveVisitPromise();
2635
2658
  }
2636
- setProgressBarDelay(delay) {
2637
- this.progressBarDelay = delay;
2659
+ requestFinished(request) {
2660
+ this.element.removeAttribute("busy");
2638
2661
  }
2639
- get location() {
2640
- return this.history.location;
2662
+ formSubmissionStarted(formSubmission) {
2663
+ const frame = this.findFrameElement(formSubmission.formElement);
2664
+ frame.setAttribute("busy", "");
2641
2665
  }
2642
- get restorationIdentifier() {
2643
- return this.history.restorationIdentifier;
2666
+ formSubmissionSucceededWithResponse(formSubmission, response) {
2667
+ const frame = this.findFrameElement(formSubmission.formElement);
2668
+ frame.delegate.loadResponse(response);
2644
2669
  }
2645
- historyPoppedToLocationWithRestorationIdentifier(location) {
2646
- if (this.enabled) {
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
- scrollPositionChanged(position) {
2656
- this.history.updateRestorationData({
2657
- scrollPosition: position
2658
- });
2673
+ formSubmissionErrored(formSubmission, error) {
2674
+ console.error(error);
2659
2675
  }
2660
- willFollowLinkToLocation(link, location) {
2661
- return this.elementIsNavigable(link) && this.locationIsVisitable(location) && this.applicationAllowsFollowingLinkToLocation(link, location);
2676
+ formSubmissionFinished(formSubmission) {
2677
+ const frame = this.findFrameElement(formSubmission.formElement);
2678
+ frame.removeAttribute("busy");
2662
2679
  }
2663
- followedLinkToLocation(link, location) {
2664
- const action = this.getActionForLink(link);
2665
- this.visit(location.href, {
2666
- action: action
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
- allowsVisitingLocation(location) {
2670
- return this.applicationAllowsVisitingLocation(location);
2693
+ navigateFrame(element, url) {
2694
+ const frame = this.findFrameElement(element);
2695
+ frame.src = url;
2671
2696
  }
2672
- visitProposedToLocation(location, options) {
2673
- extendURLWithDeprecatedProperties(location);
2674
- this.adapter.visitProposedToLocation(location, options);
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
- visitStarted(visit) {
2677
- extendURLWithDeprecatedProperties(visit.location);
2678
- this.notifyApplicationAfterVisitingLocation(visit.location);
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
- visitCompleted(visit) {
2681
- this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2738
+ get id() {
2739
+ return this.element.id;
2682
2740
  }
2683
- willSubmitForm(form, submitter) {
2684
- return this.elementIsNavigable(form) && this.elementIsNavigable(submitter);
2741
+ get enabled() {
2742
+ return !this.element.disabled;
2685
2743
  }
2686
- formSubmitted(form, submitter) {
2687
- this.navigator.submitForm(form, submitter);
2744
+ get sourceURL() {
2745
+ if (this.element.src) {
2746
+ return this.element.src;
2747
+ }
2688
2748
  }
2689
- pageBecameInteractive() {
2690
- this.view.lastRenderedLocation = this.location;
2691
- this.notifyApplicationAfterPageLoad();
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
- pageLoaded() {
2694
- this.history.assumeControlOfScrollRestoration();
2754
+ get loadingStyle() {
2755
+ return this.element.loading;
2695
2756
  }
2696
- pageWillUnload() {
2697
- this.history.relinquishControlOfScrollRestoration();
2757
+ get isLoading() {
2758
+ return this.formSubmission !== undefined || this.resolveVisitPromise !== undefined;
2698
2759
  }
2699
- receivedMessageFromStream(message) {
2700
- this.renderStreamMessage(message);
2760
+ get isActive() {
2761
+ return this.element.isActive && this.connected;
2701
2762
  }
2702
- viewWillCacheSnapshot() {
2703
- this.notifyApplicationBeforeCachingSnapshot();
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
- viewWillRenderSnapshot({element: element}, isPreview) {
2706
- this.notifyApplicationBeforeRender(element);
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
- viewRenderedSnapshot(snapshot, isPreview) {
2709
- this.view.lastRenderedLocation = this.history.location;
2710
- this.notifyApplicationAfterRender();
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
- viewInvalidated() {
2713
- this.adapter.pageInvalidated();
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
- applicationAllowsFollowingLinkToLocation(link, location) {
2716
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
2717
- return !event.defaultPrevented;
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
- applicationAllowsVisitingLocation(location) {
2720
- const event = this.notifyApplicationBeforeVisitingLocation(location);
2721
- return !event.defaultPrevented;
2844
+ disconnect() {
2845
+ try {
2846
+ this.remove();
2847
+ } catch (_a) {}
2722
2848
  }
2723
- notifyApplicationAfterClickingLinkToLocation(link, location) {
2724
- return dispatch("turbo:click", {
2725
- target: link,
2726
- detail: {
2727
- url: location.href
2728
- },
2729
- cancelable: true
2730
- });
2849
+ removeDuplicateTargetChildren() {
2850
+ this.duplicateChildren.forEach((({targetChild: targetChild}) => {
2851
+ targetChild.remove();
2852
+ }));
2731
2853
  }
2732
- notifyApplicationBeforeVisitingLocation(location) {
2733
- return dispatch("turbo:before-visit", {
2734
- detail: {
2735
- url: location.href
2736
- },
2737
- cancelable: true
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
- notifyApplicationAfterVisitingLocation(location) {
2741
- return dispatch("turbo:visit", {
2742
- detail: {
2743
- url: location.href
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
- notifyApplicationBeforeCachingSnapshot() {
2748
- return dispatch("turbo:before-cache");
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
- notifyApplicationBeforeRender(newBody) {
2751
- return dispatch("turbo:before-render", {
2752
- detail: {
2753
- newBody: newBody
2754
- }
2755
- });
2881
+ get templateContent() {
2882
+ return this.templateElement.content;
2756
2883
  }
2757
- notifyApplicationAfterRender() {
2758
- return dispatch("turbo:render");
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
- notifyApplicationAfterPageLoad(timing = {}) {
2761
- return dispatch("turbo:load", {
2762
- detail: {
2763
- url: this.location.href,
2764
- timing: timing
2765
- }
2766
- });
2890
+ get action() {
2891
+ return this.getAttribute("action");
2767
2892
  }
2768
- getActionForLink(link) {
2769
- const action = link.getAttribute("data-turbo-action");
2770
- return isAction(action) ? action : "advance";
2893
+ get target() {
2894
+ return this.getAttribute("target");
2771
2895
  }
2772
- elementIsNavigable(element) {
2773
- const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
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
- locationIsVisitable(location) {
2781
- return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
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 snapshot() {
2784
- return this.view.snapshot;
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
- function extendURLWithDeprecatedProperties(url) {
2789
- Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
2790
- }
2911
+ FrameElement.delegateConstructor = FrameController;
2791
2912
 
2792
- const deprecatedLocationPropertyDescriptors = {
2793
- absoluteURL: {
2794
- get() {
2795
- return this.toString();
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
- if (consumer) return consumer;
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