turbo-rails 0.5.9 → 0.5.11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 025ba92afa27783a938ec698ff465685559d71698ab3023b091a54d14f0d6dd1
4
- data.tar.gz: ae377ccfdb57c9f9f2f862dc81e5863a5bc614002bbdfe39547cb1ede2aa797a
3
+ metadata.gz: 349e14ec6f35af3029178e7ba854d16047aaf5e00ab700b1f2fb751170347518
4
+ data.tar.gz: b5177b878833a5abda62f208d787de1931d4db4cf3ba373a67458bee8c9d49a3
5
5
  SHA512:
6
- metadata.gz: a7f03c2b76b07d65f64b3170b9f9ed6c1a9c4429c9f7eee3abf8bffbdbed8a320806f239df725c5aad37c826d2171ec2abc0c7787d42f929e2741584fc4d84e3
7
- data.tar.gz: b2566bd6203b982d5b3cad1c852517a41b3a11ca8c8f097cac3cf7ff45f8d83db3eab3337807a80299cf61fc21f58990275f2e5dc92af75b03b167bf04f05ecc
6
+ metadata.gz: 6e0e11bc2c95419d394c758ff04e03dd734f85a45e61001e623c25bffdfc83530977637a89dbe5f173d81fd6dc4a86d81a1ff99ffedb4f23b00215e76c16ecad
7
+ data.tar.gz: 48dfec6f22f6eee01950f77c79c54f4d6a8fde629454ac2845b2d6bff6f8b464d18bcf7ede203a16a83dcc7421cbe33f92c74bc3a76124048ebbc897641ef388
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,690 @@ 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.currentURL = this.element.src;
2753
+ this.settingSourceURL = false;
2692
2754
  }
