turbo-rails 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,