turbo-rails 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -88,7 +88,7 @@ class FrameElement extends HTMLElement {
88
88
  this.delegate = new FrameElement.delegateConstructor(this);
89
89
  }
90
90
  static get observedAttributes() {
91
- return [ "disabled", "loading", "src" ];
91
+ return [ "disabled", "complete", "loading", "src" ];
92
92
  }
93
93
  connectedCallback() {
94
94
  this.delegate.connect();
@@ -98,12 +98,16 @@ class FrameElement extends HTMLElement {
98
98
  }
99
99
  reload() {
100
100
  const {src: src} = this;
101
+ this.removeAttribute("complete");
101
102
  this.src = null;
102
103
  this.src = src;
104
+ return this.loaded;
103
105
  }
104
106
  attributeChangedCallback(name) {
105
107
  if (name == "loading") {
106
108
  this.delegate.loadingStyleChanged();
109
+ } else if (name == "complete") {
110
+ this.delegate.completeChanged();
107
111
  } else if (name == "src") {
108
112
  this.delegate.sourceURLChanged();
109
113
  } else {
@@ -195,7 +199,7 @@ function getExtension(url) {
195
199
  }
196
200
 
197
201
  function isHTML(url) {
198
- return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
202
+ return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
199
203
  }
200
204
 
201
205
  function isPrefixedBy(baseURL, url) {
@@ -282,6 +286,38 @@ class FetchResponse {
282
286
  }
283
287
  }
284
288
 
289
+ function isAction(action) {
290
+ return action == "advance" || action == "replace" || action == "restore";
291
+ }
292
+
293
+ function activateScriptElement(element) {
294
+ if (element.getAttribute("data-turbo-eval") == "false") {
295
+ return element;
296
+ } else {
297
+ const createdScriptElement = document.createElement("script");
298
+ const cspNonce = getMetaContent("csp-nonce");
299
+ if (cspNonce) {
300
+ createdScriptElement.nonce = cspNonce;
301
+ }
302
+ createdScriptElement.textContent = element.textContent;
303
+ createdScriptElement.async = false;
304
+ copyElementAttributes(createdScriptElement, element);
305
+ return createdScriptElement;
306
+ }
307
+ }
308
+
309
+ function copyElementAttributes(destinationElement, sourceElement) {
310
+ for (const {name: name, value: value} of sourceElement.attributes) {
311
+ destinationElement.setAttribute(name, value);
312
+ }
313
+ }
314
+
315
+ function createDocumentFragment(html) {
316
+ const template = document.createElement("template");
317
+ template.innerHTML = html;
318
+ return template.content;
319
+ }
320
+
285
321
  function dispatch(eventName, {target: target, cancelable: cancelable, detail: detail} = {}) {
286
322
  const event = new CustomEvent(eventName, {
287
323
  cancelable: cancelable,
@@ -327,7 +363,7 @@ function interpolate(strings, values) {
327
363
  }
328
364
 
329
365
  function uuid() {
330
- return Array.apply(null, {
366
+ return Array.from({
331
367
  length: 36
332
368
  }).map(((_, i) => {
333
369
  if (i == 8 || i == 13 || i == 18 || i == 23) {
@@ -349,6 +385,10 @@ function getAttribute(attributeName, ...elements) {
349
385
  return null;
350
386
  }
351
387
 
388
+ function hasAttribute(attributeName, ...elements) {
389
+ return elements.some((element => element && element.hasAttribute(attributeName)));
390
+ }
391
+
352
392
  function markAsBusy(...elements) {
353
393
  for (const element of elements) {
354
394
  if (element.localName == "turbo-frame") {
@@ -367,6 +407,59 @@ function clearBusyState(...elements) {
367
407
  }
368
408
  }
369
409
 
410
+ function waitForLoad(element, timeoutInMilliseconds = 2e3) {
411
+ return new Promise((resolve => {
412
+ const onComplete = () => {
413
+ element.removeEventListener("error", onComplete);
414
+ element.removeEventListener("load", onComplete);
415
+ resolve();
416
+ };
417
+ element.addEventListener("load", onComplete, {
418
+ once: true
419
+ });
420
+ element.addEventListener("error", onComplete, {
421
+ once: true
422
+ });
423
+ setTimeout(resolve, timeoutInMilliseconds);
424
+ }));
425
+ }
426
+
427
+ function getHistoryMethodForAction(action) {
428
+ switch (action) {
429
+ case "replace":
430
+ return history.replaceState;
431
+
432
+ case "advance":
433
+ case "restore":
434
+ return history.pushState;
435
+ }
436
+ }
437
+
438
+ function getVisitAction(...elements) {
439
+ const action = getAttribute("data-turbo-action", ...elements);
440
+ return isAction(action) ? action : null;
441
+ }
442
+
443
+ function getMetaElement(name) {
444
+ return document.querySelector(`meta[name="${name}"]`);
445
+ }
446
+
447
+ function getMetaContent(name) {
448
+ const element = getMetaElement(name);
449
+ return element && element.content;
450
+ }
451
+
452
+ function setMetaContent(name, content) {
453
+ let element = getMetaElement(name);
454
+ if (!element) {
455
+ element = document.createElement("meta");
456
+ element.setAttribute("name", name);
457
+ document.head.appendChild(element);
458
+ }
459
+ element.setAttribute("content", content);
460
+ return element;
461
+ }
462
+
370
463
  var FetchMethod;
371
464
 
372
465
  (function(FetchMethod) {
@@ -399,7 +492,7 @@ function fetchMethodFromString(method) {
399
492
  class FetchRequest {
400
493
  constructor(delegate, method, location, body = new URLSearchParams, target = null) {
401
494
  this.abortController = new AbortController;
402
- this.resolveRequestPromise = value => {};
495
+ this.resolveRequestPromise = _value => {};
403
496
  this.delegate = delegate;
404
497
  this.method = method;
405
498
  this.headers = this.defaultHeaders;
@@ -430,7 +523,9 @@ class FetchRequest {
430
523
  return await this.receive(response);
431
524
  } catch (error) {
432
525
  if (error.name !== "AbortError") {
433
- this.delegate.requestErrored(this, error);
526
+ if (this.willDelegateErrorHandling(error)) {
527
+ this.delegate.requestErrored(this, error);
528
+ }
434
529
  throw error;
435
530
  }
436
531
  } finally {
@@ -478,6 +573,9 @@ class FetchRequest {
478
573
  get abortSignal() {
479
574
  return this.abortController.signal;
480
575
  }
576
+ acceptResponseType(mimeType) {
577
+ this.headers["Accept"] = [ mimeType, this.headers["Accept"] ].join(", ");
578
+ }
481
579
  async allowRequestToBeIntercepted(fetchOptions) {
482
580
  const requestInterception = new Promise((resolve => this.resolveRequestPromise = resolve));
483
581
  const event = dispatch("turbo:before-fetch-request", {
@@ -491,6 +589,17 @@ class FetchRequest {
491
589
  });
492
590
  if (event.defaultPrevented) await requestInterception;
493
591
  }
592
+ willDelegateErrorHandling(error) {
593
+ const event = dispatch("turbo:fetch-request-error", {
594
+ target: this.target,
595
+ cancelable: true,
596
+ detail: {
597
+ request: this,
598
+ error: error
599
+ }
600
+ });
601
+ return !event.defaultPrevented;
602
+ }
494
603
  }
495
604
 
496
605
  class AppearanceObserver {
@@ -521,40 +630,31 @@ class AppearanceObserver {
521
630
  }
522
631
 
523
632
  class StreamMessage {
524
- constructor(html) {
525
- this.templateElement = document.createElement("template");
526
- this.templateElement.innerHTML = html;
633
+ constructor(fragment) {
634
+ this.fragment = importStreamElements(fragment);
527
635
  }
528
636
  static wrap(message) {
529
637
  if (typeof message == "string") {
530
- return new this(message);
638
+ return new this(createDocumentFragment(message));
531
639
  } else {
532
640
  return message;
533
641
  }
534
642
  }
535
- get fragment() {
536
- const fragment = document.createDocumentFragment();
537
- for (const element of this.foreignElements) {
538
- fragment.appendChild(document.importNode(element, true));
539
- }
540
- return fragment;
541
- }
542
- get foreignElements() {
543
- return this.templateChildren.reduce(((streamElements, child) => {
544
- if (child.tagName.toLowerCase() == "turbo-stream") {
545
- return [ ...streamElements, child ];
546
- } else {
547
- return streamElements;
548
- }
549
- }), []);
550
- }
551
- get templateChildren() {
552
- return Array.from(this.templateElement.content.children);
553
- }
554
643
  }
555
644
 
556
645
  StreamMessage.contentType = "text/vnd.turbo-stream.html";
557
646
 
647
+ function importStreamElements(fragment) {
648
+ for (const element of fragment.querySelectorAll("turbo-stream")) {
649
+ const streamElement = document.importNode(element, true);
650
+ for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) {
651
+ inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));
652
+ }
653
+ element.replaceWith(streamElement);
654
+ }
655
+ return fragment;
656
+ }
657
+
558
658
  var FormSubmissionState;
559
659
 
560
660
  (function(FormSubmissionState) {
@@ -601,8 +701,8 @@ class FormSubmission {
601
701
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
602
702
  this.mustRedirect = mustRedirect;
603
703
  }
604
- static confirmMethod(message, element) {
605
- return confirm(message);
704
+ static confirmMethod(message, _element, _submitter) {
705
+ return Promise.resolve(confirm(message));
606
706
  }
607
707
  get method() {
608
708
  var _a;
@@ -612,7 +712,11 @@ class FormSubmission {
612
712
  get action() {
613
713
  var _a;
614
714
  const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
615
- return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.getAttribute("action") || formElementAction || "";
715
+ if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
716
+ return this.submitter.getAttribute("formaction") || "";
717
+ } else {
718
+ return this.formElement.getAttribute("action") || formElementAction || "";
719
+ }
616
720
  }
617
721
  get body() {
618
722
  if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
@@ -631,16 +735,11 @@ class FormSubmission {
631
735
  get stringFormData() {
632
736
  return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
633
737
  }
634
- get confirmationMessage() {
635
- return this.formElement.getAttribute("data-turbo-confirm");
636
- }
637
- get needsConfirmation() {
638
- return this.confirmationMessage !== null;
639
- }
640
738
  async start() {
641
739
  const {initialized: initialized, requesting: requesting} = FormSubmissionState;
642
- if (this.needsConfirmation) {
643
- const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
740
+ const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
741
+ if (typeof confirmationMessage === "string") {
742
+ const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);
644
743
  if (!answer) {
645
744
  return;
646
745
  }
@@ -664,10 +763,12 @@ class FormSubmission {
664
763
  if (token) {
665
764
  headers["X-CSRF-Token"] = token;
666
765
  }
667
- headers["Accept"] = [ StreamMessage.contentType, headers["Accept"] ].join(", ");
766
+ }
767
+ if (this.requestAcceptsTurboStreamResponse(request)) {
768
+ request.acceptResponseType(StreamMessage.contentType);
668
769
  }
669
770
  }
670
- requestStarted(request) {
771
+ requestStarted(_request) {
671
772
  var _a;
672
773
  this.state = FormSubmissionState.waiting;
673
774
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
@@ -714,7 +815,7 @@ class FormSubmission {
714
815
  };
715
816
  this.delegate.formSubmissionErrored(this, error);
716
817
  }
717
- requestFinished(request) {
818
+ requestFinished(_request) {
718
819
  var _a;
719
820
  this.state = FormSubmissionState.stopped;
720
821
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
@@ -729,14 +830,17 @@ class FormSubmission {
729
830
  requestMustRedirect(request) {
730
831
  return !request.isIdempotent && this.mustRedirect;
731
832
  }
833
+ requestAcceptsTurboStreamResponse(request) {
834
+ return !request.isIdempotent || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
835
+ }
732
836
  }
733
837
 
734
838
  function buildFormData(formElement, submitter) {
735
839
  const formData = new FormData(formElement);
736
840
  const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
737
841
  const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
738
- if (name && value != null && formData.get(name) != value) {
739
- formData.append(name, value);
842
+ if (name) {
843
+ formData.append(name, value || "");
740
844
  }
741
845
  return formData;
742
846
  }
@@ -752,11 +856,6 @@ function getCookieValue(cookieName) {
752
856
  }
753
857
  }
754
858
 
755
- function getMetaContent(name) {
756
- const element = document.querySelector(`meta[name="${name}"]`);
757
- return element && element.content;
758
- }
759
-
760
859
  function responseSucceededWithoutRedirect(response) {
761
860
  return response.statusCode == 200 && !response.redirected;
762
861
  }
@@ -775,6 +874,9 @@ class Snapshot {
775
874
  constructor(element) {
776
875
  this.element = element;
777
876
  }
877
+ get activeElement() {
878
+ return this.element.ownerDocument.activeElement;
879
+ }
778
880
  get children() {
779
881
  return [ ...this.element.children ];
780
882
  }
@@ -788,13 +890,17 @@ class Snapshot {
788
890
  return this.element.isConnected;
789
891
  }
790
892
  get firstAutofocusableElement() {
791
- return this.element.querySelector("[autofocus]");
893
+ const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
894
+ for (const element of this.element.querySelectorAll("[autofocus]")) {
895
+ if (element.closest(inertDisabledOrHidden) == null) return element; else continue;
896
+ }
897
+ return null;
792
898
  }
793
899
  get permanentElements() {
794
- return [ ...this.element.querySelectorAll("[id][data-turbo-permanent]") ];
900
+ return queryPermanentElementsAll(this.element);
795
901
  }
796
902
  getPermanentElementById(id) {
797
- return this.element.querySelector(`#${id}[data-turbo-permanent]`);
903
+ return getPermanentElementById(this.element, id);
798
904
  }
799
905
  getPermanentElementMapForSnapshot(snapshot) {
800
906
  const permanentElementMap = {};
@@ -809,35 +915,65 @@ class Snapshot {
809
915
  }
810
916
  }
811
917
 
812
- class FormInterceptor {
813
- constructor(delegate, element) {
918
+ function getPermanentElementById(node, id) {
919
+ return node.querySelector(`#${id}[data-turbo-permanent]`);
920
+ }
921
+
922
+ function queryPermanentElementsAll(node) {
923
+ return node.querySelectorAll("[id][data-turbo-permanent]");
924
+ }
925
+
926
+ class FormSubmitObserver {
927
+ constructor(delegate, eventTarget) {
928
+ this.started = false;
929
+ this.submitCaptured = () => {
930
+ this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
931
+ this.eventTarget.addEventListener("submit", this.submitBubbled, false);
932
+ };
814
933
  this.submitBubbled = event => {
815
- const form = event.target;
816
- if (!event.defaultPrevented && form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
934
+ if (!event.defaultPrevented) {
935
+ const form = event.target instanceof HTMLFormElement ? event.target : undefined;
817
936
  const submitter = event.submitter || undefined;
818
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
819
- if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
937
+ if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
820
938
  event.preventDefault();
821
- event.stopImmediatePropagation();
822
- this.delegate.formSubmissionIntercepted(form, submitter);
939
+ this.delegate.formSubmitted(form, submitter);
823
940
  }
824
941
  }
825
942
  };
826
943
  this.delegate = delegate;
827
- this.element = element;
944
+ this.eventTarget = eventTarget;
828
945
  }
829
946
  start() {
830
- this.element.addEventListener("submit", this.submitBubbled);
947
+ if (!this.started) {
948
+ this.eventTarget.addEventListener("submit", this.submitCaptured, true);
949
+ this.started = true;
950
+ }
831
951
  }
832
952
  stop() {
833
- this.element.removeEventListener("submit", this.submitBubbled);
953
+ if (this.started) {
954
+ this.eventTarget.removeEventListener("submit", this.submitCaptured, true);
955
+ this.started = false;
956
+ }
957
+ }
958
+ }
959
+
960
+ function submissionDoesNotDismissDialog(form, submitter) {
961
+ const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
962
+ return method != "dialog";
963
+ }
964
+
965
+ function submissionDoesNotTargetIFrame(form, submitter) {
966
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
967
+ for (const element of document.getElementsByName(target)) {
968
+ if (element instanceof HTMLIFrameElement) return false;
834
969
  }
970
+ return true;
835
971
  }
836
972
 
837
973
  class View {
838
974
  constructor(delegate, element) {
839
- this.resolveRenderPromise = value => {};
840
- this.resolveInterceptionPromise = value => {};
975
+ this.resolveRenderPromise = _value => {};
976
+ this.resolveInterceptionPromise = _value => {};
841
977
  this.delegate = delegate;
842
978
  this.element = element;
843
979
  }
@@ -888,12 +1024,17 @@ class View {
888
1024
  try {
889
1025
  this.renderPromise = new Promise((resolve => this.resolveRenderPromise = resolve));
890
1026
  this.renderer = renderer;
891
- this.prepareToRenderSnapshot(renderer);
1027
+ await this.prepareToRenderSnapshot(renderer);
892
1028
  const renderInterception = new Promise((resolve => this.resolveInterceptionPromise = resolve));
893
- const immediateRender = this.delegate.allowsImmediateRender(snapshot, this.resolveInterceptionPromise);
1029
+ const options = {
1030
+ resume: this.resolveInterceptionPromise,
1031
+ render: this.renderer.renderElement
1032
+ };
1033
+ const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
894
1034
  if (!immediateRender) await renderInterception;
895
1035
  await this.renderSnapshot(renderer);
896
1036
  this.delegate.viewRenderedSnapshot(snapshot, isPreview);
1037
+ this.delegate.preloadOnLoadLinksForView(this.element);
897
1038
  this.finishRenderingSnapshot(renderer);
898
1039
  } finally {
899
1040
  delete this.renderer;
@@ -901,15 +1042,15 @@ class View {
901
1042
  delete this.renderPromise;
902
1043
  }
903
1044
  } else {
904
- this.invalidate();
1045
+ this.invalidate(renderer.reloadReason);
905
1046
  }
906
1047
  }
907
- invalidate() {
908
- this.delegate.viewInvalidated();
1048
+ invalidate(reason) {
1049
+ this.delegate.viewInvalidated(reason);
909
1050
  }
910
- prepareToRenderSnapshot(renderer) {
1051
+ async prepareToRenderSnapshot(renderer) {
911
1052
  this.markAsPreview(renderer.isPreview);
912
- renderer.prepareToRender();
1053
+ await renderer.prepareToRender();
913
1054
  }
914
1055
  markAsPreview(isPreview) {
915
1056
  if (isPreview) {
@@ -935,60 +1076,115 @@ class FrameView extends View {
935
1076
  }
936
1077
  }
937
1078
 
938
- class LinkInterceptor {
939
- constructor(delegate, element) {
940
- this.clickBubbled = event => {
941
- if (this.respondsToEventTarget(event.target)) {
942
- this.clickEvent = event;
943
- } else {
944
- delete this.clickEvent;
945
- }
1079
+ class LinkClickObserver {
1080
+ constructor(delegate, eventTarget) {
1081
+ this.started = false;
1082
+ this.clickCaptured = () => {
1083
+ this.eventTarget.removeEventListener("click", this.clickBubbled, false);
1084
+ this.eventTarget.addEventListener("click", this.clickBubbled, false);
946
1085
  };
947
- this.linkClicked = event => {
948
- if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
949
- if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) {
950
- this.clickEvent.preventDefault();
951
- event.preventDefault();
952
- this.delegate.linkClickIntercepted(event.target, event.detail.url);
1086
+ this.clickBubbled = event => {
1087
+ if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
1088
+ const target = event.composedPath && event.composedPath()[0] || event.target;
1089
+ const link = this.findLinkFromClickTarget(target);
1090
+ if (link && doesNotTargetIFrame(link)) {
1091
+ const location = this.getLocationForLink(link);
1092
+ if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1093
+ event.preventDefault();
1094
+ this.delegate.followedLinkToLocation(link, location);
1095
+ }
953
1096
  }
954
1097
  }
955
- delete this.clickEvent;
956
- };
957
- this.willVisit = () => {
958
- delete this.clickEvent;
959
1098
  };
960
1099
  this.delegate = delegate;
961
- this.element = element;
1100
+ this.eventTarget = eventTarget;
1101
+ }
1102
+ start() {
1103
+ if (!this.started) {
1104
+ this.eventTarget.addEventListener("click", this.clickCaptured, true);
1105
+ this.started = true;
1106
+ }
1107
+ }
1108
+ stop() {
1109
+ if (this.started) {
1110
+ this.eventTarget.removeEventListener("click", this.clickCaptured, true);
1111
+ this.started = false;
1112
+ }
1113
+ }
1114
+ clickEventIsSignificant(event) {
1115
+ return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
1116
+ }
1117
+ findLinkFromClickTarget(target) {
1118
+ if (target instanceof Element) {
1119
+ return target.closest("a[href]:not([target^=_]):not([download])");
1120
+ }
1121
+ }
1122
+ getLocationForLink(link) {
1123
+ return expandURL(link.getAttribute("href") || "");
1124
+ }
1125
+ }
1126
+
1127
+ function doesNotTargetIFrame(anchor) {
1128
+ for (const element of document.getElementsByName(anchor.target)) {
1129
+ if (element instanceof HTMLIFrameElement) return false;
1130
+ }
1131
+ return true;
1132
+ }
1133
+
1134
+ class FormLinkClickObserver {
1135
+ constructor(delegate, element) {
1136
+ this.delegate = delegate;
1137
+ this.linkClickObserver = new LinkClickObserver(this, element);
962
1138
  }
963
1139
  start() {
964
- this.element.addEventListener("click", this.clickBubbled);
965
- document.addEventListener("turbo:click", this.linkClicked);
966
- document.addEventListener("turbo:before-visit", this.willVisit);
1140
+ this.linkClickObserver.start();
967
1141
  }
968
1142
  stop() {
969
- this.element.removeEventListener("click", this.clickBubbled);
970
- document.removeEventListener("turbo:click", this.linkClicked);
971
- document.removeEventListener("turbo:before-visit", this.willVisit);
1143
+ this.linkClickObserver.stop();
972
1144
  }
973
- respondsToEventTarget(target) {
974
- const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
975
- return element && element.closest("turbo-frame, html") == this.element;
1145
+ willFollowLinkToLocation(link, location, originalEvent) {
1146
+ return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
1147
+ }
1148
+ followedLinkToLocation(link, location) {
1149
+ const action = location.href;
1150
+ const form = document.createElement("form");
1151
+ form.setAttribute("data-turbo", "true");
1152
+ form.setAttribute("action", action);
1153
+ form.setAttribute("hidden", "");
1154
+ const method = link.getAttribute("data-turbo-method");
1155
+ if (method) form.setAttribute("method", method);
1156
+ const turboFrame = link.getAttribute("data-turbo-frame");
1157
+ if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
1158
+ const turboAction = link.getAttribute("data-turbo-action");
1159
+ if (turboAction) form.setAttribute("data-turbo-action", turboAction);
1160
+ const turboConfirm = link.getAttribute("data-turbo-confirm");
1161
+ if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
1162
+ const turboStream = link.hasAttribute("data-turbo-stream");
1163
+ if (turboStream) form.setAttribute("data-turbo-stream", "");
1164
+ this.delegate.submittedFormLinkToLocation(link, location, form);
1165
+ document.body.appendChild(form);
1166
+ form.addEventListener("turbo:submit-end", (() => form.remove()), {
1167
+ once: true
1168
+ });
1169
+ requestAnimationFrame((() => form.requestSubmit()));
976
1170
  }
977
1171
  }
978
1172
 
979
1173
  class Bardo {
980
- constructor(permanentElementMap) {
1174
+ constructor(delegate, permanentElementMap) {
1175
+ this.delegate = delegate;
981
1176
  this.permanentElementMap = permanentElementMap;
982
1177
  }
983
- static preservingPermanentElements(permanentElementMap, callback) {
984
- const bardo = new this(permanentElementMap);
1178
+ static preservingPermanentElements(delegate, permanentElementMap, callback) {
1179
+ const bardo = new this(delegate, permanentElementMap);
985
1180
  bardo.enter();
986
1181
  callback();
987
1182
  bardo.leave();
988
1183
  }
989
1184
  enter() {
990
1185
  for (const id in this.permanentElementMap) {
991
- const [, newPermanentElement] = this.permanentElementMap[id];
1186
+ const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
1187
+ this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
992
1188
  this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
993
1189
  }
994
1190
  }
@@ -997,6 +1193,7 @@ class Bardo {
997
1193
  const [currentPermanentElement] = this.permanentElementMap[id];
998
1194
  this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
999
1195
  this.replacePlaceholderWithPermanentElement(currentPermanentElement);
1196
+ this.delegate.leavingBardo(currentPermanentElement);
1000
1197
  }
1001
1198
  }
1002
1199
  replaceNewPermanentElementWithPlaceholder(permanentElement) {
@@ -1027,11 +1224,13 @@ function createPlaceholderForPermanentElement(permanentElement) {
1027
1224
  }
1028
1225
 
1029
1226
  class Renderer {
1030
- constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
1227
+ constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1228
+ this.activeElement = null;
1031
1229
  this.currentSnapshot = currentSnapshot;
1032
1230
  this.newSnapshot = newSnapshot;
1033
1231
  this.isPreview = isPreview;
1034
1232
  this.willRender = willRender;
1233
+ this.renderElement = renderElement;
1035
1234
  this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
1036
1235
  resolve: resolve,
1037
1236
  reject: reject
@@ -1040,6 +1239,9 @@ class Renderer {
1040
1239
  get shouldRender() {
1041
1240
  return true;
1042
1241
  }
1242
+ get reloadReason() {
1243
+ return;
1244
+ }
1043
1245
  prepareToRender() {
1044
1246
  return;
1045
1247
  }
@@ -1049,22 +1251,8 @@ class Renderer {
1049
1251
  delete this.resolvingFunctions;
1050
1252
  }
1051
1253
  }
1052
- createScriptElement(element) {
1053
- if (element.getAttribute("data-turbo-eval") == "false") {
1054
- return element;
1055
- } else {
1056
- const createdScriptElement = document.createElement("script");
1057
- if (this.cspNonce) {
1058
- createdScriptElement.nonce = this.cspNonce;
1059
- }
1060
- createdScriptElement.textContent = element.textContent;
1061
- createdScriptElement.async = false;
1062
- copyElementAttributes(createdScriptElement, element);
1063
- return createdScriptElement;
1064
- }
1065
- }
1066
1254
  preservingPermanentElements(callback) {
1067
- Bardo.preservingPermanentElements(this.permanentElementMap, callback);
1255
+ Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1068
1256
  }
1069
1257
  focusFirstAutofocusableElement() {
1070
1258
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1072,6 +1260,18 @@ class Renderer {
1072
1260
  element.focus();
1073
1261
  }
1074
1262
  }
1263
+ enteringBardo(currentPermanentElement) {
1264
+ if (this.activeElement) return;
1265
+ if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
1266
+ this.activeElement = this.currentSnapshot.activeElement;
1267
+ }
1268
+ }
1269
+ leavingBardo(currentPermanentElement) {
1270
+ if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
1271
+ this.activeElement.focus();
1272
+ this.activeElement = null;
1273
+ }
1274
+ }
1075
1275
  get connectedSnapshot() {
1076
1276
  return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
1077
1277
  }
@@ -1084,16 +1284,6 @@ class Renderer {
1084
1284
  get permanentElementMap() {
1085
1285
  return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1086
1286
  }
1087
- get cspNonce() {
1088
- var _a;
1089
- return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
1090
- }
1091
- }
1092
-
1093
- function copyElementAttributes(destinationElement, sourceElement) {
1094
- for (const {name: name, value: value} of [ ...sourceElement.attributes ]) {
1095
- destinationElement.setAttribute(name, value);
1096
- }
1097
1287
  }
1098
1288
 
1099
1289
  function elementIsFocusable(element) {
@@ -1101,6 +1291,22 @@ function elementIsFocusable(element) {
1101
1291
  }
1102
1292
 
1103
1293
  class FrameRenderer extends Renderer {
1294
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1295
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1296
+ this.delegate = delegate;
1297
+ }
1298
+ static renderElement(currentElement, newElement) {
1299
+ var _a;
1300
+ const destinationRange = document.createRange();
1301
+ destinationRange.selectNodeContents(currentElement);
1302
+ destinationRange.deleteContents();
1303
+ const frameElement = newElement;
1304
+ const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1305
+ if (sourceRange) {
1306
+ sourceRange.selectNodeContents(frameElement);
1307
+ currentElement.appendChild(sourceRange.extractContents());
1308
+ }
1309
+ }
1104
1310
  get shouldRender() {
1105
1311
  return true;
1106
1312
  }
@@ -1116,24 +1322,18 @@ class FrameRenderer extends Renderer {
1116
1322
  this.activateScriptElements();
1117
1323
  }
1118
1324
  loadFrameElement() {
1119
- var _a;
1120
- const destinationRange = document.createRange();
1121
- destinationRange.selectNodeContents(this.currentElement);
1122
- destinationRange.deleteContents();
1123
- const frameElement = this.newElement;
1124
- const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1125
- if (sourceRange) {
1126
- sourceRange.selectNodeContents(frameElement);
1127
- this.currentElement.appendChild(sourceRange.extractContents());
1128
- }
1325
+ this.delegate.willRenderFrame(this.currentElement, this.newElement);
1326
+ this.renderElement(this.currentElement, this.newElement);
1129
1327
  }
1130
1328
  scrollFrameIntoView() {
1131
1329
  if (this.currentElement.autoscroll || this.newElement.autoscroll) {
1132
1330
  const element = this.currentElement.firstElementChild;
1133
1331
  const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
1332
+ const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
1134
1333
  if (element) {
1135
1334
  element.scrollIntoView({
1136
- block: block
1335
+ block: block,
1336
+ behavior: behavior
1137
1337
  });
1138
1338
  return true;
1139
1339
  }
@@ -1142,7 +1342,7 @@ class FrameRenderer extends Renderer {
1142
1342
  }
1143
1343
  activateScriptElements() {
1144
1344
  for (const inertScriptElement of this.newScriptElements) {
1145
- const activatedScriptElement = this.createScriptElement(inertScriptElement);
1345
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
1146
1346
  inertScriptElement.replaceWith(activatedScriptElement);
1147
1347
  }
1148
1348
  }
@@ -1159,6 +1359,14 @@ function readScrollLogicalPosition(value, defaultValue) {
1159
1359
  }
1160
1360
  }
1161
1361
 
1362
+ function readScrollBehavior(value, defaultValue) {
1363
+ if (value == "auto" || value == "smooth") {
1364
+ return value;
1365
+ } else {
1366
+ return defaultValue;
1367
+ }
1368
+ }
1369
+
1162
1370
  class ProgressBar {
1163
1371
  constructor() {
1164
1372
  this.hiding = false;
@@ -1181,7 +1389,7 @@ class ProgressBar {
1181
1389
  left: 0;
1182
1390
  height: 3px;
1183
1391
  background: #0076ff;
1184
- z-index: 9999;
1392
+ z-index: 2147483647;
1185
1393
  transition:
1186
1394
  width ${ProgressBar.animationDuration}ms ease-out,
1187
1395
  opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
@@ -1247,6 +1455,9 @@ class ProgressBar {
1247
1455
  const element = document.createElement("style");
1248
1456
  element.type = "text/css";
1249
1457
  element.textContent = ProgressBar.defaultCSS;
1458
+ if (this.cspNonce) {
1459
+ element.nonce = this.cspNonce;
1460
+ }
1250
1461
  return element;
1251
1462
  }
1252
1463
  createProgressElement() {
@@ -1254,6 +1465,9 @@ class ProgressBar {
1254
1465
  element.className = "turbo-progress-bar";
1255
1466
  return element;
1256
1467
  }
1468
+ get cspNonce() {
1469
+ return getMetaContent("csp-nonce");
1470
+ }
1257
1471
  }
1258
1472
 
1259
1473
  ProgressBar.animationDuration = 300;
@@ -1324,22 +1538,22 @@ function elementIsTracked(element) {
1324
1538
  }
1325
1539
 
1326
1540
  function elementIsScript(element) {
1327
- const tagName = element.tagName.toLowerCase();
1541
+ const tagName = element.localName;
1328
1542
  return tagName == "script";
1329
1543
  }
1330
1544
 
1331
1545
  function elementIsNoscript(element) {
1332
- const tagName = element.tagName.toLowerCase();
1546
+ const tagName = element.localName;
1333
1547
  return tagName == "noscript";
1334
1548
  }
1335
1549
 
1336
1550
  function elementIsStylesheet(element) {
1337
- const tagName = element.tagName.toLowerCase();
1551
+ const tagName = element.localName;
1338
1552
  return tagName == "style" || tagName == "link" && element.getAttribute("rel") == "stylesheet";
1339
1553
  }
1340
1554
 
1341
1555
  function elementIsMetaElementWithName(element, name) {
1342
- const tagName = element.tagName.toLowerCase();
1556
+ const tagName = element.localName;
1343
1557
  return tagName == "meta" && element.getAttribute("name") == name;
1344
1558
  }
1345
1559
 
@@ -1365,7 +1579,18 @@ class PageSnapshot extends Snapshot {
1365
1579
  return new this(body, new HeadSnapshot(head));
1366
1580
  }
1367
1581
  clone() {
1368
- return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
1582
+ const clonedElement = this.element.cloneNode(true);
1583
+ const selectElements = this.element.querySelectorAll("select");
1584
+ const clonedSelectElements = clonedElement.querySelectorAll("select");
1585
+ for (const [index, source] of selectElements.entries()) {
1586
+ const clone = clonedSelectElements[index];
1587
+ for (const option of clone.selectedOptions) option.selected = false;
1588
+ for (const option of source.selectedOptions) clone.options[option.index].selected = true;
1589
+ }
1590
+ for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
1591
+ clonedPasswordInput.value = "";
1592
+ }
1593
+ return new PageSnapshot(clonedElement, this.headSnapshot);
1369
1594
  }
1370
1595
  get headElement() {
1371
1596
  return this.headSnapshot.element;
@@ -1415,7 +1640,10 @@ const defaultOptions = {
1415
1640
  action: "advance",
1416
1641
  historyChanged: false,
1417
1642
  visitCachedSnapshot: () => {},
1418
- willRender: true
1643
+ willRender: true,
1644
+ updateHistory: true,
1645
+ shouldCacheSnapshot: true,
1646
+ acceptsStreamResponse: false
1419
1647
  };
1420
1648
 
1421
1649
  var SystemStatusCode;
@@ -1433,12 +1661,14 @@ class Visit {
1433
1661
  this.followedRedirect = false;
1434
1662
  this.historyChanged = false;
1435
1663
  this.scrolled = false;
1664
+ this.shouldCacheSnapshot = true;
1665
+ this.acceptsStreamResponse = false;
1436
1666
  this.snapshotCached = false;
1437
1667
  this.state = VisitState.initialized;
1438
1668
  this.delegate = delegate;
1439
1669
  this.location = location;
1440
1670
  this.restorationIdentifier = restorationIdentifier || uuid();
1441
- const {action: action, historyChanged: historyChanged, referrer: referrer, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender} = Object.assign(Object.assign({}, defaultOptions), options);
1671
+ const {action: action, historyChanged: historyChanged, referrer: referrer, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = Object.assign(Object.assign({}, defaultOptions), options);
1442
1672
  this.action = action;
1443
1673
  this.historyChanged = historyChanged;
1444
1674
  this.referrer = referrer;
@@ -1447,7 +1677,10 @@ class Visit {
1447
1677
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
1448
1678
  this.visitCachedSnapshot = visitCachedSnapshot;
1449
1679
  this.willRender = willRender;
1680
+ this.updateHistory = updateHistory;
1450
1681
  this.scrolled = !willRender;
1682
+ this.shouldCacheSnapshot = shouldCacheSnapshot;
1683
+ this.acceptsStreamResponse = acceptsStreamResponse;
1451
1684
  }
1452
1685
  get adapter() {
1453
1686
  return this.delegate.adapter;
@@ -1485,9 +1718,11 @@ class Visit {
1485
1718
  if (this.state == VisitState.started) {
1486
1719
  this.recordTimingMetric(TimingMetric.visitEnd);
1487
1720
  this.state = VisitState.completed;
1488
- this.adapter.visitCompleted(this);
1489
- this.delegate.visitCompleted(this);
1490
1721
  this.followRedirect();
1722
+ if (!this.followedRedirect) {
1723
+ this.adapter.visitCompleted(this);
1724
+ this.delegate.visitCompleted(this);
1725
+ }
1491
1726
  }
1492
1727
  }
1493
1728
  fail() {
@@ -1498,9 +1733,9 @@ class Visit {
1498
1733
  }
1499
1734
  changeHistory() {
1500
1735
  var _a;
1501
- if (!this.historyChanged) {
1736
+ if (!this.historyChanged && this.updateHistory) {
1502
1737
  const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
1503
- const method = this.getHistoryMethodForAction(actionForHistory);
1738
+ const method = getHistoryMethodForAction(actionForHistory);
1504
1739
  this.history.update(method, this.location, this.restorationIdentifier);
1505
1740
  this.historyChanged = true;
1506
1741
  }
@@ -1543,14 +1778,15 @@ class Visit {
1543
1778
  if (this.response) {
1544
1779
  const {statusCode: statusCode, responseHTML: responseHTML} = this.response;
1545
1780
  this.render((async () => {
1546
- this.cacheSnapshot();
1781
+ if (this.shouldCacheSnapshot) this.cacheSnapshot();
1547
1782
  if (this.view.renderPromise) await this.view.renderPromise;
1548
1783
  if (isSuccessful(statusCode) && responseHTML != null) {
1549
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
1784
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
1785
+ this.performScroll();
1550
1786
  this.adapter.visitRendered(this);
1551
1787
  this.complete();
1552
1788
  } else {
1553
- await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
1789
+ await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
1554
1790
  this.adapter.visitRendered(this);
1555
1791
  this.fail();
1556
1792
  }
@@ -1583,7 +1819,8 @@ class Visit {
1583
1819
  this.adapter.visitRendered(this);
1584
1820
  } else {
1585
1821
  if (this.view.renderPromise) await this.view.renderPromise;
1586
- await this.view.renderPage(snapshot, isPreview, this.willRender);
1822
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
1823
+ this.performScroll();
1587
1824
  this.adapter.visitRendered(this);
1588
1825
  if (!isPreview) {
1589
1826
  this.complete();
@@ -1606,14 +1843,20 @@ class Visit {
1606
1843
  if (this.isSamePage) {
1607
1844
  this.render((async () => {
1608
1845
  this.cacheSnapshot();
1846
+ this.performScroll();
1609
1847
  this.adapter.visitRendered(this);
1610
1848
  }));
1611
1849
  }
1612
1850
  }
1851
+ prepareHeadersForRequest(headers, request) {
1852
+ if (this.acceptsStreamResponse) {
1853
+ request.acceptResponseType(StreamMessage.contentType);
1854
+ }
1855
+ }
1613
1856
  requestStarted() {
1614
1857
  this.startRequest();
1615
1858
  }
1616
- requestPreventedHandlingResponse(request, response) {}
1859
+ requestPreventedHandlingResponse(_request, _response) {}
1617
1860
  async requestSucceededWithResponse(request, response) {
1618
1861
  const responseHTML = await response.responseHTML;
1619
1862
  const {redirected: redirected, statusCode: statusCode} = response;
@@ -1647,7 +1890,7 @@ class Visit {
1647
1890
  });
1648
1891
  }
1649
1892
  }
1650
- requestErrored(request, error) {
1893
+ requestErrored(_request, _error) {
1651
1894
  this.recordResponse({
1652
1895
  statusCode: SystemStatusCode.networkFailure,
1653
1896
  redirected: false
@@ -1657,7 +1900,7 @@ class Visit {
1657
1900
  this.finishRequest();
1658
1901
  }
1659
1902
  performScroll() {
1660
- if (!this.scrolled) {
1903
+ if (!this.scrolled && !this.view.forceReloaded) {
1661
1904
  if (this.action == "restore") {
1662
1905
  this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
1663
1906
  } else {
@@ -1724,7 +1967,6 @@ class Visit {
1724
1967
  }));
1725
1968
  await callback();
1726
1969
  delete this.frame;
1727
- this.performScroll();
1728
1970
  }
1729
1971
  cancelRender() {
1730
1972
  if (this.frame) {
@@ -1747,12 +1989,12 @@ class BrowserAdapter {
1747
1989
  this.session = session;
1748
1990
  }
1749
1991
  visitProposedToLocation(location, options) {
1750
- this.navigator.startVisit(location, uuid(), options);
1992
+ this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
1751
1993
  }
1752
1994
  visitStarted(visit) {
1995
+ this.location = visit.location;
1753
1996
  visit.loadCachedSnapshot();
1754
1997
  visit.issueRequest();
1755
- visit.changeHistory();
1756
1998
  visit.goToSamePageAnchor();
1757
1999
  }
1758
2000
  visitRequestStarted(visit) {
@@ -1771,27 +2013,32 @@ class BrowserAdapter {
1771
2013
  case SystemStatusCode.networkFailure:
1772
2014
  case SystemStatusCode.timeoutFailure:
1773
2015
  case SystemStatusCode.contentTypeMismatch:
1774
- return this.reload();
2016
+ return this.reload({
2017
+ reason: "request_failed",
2018
+ context: {
2019
+ statusCode: statusCode
2020
+ }
2021
+ });
1775
2022
 
1776
2023
  default:
1777
2024
  return visit.loadResponse();
1778
2025
  }
1779
2026
  }
1780
- visitRequestFinished(visit) {
2027
+ visitRequestFinished(_visit) {
1781
2028
  this.progressBar.setValue(1);
1782
2029
  this.hideVisitProgressBar();
1783
2030
  }
1784
- visitCompleted(visit) {}
1785
- pageInvalidated() {
1786
- this.reload();
2031
+ visitCompleted(_visit) {}
2032
+ pageInvalidated(reason) {
2033
+ this.reload(reason);
1787
2034
  }
1788
- visitFailed(visit) {}
1789
- visitRendered(visit) {}
1790
- formSubmissionStarted(formSubmission) {
2035
+ visitFailed(_visit) {}
2036
+ visitRendered(_visit) {}
2037
+ formSubmissionStarted(_formSubmission) {
1791
2038
  this.progressBar.setValue(0);
1792
2039
  this.showFormProgressBarAfterDelay();
1793
2040
  }
1794
- formSubmissionFinished(formSubmission) {
2041
+ formSubmissionFinished(_formSubmission) {
1795
2042
  this.progressBar.setValue(1);
1796
2043
  this.hideFormProgressBar();
1797
2044
  }
@@ -1817,8 +2064,12 @@ class BrowserAdapter {
1817
2064
  delete this.formProgressBarTimeout;
1818
2065
  }
1819
2066
  }
1820
- reload() {
1821
- window.location.reload();
2067
+ reload(reason) {
2068
+ dispatch("turbo:reload", {
2069
+ detail: reason
2070
+ });
2071
+ if (!this.location) return;
2072
+ window.location.href = this.location.toString();
1822
2073
  }
1823
2074
  get navigator() {
1824
2075
  return this.session.navigator;
@@ -1828,96 +2079,71 @@ class BrowserAdapter {
1828
2079
  class CacheObserver {
1829
2080
  constructor() {
1830
2081
  this.started = false;
1831
- }
1832
- start() {
1833
- if (!this.started) {
1834
- this.started = true;
1835
- addEventListener("turbo:before-cache", this.removeStaleElements, false);
1836
- }
1837
- }
1838
- stop() {
1839
- if (this.started) {
1840
- this.started = false;
1841
- removeEventListener("turbo:before-cache", this.removeStaleElements, false);
1842
- }
1843
- }
1844
- removeStaleElements() {
1845
- const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
1846
- for (const element of staleElements) {
1847
- element.remove();
1848
- }
1849
- }
1850
- }
1851
-
1852
- class FormSubmitObserver {
1853
- constructor(delegate) {
1854
- this.started = false;
1855
- this.submitCaptured = () => {
1856
- removeEventListener("submit", this.submitBubbled, false);
1857
- addEventListener("submit", this.submitBubbled, false);
1858
- };
1859
- this.submitBubbled = event => {
1860
- if (!event.defaultPrevented) {
1861
- const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1862
- const submitter = event.submitter || undefined;
1863
- if (form) {
1864
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
1865
- if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
1866
- event.preventDefault();
1867
- this.delegate.formSubmitted(form, submitter);
1868
- }
1869
- }
2082
+ this.removeStaleElements = _event => {
2083
+ const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
2084
+ for (const element of staleElements) {
2085
+ element.remove();
1870
2086
  }
1871
2087
  };
1872
- this.delegate = delegate;
1873
2088
  }
1874
2089
  start() {
1875
2090
  if (!this.started) {
1876
- addEventListener("submit", this.submitCaptured, true);
1877
2091
  this.started = true;
2092
+ addEventListener("turbo:before-cache", this.removeStaleElements, false);
1878
2093
  }
1879
2094
  }
1880
2095
  stop() {
1881
2096
  if (this.started) {
1882
- removeEventListener("submit", this.submitCaptured, true);
1883
2097
  this.started = false;
2098
+ removeEventListener("turbo:before-cache", this.removeStaleElements, false);
1884
2099
  }
1885
2100
  }
1886
2101
  }
1887
2102
 
1888
2103
  class FrameRedirector {
1889
- constructor(element) {
2104
+ constructor(session, element) {
2105
+ this.session = session;
1890
2106
  this.element = element;
1891
- this.linkInterceptor = new LinkInterceptor(this, element);
1892
- this.formInterceptor = new FormInterceptor(this, element);
2107
+ this.linkClickObserver = new LinkClickObserver(this, element);
2108
+ this.formSubmitObserver = new FormSubmitObserver(this, element);
1893
2109
  }
1894
2110
  start() {
1895
- this.linkInterceptor.start();
1896
- this.formInterceptor.start();
2111
+ this.linkClickObserver.start();
2112
+ this.formSubmitObserver.start();
1897
2113
  }
1898
2114
  stop() {
1899
- this.linkInterceptor.stop();
1900
- this.formInterceptor.stop();
2115
+ this.linkClickObserver.stop();
2116
+ this.formSubmitObserver.stop();
1901
2117
  }
1902
- shouldInterceptLinkClick(element, url) {
1903
- return this.shouldRedirect(element);
2118
+ willFollowLinkToLocation(element, location, event) {
2119
+ return this.shouldRedirect(element) && this.frameAllowsVisitingLocation(element, location, event);
1904
2120
  }
1905
- linkClickIntercepted(element, url) {
2121
+ followedLinkToLocation(element, url) {
1906
2122
  const frame = this.findFrameElement(element);
1907
2123
  if (frame) {
1908
- frame.delegate.linkClickIntercepted(element, url);
2124
+ frame.delegate.followedLinkToLocation(element, url);
1909
2125
  }
1910
2126
  }
1911
- shouldInterceptFormSubmission(element, submitter) {
1912
- return this.shouldSubmit(element, submitter);
2127
+ willSubmitForm(element, submitter) {
2128
+ return element.closest("turbo-frame") == null && this.shouldSubmit(element, submitter) && this.shouldRedirect(element, submitter);
1913
2129
  }
1914
- formSubmissionIntercepted(element, submitter) {
2130
+ formSubmitted(element, submitter) {
1915
2131
  const frame = this.findFrameElement(element, submitter);
1916
2132
  if (frame) {
1917
- frame.removeAttribute("reloadable");
1918
- frame.delegate.formSubmissionIntercepted(element, submitter);
2133
+ frame.delegate.formSubmitted(element, submitter);
1919
2134
  }
1920
2135
  }
2136
+ frameAllowsVisitingLocation(target, {href: url}, originalEvent) {
2137
+ const event = dispatch("turbo:click", {
2138
+ target: target,
2139
+ detail: {
2140
+ url: url,
2141
+ originalEvent: originalEvent
2142
+ },
2143
+ cancelable: true
2144
+ });
2145
+ return !event.defaultPrevented;
2146
+ }
1921
2147
  shouldSubmit(form, submitter) {
1922
2148
  var _a;
1923
2149
  const action = getAction(form, submitter);
@@ -1926,8 +2152,13 @@ class FrameRedirector {
1926
2152
  return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
1927
2153
  }
1928
2154
  shouldRedirect(element, submitter) {
1929
- const frame = this.findFrameElement(element, submitter);
1930
- return frame ? frame != element.closest("turbo-frame") : false;
2155
+ const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
2156
+ if (isNavigatable) {
2157
+ const frame = this.findFrameElement(element, submitter);
2158
+ return frame ? frame != element.closest("turbo-frame") : false;
2159
+ } else {
2160
+ return false;
2161
+ }
1931
2162
  }
1932
2163
  findFrameElement(element, submitter) {
1933
2164
  const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
@@ -1957,7 +2188,7 @@ class History {
1957
2188
  }
1958
2189
  }
1959
2190
  };
1960
- this.onPageLoad = async event => {
2191
+ this.onPageLoad = async _event => {
1961
2192
  await nextMicrotask();
1962
2193
  this.pageLoaded = true;
1963
2194
  };
@@ -2023,58 +2254,7 @@ class History {
2023
2254
  }
2024
2255
  }
2025
2256
 
2026
- class LinkClickObserver {
2027
- constructor(delegate) {
2028
- this.started = false;
2029
- this.clickCaptured = () => {
2030
- removeEventListener("click", this.clickBubbled, false);
2031
- addEventListener("click", this.clickBubbled, false);
2032
- };
2033
- this.clickBubbled = event => {
2034
- if (this.clickEventIsSignificant(event)) {
2035
- const target = event.composedPath && event.composedPath()[0] || event.target;
2036
- const link = this.findLinkFromClickTarget(target);
2037
- if (link) {
2038
- const location = this.getLocationForLink(link);
2039
- if (this.delegate.willFollowLinkToLocation(link, location)) {
2040
- event.preventDefault();
2041
- this.delegate.followedLinkToLocation(link, location);
2042
- }
2043
- }
2044
- }
2045
- };
2046
- this.delegate = delegate;
2047
- }
2048
- start() {
2049
- if (!this.started) {
2050
- addEventListener("click", this.clickCaptured, true);
2051
- this.started = true;
2052
- }
2053
- }
2054
- stop() {
2055
- if (this.started) {
2056
- removeEventListener("click", this.clickCaptured, true);
2057
- this.started = false;
2058
- }
2059
- }
2060
- clickEventIsSignificant(event) {
2061
- return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
2062
- }
2063
- findLinkFromClickTarget(target) {
2064
- if (target instanceof Element) {
2065
- return target.closest("a[href]:not([target^=_]):not([download])");
2066
- }
2067
- }
2068
- getLocationForLink(link) {
2069
- return expandURL(link.getAttribute("href") || "");
2070
- }
2071
- }
2072
-
2073
- function isAction(action) {
2074
- return action == "advance" || action == "replace" || action == "restore";
2075
- }
2076
-
2077
- class Navigator {
2257
+ class Navigator {
2078
2258
  constructor(delegate) {
2079
2259
  this.delegate = delegate;
2080
2260
  }
@@ -2088,6 +2268,7 @@ class Navigator {
2088
2268
  }
2089
2269
  }
2090
2270
  startVisit(locatable, restorationIdentifier, options = {}) {
2271
+ this.lastVisit = this.currentVisit;
2091
2272
  this.stop();
2092
2273
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
2093
2274
  referrer: this.location
@@ -2127,13 +2308,15 @@ class Navigator {
2127
2308
  if (formSubmission == this.formSubmission) {
2128
2309
  const responseHTML = await fetchResponse.responseHTML;
2129
2310
  if (responseHTML) {
2130
- if (formSubmission.method != FetchMethod.get) {
2311
+ const shouldCacheSnapshot = formSubmission.method == FetchMethod.get;
2312
+ if (!shouldCacheSnapshot) {
2131
2313
  this.view.clearSnapshotCache();
2132
2314
  }
2133
2315
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
2134
2316
  const action = this.getActionForFormSubmission(formSubmission);
2135
2317
  const visitOptions = {
2136
2318
  action: action,
2319
+ shouldCacheSnapshot: shouldCacheSnapshot,
2137
2320
  response: {
2138
2321
  statusCode: statusCode,
2139
2322
  responseHTML: responseHTML,
@@ -2149,9 +2332,9 @@ class Navigator {
2149
2332
  if (responseHTML) {
2150
2333
  const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2151
2334
  if (fetchResponse.serverError) {
2152
- await this.view.renderError(snapshot);
2335
+ await this.view.renderError(snapshot, this.currentVisit);
2153
2336
  } else {
2154
- await this.view.renderPage(snapshot);
2337
+ await this.view.renderPage(snapshot, false, true, this.currentVisit);
2155
2338
  }
2156
2339
  this.view.scrollToTop();
2157
2340
  this.view.clearSnapshotCache();
@@ -2172,10 +2355,12 @@ class Navigator {
2172
2355
  this.delegate.visitCompleted(visit);
2173
2356
  }
2174
2357
  locationWithActionIsSamePage(location, action) {
2358
+ var _a;
2175
2359
  const anchor = getAnchor(location);
2176
- const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2360
+ const lastLocation = ((_a = this.lastVisit) === null || _a === void 0 ? void 0 : _a.location) || this.view.lastRenderedLocation;
2361
+ const currentAnchor = getAnchor(lastLocation);
2177
2362
  const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2178
- return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
2363
+ return action !== "replace" && getRequestURL(location) === getRequestURL(lastLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
2179
2364
  }
2180
2365
  visitScrolledToSamePageLocation(oldURL, newURL) {
2181
2366
  this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
@@ -2283,6 +2468,31 @@ class ScrollObserver {
2283
2468
  }
2284
2469
  }
2285
2470
 
2471
+ class StreamMessageRenderer {
2472
+ render({fragment: fragment}) {
2473
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => document.documentElement.appendChild(fragment)));
2474
+ }
2475
+ enteringBardo(currentPermanentElement, newPermanentElement) {
2476
+ newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
2477
+ }
2478
+ leavingBardo() {}
2479
+ }
2480
+
2481
+ function getPermanentElementMapForFragment(fragment) {
2482
+ const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
2483
+ const permanentElementMap = {};
2484
+ for (const permanentElementInDocument of permanentElementsInDocument) {
2485
+ const {id: id} = permanentElementInDocument;
2486
+ for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
2487
+ const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
2488
+ if (elementInStream) {
2489
+ permanentElementMap[id] = [ permanentElementInDocument, elementInStream ];
2490
+ }
2491
+ }
2492
+ }
2493
+ return permanentElementMap;
2494
+ }
2495
+
2286
2496
  class StreamObserver {
2287
2497
  constructor(delegate) {
2288
2498
  this.sources = new Set;
@@ -2335,7 +2545,7 @@ class StreamObserver {
2335
2545
  }
2336
2546
  }
2337
2547
  receiveMessageHTML(html) {
2338
- this.delegate.receivedMessageFromStream(new StreamMessage(html));
2548
+ this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
2339
2549
  }
2340
2550
  }
2341
2551
 
@@ -2354,20 +2564,24 @@ function fetchResponseIsStream(response) {
2354
2564
  }
2355
2565
 
2356
2566
  class ErrorRenderer extends Renderer {
2567
+ static renderElement(currentElement, newElement) {
2568
+ const {documentElement: documentElement, body: body} = document;
2569
+ documentElement.replaceChild(newElement, body);
2570
+ }
2357
2571
  async render() {
2358
2572
  this.replaceHeadAndBody();
2359
2573
  this.activateScriptElements();
2360
2574
  }
2361
2575
  replaceHeadAndBody() {
2362
- const {documentElement: documentElement, head: head, body: body} = document;
2576
+ const {documentElement: documentElement, head: head} = document;
2363
2577
  documentElement.replaceChild(this.newHead, head);
2364
- documentElement.replaceChild(this.newElement, body);
2578
+ this.renderElement(this.currentElement, this.newElement);
2365
2579
  }
2366
2580
  activateScriptElements() {
2367
2581
  for (const replaceableElement of this.scriptElements) {
2368
2582
  const parentNode = replaceableElement.parentNode;
2369
2583
  if (parentNode) {
2370
- const element = this.createScriptElement(replaceableElement);
2584
+ const element = activateScriptElement(replaceableElement);
2371
2585
  parentNode.replaceChild(element, replaceableElement);
2372
2586
  }
2373
2587
  }
@@ -2376,16 +2590,35 @@ class ErrorRenderer extends Renderer {
2376
2590
  return this.newSnapshot.headSnapshot.element;
2377
2591
  }
2378
2592
  get scriptElements() {
2379
- return [ ...document.documentElement.querySelectorAll("script") ];
2593
+ return document.documentElement.querySelectorAll("script");
2380
2594
  }
2381
2595
  }
2382
2596
 
2383
2597
  class PageRenderer extends Renderer {
2598
+ static renderElement(currentElement, newElement) {
2599
+ if (document.body && newElement instanceof HTMLBodyElement) {
2600
+ document.body.replaceWith(newElement);
2601
+ } else {
2602
+ document.documentElement.appendChild(newElement);
2603
+ }
2604
+ }
2384
2605
  get shouldRender() {
2385
2606
  return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
2386
2607
  }
2387
- prepareToRender() {
2388
- this.mergeHead();
2608
+ get reloadReason() {
2609
+ if (!this.newSnapshot.isVisitable) {
2610
+ return {
2611
+ reason: "turbo_visit_control_is_reload"
2612
+ };
2613
+ }
2614
+ if (!this.trackedElementsAreIdentical) {
2615
+ return {
2616
+ reason: "tracked_element_mismatch"
2617
+ };
2618
+ }
2619
+ }
2620
+ async prepareToRender() {
2621
+ await this.mergeHead();
2389
2622
  }
2390
2623
  async render() {
2391
2624
  if (this.willRender) {
@@ -2407,11 +2640,12 @@ class PageRenderer extends Renderer {
2407
2640
  get newElement() {
2408
2641
  return this.newSnapshot.element;
2409
2642
  }
2410
- mergeHead() {
2411
- this.copyNewHeadStylesheetElements();
2643
+ async mergeHead() {
2644
+ const newStylesheetElements = this.copyNewHeadStylesheetElements();
2412
2645
  this.copyNewHeadScriptElements();
2413
2646
  this.removeCurrentHeadProvisionalElements();
2414
2647
  this.copyNewHeadProvisionalElements();
2648
+ await newStylesheetElements;
2415
2649
  }
2416
2650
  replaceBody() {
2417
2651
  this.preservingPermanentElements((() => {
@@ -2422,14 +2656,17 @@ class PageRenderer extends Renderer {
2422
2656
  get trackedElementsAreIdentical() {
2423
2657
  return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
2424
2658
  }
2425
- copyNewHeadStylesheetElements() {
2659
+ async copyNewHeadStylesheetElements() {
2660
+ const loadingElements = [];
2426
2661
  for (const element of this.newHeadStylesheetElements) {
2662
+ loadingElements.push(waitForLoad(element));
2427
2663
  document.head.appendChild(element);
2428
2664
  }
2665
+ await Promise.all(loadingElements);
2429
2666
  }
2430
2667
  copyNewHeadScriptElements() {
2431
2668
  for (const element of this.newHeadScriptElements) {
2432
- document.head.appendChild(this.createScriptElement(element));
2669
+ document.head.appendChild(activateScriptElement(element));
2433
2670
  }
2434
2671
  }
2435
2672
  removeCurrentHeadProvisionalElements() {
@@ -2448,16 +2685,12 @@ class PageRenderer extends Renderer {
2448
2685
  }
2449
2686
  activateNewBodyScriptElements() {
2450
2687
  for (const inertScriptElement of this.newBodyScriptElements) {
2451
- const activatedScriptElement = this.createScriptElement(inertScriptElement);
2688
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
2452
2689
  inertScriptElement.replaceWith(activatedScriptElement);
2453
2690
  }
2454
2691
  }
2455
2692
  assignNewBody() {
2456
- if (document.body && this.newElement instanceof HTMLBodyElement) {
2457
- document.body.replaceWith(this.newElement);
2458
- } else {
2459
- document.documentElement.appendChild(this.newElement);
2460
- }
2693
+ this.renderElement(this.currentElement, this.newElement);
2461
2694
  }
2462
2695
  get newHeadStylesheetElements() {
2463
2696
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -2525,13 +2758,20 @@ class PageView extends View {
2525
2758
  super(...arguments);
2526
2759
  this.snapshotCache = new SnapshotCache(10);
2527
2760
  this.lastRenderedLocation = new URL(location.href);
2761
+ this.forceReloaded = false;
2528
2762
  }
2529
- renderPage(snapshot, isPreview = false, willRender = true) {
2530
- const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
2763
+ renderPage(snapshot, isPreview = false, willRender = true, visit) {
2764
+ const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
2765
+ if (!renderer.shouldRender) {
2766
+ this.forceReloaded = true;
2767
+ } else {
2768
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2769
+ }
2531
2770
  return this.render(renderer);
2532
2771
  }
2533
- renderError(snapshot) {
2534
- const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2772
+ renderError(snapshot, visit) {
2773
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2774
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
2535
2775
  return this.render(renderer);
2536
2776
  }
2537
2777
  clearSnapshotCache() {
@@ -2558,34 +2798,81 @@ class PageView extends View {
2558
2798
  }
2559
2799
  }
2560
2800
 
2801
+ class Preloader {
2802
+ constructor(delegate) {
2803
+ this.selector = "a[data-turbo-preload]";
2804
+ this.delegate = delegate;
2805
+ }
2806
+ get snapshotCache() {
2807
+ return this.delegate.navigator.view.snapshotCache;
2808
+ }
2809
+ start() {
2810
+ if (document.readyState === "loading") {
2811
+ return document.addEventListener("DOMContentLoaded", (() => {
2812
+ this.preloadOnLoadLinksForView(document.body);
2813
+ }));
2814
+ } else {
2815
+ this.preloadOnLoadLinksForView(document.body);
2816
+ }
2817
+ }
2818
+ preloadOnLoadLinksForView(element) {
2819
+ for (const link of element.querySelectorAll(this.selector)) {
2820
+ this.preloadURL(link);
2821
+ }
2822
+ }
2823
+ async preloadURL(link) {
2824
+ const location = new URL(link.href);
2825
+ if (this.snapshotCache.has(location)) {
2826
+ return;
2827
+ }
2828
+ try {
2829
+ const response = await fetch(location.toString(), {
2830
+ headers: {
2831
+ "VND.PREFETCH": "true",
2832
+ Accept: "text/html"
2833
+ }
2834
+ });
2835
+ const responseText = await response.text();
2836
+ const snapshot = PageSnapshot.fromHTMLString(responseText);
2837
+ this.snapshotCache.put(location, snapshot);
2838
+ } catch (_) {}
2839
+ }
2840
+ }
2841
+
2561
2842
  class Session {
2562
2843
  constructor() {
2563
2844
  this.navigator = new Navigator(this);
2564
2845
  this.history = new History(this);
2846
+ this.preloader = new Preloader(this);
2565
2847
  this.view = new PageView(this, document.documentElement);
2566
2848
  this.adapter = new BrowserAdapter(this);
2567
2849
  this.pageObserver = new PageObserver(this);
2568
2850
  this.cacheObserver = new CacheObserver;
2569
- this.linkClickObserver = new LinkClickObserver(this);
2570
- this.formSubmitObserver = new FormSubmitObserver(this);
2851
+ this.linkClickObserver = new LinkClickObserver(this, window);
2852
+ this.formSubmitObserver = new FormSubmitObserver(this, document);
2571
2853
  this.scrollObserver = new ScrollObserver(this);
2572
2854
  this.streamObserver = new StreamObserver(this);
2573
- this.frameRedirector = new FrameRedirector(document.documentElement);
2855
+ this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
2856
+ this.frameRedirector = new FrameRedirector(this, document.documentElement);
2857
+ this.streamMessageRenderer = new StreamMessageRenderer;
2574
2858
  this.drive = true;
2575
2859
  this.enabled = true;
2576
2860
  this.progressBarDelay = 500;
2577
2861
  this.started = false;
2862
+ this.formMode = "on";
2578
2863
  }
2579
2864
  start() {
2580
2865
  if (!this.started) {
2581
2866
  this.pageObserver.start();
2582
2867
  this.cacheObserver.start();
2868
+ this.formLinkClickObserver.start();
2583
2869
  this.linkClickObserver.start();
2584
2870
  this.formSubmitObserver.start();
2585
2871
  this.scrollObserver.start();
2586
2872
  this.streamObserver.start();
2587
2873
  this.frameRedirector.start();
2588
2874
  this.history.start();
2875
+ this.preloader.start();
2589
2876
  this.started = true;
2590
2877
  this.enabled = true;
2591
2878
  }
@@ -2597,6 +2884,7 @@ class Session {
2597
2884
  if (this.started) {
2598
2885
  this.pageObserver.stop();
2599
2886
  this.cacheObserver.stop();
2887
+ this.formLinkClickObserver.stop();
2600
2888
  this.linkClickObserver.stop();
2601
2889
  this.formSubmitObserver.stop();
2602
2890
  this.scrollObserver.stop();
@@ -2610,7 +2898,13 @@ class Session {
2610
2898
  this.adapter = adapter;
2611
2899
  }
2612
2900
  visit(location, options = {}) {
2613
- this.navigator.proposeVisit(expandURL(location), options);
2901
+ const frameElement = options.frame ? document.getElementById(options.frame) : null;
2902
+ if (frameElement instanceof FrameElement) {
2903
+ frameElement.src = location.toString();
2904
+ frameElement.loaded;
2905
+ } else {
2906
+ this.navigator.proposeVisit(expandURL(location), options);
2907
+ }
2614
2908
  }
2615
2909
  connectStreamSource(source) {
2616
2910
  this.streamObserver.connectStreamSource(source);
@@ -2619,7 +2913,7 @@ class Session {
2619
2913
  this.streamObserver.disconnectStreamSource(source);
2620
2914
  }
2621
2915
  renderStreamMessage(message) {
2622
- document.documentElement.appendChild(StreamMessage.wrap(message).fragment);
2916
+ this.streamMessageRenderer.render(StreamMessage.wrap(message));
2623
2917
  }
2624
2918
  clearCache() {
2625
2919
  this.view.clearSnapshotCache();
@@ -2627,6 +2921,9 @@ class Session {
2627
2921
  setProgressBarDelay(delay) {
2628
2922
  this.progressBarDelay = delay;
2629
2923
  }
2924
+ setFormMode(mode) {
2925
+ this.formMode = mode;
2926
+ }
2630
2927
  get location() {
2631
2928
  return this.history.location;
2632
2929
  }
@@ -2640,7 +2937,9 @@ class Session {
2640
2937
  historyChanged: true
2641
2938
  });
2642
2939
  } else {
2643
- this.adapter.pageInvalidated();
2940
+ this.adapter.pageInvalidated({
2941
+ reason: "turbo_disabled"
2942
+ });
2644
2943
  }
2645
2944
  }
2646
2945
  scrollPositionChanged(position) {
@@ -2648,41 +2947,21 @@ class Session {
2648
2947
  scrollPosition: position
2649
2948
  });
2650
2949
  }
2651
- willFollowLinkToLocation(link, location) {
2652
- return this.elementDriveEnabled(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location);
2950
+ willSubmitFormLinkToLocation(link, location) {
2951
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
2952
+ }
2953
+ submittedFormLinkToLocation() {}
2954
+ willFollowLinkToLocation(link, location, event) {
2955
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
2653
2956
  }
2654
2957
  followedLinkToLocation(link, location) {
2655
2958
  const action = this.getActionForLink(link);
2656
- this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, {
2657
- action: action
2959
+ const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
2960
+ this.visit(location.href, {
2961
+ action: action,
2962
+ acceptsStreamResponse: acceptsStreamResponse
2658
2963
  });
2659
2964
  }
2660
- convertLinkWithMethodClickToFormSubmission(link) {
2661
- const linkMethod = link.getAttribute("data-turbo-method");
2662
- if (linkMethod) {
2663
- const form = document.createElement("form");
2664
- form.method = linkMethod;
2665
- form.action = link.getAttribute("href") || "undefined";
2666
- form.hidden = true;
2667
- if (link.hasAttribute("data-turbo-confirm")) {
2668
- form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
2669
- }
2670
- const frame = this.getTargetFrameForLink(link);
2671
- if (frame) {
2672
- form.setAttribute("data-turbo-frame", frame);
2673
- form.addEventListener("turbo:submit-start", (() => form.remove()));
2674
- } else {
2675
- form.addEventListener("submit", (() => form.remove()));
2676
- }
2677
- document.body.appendChild(form);
2678
- return dispatch("submit", {
2679
- cancelable: true,
2680
- target: form
2681
- });
2682
- } else {
2683
- return false;
2684
- }
2685
- }
2686
2965
  allowsVisitingLocationWithAction(location, action) {
2687
2966
  return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
2688
2967
  }
@@ -2691,12 +2970,16 @@ class Session {
2691
2970
  this.adapter.visitProposedToLocation(location, options);
2692
2971
  }
2693
2972
  visitStarted(visit) {
2973
+ if (!visit.acceptsStreamResponse) {
2974
+ markAsBusy(document.documentElement);
2975
+ }
2694
2976
  extendURLWithDeprecatedProperties(visit.location);
2695
2977
  if (!visit.silent) {
2696
2978
  this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
2697
2979
  }
2698
2980
  }
2699
2981
  visitCompleted(visit) {
2982
+ clearBusyState(document.documentElement);
2700
2983
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2701
2984
  }
2702
2985
  locationWithActionIsSamePage(location, action) {
@@ -2707,7 +2990,7 @@ class Session {
2707
2990
  }
2708
2991
  willSubmitForm(form, submitter) {
2709
2992
  const action = getAction(form, submitter);
2710
- return this.elementDriveEnabled(form) && (!submitter || this.elementDriveEnabled(submitter)) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
2993
+ return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
2711
2994
  }
2712
2995
  formSubmitted(form, submitter) {
2713
2996
  this.navigator.submitForm(form, submitter);
@@ -2731,16 +3014,23 @@ class Session {
2731
3014
  this.notifyApplicationBeforeCachingSnapshot();
2732
3015
  }
2733
3016
  }
2734
- allowsImmediateRender({element: element}, resume) {
2735
- const event = this.notifyApplicationBeforeRender(element, resume);
2736
- return !event.defaultPrevented;
3017
+ allowsImmediateRender({element: element}, options) {
3018
+ const event = this.notifyApplicationBeforeRender(element, options);
3019
+ const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
3020
+ if (this.view.renderer && render) {
3021
+ this.view.renderer.renderElement = render;
3022
+ }
3023
+ return !defaultPrevented;
2737
3024
  }
2738
- viewRenderedSnapshot(snapshot, isPreview) {
3025
+ viewRenderedSnapshot(_snapshot, _isPreview) {
2739
3026
  this.view.lastRenderedLocation = this.history.location;
2740
3027
  this.notifyApplicationAfterRender();
2741
3028
  }
2742
- viewInvalidated() {
2743
- this.adapter.pageInvalidated();
3029
+ preloadOnLoadLinksForView(element) {
3030
+ this.preloader.preloadOnLoadLinksForView(element);
3031
+ }
3032
+ viewInvalidated(reason) {
3033
+ this.adapter.pageInvalidated(reason);
2744
3034
  }
2745
3035
  frameLoaded(frame) {
2746
3036
  this.notifyApplicationAfterFrameLoad(frame);
@@ -2748,19 +3038,20 @@ class Session {
2748
3038
  frameRendered(fetchResponse, frame) {
2749
3039
  this.notifyApplicationAfterFrameRender(fetchResponse, frame);
2750
3040
  }
2751
- applicationAllowsFollowingLinkToLocation(link, location) {
2752
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
3041
+ applicationAllowsFollowingLinkToLocation(link, location, ev) {
3042
+ const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
2753
3043
  return !event.defaultPrevented;
2754
3044
  }
2755
3045
  applicationAllowsVisitingLocation(location) {
2756
3046
  const event = this.notifyApplicationBeforeVisitingLocation(location);
2757
3047
  return !event.defaultPrevented;
2758
3048
  }
2759
- notifyApplicationAfterClickingLinkToLocation(link, location) {
3049
+ notifyApplicationAfterClickingLinkToLocation(link, location, event) {
2760
3050
  return dispatch("turbo:click", {
2761
3051
  target: link,
2762
3052
  detail: {
2763
- url: location.href
3053
+ url: location.href,
3054
+ originalEvent: event
2764
3055
  },
2765
3056
  cancelable: true
2766
3057
  });
@@ -2774,7 +3065,6 @@ class Session {
2774
3065
  });
2775
3066
  }
2776
3067
  notifyApplicationAfterVisitingLocation(location, action) {
2777
- markAsBusy(document.documentElement);
2778
3068
  return dispatch("turbo:visit", {
2779
3069
  detail: {
2780
3070
  url: location.href,
@@ -2785,12 +3075,11 @@ class Session {
2785
3075
  notifyApplicationBeforeCachingSnapshot() {
2786
3076
  return dispatch("turbo:before-cache");
2787
3077
  }
2788
- notifyApplicationBeforeRender(newBody, resume) {
3078
+ notifyApplicationBeforeRender(newBody, options) {
2789
3079
  return dispatch("turbo:before-render", {
2790
- detail: {
2791
- newBody: newBody,
2792
- resume: resume
2793
- },
3080
+ detail: Object.assign({
3081
+ newBody: newBody
3082
+ }, options),
2794
3083
  cancelable: true
2795
3084
  });
2796
3085
  }
@@ -2798,7 +3087,6 @@ class Session {
2798
3087
  return dispatch("turbo:render");
2799
3088
  }
2800
3089
  notifyApplicationAfterPageLoad(timing = {}) {
2801
- clearBusyState(document.documentElement);
2802
3090
  return dispatch("turbo:load", {
2803
3091
  detail: {
2804
3092
  url: this.location.href,
@@ -2826,9 +3114,22 @@ class Session {
2826
3114
  cancelable: true
2827
3115
  });
2828
3116
  }
2829
- elementDriveEnabled(element) {
2830
- const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
2831
- if (this.drive) {
3117
+ submissionIsNavigatable(form, submitter) {
3118
+ if (this.formMode == "off") {
3119
+ return false;
3120
+ } else {
3121
+ const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
3122
+ if (this.formMode == "optin") {
3123
+ return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null;
3124
+ } else {
3125
+ return submitterIsNavigatable && this.elementIsNavigatable(form);
3126
+ }
3127
+ }
3128
+ }
3129
+ elementIsNavigatable(element) {
3130
+ const container = element.closest("[data-turbo]");
3131
+ const withinFrame = element.closest("turbo-frame");
3132
+ if (this.drive || withinFrame) {
2832
3133
  if (container) {
2833
3134
  return container.getAttribute("data-turbo") != "false";
2834
3135
  } else {
@@ -2846,17 +3147,6 @@ class Session {
2846
3147
  const action = link.getAttribute("data-turbo-action");
2847
3148
  return isAction(action) ? action : "advance";
2848
3149
  }
2849
- getTargetFrameForLink(link) {
2850
- const frame = link.getAttribute("data-turbo-frame");
2851
- if (frame) {
2852
- return frame;
2853
- } else {
2854
- const container = link.closest("turbo-frame");
2855
- if (container) {
2856
- return container.id;
2857
- }
2858
- }
2859
- }
2860
3150
  get snapshot() {
2861
3151
  return this.view.snapshot;
2862
3152
  }
@@ -2874,8 +3164,63 @@ const deprecatedLocationPropertyDescriptors = {
2874
3164
  }
2875
3165
  };
2876
3166
 
3167
+ class Cache {
3168
+ constructor(session) {
3169
+ this.session = session;
3170
+ }
3171
+ clear() {
3172
+ this.session.clearCache();
3173
+ }
3174
+ resetCacheControl() {
3175
+ this.setCacheControl("");
3176
+ }
3177
+ exemptPageFromCache() {
3178
+ this.setCacheControl("no-cache");
3179
+ }
3180
+ exemptPageFromPreview() {
3181
+ this.setCacheControl("no-preview");
3182
+ }
3183
+ setCacheControl(value) {
3184
+ setMetaContent("turbo-cache-control", value);
3185
+ }
3186
+ }
3187
+
3188
+ const StreamActions = {
3189
+ after() {
3190
+ this.targetElements.forEach((e => {
3191
+ var _a;
3192
+ return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
3193
+ }));
3194
+ },
3195
+ append() {
3196
+ this.removeDuplicateTargetChildren();
3197
+ this.targetElements.forEach((e => e.append(this.templateContent)));
3198
+ },
3199
+ before() {
3200
+ this.targetElements.forEach((e => {
3201
+ var _a;
3202
+ return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
3203
+ }));
3204
+ },
3205
+ prepend() {
3206
+ this.removeDuplicateTargetChildren();
3207
+ this.targetElements.forEach((e => e.prepend(this.templateContent)));
3208
+ },
3209
+ remove() {
3210
+ this.targetElements.forEach((e => e.remove()));
3211
+ },
3212
+ replace() {
3213
+ this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3214
+ },
3215
+ update() {
3216
+ this.targetElements.forEach((e => e.replaceChildren(this.templateContent)));
3217
+ }
3218
+ };
3219
+
2877
3220
  const session = new Session;
2878
3221
 
3222
+ const cache = new Cache(session);
3223
+
2879
3224
  const {navigator: navigator$1} = session;
2880
3225
 
2881
3226
  function start() {
@@ -2903,6 +3248,7 @@ function renderStreamMessage(message) {
2903
3248
  }
2904
3249
 
2905
3250
  function clearCache() {
3251
+ console.warn("Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
2906
3252
  session.clearCache();
2907
3253
  }
2908
3254
 
@@ -2914,12 +3260,18 @@ function setConfirmMethod(confirmMethod) {
2914
3260
  FormSubmission.confirmMethod = confirmMethod;
2915
3261
  }
2916
3262
 
3263
+ function setFormMode(mode) {
3264
+ session.setFormMode(mode);
3265
+ }
3266
+
2917
3267
  var Turbo = Object.freeze({
2918
3268
  __proto__: null,
2919
3269
  navigator: navigator$1,
2920
3270
  session: session,
3271
+ cache: cache,
2921
3272
  PageRenderer: PageRenderer,
2922
3273
  PageSnapshot: PageSnapshot,
3274
+ FrameRenderer: FrameRenderer,
2923
3275
  start: start,
2924
3276
  registerAdapter: registerAdapter,
2925
3277
  visit: visit,
@@ -2928,41 +3280,55 @@ var Turbo = Object.freeze({
2928
3280
  renderStreamMessage: renderStreamMessage,
2929
3281
  clearCache: clearCache,
2930
3282
  setProgressBarDelay: setProgressBarDelay,
2931
- setConfirmMethod: setConfirmMethod
3283
+ setConfirmMethod: setConfirmMethod,
3284
+ setFormMode: setFormMode,
3285
+ StreamActions: StreamActions
2932
3286
  });
2933
3287
 
2934
3288
  class FrameController {
2935
3289
  constructor(element) {
2936
- this.fetchResponseLoaded = fetchResponse => {};
3290
+ this.fetchResponseLoaded = _fetchResponse => {};
2937
3291
  this.currentFetchRequest = null;
2938
3292
  this.resolveVisitPromise = () => {};
2939
3293
  this.connected = false;
2940
3294
  this.hasBeenLoaded = false;
2941
- this.settingSourceURL = false;
3295
+ this.ignoredAttributes = new Set;
3296
+ this.action = null;
3297
+ this.visitCachedSnapshot = ({element: element}) => {
3298
+ const frame = element.querySelector("#" + this.element.id);
3299
+ if (frame && this.previousFrameElement) {
3300
+ frame.replaceChildren(...this.previousFrameElement.children);
3301
+ }
3302
+ delete this.previousFrameElement;
3303
+ };
2942
3304
  this.element = element;
2943
3305
  this.view = new FrameView(this, this.element);
2944
3306
  this.appearanceObserver = new AppearanceObserver(this, this.element);
2945
- this.linkInterceptor = new LinkInterceptor(this, this.element);
2946
- this.formInterceptor = new FormInterceptor(this, this.element);
3307
+ this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);
3308
+ this.linkClickObserver = new LinkClickObserver(this, this.element);
3309
+ this.restorationIdentifier = uuid();
3310
+ this.formSubmitObserver = new FormSubmitObserver(this, this.element);
2947
3311
  }
2948
3312
  connect() {
2949
3313
  if (!this.connected) {
2950
3314
  this.connected = true;
2951
- this.reloadable = false;
2952
3315
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2953
3316
  this.appearanceObserver.start();
3317
+ } else {
3318
+ this.loadSourceURL();
2954
3319
  }
2955
- this.linkInterceptor.start();
2956
- this.formInterceptor.start();
2957
- this.sourceURLChanged();
3320
+ this.formLinkClickObserver.start();
3321
+ this.linkClickObserver.start();
3322
+ this.formSubmitObserver.start();
2958
3323
  }
2959
3324
  }
2960
3325
  disconnect() {
2961
3326
  if (this.connected) {
2962
3327
  this.connected = false;
2963
3328
  this.appearanceObserver.stop();
2964
- this.linkInterceptor.stop();
2965
- this.formInterceptor.stop();
3329
+ this.formLinkClickObserver.stop();
3330
+ this.linkClickObserver.stop();
3331
+ this.formSubmitObserver.stop();
2966
3332
  }
2967
3333
  }
2968
3334
  disabledChanged() {
@@ -2971,10 +3337,18 @@ class FrameController {
2971
3337
  }
2972
3338
  }
2973
3339
  sourceURLChanged() {
3340
+ if (this.isIgnoringChangesTo("src")) return;
3341
+ if (this.element.isConnected) {
3342
+ this.complete = false;
3343
+ }
2974
3344
  if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
2975
3345
  this.loadSourceURL();
2976
3346
  }
2977
3347
  }
3348
+ completeChanged() {
3349
+ if (this.isIgnoringChangesTo("complete")) return;
3350
+ this.loadSourceURL();
3351
+ }
2978
3352
  loadingStyleChanged() {
2979
3353
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2980
3354
  this.appearanceObserver.start();
@@ -2984,20 +3358,11 @@ class FrameController {
2984
3358
  }
2985
3359
  }
2986
3360
  async loadSourceURL() {
2987
- if (!this.settingSourceURL && this.enabled && this.isActive && (this.reloadable || this.sourceURL != this.currentURL)) {
2988
- const previousURL = this.currentURL;
2989
- this.currentURL = this.sourceURL;
2990
- if (this.sourceURL) {
2991
- try {
2992
- this.element.loaded = this.visit(expandURL(this.sourceURL));
2993
- this.appearanceObserver.stop();
2994
- await this.element.loaded;
2995
- this.hasBeenLoaded = true;
2996
- } catch (error) {
2997
- this.currentURL = previousURL;
2998
- throw error;
2999
- }
3000
- }
3361
+ if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
3362
+ this.element.loaded = this.visit(expandURL(this.sourceURL));
3363
+ this.appearanceObserver.stop();
3364
+ await this.element.loaded;
3365
+ this.hasBeenLoaded = true;
3001
3366
  }
3002
3367
  }
3003
3368
  async loadResponse(fetchResponse) {
@@ -3008,13 +3373,21 @@ class FrameController {
3008
3373
  const html = await fetchResponse.responseHTML;
3009
3374
  if (html) {
3010
3375
  const {body: body} = parseHTMLDocument(html);
3011
- const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
3012
- const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
3013
- if (this.view.renderPromise) await this.view.renderPromise;
3014
- await this.view.render(renderer);
3015
- session.frameRendered(fetchResponse, this.element);
3016
- session.frameLoaded(this.element);
3017
- this.fetchResponseLoaded(fetchResponse);
3376
+ const newFrameElement = await this.extractForeignFrameElement(body);
3377
+ if (newFrameElement) {
3378
+ const snapshot = new Snapshot(newFrameElement);
3379
+ const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
3380
+ if (this.view.renderPromise) await this.view.renderPromise;
3381
+ this.changeHistory();
3382
+ await this.view.render(renderer);
3383
+ this.complete = true;
3384
+ session.frameRendered(fetchResponse, this.element);
3385
+ session.frameLoaded(this.element);
3386
+ this.fetchResponseLoaded(fetchResponse);
3387
+ } else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3388
+ console.warn(`A matching frame for #${this.element.id} was missing from the response, transforming into full-page Visit.`);
3389
+ this.visitResponse(fetchResponse.response);
3390
+ }
3018
3391
  }
3019
3392
  } catch (error) {
3020
3393
  console.error(error);
@@ -3023,55 +3396,61 @@ class FrameController {
3023
3396
  this.fetchResponseLoaded = () => {};
3024
3397
  }
3025
3398
  }
3026
- elementAppearedInViewport(element) {
3399
+ elementAppearedInViewport(_element) {
3027
3400
  this.loadSourceURL();
3028
3401
  }
3029
- shouldInterceptLinkClick(element, url) {
3030
- if (element.hasAttribute("data-turbo-method")) {
3031
- return false;
3032
- } else {
3033
- return this.shouldInterceptNavigation(element);
3034
- }
3402
+ willSubmitFormLinkToLocation(link) {
3403
+ return link.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(link);
3404
+ }
3405
+ submittedFormLinkToLocation(link, _location, form) {
3406
+ const frame = this.findFrameElement(link);
3407
+ if (frame) form.setAttribute("data-turbo-frame", frame.id);
3035
3408
  }
3036
- linkClickIntercepted(element, url) {
3037
- this.reloadable = true;
3038
- this.navigateFrame(element, url);
3409
+ willFollowLinkToLocation(element, location, event) {
3410
+ return this.shouldInterceptNavigation(element) && this.frameAllowsVisitingLocation(element, location, event);
3039
3411
  }
3040
- shouldInterceptFormSubmission(element, submitter) {
3041
- return this.shouldInterceptNavigation(element, submitter);
3412
+ followedLinkToLocation(element, location) {
3413
+ this.navigateFrame(element, location.href);
3042
3414
  }
3043
- formSubmissionIntercepted(element, submitter) {
3415
+ willSubmitForm(element, submitter) {
3416
+ return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
3417
+ }
3418
+ formSubmitted(element, submitter) {
3044
3419
  if (this.formSubmission) {
3045
3420
  this.formSubmission.stop();
3046
3421
  }
3047
- this.reloadable = false;
3048
3422
  this.formSubmission = new FormSubmission(this, element, submitter);
3049
3423
  const {fetchRequest: fetchRequest} = this.formSubmission;
3050
3424
  this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3051
3425
  this.formSubmission.start();
3052
3426
  }
3053
3427
  prepareHeadersForRequest(headers, request) {
3428
+ var _a;
3054
3429
  headers["Turbo-Frame"] = this.id;
3430
+ if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3431
+ request.acceptResponseType(StreamMessage.contentType);
3432
+ }
3055
3433
  }
3056
- requestStarted(request) {
3434
+ requestStarted(_request) {
3057
3435
  markAsBusy(this.element);
3058
3436
  }
3059
- requestPreventedHandlingResponse(request, response) {
3437
+ requestPreventedHandlingResponse(_request, _response) {
3060
3438
  this.resolveVisitPromise();
3061
3439
  }
3062
3440
  async requestSucceededWithResponse(request, response) {
3063
3441
  await this.loadResponse(response);
3064
3442
  this.resolveVisitPromise();
3065
3443
  }
3066
- requestFailedWithResponse(request, response) {
3444
+ async requestFailedWithResponse(request, response) {
3067
3445
  console.error(response);
3446
+ await this.loadResponse(response);
3068
3447
  this.resolveVisitPromise();
3069
3448
  }
3070
3449
  requestErrored(request, error) {
3071
3450
  console.error(error);
3072
3451
  this.resolveVisitPromise();
3073
3452
  }
3074
- requestFinished(request) {
3453
+ requestFinished(_request) {
3075
3454
  clearBusyState(this.element);
3076
3455
  }
3077
3456
  formSubmissionStarted({formElement: formElement}) {
@@ -3091,11 +3470,28 @@ class FrameController {
3091
3470
  formSubmissionFinished({formElement: formElement}) {
3092
3471
  clearBusyState(formElement, this.findFrameElement(formElement));
3093
3472
  }
3094
- allowsImmediateRender(snapshot, resume) {
3095
- return true;
3473
+ allowsImmediateRender({element: newFrame}, options) {
3474
+ const event = dispatch("turbo:before-frame-render", {
3475
+ target: this.element,
3476
+ detail: Object.assign({
3477
+ newFrame: newFrame
3478
+ }, options),
3479
+ cancelable: true
3480
+ });
3481
+ const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
3482
+ if (this.view.renderer && render) {
3483
+ this.view.renderer.renderElement = render;
3484
+ }
3485
+ return !defaultPrevented;
3486
+ }
3487
+ viewRenderedSnapshot(_snapshot, _isPreview) {}
3488
+ preloadOnLoadLinksForView(element) {
3489
+ session.preloadOnLoadLinksForView(element);
3096
3490
  }
3097
- viewRenderedSnapshot(snapshot, isPreview) {}
3098
3491
  viewInvalidated() {}
3492
+ willRenderFrame(currentElement, _newElement) {
3493
+ this.previousFrameElement = currentElement.cloneNode(true);
3494
+ }
3099
3495
  async visit(url) {
3100
3496
  var _a;
3101
3497
  const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
@@ -3113,13 +3509,15 @@ class FrameController {
3113
3509
  navigateFrame(element, url, submitter) {
3114
3510
  const frame = this.findFrameElement(element, submitter);
3115
3511
  this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3116
- frame.setAttribute("reloadable", "");
3117
- frame.src = url;
3512
+ this.withCurrentNavigationElement(element, (() => {
3513
+ frame.src = url;
3514
+ }));
3118
3515
  }
3119
3516
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3120
- const action = getAttribute("data-turbo-action", submitter, element, frame);
3121
- if (isAction(action)) {
3122
- const {visitCachedSnapshot: visitCachedSnapshot} = new SnapshotSubstitution(frame);
3517
+ this.action = getVisitAction(submitter, element, frame);
3518
+ this.frame = frame;
3519
+ if (isAction(this.action)) {
3520
+ const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
3123
3521
  frame.delegate.fetchResponseLoaded = fetchResponse => {
3124
3522
  if (frame.src) {
3125
3523
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
@@ -3129,16 +3527,57 @@ class FrameController {
3129
3527
  redirected: redirected,
3130
3528
  responseHTML: responseHTML
3131
3529
  };
3132
- session.visit(frame.src, {
3133
- action: action,
3530
+ const options = {
3134
3531
  response: response,
3135
3532
  visitCachedSnapshot: visitCachedSnapshot,
3136
- willRender: false
3137
- });
3533
+ willRender: false,
3534
+ updateHistory: false,
3535
+ restorationIdentifier: this.restorationIdentifier
3536
+ };
3537
+ if (this.action) options.action = this.action;
3538
+ session.visit(frame.src, options);
3138
3539
  }
3139
3540
  };
3140
3541
  }
3141
3542
  }
3543
+ changeHistory() {
3544
+ if (this.action && this.frame) {
3545
+ const method = getHistoryMethodForAction(this.action);
3546
+ session.history.update(method, expandURL(this.frame.src || ""), this.restorationIdentifier);
3547
+ }
3548
+ }
3549
+ willHandleFrameMissingFromResponse(fetchResponse) {
3550
+ this.element.setAttribute("complete", "");
3551
+ const response = fetchResponse.response;
3552
+ const visit = async (url, options = {}) => {
3553
+ if (url instanceof Response) {
3554
+ this.visitResponse(url);
3555
+ } else {
3556
+ session.visit(url, options);
3557
+ }
3558
+ };
3559
+ const event = dispatch("turbo:frame-missing", {
3560
+ target: this.element,
3561
+ detail: {
3562
+ response: response,
3563
+ visit: visit
3564
+ },
3565
+ cancelable: true
3566
+ });
3567
+ return !event.defaultPrevented;
3568
+ }
3569
+ async visitResponse(response) {
3570
+ const wrapped = new FetchResponse(response);
3571
+ const responseHTML = await wrapped.responseHTML;
3572
+ const {location: location, redirected: redirected, statusCode: statusCode} = wrapped;
3573
+ return session.visit(location, {
3574
+ response: {
3575
+ redirected: redirected,
3576
+ statusCode: statusCode,
3577
+ responseHTML: responseHTML
3578
+ }
3579
+ });
3580
+ }
3142
3581
  findFrameElement(element, submitter) {
3143
3582
  var _a;
3144
3583
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
@@ -3148,18 +3587,20 @@ class FrameController {
3148
3587
  let element;
3149
3588
  const id = CSS.escape(this.id);
3150
3589
  try {
3151
- if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
3590
+ element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
3591
+ if (element) {
3152
3592
  return element;
3153
3593
  }
3154
- if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
3594
+ element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);
3595
+ if (element) {
3155
3596
  await element.loaded;
3156
3597
  return await this.extractForeignFrameElement(element);
3157
3598
  }
3158
- console.error(`Response has no matching <turbo-frame id="${id}"> element`);
3159
3599
  } catch (error) {
3160
3600
  console.error(error);
3601
+ return new FrameElement;
3161
3602
  }
3162
- return new FrameElement;
3603
+ return null;
3163
3604
  }
3164
3605
  formActionIsVisitable(form, submitter) {
3165
3606
  const action = getAction(form, submitter);
@@ -3179,10 +3620,10 @@ class FrameController {
3179
3620
  return !frameElement.disabled;
3180
3621
  }
3181
3622
  }
3182
- if (!session.elementDriveEnabled(element)) {
3623
+ if (!session.elementIsNavigatable(element)) {
3183
3624
  return false;
3184
3625
  }
3185
- if (submitter && !session.elementDriveEnabled(submitter)) {
3626
+ if (submitter && !session.elementIsNavigatable(submitter)) {
3186
3627
  return false;
3187
3628
  }
3188
3629
  return true;
@@ -3198,23 +3639,10 @@ class FrameController {
3198
3639
  return this.element.src;
3199
3640
  }
3200
3641
  }
3201
- get reloadable() {
3202
- const frame = this.findFrameElement(this.element);
3203
- return frame.hasAttribute("reloadable");
3204
- }
3205
- set reloadable(value) {
3206
- const frame = this.findFrameElement(this.element);
3207
- if (value) {
3208
- frame.setAttribute("reloadable", "");
3209
- } else {
3210
- frame.removeAttribute("reloadable");
3211
- }
3212
- }
3213
3642
  set sourceURL(sourceURL) {
3214
- this.settingSourceURL = true;
3215
- this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3216
- this.currentURL = this.element.src;
3217
- this.settingSourceURL = false;
3643
+ this.ignoringChangesToAttribute("src", (() => {
3644
+ this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3645
+ }));
3218
3646
  }
3219
3647
  get loadingStyle() {
3220
3648
  return this.element.loading;
@@ -3222,6 +3650,18 @@ class FrameController {
3222
3650
  get isLoading() {
3223
3651
  return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
3224
3652
  }
3653
+ get complete() {
3654
+ return this.element.hasAttribute("complete");
3655
+ }
3656
+ set complete(value) {
3657
+ this.ignoringChangesToAttribute("complete", (() => {
3658
+ if (value) {
3659
+ this.element.setAttribute("complete", "");
3660
+ } else {
3661
+ this.element.removeAttribute("complete");
3662
+ }
3663
+ }));
3664
+ }
3225
3665
  get isActive() {
3226
3666
  return this.element.isActive && this.connected;
3227
3667
  }
@@ -3231,17 +3671,29 @@ class FrameController {
3231
3671
  const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3232
3672
  return expandURL(root);
3233
3673
  }
3234
- }
3235
-
3236
- class SnapshotSubstitution {
3237
- constructor(element) {
3238
- this.visitCachedSnapshot = ({element: element}) => {
3239
- var _a;
3240
- const {id: id, clone: clone} = this;
3241
- (_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
3242
- };
3243
- this.clone = element.cloneNode(true);
3244
- this.id = element.id;
3674
+ frameAllowsVisitingLocation(target, {href: url}, originalEvent) {
3675
+ const event = dispatch("turbo:click", {
3676
+ target: target,
3677
+ detail: {
3678
+ url: url,
3679
+ originalEvent: originalEvent
3680
+ },
3681
+ cancelable: true
3682
+ });
3683
+ return !event.defaultPrevented;
3684
+ }
3685
+ isIgnoringChangesTo(attributeName) {
3686
+ return this.ignoredAttributes.has(attributeName);
3687
+ }
3688
+ ignoringChangesToAttribute(attributeName, callback) {
3689
+ this.ignoredAttributes.add(attributeName);
3690
+ callback();
3691
+ this.ignoredAttributes.delete(attributeName);
3692
+ }
3693
+ withCurrentNavigationElement(element, callback) {
3694
+ this.currentNavigationElement = element;
3695
+ callback();
3696
+ delete this.currentNavigationElement;
3245
3697
  }
3246
3698
  }
3247
3699
 
@@ -3271,42 +3723,10 @@ function activateElement(element, currentURL) {
3271
3723
  }
3272
3724
  }
3273
3725
 
3274
- const StreamActions = {
3275
- after() {
3276
- this.targetElements.forEach((e => {
3277
- var _a;
3278
- return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
3279
- }));
3280
- },
3281
- append() {
3282
- this.removeDuplicateTargetChildren();
3283
- this.targetElements.forEach((e => e.append(this.templateContent)));
3284
- },
3285
- before() {
3286
- this.targetElements.forEach((e => {
3287
- var _a;
3288
- return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
3289
- }));
3290
- },
3291
- prepend() {
3292
- this.removeDuplicateTargetChildren();
3293
- this.targetElements.forEach((e => e.prepend(this.templateContent)));
3294
- },
3295
- remove() {
3296
- this.targetElements.forEach((e => e.remove()));
3297
- },
3298
- replace() {
3299
- this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3300
- },
3301
- update() {
3302
- this.targetElements.forEach((e => {
3303
- e.innerHTML = "";
3304
- e.append(this.templateContent);
3305
- }));
3306
- }
3307
- };
3308
-
3309
3726
  class StreamElement extends HTMLElement {
3727
+ static async renderElement(newElement) {
3728
+ await newElement.performAction();
3729
+ }
3310
3730
  async connectedCallback() {
3311
3731
  try {
3312
3732
  await this.render();
@@ -3319,9 +3739,10 @@ class StreamElement extends HTMLElement {
3319
3739
  async render() {
3320
3740
  var _a;
3321
3741
  return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
3322
- if (this.dispatchEvent(this.beforeRenderEvent)) {
3742
+ const event = this.beforeRenderEvent;
3743
+ if (this.dispatchEvent(event)) {
3323
3744
  await nextAnimationFrame();
3324
- this.performAction();
3745
+ await event.detail.render(this);
3325
3746
  }
3326
3747
  })();
3327
3748
  }
@@ -3336,7 +3757,7 @@ class StreamElement extends HTMLElement {
3336
3757
  get duplicateChildren() {
3337
3758
  var _a;
3338
3759
  const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
3339
- const newChildrenIds = [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].filter((c => !!c.id)).map((c => c.id));
3760
+ const newChildrenIds = [ ...((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [] ].filter((c => !!c.id)).map((c => c.id));
3340
3761
  return existingChildren.filter((c => newChildrenIds.includes(c.id)));
3341
3762
  }
3342
3763
  get performAction() {
@@ -3362,7 +3783,11 @@ class StreamElement extends HTMLElement {
3362
3783
  return this.templateElement.content.cloneNode(true);
3363
3784
  }
3364
3785
  get templateElement() {
3365
- if (this.firstElementChild instanceof HTMLTemplateElement) {
3786
+ if (this.firstElementChild === null) {
3787
+ const template = this.ownerDocument.createElement("template");
3788
+ this.appendChild(template);
3789
+ return template;
3790
+ } else if (this.firstElementChild instanceof HTMLTemplateElement) {
3366
3791
  return this.firstElementChild;
3367
3792
  }
3368
3793
  this.raise("first child element must be a <template> element");
@@ -3386,7 +3811,11 @@ class StreamElement extends HTMLElement {
3386
3811
  get beforeRenderEvent() {
3387
3812
  return new CustomEvent("turbo:before-stream-render", {
3388
3813
  bubbles: true,
3389
- cancelable: true
3814
+ cancelable: true,
3815
+ detail: {
3816
+ newStream: this,
3817
+ render: StreamElement.renderElement
3818
+ }
3390
3819
  });
3391
3820
  }
3392
3821
  get targetElementsById() {
@@ -3409,17 +3838,45 @@ class StreamElement extends HTMLElement {
3409
3838
  }
3410
3839
  }
3411
3840
 
3841
+ class StreamSourceElement extends HTMLElement {
3842
+ constructor() {
3843
+ super(...arguments);
3844
+ this.streamSource = null;
3845
+ }
3846
+ connectedCallback() {
3847
+ this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
3848
+ connectStreamSource(this.streamSource);
3849
+ }
3850
+ disconnectedCallback() {
3851
+ if (this.streamSource) {
3852
+ disconnectStreamSource(this.streamSource);
3853
+ }
3854
+ }
3855
+ get src() {
3856
+ return this.getAttribute("src") || "";
3857
+ }
3858
+ }
3859
+
3412
3860
  FrameElement.delegateConstructor = FrameController;
3413
3861
 
3414
- customElements.define("turbo-frame", FrameElement);
3862
+ if (customElements.get("turbo-frame") === undefined) {
3863
+ customElements.define("turbo-frame", FrameElement);
3864
+ }
3415
3865
 
3416
- customElements.define("turbo-stream", StreamElement);
3866
+ if (customElements.get("turbo-stream") === undefined) {
3867
+ customElements.define("turbo-stream", StreamElement);
3868
+ }
3869
+
3870
+ if (customElements.get("turbo-stream-source") === undefined) {
3871
+ customElements.define("turbo-stream-source", StreamSourceElement);
3872
+ }
3417
3873
 
3418
3874
  (() => {
3419
3875
  let element = document.currentScript;
3420
3876
  if (!element) return;
3421
3877
  if (element.hasAttribute("data-turbo-suppress-warning")) return;
3422
- while (element = element.parentElement) {
3878
+ element = element.parentElement;
3879
+ while (element) {
3423
3880
  if (element == document.body) {
3424
3881
  return console.warn(unindent`
3425
3882
  You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
@@ -3432,6 +3889,7 @@ customElements.define("turbo-stream", StreamElement);
3432
3889
  Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
3433
3890
  `, element.outerHTML);
3434
3891
  }
3892
+ element = element.parentElement;
3435
3893
  }
3436
3894
  })();
3437
3895
 
@@ -3441,8 +3899,17 @@ start();
3441
3899
 
3442
3900
  var turbo_es2017Esm = Object.freeze({
3443
3901
  __proto__: null,
3902
+ FrameElement: FrameElement,
3903
+ get FrameLoadingStyle() {
3904
+ return FrameLoadingStyle;
3905
+ },
3906
+ FrameRenderer: FrameRenderer,
3444
3907
  PageRenderer: PageRenderer,
3445
3908
  PageSnapshot: PageSnapshot,
3909
+ StreamActions: StreamActions,
3910
+ StreamElement: StreamElement,
3911
+ StreamSourceElement: StreamSourceElement,
3912
+ cache: cache,
3446
3913
  clearCache: clearCache,
3447
3914
  connectStreamSource: connectStreamSource,
3448
3915
  disconnectStreamSource: disconnectStreamSource,
@@ -3451,6 +3918,7 @@ var turbo_es2017Esm = Object.freeze({
3451
3918
  renderStreamMessage: renderStreamMessage,
3452
3919
  session: session,
3453
3920
  setConfirmMethod: setConfirmMethod,
3921
+ setFormMode: setFormMode,
3454
3922
  setProgressBarDelay: setProgressBarDelay,
3455
3923
  start: start,
3456
3924
  visit: visit
@@ -3531,14 +3999,26 @@ class TurboCableStreamSourceElement extends HTMLElement {
3531
3999
 
3532
4000
  customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement);
3533
4001
 
3534
- function overrideMethodWithFormmethod({detail: {formSubmission: {fetchRequest: fetchRequest, submitter: submitter}}}) {
3535
- const formMethod = submitter?.formMethod;
3536
- if (formMethod && fetchRequest.body.has("_method")) {
3537
- fetchRequest.body.set("_method", formMethod);
4002
+ function encodeMethodIntoRequestBody(event) {
4003
+ if (event.target instanceof HTMLFormElement) {
4004
+ const {target: form, detail: {fetchOptions: fetchOptions}} = event;
4005
+ form.addEventListener("turbo:submit-start", (({detail: {formSubmission: {submitter: submitter}}}) => {
4006
+ const method = submitter && submitter.formMethod || fetchOptions.body && fetchOptions.body.get("_method") || form.getAttribute("method");
4007
+ if (!/get/i.test(method)) {
4008
+ if (/post/i.test(method)) {
4009
+ fetchOptions.body.delete("_method");
4010
+ } else {
4011
+ fetchOptions.body.set("_method", method);
4012
+ }
4013
+ fetchOptions.method = "post";
4014
+ }
4015
+ }), {
4016
+ once: true
4017
+ });
3538
4018
  }
3539
4019
  }
3540
4020
 
3541
- addEventListener("turbo:submit-start", overrideMethodWithFormmethod);
4021
+ addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
3542
4022
 
3543
4023
  var adapters = {
3544
4024
  logger: self.console,