2693
- pageLoaded() {
2694
- this.history.assumeControlOfScrollRestoration();
2755
+ get loadingStyle() {
2756
+ return this.element.loading;
2695
2757
  }
2696
- pageWillUnload() {
2697
- this.history.relinquishControlOfScrollRestoration();
2758
+ get isLoading() {
2759
+ return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
2698
2760
  }
2699
- receivedMessageFromStream(message) {
2700
- this.renderStreamMessage(message);
2761
+ get isActive() {
2762
+ return this.element.isActive && this.connected;
2701
2763
  }
2702
- viewWillCacheSnapshot() {
2703
- this.notifyApplicationBeforeCachingSnapshot();
2764
+ }
2765
+
2766
+ function getFrameElementById(id) {
2767
+ if (id != null) {
2768
+ const element = document.getElementById(id);
2769
+ if (element instanceof FrameElement) {
2770
+ return element;
2771
+ }
2704
2772
  }
2705
- viewWillRenderSnapshot({element: element}, isPreview) {
2706
- this.notifyApplicationBeforeRender(element);
2773
+ }
2774
+
2775
+ function activateElement(element, currentURL) {
2776
+ if (element) {
2777
+ const src = element.getAttribute("src");
2778
+ if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {
2779
+ throw new Error(`Matching <turbo-frame id="${element.id}"> element has a source URL which references itself`);
2780
+ }
2781
+ if (element.ownerDocument !== document) {
2782
+ element = document.importNode(element, true);
2783
+ }
2784
+ if (element instanceof FrameElement) {
2785
+ element.connectedCallback();
2786
+ return element;
2787
+ }
2707
2788
  }
2708
- viewRenderedSnapshot(snapshot, isPreview) {
2709
- this.view.lastRenderedLocation = this.history.location;
2710
- this.notifyApplicationAfterRender();
2789
+ }
2790
+
2791
+ const StreamActions = {
2792
+ after() {
2793
+ var _a, _b;
2794
+ (_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);
2795
+ },
2796
+ append() {
2797
+ var _a;
2798
+ this.removeDuplicateTargetChildren();
2799
+ (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.append(this.templateContent);
2800
+ },
2801
+ before() {
2802
+ var _a, _b;
2803
+ (_b = (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.insertBefore(this.templateContent, this.targetElement);
2804
+ },
2805
+ prepend() {
2806
+ var _a;
2807
+ this.removeDuplicateTargetChildren();
2808
+ (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.prepend(this.templateContent);
2809
+ },
2810
+ remove() {
2811
+ var _a;
2812
+ (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.remove();
2813
+ },
2814
+ replace() {
2815
+ var _a;
2816
+ (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.replaceWith(this.templateContent);
2817
+ },
2818
+ update() {
2819
+ if (this.targetElement) {
2820
+ this.targetElement.innerHTML = "";
2821
+ this.targetElement.append(this.templateContent);
2822
+ }
2711
2823
  }
2712
- viewInvalidated() {
2713
- this.adapter.pageInvalidated();
2824
+ };
2825
+
2826
+ class StreamElement extends HTMLElement {
2827
+ async connectedCallback() {
2828
+ try {
2829
+ await this.render();
2830
+ } catch (error) {
2831
+ console.error(error);
2832
+ } finally {
2833
+ this.disconnect();
2834
+ }
2714
2835
  }
2715
- applicationAllowsFollowingLinkToLocation(link, location) {
2716
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
2717
- return !event.defaultPrevented;
2836
+ async render() {
2837
+ var _a;
2838
+ return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
2839
+ if (this.dispatchEvent(this.beforeRenderEvent)) {
2840
+ await nextAnimationFrame();
2841
+ this.performAction();
2842
+ }
2843
+ })();
2718
2844
  }
2719
- applicationAllowsVisitingLocation(location) {
2720
- const event = this.notifyApplicationBeforeVisitingLocation(location);
2721
- return !event.defaultPrevented;
2845
+ disconnect() {
2846
+ try {
2847
+ this.remove();
2848
+ } catch (_a) {}
2722
2849
  }
2723
- notifyApplicationAfterClickingLinkToLocation(link, location) {
2724
- return dispatch("turbo:click", {
2725
- target: link,
2726
- detail: {
2727
- url: location.href
2728
- },
2729
- cancelable: true
2730
- });
2850
+ removeDuplicateTargetChildren() {
2851
+ this.duplicateChildren.forEach((({targetChild: targetChild}) => {
2852
+ targetChild.remove();
2853
+ }));
2731
2854
  }
2732
- notifyApplicationBeforeVisitingLocation(location) {
2733
- return dispatch("turbo:before-visit", {
2734
- detail: {
2735
- url: location.href
2736
- },
2737
- cancelable: true
2738
- });
2855
+ get duplicateChildren() {
2856
+ var _a;
2857
+ return [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].map((templateChild => {
2858
+ let targetChild = [ ...this.targetElement.children ].filter((c => c.id === templateChild.id))[0];
2859
+ return {
2860
+ targetChild: targetChild,
2861
+ templateChild: templateChild
2862
+ };
2863
+ })).filter((({targetChild: targetChild}) => targetChild));
2739
2864
  }
2740
- notifyApplicationAfterVisitingLocation(location) {
2741
- return dispatch("turbo:visit", {
2742
- detail: {
2743
- url: location.href
2865
+ get performAction() {
2866
+ if (this.action) {
2867
+ const actionFunction = StreamActions[this.action];
2868
+ if (actionFunction) {
2869
+ return actionFunction;
2744
2870
  }
2745
- });
2871
+ this.raise("unknown action");
2872
+ }
2873
+ this.raise("action attribute is missing");
2746
2874
  }
2747
- notifyApplicationBeforeCachingSnapshot() {
2748
- return dispatch("turbo:before-cache");
2875
+ get targetElement() {
2876
+ var _a;
2877
+ if (this.target) {
2878
+ return (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
2879
+ }
2880
+ this.raise("target attribute is missing");
2749
2881
  }
2750
- notifyApplicationBeforeRender(newBody) {
2751
- return dispatch("turbo:before-render", {
2752
- detail: {
2753
- newBody: newBody
2754
- }
2755
- });
2882
+ get templateContent() {
2883
+ return this.templateElement.content;
2756
2884
  }
2757
- notifyApplicationAfterRender() {
2758
- return dispatch("turbo:render");
2885
+ get templateElement() {
2886
+ if (this.firstElementChild instanceof HTMLTemplateElement) {
2887
+ return this.firstElementChild;
2888
+ }
2889
+ this.raise("first child element must be a <template> element");
2759
2890
  }
2760
- notifyApplicationAfterPageLoad(timing = {}) {
2761
- return dispatch("turbo:load", {
2762
- detail: {
2763
- url: this.location.href,
2764
- timing: timing
2765
- }
2766
- });
2891
+ get action() {
2892
+ return this.getAttribute("action");
2767
2893
  }
2768
- getActionForLink(link) {
2769
- const action = link.getAttribute("data-turbo-action");
2770
- return isAction(action) ? action : "advance";
2894
+ get target() {
2895
+ return this.getAttribute("target");
2771
2896
  }
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
- }
2897
+ raise(message) {
2898
+ throw new Error(`${this.description}: ${message}`);
2779
2899
  }
2780
- locationIsVisitable(location) {
2781
- return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
2900
+ get description() {
2901
+ var _a, _b;
2902
+ return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
2782
2903
  }
2783
- get snapshot() {
2784
- return this.view.snapshot;
2904
+ get beforeRenderEvent() {
2905
+ return new CustomEvent("turbo:before-stream-render", {
2906
+ bubbles: true,
2907
+ cancelable: true
2908
+ });
2785
2909
  }
2786
2910
  }
2787
2911
 
2788
- function extendURLWithDeprecatedProperties(url) {
2789
- Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
2790
- }
2912
+ FrameElement.delegateConstructor = FrameController;
2791
2913
 
2792
- const deprecatedLocationPropertyDescriptors = {
2793
- absoluteURL: {
2794
- get() {
2795
- return this.toString();
2914
+ customElements.define("turbo-frame", FrameElement);
2915
+
2916
+ customElements.define("turbo-stream", StreamElement);
2917
+
2918
+ (() => {
2919
+ let element = document.currentScript;
2920
+ if (!element) return;
2921
+ if (element.hasAttribute("data-turbo-suppress-warning")) return;
2922
+ while (element = element.parentElement) {
2923
+ if (element == document.body) {
2924
+ return console.warn(unindent`
2925
+ You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
2926
+
2927
+ Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
2928
+
2929
+ For more information, see: https://turbo.hotwire.dev/handbook/building#working-with-script-elements
2930
+
2931
+ ——
2932
+ Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
2933
+ `, element.outerHTML);
2796
2934
  }
2797
2935
  }
2798
- };
2936
+ })();
2799
2937
 
2800
2938
  const session = new Session;
2801
2939
 
@@ -2833,6 +2971,21 @@ function setProgressBarDelay(delay) {
2833
2971
  session.setProgressBarDelay(delay);
2834
2972
  }
2835
2973
 
2974
+ var Turbo = Object.freeze({
2975
+ __proto__: null,
2976
+ navigator: navigator,
2977
+ start: start,
2978
+ registerAdapter: registerAdapter,
2979
+ visit: visit,
2980
+ connectStreamSource: connectStreamSource,
2981
+ disconnectStreamSource: disconnectStreamSource,
2982
+ renderStreamMessage: renderStreamMessage,
2983
+ clearCache: clearCache,
2984
+ setProgressBarDelay: setProgressBarDelay
2985
+ });
2986
+
2987
+ window.Turbo = Turbo;
2988
+
2836
2989
  start();
2837
2990
 
2838
2991
  var turbo_es2017Esm = Object.freeze({
@@ -2851,17 +3004,20 @@ var turbo_es2017Esm = Object.freeze({
2851
3004
  let consumer;
2852
3005
 
2853
3006
  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());
3007
+ return consumer || setConsumer(createConsumer().then(setConsumer));
2859
3008
  }
2860
3009
 
2861
3010
  function setConsumer(newConsumer) {
2862
3011
  return consumer = newConsumer;
2863
3012
  }
2864
3013
 
3014
+ async function createConsumer() {
3015
+ const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
3016
+ return index;
3017
+ }));
3018
+ return createConsumer();
3019
+ }
3020
+
2865
3021
  async function subscribeTo(channel, mixin) {
2866
3022
  const {subscriptions: subscriptions} = await getConsumer();
2867
3023
  return subscriptions.create(channel, mixin);
@@ -2871,6 +3027,7 @@ var cable = Object.freeze({
2871
3027
  __proto__: null,
2872
3028
  getConsumer: getConsumer,
2873
3029
  setConsumer: setConsumer,
3030
+ createConsumer: createConsumer,
2874
3031
  subscribeTo: subscribeTo
2875
3032
  });
2876
3033
 
@@ -3334,7 +3491,7 @@ function createWebSocketURL(url) {
3334
3491
  }
3335
3492
  }
3336
3493
 
3337
- function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
3494
+ function createConsumer$1(url = getConfig("url") || INTERNAL.default_mount_path) {
3338
3495
  return new Consumer(url);
3339
3496
  }
3340
3497
 
@@ -3356,7 +3513,7 @@ var index = Object.freeze({
3356
3513
  adapters: adapters,
3357
3514
  createWebSocketURL: createWebSocketURL,
3358
3515
  logger: logger,
3359
- createConsumer: createConsumer,
3516
+ createConsumer: createConsumer$1,
3360
3517
  getConfig: getConfig
3361
3518
  });
3362
3519