turbo-rails 1.0.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -56,13 +56,11 @@ function clickCaptured(event) {
56
56
 
57
57
  (function() {
58
58
  if ("submitter" in Event.prototype) return;
59
- let prototype;
59
+ let prototype = window.Event.prototype;
60
60
  if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
61
61
  prototype = window.SubmitEvent.prototype;
62
62
  } else if ("SubmitEvent" in window) {
63
63
  return;
64
- } else {
65
- prototype = window.Event.prototype;
66
64
  }
67
65
  addEventListener("click", clickCaptured, true);
68
66
  Object.defineProperty(prototype, "submitter", {
@@ -82,14 +80,14 @@ var FrameLoadingStyle;
82
80
  })(FrameLoadingStyle || (FrameLoadingStyle = {}));
83
81
 
84
82
  class FrameElement extends HTMLElement {
83
+ static get observedAttributes() {
84
+ return [ "disabled", "complete", "loading", "src" ];
85
+ }
85
86
  constructor() {
86
87
  super();
87
88
  this.loaded = Promise.resolve();
88
89
  this.delegate = new FrameElement.delegateConstructor(this);
89
90
  }
90
- static get observedAttributes() {
91
- return [ "disabled", "loading", "src" ];
92
- }
93
91
  connectedCallback() {
94
92
  this.delegate.connect();
95
93
  }
@@ -97,13 +95,13 @@ class FrameElement extends HTMLElement {
97
95
  this.delegate.disconnect();
98
96
  }
99
97
  reload() {
100
- const {src: src} = this;
101
- this.src = null;
102
- this.src = src;
98
+ return this.delegate.sourceURLReloaded();
103
99
  }
104
100
  attributeChangedCallback(name) {
105
101
  if (name == "loading") {
106
102
  this.delegate.loadingStyleChanged();
103
+ } else if (name == "complete") {
104
+ this.delegate.completeChanged();
107
105
  } else if (name == "src") {
108
106
  this.delegate.sourceURLChanged();
109
107
  } else {
@@ -195,7 +193,7 @@ function getExtension(url) {
195
193
  }
196
194
 
197
195
  function isHTML(url) {
198
- return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
196
+ return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
199
197
  }
200
198
 
201
199
  function isPrefixedBy(baseURL, url) {
@@ -282,10 +280,39 @@ class FetchResponse {
282
280
  }
283
281
  }
284
282
 
283
+ function activateScriptElement(element) {
284
+ if (element.getAttribute("data-turbo-eval") == "false") {
285
+ return element;
286
+ } else {
287
+ const createdScriptElement = document.createElement("script");
288
+ const cspNonce = getMetaContent("csp-nonce");
289
+ if (cspNonce) {
290
+ createdScriptElement.nonce = cspNonce;
291
+ }
292
+ createdScriptElement.textContent = element.textContent;
293
+ createdScriptElement.async = false;
294
+ copyElementAttributes(createdScriptElement, element);
295
+ return createdScriptElement;
296
+ }
297
+ }
298
+
299
+ function copyElementAttributes(destinationElement, sourceElement) {
300
+ for (const {name: name, value: value} of sourceElement.attributes) {
301
+ destinationElement.setAttribute(name, value);
302
+ }
303
+ }
304
+
305
+ function createDocumentFragment(html) {
306
+ const template = document.createElement("template");
307
+ template.innerHTML = html;
308
+ return template.content;
309
+ }
310
+
285
311
  function dispatch(eventName, {target: target, cancelable: cancelable, detail: detail} = {}) {
286
312
  const event = new CustomEvent(eventName, {
287
313
  cancelable: cancelable,
288
314
  bubbles: true,
315
+ composed: true,
289
316
  detail: detail
290
317
  });
291
318
  if (target && target.isConnected) {
@@ -327,7 +354,7 @@ function interpolate(strings, values) {
327
354
  }
328
355
 
329
356
  function uuid() {
330
- return Array.apply(null, {
357
+ return Array.from({
331
358
  length: 36
332
359
  }).map(((_, i) => {
333
360
  if (i == 8 || i == 13 || i == 18 || i == 23) {
@@ -349,6 +376,10 @@ function getAttribute(attributeName, ...elements) {
349
376
  return null;
350
377
  }
351
378
 
379
+ function hasAttribute(attributeName, ...elements) {
380
+ return elements.some((element => element && element.hasAttribute(attributeName)));
381
+ }
382
+
352
383
  function markAsBusy(...elements) {
353
384
  for (const element of elements) {
354
385
  if (element.localName == "turbo-frame") {
@@ -367,6 +398,70 @@ function clearBusyState(...elements) {
367
398
  }
368
399
  }
369
400
 
401
+ function waitForLoad(element, timeoutInMilliseconds = 2e3) {
402
+ return new Promise((resolve => {
403
+ const onComplete = () => {
404
+ element.removeEventListener("error", onComplete);
405
+ element.removeEventListener("load", onComplete);
406
+ resolve();
407
+ };
408
+ element.addEventListener("load", onComplete, {
409
+ once: true
410
+ });
411
+ element.addEventListener("error", onComplete, {
412
+ once: true
413
+ });
414
+ setTimeout(resolve, timeoutInMilliseconds);
415
+ }));
416
+ }
417
+
418
+ function getHistoryMethodForAction(action) {
419
+ switch (action) {
420
+ case "replace":
421
+ return history.replaceState;
422
+
423
+ case "advance":
424
+ case "restore":
425
+ return history.pushState;
426
+ }
427
+ }
428
+
429
+ function isAction(action) {
430
+ return action == "advance" || action == "replace" || action == "restore";
431
+ }
432
+
433
+ function getVisitAction(...elements) {
434
+ const action = getAttribute("data-turbo-action", ...elements);
435
+ return isAction(action) ? action : null;
436
+ }
437
+
438
+ function getMetaElement(name) {
439
+ return document.querySelector(`meta[name="${name}"]`);
440
+ }
441
+
442
+ function getMetaContent(name) {
443
+ const element = getMetaElement(name);
444
+ return element && element.content;
445
+ }
446
+
447
+ function setMetaContent(name, content) {
448
+ let element = getMetaElement(name);
449
+ if (!element) {
450
+ element = document.createElement("meta");
451
+ element.setAttribute("name", name);
452
+ document.head.appendChild(element);
453
+ }
454
+ element.setAttribute("content", content);
455
+ return element;
456
+ }
457
+
458
+ function findClosestRecursively(element, selector) {
459
+ var _a;
460
+ if (element instanceof Element) {
461
+ return element.closest(selector) || findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector);
462
+ }
463
+ }
464
+
370
465
  var FetchMethod;
371
466
 
372
467
  (function(FetchMethod) {
@@ -399,7 +494,7 @@ function fetchMethodFromString(method) {
399
494
  class FetchRequest {
400
495
  constructor(delegate, method, location, body = new URLSearchParams, target = null) {
401
496
  this.abortController = new AbortController;
402
- this.resolveRequestPromise = value => {};
497
+ this.resolveRequestPromise = _value => {};
403
498
  this.delegate = delegate;
404
499
  this.method = method;
405
500
  this.headers = this.defaultHeaders;
@@ -420,9 +515,8 @@ class FetchRequest {
420
515
  this.abortController.abort();
421
516
  }
422
517
  async perform() {
423
- var _a, _b;
424
518
  const {fetchOptions: fetchOptions} = this;
425
- (_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
519
+ this.delegate.prepareRequest(this);
426
520
  await this.allowRequestToBeIntercepted(fetchOptions);
427
521
  try {
428
522
  this.delegate.requestStarted(this);
@@ -430,7 +524,9 @@ class FetchRequest {
430
524
  return await this.receive(response);
431
525
  } catch (error) {
432
526
  if (error.name !== "AbortError") {
433
- this.delegate.requestErrored(this, error);
527
+ if (this.willDelegateErrorHandling(error)) {
528
+ this.delegate.requestErrored(this, error);
529
+ }
434
530
  throw error;
435
531
  }
436
532
  } finally {
@@ -462,7 +558,7 @@ class FetchRequest {
462
558
  credentials: "same-origin",
463
559
  headers: this.headers,
464
560
  redirect: "follow",
465
- body: this.isIdempotent ? null : this.body,
561
+ body: this.isSafe ? null : this.body,
466
562
  signal: this.abortSignal,
467
563
  referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
468
564
  };
@@ -472,12 +568,15 @@ class FetchRequest {
472
568
  Accept: "text/html, application/xhtml+xml"
473
569
  };
474
570
  }
475
- get isIdempotent() {
476
- return this.method == FetchMethod.get;
571
+ get isSafe() {
572
+ return this.method === FetchMethod.get;
477
573
  }
478
574
  get abortSignal() {
479
575
  return this.abortController.signal;
480
576
  }
577
+ acceptResponseType(mimeType) {
578
+ this.headers["Accept"] = [ mimeType, this.headers["Accept"] ].join(", ");
579
+ }
481
580
  async allowRequestToBeIntercepted(fetchOptions) {
482
581
  const requestInterception = new Promise((resolve => this.resolveRequestPromise = resolve));
483
582
  const event = dispatch("turbo:before-fetch-request", {
@@ -491,6 +590,17 @@ class FetchRequest {
491
590
  });
492
591
  if (event.defaultPrevented) await requestInterception;
493
592
  }
593
+ willDelegateErrorHandling(error) {
594
+ const event = dispatch("turbo:fetch-request-error", {
595
+ target: this.target,
596
+ cancelable: true,
597
+ detail: {
598
+ request: this,
599
+ error: error
600
+ }
601
+ });
602
+ return !event.defaultPrevented;
603
+ }
494
604
  }
495
605
 
496
606
  class AppearanceObserver {
@@ -521,40 +631,31 @@ class AppearanceObserver {
521
631
  }
522
632
 
523
633
  class StreamMessage {
524
- constructor(html) {
525
- this.templateElement = document.createElement("template");
526
- this.templateElement.innerHTML = html;
527
- }
528
634
  static wrap(message) {
529
635
  if (typeof message == "string") {
530
- return new this(message);
636
+ return new this(createDocumentFragment(message));
531
637
  } else {
532
638
  return message;
533
639
  }
534
640
  }
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);
641
+ constructor(fragment) {
642
+ this.fragment = importStreamElements(fragment);
553
643
  }
554
644
  }
555
645
 
556
646
  StreamMessage.contentType = "text/vnd.turbo-stream.html";
557
647
 
648
+ function importStreamElements(fragment) {
649
+ for (const element of fragment.querySelectorAll("turbo-stream")) {
650
+ const streamElement = document.importNode(element, true);
651
+ for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) {
652
+ inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));
653
+ }
654
+ element.replaceWith(streamElement);
655
+ }
656
+ return fragment;
657
+ }
658
+
558
659
  var FormSubmissionState;
559
660
 
560
661
  (function(FormSubmissionState) {
@@ -588,6 +689,9 @@ function formEnctypeFromString(encoding) {
588
689
  }
589
690
 
590
691
  class FormSubmission {
692
+ static confirmMethod(message, _element, _submitter) {
693
+ return Promise.resolve(confirm(message));
694
+ }
591
695
  constructor(delegate, formElement, submitter, mustRedirect = false) {
592
696
  this.state = FormSubmissionState.initialized;
593
697
  this.delegate = delegate;
@@ -601,9 +705,6 @@ class FormSubmission {
601
705
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
602
706
  this.mustRedirect = mustRedirect;
603
707
  }
604
- static confirmMethod(message, element) {
605
- return confirm(message);
606
- }
607
708
  get method() {
608
709
  var _a;
609
710
  const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
@@ -612,7 +713,11 @@ class FormSubmission {
612
713
  get action() {
613
714
  var _a;
614
715
  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 || "";
716
+ if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
717
+ return this.submitter.getAttribute("formaction") || "";
718
+ } else {
719
+ return this.formElement.getAttribute("action") || formElementAction || "";
720
+ }
616
721
  }
617
722
  get body() {
618
723
  if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
@@ -625,22 +730,17 @@ class FormSubmission {
625
730
  var _a;
626
731
  return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
627
732
  }
628
- get isIdempotent() {
629
- return this.fetchRequest.isIdempotent;
733
+ get isSafe() {
734
+ return this.fetchRequest.isSafe;
630
735
  }
631
736
  get stringFormData() {
632
737
  return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
633
738
  }
634
- get confirmationMessage() {
635
- return this.formElement.getAttribute("data-turbo-confirm");
636
- }
637
- get needsConfirmation() {
638
- return this.confirmationMessage !== null;
639
- }
640
739
  async start() {
641
740
  const {initialized: initialized, requesting: requesting} = FormSubmissionState;
642
- if (this.needsConfirmation) {
643
- const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
741
+ const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
742
+ if (typeof confirmationMessage === "string") {
743
+ const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);
644
744
  if (!answer) {
645
745
  return;
646
746
  }
@@ -658,19 +758,22 @@ class FormSubmission {
658
758
  return true;
659
759
  }
660
760
  }
661
- prepareHeadersForRequest(headers, request) {
662
- if (!request.isIdempotent) {
761
+ prepareRequest(request) {
762
+ if (!request.isSafe) {
663
763
  const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
664
764
  if (token) {
665
- headers["X-CSRF-Token"] = token;
765
+ request.headers["X-CSRF-Token"] = token;
666
766
  }
667
- headers["Accept"] = [ StreamMessage.contentType, headers["Accept"] ].join(", ");
767
+ }
768
+ if (this.requestAcceptsTurboStreamResponse(request)) {
769
+ request.acceptResponseType(StreamMessage.contentType);
668
770
  }
669
771
  }
670
- requestStarted(request) {
772
+ requestStarted(_request) {
671
773
  var _a;
672
774
  this.state = FormSubmissionState.waiting;
673
775
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
776
+ this.setSubmitsWith();
674
777
  dispatch("turbo:submit-start", {
675
778
  target: this.formElement,
676
779
  detail: {
@@ -714,10 +817,11 @@ class FormSubmission {
714
817
  };
715
818
  this.delegate.formSubmissionErrored(this, error);
716
819
  }
717
- requestFinished(request) {
820
+ requestFinished(_request) {
718
821
  var _a;
719
822
  this.state = FormSubmissionState.stopped;
720
823
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
824
+ this.resetSubmitterText();
721
825
  dispatch("turbo:submit-end", {
722
826
  target: this.formElement,
723
827
  detail: Object.assign({
@@ -726,8 +830,35 @@ class FormSubmission {
726
830
  });
727
831
  this.delegate.formSubmissionFinished(this);
728
832
  }
833
+ setSubmitsWith() {
834
+ if (!this.submitter || !this.submitsWith) return;
835
+ if (this.submitter.matches("button")) {
836
+ this.originalSubmitText = this.submitter.innerHTML;
837
+ this.submitter.innerHTML = this.submitsWith;
838
+ } else if (this.submitter.matches("input")) {
839
+ const input = this.submitter;
840
+ this.originalSubmitText = input.value;
841
+ input.value = this.submitsWith;
842
+ }
843
+ }
844
+ resetSubmitterText() {
845
+ if (!this.submitter || !this.originalSubmitText) return;
846
+ if (this.submitter.matches("button")) {
847
+ this.submitter.innerHTML = this.originalSubmitText;
848
+ } else if (this.submitter.matches("input")) {
849
+ const input = this.submitter;
850
+ input.value = this.originalSubmitText;
851
+ }
852
+ }
729
853
  requestMustRedirect(request) {
730
- return !request.isIdempotent && this.mustRedirect;
854
+ return !request.isSafe && this.mustRedirect;
855
+ }
856
+ requestAcceptsTurboStreamResponse(request) {
857
+ return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
858
+ }
859
+ get submitsWith() {
860
+ var _a;
861
+ return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with");
731
862
  }
732
863
  }
733
864
 
@@ -735,8 +866,8 @@ function buildFormData(formElement, submitter) {
735
866
  const formData = new FormData(formElement);
736
867
  const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
737
868
  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);
869
+ if (name) {
870
+ formData.append(name, value || "");
740
871
  }
741
872
  return formData;
742
873
  }
@@ -752,11 +883,6 @@ function getCookieValue(cookieName) {
752
883
  }
753
884
  }
754
885
 
755
- function getMetaContent(name) {
756
- const element = document.querySelector(`meta[name="${name}"]`);
757
- return element && element.content;
758
- }
759
-
760
886
  function responseSucceededWithoutRedirect(response) {
761
887
  return response.statusCode == 200 && !response.redirected;
762
888
  }
@@ -775,6 +901,9 @@ class Snapshot {
775
901
  constructor(element) {
776
902
  this.element = element;
777
903
  }
904
+ get activeElement() {
905
+ return this.element.ownerDocument.activeElement;
906
+ }
778
907
  get children() {
779
908
  return [ ...this.element.children ];
780
909
  }
@@ -788,13 +917,17 @@ class Snapshot {
788
917
  return this.element.isConnected;
789
918
  }
790
919
  get firstAutofocusableElement() {
791
- return this.element.querySelector("[autofocus]");
920
+ const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
921
+ for (const element of this.element.querySelectorAll("[autofocus]")) {
922
+ if (element.closest(inertDisabledOrHidden) == null) return element; else continue;
923
+ }
924
+ return null;
792
925
  }
793
926
  get permanentElements() {
794
- return [ ...this.element.querySelectorAll("[id][data-turbo-permanent]") ];
927
+ return queryPermanentElementsAll(this.element);
795
928
  }
796
929
  getPermanentElementById(id) {
797
- return this.element.querySelector(`#${id}[data-turbo-permanent]`);
930
+ return getPermanentElementById(this.element, id);
798
931
  }
799
932
  getPermanentElementMapForSnapshot(snapshot) {
800
933
  const permanentElementMap = {};
@@ -809,35 +942,70 @@ class Snapshot {
809
942
  }
810
943
  }
811
944
 
812
- class FormInterceptor {
813
- constructor(delegate, element) {
945
+ function getPermanentElementById(node, id) {
946
+ return node.querySelector(`#${id}[data-turbo-permanent]`);
947
+ }
948
+
949
+ function queryPermanentElementsAll(node) {
950
+ return node.querySelectorAll("[id][data-turbo-permanent]");
951
+ }
952
+
953
+ class FormSubmitObserver {
954
+ constructor(delegate, eventTarget) {
955
+ this.started = false;
956
+ this.submitCaptured = () => {
957
+ this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
958
+ this.eventTarget.addEventListener("submit", this.submitBubbled, false);
959
+ };
814
960
  this.submitBubbled = event => {
815
- const form = event.target;
816
- if (!event.defaultPrevented && form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
961
+ if (!event.defaultPrevented) {
962
+ const form = event.target instanceof HTMLFormElement ? event.target : undefined;
817
963
  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)) {
964
+ if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
820
965
  event.preventDefault();
821
966
  event.stopImmediatePropagation();
822
- this.delegate.formSubmissionIntercepted(form, submitter);
967
+ this.delegate.formSubmitted(form, submitter);
823
968
  }
824
969
  }
825
970
  };
826
971
  this.delegate = delegate;
827
- this.element = element;
972
+ this.eventTarget = eventTarget;
828
973
  }
829
974
  start() {
830
- this.element.addEventListener("submit", this.submitBubbled);
975
+ if (!this.started) {
976
+ this.eventTarget.addEventListener("submit", this.submitCaptured, true);
977
+ this.started = true;
978
+ }
831
979
  }
832
980
  stop() {
833
- this.element.removeEventListener("submit", this.submitBubbled);
981
+ if (this.started) {
982
+ this.eventTarget.removeEventListener("submit", this.submitCaptured, true);
983
+ this.started = false;
984
+ }
985
+ }
986
+ }
987
+
988
+ function submissionDoesNotDismissDialog(form, submitter) {
989
+ const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
990
+ return method != "dialog";
991
+ }
992
+
993
+ function submissionDoesNotTargetIFrame(form, submitter) {
994
+ if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
995
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
996
+ for (const element of document.getElementsByName(target)) {
997
+ if (element instanceof HTMLIFrameElement) return false;
998
+ }
999
+ return true;
1000
+ } else {
1001
+ return true;
834
1002
  }
835
1003
  }
836
1004
 
837
1005
  class View {
838
1006
  constructor(delegate, element) {
839
- this.resolveRenderPromise = value => {};
840
- this.resolveInterceptionPromise = value => {};
1007
+ this.resolveRenderPromise = _value => {};
1008
+ this.resolveInterceptionPromise = _value => {};
841
1009
  this.delegate = delegate;
842
1010
  this.element = element;
843
1011
  }
@@ -888,12 +1056,17 @@ class View {
888
1056
  try {
889
1057
  this.renderPromise = new Promise((resolve => this.resolveRenderPromise = resolve));
890
1058
  this.renderer = renderer;
891
- this.prepareToRenderSnapshot(renderer);
1059
+ await this.prepareToRenderSnapshot(renderer);
892
1060
  const renderInterception = new Promise((resolve => this.resolveInterceptionPromise = resolve));
893
- const immediateRender = this.delegate.allowsImmediateRender(snapshot, this.resolveInterceptionPromise);
1061
+ const options = {
1062
+ resume: this.resolveInterceptionPromise,
1063
+ render: this.renderer.renderElement
1064
+ };
1065
+ const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
894
1066
  if (!immediateRender) await renderInterception;
895
1067
  await this.renderSnapshot(renderer);
896
1068
  this.delegate.viewRenderedSnapshot(snapshot, isPreview);
1069
+ this.delegate.preloadOnLoadLinksForView(this.element);
897
1070
  this.finishRenderingSnapshot(renderer);
898
1071
  } finally {
899
1072
  delete this.renderer;
@@ -901,15 +1074,15 @@ class View {
901
1074
  delete this.renderPromise;
902
1075
  }
903
1076
  } else {
904
- this.invalidate();
1077
+ this.invalidate(renderer.reloadReason);
905
1078
  }
906
1079
  }
907
- invalidate() {
908
- this.delegate.viewInvalidated();
1080
+ invalidate(reason) {
1081
+ this.delegate.viewInvalidated(reason);
909
1082
  }
910
- prepareToRenderSnapshot(renderer) {
1083
+ async prepareToRenderSnapshot(renderer) {
911
1084
  this.markAsPreview(renderer.isPreview);
912
- renderer.prepareToRender();
1085
+ await renderer.prepareToRender();
913
1086
  }
914
1087
  markAsPreview(isPreview) {
915
1088
  if (isPreview) {
@@ -927,8 +1100,8 @@ class View {
927
1100
  }
928
1101
 
929
1102
  class FrameView extends View {
930
- invalidate() {
931
- this.element.innerHTML = "";
1103
+ missing() {
1104
+ this.element.innerHTML = `<strong class="turbo-frame-error">Content missing</strong>`;
932
1105
  }
933
1106
  get snapshot() {
934
1107
  return new Snapshot(this.element);
@@ -946,15 +1119,15 @@ class LinkInterceptor {
946
1119
  };
947
1120
  this.linkClicked = event => {
948
1121
  if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
949
- if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) {
1122
+ if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
950
1123
  this.clickEvent.preventDefault();
951
1124
  event.preventDefault();
952
- this.delegate.linkClickIntercepted(event.target, event.detail.url);
1125
+ this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
953
1126
  }
954
1127
  }
955
1128
  delete this.clickEvent;
956
1129
  };
957
- this.willVisit = () => {
1130
+ this.willVisit = _event => {
958
1131
  delete this.clickEvent;
959
1132
  };
960
1133
  this.delegate = delegate;
@@ -976,19 +1149,127 @@ class LinkInterceptor {
976
1149
  }
977
1150
  }
978
1151
 
979
- class Bardo {
980
- constructor(permanentElementMap) {
981
- this.permanentElementMap = permanentElementMap;
1152
+ class LinkClickObserver {
1153
+ constructor(delegate, eventTarget) {
1154
+ this.started = false;
1155
+ this.clickCaptured = () => {
1156
+ this.eventTarget.removeEventListener("click", this.clickBubbled, false);
1157
+ this.eventTarget.addEventListener("click", this.clickBubbled, false);
1158
+ };
1159
+ this.clickBubbled = event => {
1160
+ if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
1161
+ const target = event.composedPath && event.composedPath()[0] || event.target;
1162
+ const link = this.findLinkFromClickTarget(target);
1163
+ if (link && doesNotTargetIFrame(link)) {
1164
+ const location = this.getLocationForLink(link);
1165
+ if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1166
+ event.preventDefault();
1167
+ this.delegate.followedLinkToLocation(link, location);
1168
+ }
1169
+ }
1170
+ }
1171
+ };
1172
+ this.delegate = delegate;
1173
+ this.eventTarget = eventTarget;
1174
+ }
1175
+ start() {
1176
+ if (!this.started) {
1177
+ this.eventTarget.addEventListener("click", this.clickCaptured, true);
1178
+ this.started = true;
1179
+ }
1180
+ }
1181
+ stop() {
1182
+ if (this.started) {
1183
+ this.eventTarget.removeEventListener("click", this.clickCaptured, true);
1184
+ this.started = false;
1185
+ }
1186
+ }
1187
+ clickEventIsSignificant(event) {
1188
+ return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
1189
+ }
1190
+ findLinkFromClickTarget(target) {
1191
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
1192
+ }
1193
+ getLocationForLink(link) {
1194
+ return expandURL(link.getAttribute("href") || "");
1195
+ }
1196
+ }
1197
+
1198
+ function doesNotTargetIFrame(anchor) {
1199
+ if (anchor.hasAttribute("target")) {
1200
+ for (const element of document.getElementsByName(anchor.target)) {
1201
+ if (element instanceof HTMLIFrameElement) return false;
1202
+ }
1203
+ return true;
1204
+ } else {
1205
+ return true;
1206
+ }
1207
+ }
1208
+
1209
+ class FormLinkClickObserver {
1210
+ constructor(delegate, element) {
1211
+ this.delegate = delegate;
1212
+ this.linkInterceptor = new LinkClickObserver(this, element);
1213
+ }
1214
+ start() {
1215
+ this.linkInterceptor.start();
1216
+ }
1217
+ stop() {
1218
+ this.linkInterceptor.stop();
1219
+ }
1220
+ willFollowLinkToLocation(link, location, originalEvent) {
1221
+ return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
1222
+ }
1223
+ followedLinkToLocation(link, location) {
1224
+ const form = document.createElement("form");
1225
+ const type = "hidden";
1226
+ for (const [name, value] of location.searchParams) {
1227
+ form.append(Object.assign(document.createElement("input"), {
1228
+ type: type,
1229
+ name: name,
1230
+ value: value
1231
+ }));
1232
+ }
1233
+ const action = Object.assign(location, {
1234
+ search: ""
1235
+ });
1236
+ form.setAttribute("data-turbo", "true");
1237
+ form.setAttribute("action", action.href);
1238
+ form.setAttribute("hidden", "");
1239
+ const method = link.getAttribute("data-turbo-method");
1240
+ if (method) form.setAttribute("method", method);
1241
+ const turboFrame = link.getAttribute("data-turbo-frame");
1242
+ if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
1243
+ const turboAction = getVisitAction(link);
1244
+ if (turboAction) form.setAttribute("data-turbo-action", turboAction);
1245
+ const turboConfirm = link.getAttribute("data-turbo-confirm");
1246
+ if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
1247
+ const turboStream = link.hasAttribute("data-turbo-stream");
1248
+ if (turboStream) form.setAttribute("data-turbo-stream", "");
1249
+ this.delegate.submittedFormLinkToLocation(link, location, form);
1250
+ document.body.appendChild(form);
1251
+ form.addEventListener("turbo:submit-end", (() => form.remove()), {
1252
+ once: true
1253
+ });
1254
+ requestAnimationFrame((() => form.requestSubmit()));
982
1255
  }
983
- static preservingPermanentElements(permanentElementMap, callback) {
984
- const bardo = new this(permanentElementMap);
1256
+ }
1257
+
1258
+ class Bardo {
1259
+ static async preservingPermanentElements(delegate, permanentElementMap, callback) {
1260
+ const bardo = new this(delegate, permanentElementMap);
985
1261
  bardo.enter();
986
- callback();
1262
+ await callback();
987
1263
  bardo.leave();
988
1264
  }
1265
+ constructor(delegate, permanentElementMap) {
1266
+ this.delegate = delegate;
1267
+ this.permanentElementMap = permanentElementMap;
1268
+ }
989
1269
  enter() {
990
1270
  for (const id in this.permanentElementMap) {
991
- const [, newPermanentElement] = this.permanentElementMap[id];
1271
+ const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
1272
+ this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
992
1273
  this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
993
1274
  }
994
1275
  }
@@ -997,6 +1278,7 @@ class Bardo {
997
1278
  const [currentPermanentElement] = this.permanentElementMap[id];
998
1279
  this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
999
1280
  this.replacePlaceholderWithPermanentElement(currentPermanentElement);
1281
+ this.delegate.leavingBardo(currentPermanentElement);
1000
1282
  }
1001
1283
  }
1002
1284
  replaceNewPermanentElementWithPlaceholder(permanentElement) {
@@ -1027,11 +1309,13 @@ function createPlaceholderForPermanentElement(permanentElement) {
1027
1309
  }
1028
1310
 
1029
1311
  class Renderer {
1030
- constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
1312
+ constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1313
+ this.activeElement = null;
1031
1314
  this.currentSnapshot = currentSnapshot;
1032
1315
  this.newSnapshot = newSnapshot;
1033
1316
  this.isPreview = isPreview;
1034
1317
  this.willRender = willRender;
1318
+ this.renderElement = renderElement;
1035
1319
  this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
1036
1320
  resolve: resolve,
1037
1321
  reject: reject
@@ -1040,6 +1324,9 @@ class Renderer {
1040
1324
  get shouldRender() {
1041
1325
  return true;
1042
1326
  }
1327
+ get reloadReason() {
1328
+ return;
1329
+ }
1043
1330
  prepareToRender() {
1044
1331
  return;
1045
1332
  }
@@ -1049,22 +1336,8 @@ class Renderer {
1049
1336
  delete this.resolvingFunctions;
1050
1337
  }
1051
1338
  }
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
- preservingPermanentElements(callback) {
1067
- Bardo.preservingPermanentElements(this.permanentElementMap, callback);
1339
+ async preservingPermanentElements(callback) {
1340
+ await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1068
1341
  }
1069
1342
  focusFirstAutofocusableElement() {
1070
1343
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1072,6 +1345,18 @@ class Renderer {
1072
1345
  element.focus();
1073
1346
  }
1074
1347
  }
1348
+ enteringBardo(currentPermanentElement) {
1349
+ if (this.activeElement) return;
1350
+ if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
1351
+ this.activeElement = this.currentSnapshot.activeElement;
1352
+ }
1353
+ }
1354
+ leavingBardo(currentPermanentElement) {
1355
+ if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
1356
+ this.activeElement.focus();
1357
+ this.activeElement = null;
1358
+ }
1359
+ }
1075
1360
  get connectedSnapshot() {
1076
1361
  return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
1077
1362
  }
@@ -1084,16 +1369,6 @@ class Renderer {
1084
1369
  get permanentElementMap() {
1085
1370
  return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1086
1371
  }
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
1372
  }
1098
1373
 
1099
1374
  function elementIsFocusable(element) {
@@ -1101,6 +1376,22 @@ function elementIsFocusable(element) {
1101
1376
  }
1102
1377
 
1103
1378
  class FrameRenderer extends Renderer {
1379
+ static renderElement(currentElement, newElement) {
1380
+ var _a;
1381
+ const destinationRange = document.createRange();
1382
+ destinationRange.selectNodeContents(currentElement);
1383
+ destinationRange.deleteContents();
1384
+ const frameElement = newElement;
1385
+ const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1386
+ if (sourceRange) {
1387
+ sourceRange.selectNodeContents(frameElement);
1388
+ currentElement.appendChild(sourceRange.extractContents());
1389
+ }
1390
+ }
1391
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1392
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1393
+ this.delegate = delegate;
1394
+ }
1104
1395
  get shouldRender() {
1105
1396
  return true;
1106
1397
  }
@@ -1116,24 +1407,18 @@ class FrameRenderer extends Renderer {
1116
1407
  this.activateScriptElements();
1117
1408
  }
1118
1409
  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
- }
1410
+ this.delegate.willRenderFrame(this.currentElement, this.newElement);
1411
+ this.renderElement(this.currentElement, this.newElement);
1129
1412
  }
1130
1413
  scrollFrameIntoView() {
1131
1414
  if (this.currentElement.autoscroll || this.newElement.autoscroll) {
1132
1415
  const element = this.currentElement.firstElementChild;
1133
1416
  const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
1417
+ const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
1134
1418
  if (element) {
1135
1419
  element.scrollIntoView({
1136
- block: block
1420
+ block: block,
1421
+ behavior: behavior
1137
1422
  });
1138
1423
  return true;
1139
1424
  }
@@ -1142,7 +1427,7 @@ class FrameRenderer extends Renderer {
1142
1427
  }
1143
1428
  activateScriptElements() {
1144
1429
  for (const inertScriptElement of this.newScriptElements) {
1145
- const activatedScriptElement = this.createScriptElement(inertScriptElement);
1430
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
1146
1431
  inertScriptElement.replaceWith(activatedScriptElement);
1147
1432
  }
1148
1433
  }
@@ -1159,19 +1444,15 @@ function readScrollLogicalPosition(value, defaultValue) {
1159
1444
  }
1160
1445
  }
1161
1446
 
1162
- class ProgressBar {
1163
- constructor() {
1164
- this.hiding = false;
1165
- this.value = 0;
1166
- this.visible = false;
1167
- this.trickle = () => {
1168
- this.setValue(this.value + Math.random() / 100);
1169
- };
1170
- this.stylesheetElement = this.createStylesheetElement();
1171
- this.progressElement = this.createProgressElement();
1172
- this.installStylesheetElement();
1173
- this.setValue(0);
1447
+ function readScrollBehavior(value, defaultValue) {
1448
+ if (value == "auto" || value == "smooth") {
1449
+ return value;
1450
+ } else {
1451
+ return defaultValue;
1174
1452
  }
1453
+ }
1454
+
1455
+ class ProgressBar {
1175
1456
  static get defaultCSS() {
1176
1457
  return unindent`
1177
1458
  .turbo-progress-bar {
@@ -1181,7 +1462,7 @@ class ProgressBar {
1181
1462
  left: 0;
1182
1463
  height: 3px;
1183
1464
  background: #0076ff;
1184
- z-index: 9999;
1465
+ z-index: 2147483647;
1185
1466
  transition:
1186
1467
  width ${ProgressBar.animationDuration}ms ease-out,
1187
1468
  opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
@@ -1189,6 +1470,18 @@ class ProgressBar {
1189
1470
  }
1190
1471
  `;
1191
1472
  }
1473
+ constructor() {
1474
+ this.hiding = false;
1475
+ this.value = 0;
1476
+ this.visible = false;
1477
+ this.trickle = () => {
1478
+ this.setValue(this.value + Math.random() / 100);
1479
+ };
1480
+ this.stylesheetElement = this.createStylesheetElement();
1481
+ this.progressElement = this.createProgressElement();
1482
+ this.installStylesheetElement();
1483
+ this.setValue(0);
1484
+ }
1192
1485
  show() {
1193
1486
  if (!this.visible) {
1194
1487
  this.visible = true;
@@ -1247,6 +1540,9 @@ class ProgressBar {
1247
1540
  const element = document.createElement("style");
1248
1541
  element.type = "text/css";
1249
1542
  element.textContent = ProgressBar.defaultCSS;
1543
+ if (this.cspNonce) {
1544
+ element.nonce = this.cspNonce;
1545
+ }
1250
1546
  return element;
1251
1547
  }
1252
1548
  createProgressElement() {
@@ -1254,6 +1550,9 @@ class ProgressBar {
1254
1550
  element.className = "turbo-progress-bar";
1255
1551
  return element;
1256
1552
  }
1553
+ get cspNonce() {
1554
+ return getMetaContent("csp-nonce");
1555
+ }
1257
1556
  }
1258
1557
 
1259
1558
  ProgressBar.animationDuration = 300;
@@ -1324,22 +1623,22 @@ function elementIsTracked(element) {
1324
1623
  }
1325
1624
 
1326
1625
  function elementIsScript(element) {
1327
- const tagName = element.tagName.toLowerCase();
1626
+ const tagName = element.localName;
1328
1627
  return tagName == "script";
1329
1628
  }
1330
1629
 
1331
1630
  function elementIsNoscript(element) {
1332
- const tagName = element.tagName.toLowerCase();
1631
+ const tagName = element.localName;
1333
1632
  return tagName == "noscript";
1334
1633
  }
1335
1634
 
1336
1635
  function elementIsStylesheet(element) {
1337
- const tagName = element.tagName.toLowerCase();
1636
+ const tagName = element.localName;
1338
1637
  return tagName == "style" || tagName == "link" && element.getAttribute("rel") == "stylesheet";
1339
1638
  }
1340
1639
 
1341
1640
  function elementIsMetaElementWithName(element, name) {
1342
- const tagName = element.tagName.toLowerCase();
1641
+ const tagName = element.localName;
1343
1642
  return tagName == "meta" && element.getAttribute("name") == name;
1344
1643
  }
1345
1644
 
@@ -1351,10 +1650,6 @@ function elementWithoutNonce(element) {
1351
1650
  }
1352
1651
 
1353
1652
  class PageSnapshot extends Snapshot {
1354
- constructor(element, headSnapshot) {
1355
- super(element);
1356
- this.headSnapshot = headSnapshot;
1357
- }
1358
1653
  static fromHTMLString(html = "") {
1359
1654
  return this.fromDocument(parseHTMLDocument(html));
1360
1655
  }
@@ -1364,8 +1659,23 @@ class PageSnapshot extends Snapshot {
1364
1659
  static fromDocument({head: head, body: body}) {
1365
1660
  return new this(body, new HeadSnapshot(head));
1366
1661
  }
1662
+ constructor(element, headSnapshot) {
1663
+ super(element);
1664
+ this.headSnapshot = headSnapshot;
1665
+ }
1367
1666
  clone() {
1368
- return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
1667
+ const clonedElement = this.element.cloneNode(true);
1668
+ const selectElements = this.element.querySelectorAll("select");
1669
+ const clonedSelectElements = clonedElement.querySelectorAll("select");
1670
+ for (const [index, source] of selectElements.entries()) {
1671
+ const clone = clonedSelectElements[index];
1672
+ for (const option of clone.selectedOptions) option.selected = false;
1673
+ for (const option of source.selectedOptions) clone.options[option.index].selected = true;
1674
+ }
1675
+ for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
1676
+ clonedPasswordInput.value = "";
1677
+ }
1678
+ return new PageSnapshot(clonedElement, this.headSnapshot);
1369
1679
  }
1370
1680
  get headElement() {
1371
1681
  return this.headSnapshot.element;
@@ -1415,7 +1725,10 @@ const defaultOptions = {
1415
1725
  action: "advance",
1416
1726
  historyChanged: false,
1417
1727
  visitCachedSnapshot: () => {},
1418
- willRender: true
1728
+ willRender: true,
1729
+ updateHistory: true,
1730
+ shouldCacheSnapshot: true,
1731
+ acceptsStreamResponse: false
1419
1732
  };
1420
1733
 
1421
1734
  var SystemStatusCode;
@@ -1433,21 +1746,27 @@ class Visit {
1433
1746
  this.followedRedirect = false;
1434
1747
  this.historyChanged = false;
1435
1748
  this.scrolled = false;
1749
+ this.shouldCacheSnapshot = true;
1750
+ this.acceptsStreamResponse = false;
1436
1751
  this.snapshotCached = false;
1437
1752
  this.state = VisitState.initialized;
1438
1753
  this.delegate = delegate;
1439
1754
  this.location = location;
1440
1755
  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);
1756
+ const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = Object.assign(Object.assign({}, defaultOptions), options);
1442
1757
  this.action = action;
1443
1758
  this.historyChanged = historyChanged;
1444
1759
  this.referrer = referrer;
1760
+ this.snapshot = snapshot;
1445
1761
  this.snapshotHTML = snapshotHTML;
1446
1762
  this.response = response;
1447
1763
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
1448
1764
  this.visitCachedSnapshot = visitCachedSnapshot;
1449
1765
  this.willRender = willRender;
1766
+ this.updateHistory = updateHistory;
1450
1767
  this.scrolled = !willRender;
1768
+ this.shouldCacheSnapshot = shouldCacheSnapshot;
1769
+ this.acceptsStreamResponse = acceptsStreamResponse;
1451
1770
  }
1452
1771
  get adapter() {
1453
1772
  return this.delegate.adapter;
@@ -1485,9 +1804,11 @@ class Visit {
1485
1804
  if (this.state == VisitState.started) {
1486
1805
  this.recordTimingMetric(TimingMetric.visitEnd);
1487
1806
  this.state = VisitState.completed;
1488
- this.adapter.visitCompleted(this);
1489
- this.delegate.visitCompleted(this);
1490
1807
  this.followRedirect();
1808
+ if (!this.followedRedirect) {
1809
+ this.adapter.visitCompleted(this);
1810
+ this.delegate.visitCompleted(this);
1811
+ }
1491
1812
  }
1492
1813
  }
1493
1814
  fail() {
@@ -1498,9 +1819,9 @@ class Visit {
1498
1819
  }
1499
1820
  changeHistory() {
1500
1821
  var _a;
1501
- if (!this.historyChanged) {
1822
+ if (!this.historyChanged && this.updateHistory) {
1502
1823
  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);
1824
+ const method = getHistoryMethodForAction(actionForHistory);
1504
1825
  this.history.update(method, this.location, this.restorationIdentifier);
1505
1826
  this.historyChanged = true;
1506
1827
  }
@@ -1543,14 +1864,15 @@ class Visit {
1543
1864
  if (this.response) {
1544
1865
  const {statusCode: statusCode, responseHTML: responseHTML} = this.response;
1545
1866
  this.render((async () => {
1546
- this.cacheSnapshot();
1867
+ if (this.shouldCacheSnapshot) this.cacheSnapshot();
1547
1868
  if (this.view.renderPromise) await this.view.renderPromise;
1548
1869
  if (isSuccessful(statusCode) && responseHTML != null) {
1549
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
1870
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
1871
+ this.performScroll();
1550
1872
  this.adapter.visitRendered(this);
1551
1873
  this.complete();
1552
1874
  } else {
1553
- await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
1875
+ await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
1554
1876
  this.adapter.visitRendered(this);
1555
1877
  this.fail();
1556
1878
  }
@@ -1583,7 +1905,8 @@ class Visit {
1583
1905
  this.adapter.visitRendered(this);
1584
1906
  } else {
1585
1907
  if (this.view.renderPromise) await this.view.renderPromise;
1586
- await this.view.renderPage(snapshot, isPreview, this.willRender);
1908
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
1909
+ this.performScroll();
1587
1910
  this.adapter.visitRendered(this);
1588
1911
  if (!isPreview) {
1589
1912
  this.complete();
@@ -1597,7 +1920,9 @@ class Visit {
1597
1920
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1598
1921
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1599
1922
  action: "replace",
1600
- response: this.response
1923
+ response: this.response,
1924
+ shouldCacheSnapshot: false,
1925
+ willRender: false
1601
1926
  });
1602
1927
  this.followedRedirect = true;
1603
1928
  }
@@ -1606,14 +1931,21 @@ class Visit {
1606
1931
  if (this.isSamePage) {
1607
1932
  this.render((async () => {
1608
1933
  this.cacheSnapshot();
1934
+ this.performScroll();
1935
+ this.changeHistory();
1609
1936
  this.adapter.visitRendered(this);
1610
1937
  }));
1611
1938
  }
1612
1939
  }
1940
+ prepareRequest(request) {
1941
+ if (this.acceptsStreamResponse) {
1942
+ request.acceptResponseType(StreamMessage.contentType);
1943
+ }
1944
+ }
1613
1945
  requestStarted() {
1614
1946
  this.startRequest();
1615
1947
  }
1616
- requestPreventedHandlingResponse(request, response) {}
1948
+ requestPreventedHandlingResponse(_request, _response) {}
1617
1949
  async requestSucceededWithResponse(request, response) {
1618
1950
  const responseHTML = await response.responseHTML;
1619
1951
  const {redirected: redirected, statusCode: statusCode} = response;
@@ -1647,7 +1979,7 @@ class Visit {
1647
1979
  });
1648
1980
  }
1649
1981
  }
1650
- requestErrored(request, error) {
1982
+ requestErrored(_request, _error) {
1651
1983
  this.recordResponse({
1652
1984
  statusCode: SystemStatusCode.networkFailure,
1653
1985
  redirected: false
@@ -1657,7 +1989,7 @@ class Visit {
1657
1989
  this.finishRequest();
1658
1990
  }
1659
1991
  performScroll() {
1660
- if (!this.scrolled) {
1992
+ if (!this.scrolled && !this.view.forceReloaded) {
1661
1993
  if (this.action == "restore") {
1662
1994
  this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
1663
1995
  } else {
@@ -1713,7 +2045,7 @@ class Visit {
1713
2045
  }
1714
2046
  cacheSnapshot() {
1715
2047
  if (!this.snapshotCached) {
1716
- this.view.cacheSnapshot().then((snapshot => snapshot && this.visitCachedSnapshot(snapshot)));
2048
+ this.view.cacheSnapshot(this.snapshot).then((snapshot => snapshot && this.visitCachedSnapshot(snapshot)));
1717
2049
  this.snapshotCached = true;
1718
2050
  }
1719
2051
  }
@@ -1724,7 +2056,6 @@ class Visit {
1724
2056
  }));
1725
2057
  await callback();
1726
2058
  delete this.frame;
1727
- this.performScroll();
1728
2059
  }
1729
2060
  cancelRender() {
1730
2061
  if (this.frame) {
@@ -1747,12 +2078,12 @@ class BrowserAdapter {
1747
2078
  this.session = session;
1748
2079
  }
1749
2080
  visitProposedToLocation(location, options) {
1750
- this.navigator.startVisit(location, uuid(), options);
2081
+ this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
1751
2082
  }
1752
2083
  visitStarted(visit) {
2084
+ this.location = visit.location;
1753
2085
  visit.loadCachedSnapshot();
1754
2086
  visit.issueRequest();
1755
- visit.changeHistory();
1756
2087
  visit.goToSamePageAnchor();
1757
2088
  }
1758
2089
  visitRequestStarted(visit) {
@@ -1771,27 +2102,32 @@ class BrowserAdapter {
1771
2102
  case SystemStatusCode.networkFailure:
1772
2103
  case SystemStatusCode.timeoutFailure:
1773
2104
  case SystemStatusCode.contentTypeMismatch:
1774
- return this.reload();
2105
+ return this.reload({
2106
+ reason: "request_failed",
2107
+ context: {
2108
+ statusCode: statusCode
2109
+ }
2110
+ });
1775
2111
 
1776
2112
  default:
1777
2113
  return visit.loadResponse();
1778
2114
  }
1779
2115
  }
1780
- visitRequestFinished(visit) {
2116
+ visitRequestFinished(_visit) {
1781
2117
  this.progressBar.setValue(1);
1782
2118
  this.hideVisitProgressBar();
1783
2119
  }
1784
- visitCompleted(visit) {}
1785
- pageInvalidated() {
1786
- this.reload();
2120
+ visitCompleted(_visit) {}
2121
+ pageInvalidated(reason) {
2122
+ this.reload(reason);
1787
2123
  }
1788
- visitFailed(visit) {}
1789
- visitRendered(visit) {}
1790
- formSubmissionStarted(formSubmission) {
2124
+ visitFailed(_visit) {}
2125
+ visitRendered(_visit) {}
2126
+ formSubmissionStarted(_formSubmission) {
1791
2127
  this.progressBar.setValue(0);
1792
2128
  this.showFormProgressBarAfterDelay();
1793
2129
  }
1794
- formSubmissionFinished(formSubmission) {
2130
+ formSubmissionFinished(_formSubmission) {
1795
2131
  this.progressBar.setValue(1);
1796
2132
  this.hideFormProgressBar();
1797
2133
  }
@@ -1817,8 +2153,12 @@ class BrowserAdapter {
1817
2153
  delete this.formProgressBarTimeout;
1818
2154
  }
1819
2155
  }
1820
- reload() {
1821
- window.location.reload();
2156
+ reload(reason) {
2157
+ var _a;
2158
+ dispatch("turbo:reload", {
2159
+ detail: reason
2160
+ });
2161
+ window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;
1822
2162
  }
1823
2163
  get navigator() {
1824
2164
  return this.session.navigator;
@@ -1827,95 +2167,70 @@ class BrowserAdapter {
1827
2167
 
1828
2168
  class CacheObserver {
1829
2169
  constructor() {
2170
+ this.selector = "[data-turbo-temporary]";
2171
+ this.deprecatedSelector = "[data-turbo-cache=false]";
1830
2172
  this.started = false;
2173
+ this.removeTemporaryElements = _event => {
2174
+ for (const element of this.temporaryElements) {
2175
+ element.remove();
2176
+ }
2177
+ };
1831
2178
  }
1832
2179
  start() {
1833
2180
  if (!this.started) {
1834
2181
  this.started = true;
1835
- addEventListener("turbo:before-cache", this.removeStaleElements, false);
2182
+ addEventListener("turbo:before-cache", this.removeTemporaryElements, false);
1836
2183
  }
1837
2184
  }
1838
2185
  stop() {
1839
2186
  if (this.started) {
1840
2187
  this.started = false;
1841
- removeEventListener("turbo:before-cache", this.removeStaleElements, false);
2188
+ removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
1842
2189
  }
1843
2190
  }
1844
- removeStaleElements() {
1845
- const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
1846
- for (const element of staleElements) {
1847
- element.remove();
1848
- }
2191
+ get temporaryElements() {
2192
+ return [ ...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation ];
1849
2193
  }
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
- }
1870
- }
1871
- };
1872
- this.delegate = delegate;
1873
- }
1874
- start() {
1875
- if (!this.started) {
1876
- addEventListener("submit", this.submitCaptured, true);
1877
- this.started = true;
1878
- }
1879
- }
1880
- stop() {
1881
- if (this.started) {
1882
- removeEventListener("submit", this.submitCaptured, true);
1883
- this.started = false;
2194
+ get temporaryElementsWithDeprecation() {
2195
+ const elements = document.querySelectorAll(this.deprecatedSelector);
2196
+ if (elements.length) {
2197
+ console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);
1884
2198
  }
2199
+ return [ ...elements ];
1885
2200
  }
1886
2201
  }
1887
2202
 
1888
2203
  class FrameRedirector {
1889
- constructor(element) {
2204
+ constructor(session, element) {
2205
+ this.session = session;
1890
2206
  this.element = element;
1891
2207
  this.linkInterceptor = new LinkInterceptor(this, element);
1892
- this.formInterceptor = new FormInterceptor(this, element);
2208
+ this.formSubmitObserver = new FormSubmitObserver(this, element);
1893
2209
  }
1894
2210
  start() {
1895
2211
  this.linkInterceptor.start();
1896
- this.formInterceptor.start();
2212
+ this.formSubmitObserver.start();
1897
2213
  }
1898
2214
  stop() {
1899
2215
  this.linkInterceptor.stop();
1900
- this.formInterceptor.stop();
2216
+ this.formSubmitObserver.stop();
1901
2217
  }
1902
- shouldInterceptLinkClick(element, url) {
2218
+ shouldInterceptLinkClick(element, _location, _event) {
1903
2219
  return this.shouldRedirect(element);
1904
2220
  }
1905
- linkClickIntercepted(element, url) {
2221
+ linkClickIntercepted(element, url, event) {
1906
2222
  const frame = this.findFrameElement(element);
1907
2223
  if (frame) {
1908
- frame.delegate.linkClickIntercepted(element, url);
2224
+ frame.delegate.linkClickIntercepted(element, url, event);
1909
2225
  }
1910
2226
  }
1911
- shouldInterceptFormSubmission(element, submitter) {
1912
- return this.shouldSubmit(element, submitter);
2227
+ willSubmitForm(element, submitter) {
2228
+ return element.closest("turbo-frame") == null && this.shouldSubmit(element, submitter) && this.shouldRedirect(element, submitter);
1913
2229
  }
1914
- formSubmissionIntercepted(element, submitter) {
2230
+ formSubmitted(element, submitter) {
1915
2231
  const frame = this.findFrameElement(element, submitter);
1916
2232
  if (frame) {
1917
- frame.removeAttribute("reloadable");
1918
- frame.delegate.formSubmissionIntercepted(element, submitter);
2233
+ frame.delegate.formSubmitted(element, submitter);
1919
2234
  }
1920
2235
  }
1921
2236
  shouldSubmit(form, submitter) {
@@ -1926,8 +2241,13 @@ class FrameRedirector {
1926
2241
  return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
1927
2242
  }
1928
2243
  shouldRedirect(element, submitter) {
1929
- const frame = this.findFrameElement(element, submitter);
1930
- return frame ? frame != element.closest("turbo-frame") : false;
2244
+ const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
2245
+ if (isNavigatable) {
2246
+ const frame = this.findFrameElement(element, submitter);
2247
+ return frame ? frame != element.closest("turbo-frame") : false;
2248
+ } else {
2249
+ return false;
2250
+ }
1931
2251
  }
1932
2252
  findFrameElement(element, submitter) {
1933
2253
  const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
@@ -1957,7 +2277,7 @@ class History {
1957
2277
  }
1958
2278
  }
1959
2279
  };
1960
- this.onPageLoad = async event => {
2280
+ this.onPageLoad = async _event => {
1961
2281
  await nextMicrotask();
1962
2282
  this.pageLoaded = true;
1963
2283
  };
@@ -2023,57 +2343,6 @@ class History {
2023
2343
  }
2024
2344
  }
2025
2345
 
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
2346
  class Navigator {
2078
2347
  constructor(delegate) {
2079
2348
  this.delegate = delegate;
@@ -2127,13 +2396,15 @@ class Navigator {
2127
2396
  if (formSubmission == this.formSubmission) {
2128
2397
  const responseHTML = await fetchResponse.responseHTML;
2129
2398
  if (responseHTML) {
2130
- if (formSubmission.method != FetchMethod.get) {
2399
+ const shouldCacheSnapshot = formSubmission.isSafe;
2400
+ if (!shouldCacheSnapshot) {
2131
2401
  this.view.clearSnapshotCache();
2132
2402
  }
2133
2403
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
2134
2404
  const action = this.getActionForFormSubmission(formSubmission);
2135
2405
  const visitOptions = {
2136
2406
  action: action,
2407
+ shouldCacheSnapshot: shouldCacheSnapshot,
2137
2408
  response: {
2138
2409
  statusCode: statusCode,
2139
2410
  responseHTML: responseHTML,
@@ -2149,9 +2420,9 @@ class Navigator {
2149
2420
  if (responseHTML) {
2150
2421
  const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2151
2422
  if (fetchResponse.serverError) {
2152
- await this.view.renderError(snapshot);
2423
+ await this.view.renderError(snapshot, this.currentVisit);
2153
2424
  } else {
2154
- await this.view.renderPage(snapshot);
2425
+ await this.view.renderPage(snapshot, false, true, this.currentVisit);
2155
2426
  }
2156
2427
  this.view.scrollToTop();
2157
2428
  this.view.clearSnapshotCache();
@@ -2186,10 +2457,8 @@ class Navigator {
2186
2457
  get restorationIdentifier() {
2187
2458
  return this.history.restorationIdentifier;
2188
2459
  }
2189
- getActionForFormSubmission(formSubmission) {
2190
- const {formElement: formElement, submitter: submitter} = formSubmission;
2191
- const action = getAttribute("data-turbo-action", submitter, formElement);
2192
- return isAction(action) ? action : "advance";
2460
+ getActionForFormSubmission({submitter: submitter, formElement: formElement}) {
2461
+ return getVisitAction(submitter, formElement) || "advance";
2193
2462
  }
2194
2463
  }
2195
2464
 
@@ -2283,6 +2552,31 @@ class ScrollObserver {
2283
2552
  }
2284
2553
  }
2285
2554
 
2555
+ class StreamMessageRenderer {
2556
+ render({fragment: fragment}) {
2557
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => document.documentElement.appendChild(fragment)));
2558
+ }
2559
+ enteringBardo(currentPermanentElement, newPermanentElement) {
2560
+ newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
2561
+ }
2562
+ leavingBardo() {}
2563
+ }
2564
+
2565
+ function getPermanentElementMapForFragment(fragment) {
2566
+ const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
2567
+ const permanentElementMap = {};
2568
+ for (const permanentElementInDocument of permanentElementsInDocument) {
2569
+ const {id: id} = permanentElementInDocument;
2570
+ for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
2571
+ const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
2572
+ if (elementInStream) {
2573
+ permanentElementMap[id] = [ permanentElementInDocument, elementInStream ];
2574
+ }
2575
+ }
2576
+ }
2577
+ return permanentElementMap;
2578
+ }
2579
+
2286
2580
  class StreamObserver {
2287
2581
  constructor(delegate) {
2288
2582
  this.sources = new Set;
@@ -2335,7 +2629,7 @@ class StreamObserver {
2335
2629
  }
2336
2630
  }
2337
2631
  receiveMessageHTML(html) {
2338
- this.delegate.receivedMessageFromStream(new StreamMessage(html));
2632
+ this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
2339
2633
  }
2340
2634
  }
2341
2635
 
@@ -2354,20 +2648,24 @@ function fetchResponseIsStream(response) {
2354
2648
  }
2355
2649
 
2356
2650
  class ErrorRenderer extends Renderer {
2651
+ static renderElement(currentElement, newElement) {
2652
+ const {documentElement: documentElement, body: body} = document;
2653
+ documentElement.replaceChild(newElement, body);
2654
+ }
2357
2655
  async render() {
2358
2656
  this.replaceHeadAndBody();
2359
2657
  this.activateScriptElements();
2360
2658
  }
2361
2659
  replaceHeadAndBody() {
2362
- const {documentElement: documentElement, head: head, body: body} = document;
2660
+ const {documentElement: documentElement, head: head} = document;
2363
2661
  documentElement.replaceChild(this.newHead, head);
2364
- documentElement.replaceChild(this.newElement, body);
2662
+ this.renderElement(this.currentElement, this.newElement);
2365
2663
  }
2366
2664
  activateScriptElements() {
2367
2665
  for (const replaceableElement of this.scriptElements) {
2368
2666
  const parentNode = replaceableElement.parentNode;
2369
2667
  if (parentNode) {
2370
- const element = this.createScriptElement(replaceableElement);
2668
+ const element = activateScriptElement(replaceableElement);
2371
2669
  parentNode.replaceChild(element, replaceableElement);
2372
2670
  }
2373
2671
  }
@@ -2376,20 +2674,39 @@ class ErrorRenderer extends Renderer {
2376
2674
  return this.newSnapshot.headSnapshot.element;
2377
2675
  }
2378
2676
  get scriptElements() {
2379
- return [ ...document.documentElement.querySelectorAll("script") ];
2677
+ return document.documentElement.querySelectorAll("script");
2380
2678
  }
2381
2679
  }
2382
2680
 
2383
2681
  class PageRenderer extends Renderer {
2682
+ static renderElement(currentElement, newElement) {
2683
+ if (document.body && newElement instanceof HTMLBodyElement) {
2684
+ document.body.replaceWith(newElement);
2685
+ } else {
2686
+ document.documentElement.appendChild(newElement);
2687
+ }
2688
+ }
2384
2689
  get shouldRender() {
2385
2690
  return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
2386
2691
  }
2387
- prepareToRender() {
2388
- this.mergeHead();
2692
+ get reloadReason() {
2693
+ if (!this.newSnapshot.isVisitable) {
2694
+ return {
2695
+ reason: "turbo_visit_control_is_reload"
2696
+ };
2697
+ }
2698
+ if (!this.trackedElementsAreIdentical) {
2699
+ return {
2700
+ reason: "tracked_element_mismatch"
2701
+ };
2702
+ }
2703
+ }
2704
+ async prepareToRender() {
2705
+ await this.mergeHead();
2389
2706
  }
2390
2707
  async render() {
2391
2708
  if (this.willRender) {
2392
- this.replaceBody();
2709
+ await this.replaceBody();
2393
2710
  }
2394
2711
  }
2395
2712
  finishRendering() {
@@ -2407,31 +2724,64 @@ class PageRenderer extends Renderer {
2407
2724
  get newElement() {
2408
2725
  return this.newSnapshot.element;
2409
2726
  }
2410
- mergeHead() {
2411
- this.copyNewHeadStylesheetElements();
2727
+ async mergeHead() {
2728
+ const mergedHeadElements = this.mergeProvisionalElements();
2729
+ const newStylesheetElements = this.copyNewHeadStylesheetElements();
2412
2730
  this.copyNewHeadScriptElements();
2413
- this.removeCurrentHeadProvisionalElements();
2414
- this.copyNewHeadProvisionalElements();
2731
+ await mergedHeadElements;
2732
+ await newStylesheetElements;
2415
2733
  }
2416
- replaceBody() {
2417
- this.preservingPermanentElements((() => {
2734
+ async replaceBody() {
2735
+ await this.preservingPermanentElements((async () => {
2418
2736
  this.activateNewBody();
2419
- this.assignNewBody();
2737
+ await this.assignNewBody();
2420
2738
  }));
2421
2739
  }
2422
2740
  get trackedElementsAreIdentical() {
2423
2741
  return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
2424
2742
  }
2425
- copyNewHeadStylesheetElements() {
2743
+ async copyNewHeadStylesheetElements() {
2744
+ const loadingElements = [];
2426
2745
  for (const element of this.newHeadStylesheetElements) {
2746
+ loadingElements.push(waitForLoad(element));
2427
2747
  document.head.appendChild(element);
2428
2748
  }
2749
+ await Promise.all(loadingElements);
2429
2750
  }
2430
2751
  copyNewHeadScriptElements() {
2431
2752
  for (const element of this.newHeadScriptElements) {
2432
- document.head.appendChild(this.createScriptElement(element));
2753
+ document.head.appendChild(activateScriptElement(element));
2433
2754
  }
2434
2755
  }
2756
+ async mergeProvisionalElements() {
2757
+ const newHeadElements = [ ...this.newHeadProvisionalElements ];
2758
+ for (const element of this.currentHeadProvisionalElements) {
2759
+ if (!this.isCurrentElementInElementList(element, newHeadElements)) {
2760
+ document.head.removeChild(element);
2761
+ }
2762
+ }
2763
+ for (const element of newHeadElements) {
2764
+ document.head.appendChild(element);
2765
+ }
2766
+ }
2767
+ isCurrentElementInElementList(element, elementList) {
2768
+ for (const [index, newElement] of elementList.entries()) {
2769
+ if (element.tagName == "TITLE") {
2770
+ if (newElement.tagName != "TITLE") {
2771
+ continue;
2772
+ }
2773
+ if (element.innerHTML == newElement.innerHTML) {
2774
+ elementList.splice(index, 1);
2775
+ return true;
2776
+ }
2777
+ }
2778
+ if (newElement.isEqualNode(element)) {
2779
+ elementList.splice(index, 1);
2780
+ return true;
2781
+ }
2782
+ }
2783
+ return false;
2784
+ }
2435
2785
  removeCurrentHeadProvisionalElements() {
2436
2786
  for (const element of this.currentHeadProvisionalElements) {
2437
2787
  document.head.removeChild(element);
@@ -2448,16 +2798,12 @@ class PageRenderer extends Renderer {
2448
2798
  }
2449
2799
  activateNewBodyScriptElements() {
2450
2800
  for (const inertScriptElement of this.newBodyScriptElements) {
2451
- const activatedScriptElement = this.createScriptElement(inertScriptElement);
2801
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
2452
2802
  inertScriptElement.replaceWith(activatedScriptElement);
2453
2803
  }
2454
2804
  }
2455
- assignNewBody() {
2456
- if (document.body && this.newElement instanceof HTMLBodyElement) {
2457
- document.body.replaceWith(this.newElement);
2458
- } else {
2459
- document.documentElement.appendChild(this.newElement);
2460
- }
2805
+ async assignNewBody() {
2806
+ await this.renderElement(this.currentElement, this.newElement);
2461
2807
  }
2462
2808
  get newHeadStylesheetElements() {
2463
2809
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -2525,22 +2871,29 @@ class PageView extends View {
2525
2871
  super(...arguments);
2526
2872
  this.snapshotCache = new SnapshotCache(10);
2527
2873
  this.lastRenderedLocation = new URL(location.href);
2874
+ this.forceReloaded = false;
2528
2875
  }
2529
- renderPage(snapshot, isPreview = false, willRender = true) {
2530
- const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
2876
+ renderPage(snapshot, isPreview = false, willRender = true, visit) {
2877
+ const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
2878
+ if (!renderer.shouldRender) {
2879
+ this.forceReloaded = true;
2880
+ } else {
2881
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2882
+ }
2531
2883
  return this.render(renderer);
2532
2884
  }
2533
- renderError(snapshot) {
2534
- const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2885
+ renderError(snapshot, visit) {
2886
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2887
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
2535
2888
  return this.render(renderer);
2536
2889
  }
2537
2890
  clearSnapshotCache() {
2538
2891
  this.snapshotCache.clear();
2539
2892
  }
2540
- async cacheSnapshot() {
2541
- if (this.shouldCacheSnapshot) {
2893
+ async cacheSnapshot(snapshot = this.snapshot) {
2894
+ if (snapshot.isCacheable) {
2542
2895
  this.delegate.viewWillCacheSnapshot();
2543
- const {snapshot: snapshot, lastRenderedLocation: location} = this;
2896
+ const {lastRenderedLocation: location} = this;
2544
2897
  await nextEventLoopTick();
2545
2898
  const cachedSnapshot = snapshot.clone();
2546
2899
  this.snapshotCache.put(location, cachedSnapshot);
@@ -2553,8 +2906,46 @@ class PageView extends View {
2553
2906
  get snapshot() {
2554
2907
  return PageSnapshot.fromElement(this.element);
2555
2908
  }
2556
- get shouldCacheSnapshot() {
2557
- return this.snapshot.isCacheable;
2909
+ }
2910
+
2911
+ class Preloader {
2912
+ constructor(delegate) {
2913
+ this.selector = "a[data-turbo-preload]";
2914
+ this.delegate = delegate;
2915
+ }
2916
+ get snapshotCache() {
2917
+ return this.delegate.navigator.view.snapshotCache;
2918
+ }
2919
+ start() {
2920
+ if (document.readyState === "loading") {
2921
+ return document.addEventListener("DOMContentLoaded", (() => {
2922
+ this.preloadOnLoadLinksForView(document.body);
2923
+ }));
2924
+ } else {
2925
+ this.preloadOnLoadLinksForView(document.body);
2926
+ }
2927
+ }
2928
+ preloadOnLoadLinksForView(element) {
2929
+ for (const link of element.querySelectorAll(this.selector)) {
2930
+ this.preloadURL(link);
2931
+ }
2932
+ }
2933
+ async preloadURL(link) {
2934
+ const location = new URL(link.href);
2935
+ if (this.snapshotCache.has(location)) {
2936
+ return;
2937
+ }
2938
+ try {
2939
+ const response = await fetch(location.toString(), {
2940
+ headers: {
2941
+ "VND.PREFETCH": "true",
2942
+ Accept: "text/html"
2943
+ }
2944
+ });
2945
+ const responseText = await response.text();
2946
+ const snapshot = PageSnapshot.fromHTMLString(responseText);
2947
+ this.snapshotCache.put(location, snapshot);
2948
+ } catch (_) {}
2558
2949
  }
2559
2950
  }
2560
2951
 
@@ -2562,30 +2953,36 @@ class Session {
2562
2953
  constructor() {
2563
2954
  this.navigator = new Navigator(this);
2564
2955
  this.history = new History(this);
2956
+ this.preloader = new Preloader(this);
2565
2957
  this.view = new PageView(this, document.documentElement);
2566
2958
  this.adapter = new BrowserAdapter(this);
2567
2959
  this.pageObserver = new PageObserver(this);
2568
2960
  this.cacheObserver = new CacheObserver;
2569
- this.linkClickObserver = new LinkClickObserver(this);
2570
- this.formSubmitObserver = new FormSubmitObserver(this);
2961
+ this.linkClickObserver = new LinkClickObserver(this, window);
2962
+ this.formSubmitObserver = new FormSubmitObserver(this, document);
2571
2963
  this.scrollObserver = new ScrollObserver(this);
2572
2964
  this.streamObserver = new StreamObserver(this);
2573
- this.frameRedirector = new FrameRedirector(document.documentElement);
2965
+ this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
2966
+ this.frameRedirector = new FrameRedirector(this, document.documentElement);
2967
+ this.streamMessageRenderer = new StreamMessageRenderer;
2574
2968
  this.drive = true;
2575
2969
  this.enabled = true;
2576
2970
  this.progressBarDelay = 500;
2577
2971
  this.started = false;
2972
+ this.formMode = "on";
2578
2973
  }
2579
2974
  start() {
2580
2975
  if (!this.started) {
2581
2976
  this.pageObserver.start();
2582
2977
  this.cacheObserver.start();
2978
+ this.formLinkClickObserver.start();
2583
2979
  this.linkClickObserver.start();
2584
2980
  this.formSubmitObserver.start();
2585
2981
  this.scrollObserver.start();
2586
2982
  this.streamObserver.start();
2587
2983
  this.frameRedirector.start();
2588
2984
  this.history.start();
2985
+ this.preloader.start();
2589
2986
  this.started = true;
2590
2987
  this.enabled = true;
2591
2988
  }
@@ -2597,6 +2994,7 @@ class Session {
2597
2994
  if (this.started) {
2598
2995
  this.pageObserver.stop();
2599
2996
  this.cacheObserver.stop();
2997
+ this.formLinkClickObserver.stop();
2600
2998
  this.linkClickObserver.stop();
2601
2999
  this.formSubmitObserver.stop();
2602
3000
  this.scrollObserver.stop();
@@ -2610,7 +3008,13 @@ class Session {
2610
3008
  this.adapter = adapter;
2611
3009
  }
2612
3010
  visit(location, options = {}) {
2613
- this.navigator.proposeVisit(expandURL(location), options);
3011
+ const frameElement = options.frame ? document.getElementById(options.frame) : null;
3012
+ if (frameElement instanceof FrameElement) {
3013
+ frameElement.src = location.toString();
3014
+ frameElement.loaded;
3015
+ } else {
3016
+ this.navigator.proposeVisit(expandURL(location), options);
3017
+ }
2614
3018
  }
2615
3019
  connectStreamSource(source) {
2616
3020
  this.streamObserver.connectStreamSource(source);
@@ -2619,7 +3023,7 @@ class Session {
2619
3023
  this.streamObserver.disconnectStreamSource(source);
2620
3024
  }
2621
3025
  renderStreamMessage(message) {
2622
- document.documentElement.appendChild(StreamMessage.wrap(message).fragment);
3026
+ this.streamMessageRenderer.render(StreamMessage.wrap(message));
2623
3027
  }
2624
3028
  clearCache() {
2625
3029
  this.view.clearSnapshotCache();
@@ -2627,6 +3031,9 @@ class Session {
2627
3031
  setProgressBarDelay(delay) {
2628
3032
  this.progressBarDelay = delay;
2629
3033
  }
3034
+ setFormMode(mode) {
3035
+ this.formMode = mode;
3036
+ }
2630
3037
  get location() {
2631
3038
  return this.history.location;
2632
3039
  }
@@ -2640,7 +3047,9 @@ class Session {
2640
3047
  historyChanged: true
2641
3048
  });
2642
3049
  } else {
2643
- this.adapter.pageInvalidated();
3050
+ this.adapter.pageInvalidated({
3051
+ reason: "turbo_disabled"
3052
+ });
2644
3053
  }
2645
3054
  }
2646
3055
  scrollPositionChanged(position) {
@@ -2648,41 +3057,21 @@ class Session {
2648
3057
  scrollPosition: position
2649
3058
  });
2650
3059
  }
2651
- willFollowLinkToLocation(link, location) {
2652
- return this.elementDriveEnabled(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location);
3060
+ willSubmitFormLinkToLocation(link, location) {
3061
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
3062
+ }
3063
+ submittedFormLinkToLocation() {}
3064
+ willFollowLinkToLocation(link, location, event) {
3065
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
2653
3066
  }
2654
3067
  followedLinkToLocation(link, location) {
2655
3068
  const action = this.getActionForLink(link);
2656
- this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, {
2657
- action: action
3069
+ const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
3070
+ this.visit(location.href, {
3071
+ action: action,
3072
+ acceptsStreamResponse: acceptsStreamResponse
2658
3073
  });
2659
3074
  }
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
3075
  allowsVisitingLocationWithAction(location, action) {
2687
3076
  return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
2688
3077
  }
@@ -2691,12 +3080,16 @@ class Session {
2691
3080
  this.adapter.visitProposedToLocation(location, options);
2692
3081
  }
2693
3082
  visitStarted(visit) {
3083
+ if (!visit.acceptsStreamResponse) {
3084
+ markAsBusy(document.documentElement);
3085
+ }
2694
3086
  extendURLWithDeprecatedProperties(visit.location);
2695
3087
  if (!visit.silent) {
2696
3088
  this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
2697
3089
  }
2698
3090
  }
2699
3091
  visitCompleted(visit) {
3092
+ clearBusyState(document.documentElement);
2700
3093
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2701
3094
  }
2702
3095
  locationWithActionIsSamePage(location, action) {
@@ -2707,7 +3100,7 @@ class Session {
2707
3100
  }
2708
3101
  willSubmitForm(form, submitter) {
2709
3102
  const action = getAction(form, submitter);
2710
- return this.elementDriveEnabled(form) && (!submitter || this.elementDriveEnabled(submitter)) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
3103
+ return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
2711
3104
  }
2712
3105
  formSubmitted(form, submitter) {
2713
3106
  this.navigator.submitForm(form, submitter);
@@ -2731,16 +3124,23 @@ class Session {
2731
3124
  this.notifyApplicationBeforeCachingSnapshot();
2732
3125
  }
2733
3126
  }
2734
- allowsImmediateRender({element: element}, resume) {
2735
- const event = this.notifyApplicationBeforeRender(element, resume);
2736
- return !event.defaultPrevented;
3127
+ allowsImmediateRender({element: element}, options) {
3128
+ const event = this.notifyApplicationBeforeRender(element, options);
3129
+ const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
3130
+ if (this.view.renderer && render) {
3131
+ this.view.renderer.renderElement = render;
3132
+ }
3133
+ return !defaultPrevented;
2737
3134
  }
2738
- viewRenderedSnapshot(snapshot, isPreview) {
3135
+ viewRenderedSnapshot(_snapshot, _isPreview) {
2739
3136
  this.view.lastRenderedLocation = this.history.location;
2740
3137
  this.notifyApplicationAfterRender();
2741
3138
  }
2742
- viewInvalidated() {
2743
- this.adapter.pageInvalidated();
3139
+ preloadOnLoadLinksForView(element) {
3140
+ this.preloader.preloadOnLoadLinksForView(element);
3141
+ }
3142
+ viewInvalidated(reason) {
3143
+ this.adapter.pageInvalidated(reason);
2744
3144
  }
2745
3145
  frameLoaded(frame) {
2746
3146
  this.notifyApplicationAfterFrameLoad(frame);
@@ -2748,19 +3148,20 @@ class Session {
2748
3148
  frameRendered(fetchResponse, frame) {
2749
3149
  this.notifyApplicationAfterFrameRender(fetchResponse, frame);
2750
3150
  }
2751
- applicationAllowsFollowingLinkToLocation(link, location) {
2752
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
3151
+ applicationAllowsFollowingLinkToLocation(link, location, ev) {
3152
+ const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
2753
3153
  return !event.defaultPrevented;
2754
3154
  }
2755
3155
  applicationAllowsVisitingLocation(location) {
2756
3156
  const event = this.notifyApplicationBeforeVisitingLocation(location);
2757
3157
  return !event.defaultPrevented;
2758
3158
  }
2759
- notifyApplicationAfterClickingLinkToLocation(link, location) {
3159
+ notifyApplicationAfterClickingLinkToLocation(link, location, event) {
2760
3160
  return dispatch("turbo:click", {
2761
3161
  target: link,
2762
3162
  detail: {
2763
- url: location.href
3163
+ url: location.href,
3164
+ originalEvent: event
2764
3165
  },
2765
3166
  cancelable: true
2766
3167
  });
@@ -2774,7 +3175,6 @@ class Session {
2774
3175
  });
2775
3176
  }
2776
3177
  notifyApplicationAfterVisitingLocation(location, action) {
2777
- markAsBusy(document.documentElement);
2778
3178
  return dispatch("turbo:visit", {
2779
3179
  detail: {
2780
3180
  url: location.href,
@@ -2785,12 +3185,11 @@ class Session {
2785
3185
  notifyApplicationBeforeCachingSnapshot() {
2786
3186
  return dispatch("turbo:before-cache");
2787
3187
  }
2788
- notifyApplicationBeforeRender(newBody, resume) {
3188
+ notifyApplicationBeforeRender(newBody, options) {
2789
3189
  return dispatch("turbo:before-render", {
2790
- detail: {
2791
- newBody: newBody,
2792
- resume: resume
2793
- },
3190
+ detail: Object.assign({
3191
+ newBody: newBody
3192
+ }, options),
2794
3193
  cancelable: true
2795
3194
  });
2796
3195
  }
@@ -2798,7 +3197,6 @@ class Session {
2798
3197
  return dispatch("turbo:render");
2799
3198
  }
2800
3199
  notifyApplicationAfterPageLoad(timing = {}) {
2801
- clearBusyState(document.documentElement);
2802
3200
  return dispatch("turbo:load", {
2803
3201
  detail: {
2804
3202
  url: this.location.href,
@@ -2826,9 +3224,22 @@ class Session {
2826
3224
  cancelable: true
2827
3225
  });
2828
3226
  }
2829
- elementDriveEnabled(element) {
2830
- const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
2831
- if (this.drive) {
3227
+ submissionIsNavigatable(form, submitter) {
3228
+ if (this.formMode == "off") {
3229
+ return false;
3230
+ } else {
3231
+ const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
3232
+ if (this.formMode == "optin") {
3233
+ return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null;
3234
+ } else {
3235
+ return submitterIsNavigatable && this.elementIsNavigatable(form);
3236
+ }
3237
+ }
3238
+ }
3239
+ elementIsNavigatable(element) {
3240
+ const container = findClosestRecursively(element, "[data-turbo]");
3241
+ const withinFrame = findClosestRecursively(element, "turbo-frame");
3242
+ if (this.drive || withinFrame) {
2832
3243
  if (container) {
2833
3244
  return container.getAttribute("data-turbo") != "false";
2834
3245
  } else {
@@ -2843,19 +3254,7 @@ class Session {
2843
3254
  }
2844
3255
  }
2845
3256
  getActionForLink(link) {
2846
- const action = link.getAttribute("data-turbo-action");
2847
- return isAction(action) ? action : "advance";
2848
- }
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
- }
3257
+ return getVisitAction(link) || "advance";
2859
3258
  }
2860
3259
  get snapshot() {
2861
3260
  return this.view.snapshot;
@@ -2874,8 +3273,66 @@ const deprecatedLocationPropertyDescriptors = {
2874
3273
  }
2875
3274
  };
2876
3275
 
3276
+ class Cache {
3277
+ constructor(session) {
3278
+ this.session = session;
3279
+ }
3280
+ clear() {
3281
+ this.session.clearCache();
3282
+ }
3283
+ resetCacheControl() {
3284
+ this.setCacheControl("");
3285
+ }
3286
+ exemptPageFromCache() {
3287
+ this.setCacheControl("no-cache");
3288
+ }
3289
+ exemptPageFromPreview() {
3290
+ this.setCacheControl("no-preview");
3291
+ }
3292
+ setCacheControl(value) {
3293
+ setMetaContent("turbo-cache-control", value);
3294
+ }
3295
+ }
3296
+
3297
+ const StreamActions = {
3298
+ after() {
3299
+ this.targetElements.forEach((e => {
3300
+ var _a;
3301
+ return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
3302
+ }));
3303
+ },
3304
+ append() {
3305
+ this.removeDuplicateTargetChildren();
3306
+ this.targetElements.forEach((e => e.append(this.templateContent)));
3307
+ },
3308
+ before() {
3309
+ this.targetElements.forEach((e => {
3310
+ var _a;
3311
+ return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
3312
+ }));
3313
+ },
3314
+ prepend() {
3315
+ this.removeDuplicateTargetChildren();
3316
+ this.targetElements.forEach((e => e.prepend(this.templateContent)));
3317
+ },
3318
+ remove() {
3319
+ this.targetElements.forEach((e => e.remove()));
3320
+ },
3321
+ replace() {
3322
+ this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3323
+ },
3324
+ update() {
3325
+ this.targetElements.forEach((targetElement => {
3326
+ targetElement.innerHTML = "";
3327
+ targetElement.append(this.templateContent);
3328
+ }));
3329
+ }
3330
+ };
3331
+
2877
3332
  const session = new Session;
2878
3333
 
3334
+ const cache = new Cache(session);
3335
+
2879
3336
  const {navigator: navigator$1} = session;
2880
3337
 
2881
3338
  function start() {
@@ -2903,6 +3360,7 @@ function renderStreamMessage(message) {
2903
3360
  }
2904
3361
 
2905
3362
  function clearCache() {
3363
+ 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
3364
  session.clearCache();
2907
3365
  }
2908
3366
 
@@ -2914,12 +3372,18 @@ function setConfirmMethod(confirmMethod) {
2914
3372
  FormSubmission.confirmMethod = confirmMethod;
2915
3373
  }
2916
3374
 
3375
+ function setFormMode(mode) {
3376
+ session.setFormMode(mode);
3377
+ }
3378
+
2917
3379
  var Turbo = Object.freeze({
2918
3380
  __proto__: null,
2919
3381
  navigator: navigator$1,
2920
3382
  session: session,
3383
+ cache: cache,
2921
3384
  PageRenderer: PageRenderer,
2922
3385
  PageSnapshot: PageSnapshot,
3386
+ FrameRenderer: FrameRenderer,
2923
3387
  start: start,
2924
3388
  registerAdapter: registerAdapter,
2925
3389
  visit: visit,
@@ -2928,41 +3392,57 @@ var Turbo = Object.freeze({
2928
3392
  renderStreamMessage: renderStreamMessage,
2929
3393
  clearCache: clearCache,
2930
3394
  setProgressBarDelay: setProgressBarDelay,
2931
- setConfirmMethod: setConfirmMethod
3395
+ setConfirmMethod: setConfirmMethod,
3396
+ setFormMode: setFormMode,
3397
+ StreamActions: StreamActions
2932
3398
  });
2933
3399
 
3400
+ class TurboFrameMissingError extends Error {}
3401
+
2934
3402
  class FrameController {
2935
3403
  constructor(element) {
2936
- this.fetchResponseLoaded = fetchResponse => {};
3404
+ this.fetchResponseLoaded = _fetchResponse => {};
2937
3405
  this.currentFetchRequest = null;
2938
3406
  this.resolveVisitPromise = () => {};
2939
3407
  this.connected = false;
2940
3408
  this.hasBeenLoaded = false;
2941
- this.settingSourceURL = false;
3409
+ this.ignoredAttributes = new Set;
3410
+ this.action = null;
3411
+ this.visitCachedSnapshot = ({element: element}) => {
3412
+ const frame = element.querySelector("#" + this.element.id);
3413
+ if (frame && this.previousFrameElement) {
3414
+ frame.replaceChildren(...this.previousFrameElement.children);
3415
+ }
3416
+ delete this.previousFrameElement;
3417
+ };
2942
3418
  this.element = element;
2943
3419
  this.view = new FrameView(this, this.element);
2944
3420
  this.appearanceObserver = new AppearanceObserver(this, this.element);
3421
+ this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);
2945
3422
  this.linkInterceptor = new LinkInterceptor(this, this.element);
2946
- this.formInterceptor = new FormInterceptor(this, this.element);
3423
+ this.restorationIdentifier = uuid();
3424
+ this.formSubmitObserver = new FormSubmitObserver(this, this.element);
2947
3425
  }
2948
3426
  connect() {
2949
3427
  if (!this.connected) {
2950
3428
  this.connected = true;
2951
- this.reloadable = false;
2952
3429
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2953
3430
  this.appearanceObserver.start();
3431
+ } else {
3432
+ this.loadSourceURL();
2954
3433
  }
3434
+ this.formLinkClickObserver.start();
2955
3435
  this.linkInterceptor.start();
2956
- this.formInterceptor.start();
2957
- this.sourceURLChanged();
3436
+ this.formSubmitObserver.start();
2958
3437
  }
2959
3438
  }
2960
3439
  disconnect() {
2961
3440
  if (this.connected) {
2962
3441
  this.connected = false;
2963
3442
  this.appearanceObserver.stop();
3443
+ this.formLinkClickObserver.stop();
2964
3444
  this.linkInterceptor.stop();
2965
- this.formInterceptor.stop();
3445
+ this.formSubmitObserver.stop();
2966
3446
  }
2967
3447
  }
2968
3448
  disabledChanged() {
@@ -2971,10 +3451,27 @@ class FrameController {
2971
3451
  }
2972
3452
  }
2973
3453
  sourceURLChanged() {
3454
+ if (this.isIgnoringChangesTo("src")) return;
3455
+ if (this.element.isConnected) {
3456
+ this.complete = false;
3457
+ }
2974
3458
  if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
2975
3459
  this.loadSourceURL();
2976
3460
  }
2977
3461
  }
3462
+ sourceURLReloaded() {
3463
+ const {src: src} = this.element;
3464
+ this.ignoringChangesToAttribute("complete", (() => {
3465
+ this.element.removeAttribute("complete");
3466
+ }));
3467
+ this.element.src = null;
3468
+ this.element.src = src;
3469
+ return this.element.loaded;
3470
+ }
3471
+ completeChanged() {
3472
+ if (this.isIgnoringChangesTo("complete")) return;
3473
+ this.loadSourceURL();
3474
+ }
2978
3475
  loadingStyleChanged() {
2979
3476
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2980
3477
  this.appearanceObserver.start();
@@ -2984,20 +3481,11 @@ class FrameController {
2984
3481
  }
2985
3482
  }
2986
3483
  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
- }
3484
+ if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
3485
+ this.element.loaded = this.visit(expandURL(this.sourceURL));
3486
+ this.appearanceObserver.stop();
3487
+ await this.element.loaded;
3488
+ this.hasBeenLoaded = true;
3001
3489
  }
3002
3490
  }
3003
3491
  async loadResponse(fetchResponse) {
@@ -3007,71 +3495,73 @@ class FrameController {
3007
3495
  try {
3008
3496
  const html = await fetchResponse.responseHTML;
3009
3497
  if (html) {
3010
- 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);
3498
+ const document = parseHTMLDocument(html);
3499
+ const pageSnapshot = PageSnapshot.fromDocument(document);
3500
+ if (pageSnapshot.isVisitable) {
3501
+ await this.loadFrameResponse(fetchResponse, document);
3502
+ } else {
3503
+ await this.handleUnvisitableFrameResponse(fetchResponse);
3504
+ }
3018
3505
  }
3019
- } catch (error) {
3020
- console.error(error);
3021
- this.view.invalidate();
3022
3506
  } finally {
3023
3507
  this.fetchResponseLoaded = () => {};
3024
3508
  }
3025
3509
  }
3026
3510
  elementAppearedInViewport(element) {
3511
+ this.proposeVisitIfNavigatedWithAction(element, element);
3027
3512
  this.loadSourceURL();
3028
3513
  }
3029
- shouldInterceptLinkClick(element, url) {
3030
- if (element.hasAttribute("data-turbo-method")) {
3031
- return false;
3032
- } else {
3033
- return this.shouldInterceptNavigation(element);
3034
- }
3514
+ willSubmitFormLinkToLocation(link) {
3515
+ return this.shouldInterceptNavigation(link);
3035
3516
  }
3036
- linkClickIntercepted(element, url) {
3037
- this.reloadable = true;
3038
- this.navigateFrame(element, url);
3517
+ submittedFormLinkToLocation(link, _location, form) {
3518
+ const frame = this.findFrameElement(link);
3519
+ if (frame) form.setAttribute("data-turbo-frame", frame.id);
3039
3520
  }
3040
- shouldInterceptFormSubmission(element, submitter) {
3041
- return this.shouldInterceptNavigation(element, submitter);
3521
+ shouldInterceptLinkClick(element, _location, _event) {
3522
+ return this.shouldInterceptNavigation(element);
3042
3523
  }
3043
- formSubmissionIntercepted(element, submitter) {
3524
+ linkClickIntercepted(element, location) {
3525
+ this.navigateFrame(element, location);
3526
+ }
3527
+ willSubmitForm(element, submitter) {
3528
+ return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
3529
+ }
3530
+ formSubmitted(element, submitter) {
3044
3531
  if (this.formSubmission) {
3045
3532
  this.formSubmission.stop();
3046
3533
  }
3047
- this.reloadable = false;
3048
3534
  this.formSubmission = new FormSubmission(this, element, submitter);
3049
3535
  const {fetchRequest: fetchRequest} = this.formSubmission;
3050
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3536
+ this.prepareRequest(fetchRequest);
3051
3537
  this.formSubmission.start();
3052
3538
  }
3053
- prepareHeadersForRequest(headers, request) {
3054
- headers["Turbo-Frame"] = this.id;
3539
+ prepareRequest(request) {
3540
+ var _a;
3541
+ request.headers["Turbo-Frame"] = this.id;
3542
+ if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3543
+ request.acceptResponseType(StreamMessage.contentType);
3544
+ }
3055
3545
  }
3056
- requestStarted(request) {
3546
+ requestStarted(_request) {
3057
3547
  markAsBusy(this.element);
3058
3548
  }
3059
- requestPreventedHandlingResponse(request, response) {
3549
+ requestPreventedHandlingResponse(_request, _response) {
3060
3550
  this.resolveVisitPromise();
3061
3551
  }
3062
3552
  async requestSucceededWithResponse(request, response) {
3063
3553
  await this.loadResponse(response);
3064
3554
  this.resolveVisitPromise();
3065
3555
  }
3066
- requestFailedWithResponse(request, response) {
3067
- console.error(response);
3556
+ async requestFailedWithResponse(request, response) {
3557
+ await this.loadResponse(response);
3068
3558
  this.resolveVisitPromise();
3069
3559
  }
3070
3560
  requestErrored(request, error) {
3071
3561
  console.error(error);
3072
3562
  this.resolveVisitPromise();
3073
3563
  }
3074
- requestFinished(request) {
3564
+ requestFinished(_request) {
3075
3565
  clearBusyState(this.element);
3076
3566
  }
3077
3567
  formSubmissionStarted({formElement: formElement}) {
@@ -3079,11 +3569,15 @@ class FrameController {
3079
3569
  }
3080
3570
  formSubmissionSucceededWithResponse(formSubmission, response) {
3081
3571
  const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
3082
- this.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
3572
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
3083
3573
  frame.delegate.loadResponse(response);
3574
+ if (!formSubmission.isSafe) {
3575
+ session.clearCache();
3576
+ }
3084
3577
  }
3085
3578
  formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
3086
3579
  this.element.delegate.loadResponse(fetchResponse);
3580
+ session.clearCache();
3087
3581
  }
3088
3582
  formSubmissionErrored(formSubmission, error) {
3089
3583
  console.error(error);
@@ -3091,11 +3585,44 @@ class FrameController {
3091
3585
  formSubmissionFinished({formElement: formElement}) {
3092
3586
  clearBusyState(formElement, this.findFrameElement(formElement));
3093
3587
  }
3094
- allowsImmediateRender(snapshot, resume) {
3095
- return true;
3588
+ allowsImmediateRender({element: newFrame}, options) {
3589
+ const event = dispatch("turbo:before-frame-render", {
3590
+ target: this.element,
3591
+ detail: Object.assign({
3592
+ newFrame: newFrame
3593
+ }, options),
3594
+ cancelable: true
3595
+ });
3596
+ const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
3597
+ if (this.view.renderer && render) {
3598
+ this.view.renderer.renderElement = render;
3599
+ }
3600
+ return !defaultPrevented;
3601
+ }
3602
+ viewRenderedSnapshot(_snapshot, _isPreview) {}
3603
+ preloadOnLoadLinksForView(element) {
3604
+ session.preloadOnLoadLinksForView(element);
3096
3605
  }
3097
- viewRenderedSnapshot(snapshot, isPreview) {}
3098
3606
  viewInvalidated() {}
3607
+ willRenderFrame(currentElement, _newElement) {
3608
+ this.previousFrameElement = currentElement.cloneNode(true);
3609
+ }
3610
+ async loadFrameResponse(fetchResponse, document) {
3611
+ const newFrameElement = await this.extractForeignFrameElement(document.body);
3612
+ if (newFrameElement) {
3613
+ const snapshot = new Snapshot(newFrameElement);
3614
+ const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
3615
+ if (this.view.renderPromise) await this.view.renderPromise;
3616
+ this.changeHistory();
3617
+ await this.view.render(renderer);
3618
+ this.complete = true;
3619
+ session.frameRendered(fetchResponse, this.element);
3620
+ session.frameLoaded(this.element);
3621
+ this.fetchResponseLoaded(fetchResponse);
3622
+ } else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3623
+ this.handleFrameMissingFromResponse(fetchResponse);
3624
+ }
3625
+ }
3099
3626
  async visit(url) {
3100
3627
  var _a;
3101
3628
  const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
@@ -3112,14 +3639,16 @@ class FrameController {
3112
3639
  }
3113
3640
  navigateFrame(element, url, submitter) {
3114
3641
  const frame = this.findFrameElement(element, submitter);
3115
- this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3116
- frame.setAttribute("reloadable", "");
3117
- frame.src = url;
3642
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3643
+ this.withCurrentNavigationElement(element, (() => {
3644
+ frame.src = url;
3645
+ }));
3118
3646
  }
3119
3647
  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);
3648
+ this.action = getVisitAction(submitter, element, frame);
3649
+ if (this.action) {
3650
+ const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3651
+ const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
3123
3652
  frame.delegate.fetchResponseLoaded = fetchResponse => {
3124
3653
  if (frame.src) {
3125
3654
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
@@ -3129,16 +3658,70 @@ class FrameController {
3129
3658
  redirected: redirected,
3130
3659
  responseHTML: responseHTML
3131
3660
  };
3132
- session.visit(frame.src, {
3133
- action: action,
3661
+ const options = {
3134
3662
  response: response,
3135
3663
  visitCachedSnapshot: visitCachedSnapshot,
3136
- willRender: false
3137
- });
3664
+ willRender: false,
3665
+ updateHistory: false,
3666
+ restorationIdentifier: this.restorationIdentifier,
3667
+ snapshot: pageSnapshot
3668
+ };
3669
+ if (this.action) options.action = this.action;
3670
+ session.visit(frame.src, options);
3138
3671
  }
3139
3672
  };
3140
3673
  }
3141
3674
  }
3675
+ changeHistory() {
3676
+ if (this.action) {
3677
+ const method = getHistoryMethodForAction(this.action);
3678
+ session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
3679
+ }
3680
+ }
3681
+ async handleUnvisitableFrameResponse(fetchResponse) {
3682
+ console.warn(`The response (${fetchResponse.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.`);
3683
+ await this.visitResponse(fetchResponse.response);
3684
+ }
3685
+ willHandleFrameMissingFromResponse(fetchResponse) {
3686
+ this.element.setAttribute("complete", "");
3687
+ const response = fetchResponse.response;
3688
+ const visit = async (url, options = {}) => {
3689
+ if (url instanceof Response) {
3690
+ this.visitResponse(url);
3691
+ } else {
3692
+ session.visit(url, options);
3693
+ }
3694
+ };
3695
+ const event = dispatch("turbo:frame-missing", {
3696
+ target: this.element,
3697
+ detail: {
3698
+ response: response,
3699
+ visit: visit
3700
+ },
3701
+ cancelable: true
3702
+ });
3703
+ return !event.defaultPrevented;
3704
+ }
3705
+ handleFrameMissingFromResponse(fetchResponse) {
3706
+ this.view.missing();
3707
+ this.throwFrameMissingError(fetchResponse);
3708
+ }
3709
+ throwFrameMissingError(fetchResponse) {
3710
+ const message = `The response (${fetchResponse.statusCode}) did not contain the expected <turbo-frame id="${this.element.id}"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;
3711
+ throw new TurboFrameMissingError(message);
3712
+ }
3713
+ async visitResponse(response) {
3714
+ const wrapped = new FetchResponse(response);
3715
+ const responseHTML = await wrapped.responseHTML;
3716
+ const {location: location, redirected: redirected, statusCode: statusCode} = wrapped;
3717
+ return session.visit(location, {
3718
+ response: {
3719
+ redirected: redirected,
3720
+ statusCode: statusCode,
3721
+ responseHTML: responseHTML
3722
+ }
3723
+ });
3724
+ }
3142
3725
  findFrameElement(element, submitter) {
3143
3726
  var _a;
3144
3727
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
@@ -3148,18 +3731,20 @@ class FrameController {
3148
3731
  let element;
3149
3732
  const id = CSS.escape(this.id);
3150
3733
  try {
3151
- if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
3734
+ element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
3735
+ if (element) {
3152
3736
  return element;
3153
3737
  }
3154
- if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
3738
+ element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);
3739
+ if (element) {
3155
3740
  await element.loaded;
3156
3741
  return await this.extractForeignFrameElement(element);
3157
3742
  }
3158
- console.error(`Response has no matching <turbo-frame id="${id}"> element`);
3159
3743
  } catch (error) {
3160
3744
  console.error(error);
3745
+ return new FrameElement;
3161
3746
  }
3162
- return new FrameElement;
3747
+ return null;
3163
3748
  }
3164
3749
  formActionIsVisitable(form, submitter) {
3165
3750
  const action = getAction(form, submitter);
@@ -3179,10 +3764,10 @@ class FrameController {
3179
3764
  return !frameElement.disabled;
3180
3765
  }
3181
3766
  }
3182
- if (!session.elementDriveEnabled(element)) {
3767
+ if (!session.elementIsNavigatable(element)) {
3183
3768
  return false;
3184
3769
  }
3185
- if (submitter && !session.elementDriveEnabled(submitter)) {
3770
+ if (submitter && !session.elementIsNavigatable(submitter)) {
3186
3771
  return false;
3187
3772
  }
3188
3773
  return true;
@@ -3198,23 +3783,10 @@ class FrameController {
3198
3783
  return this.element.src;
3199
3784
  }
3200
3785
  }
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
3786
  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;
3787
+ this.ignoringChangesToAttribute("src", (() => {
3788
+ this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3789
+ }));
3218
3790
  }
3219
3791
  get loadingStyle() {
3220
3792
  return this.element.loading;
@@ -3222,6 +3794,18 @@ class FrameController {
3222
3794
  get isLoading() {
3223
3795
  return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
3224
3796
  }
3797
+ get complete() {
3798
+ return this.element.hasAttribute("complete");
3799
+ }
3800
+ set complete(value) {
3801
+ this.ignoringChangesToAttribute("complete", (() => {
3802
+ if (value) {
3803
+ this.element.setAttribute("complete", "");
3804
+ } else {
3805
+ this.element.removeAttribute("complete");
3806
+ }
3807
+ }));
3808
+ }
3225
3809
  get isActive() {
3226
3810
  return this.element.isActive && this.connected;
3227
3811
  }
@@ -3231,17 +3815,18 @@ class FrameController {
3231
3815
  const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3232
3816
  return expandURL(root);
3233
3817
  }
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;
3818
+ isIgnoringChangesTo(attributeName) {
3819
+ return this.ignoredAttributes.has(attributeName);
3820
+ }
3821
+ ignoringChangesToAttribute(attributeName, callback) {
3822
+ this.ignoredAttributes.add(attributeName);
3823
+ callback();
3824
+ this.ignoredAttributes.delete(attributeName);
3825
+ }
3826
+ withCurrentNavigationElement(element, callback) {
3827
+ this.currentNavigationElement = element;
3828
+ callback();
3829
+ delete this.currentNavigationElement;
3245
3830
  }
3246
3831
  }
3247
3832
 
@@ -3271,42 +3856,10 @@ function activateElement(element, currentURL) {
3271
3856
  }
3272
3857
  }
3273
3858
 
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
3859
  class StreamElement extends HTMLElement {
3860
+ static async renderElement(newElement) {
3861
+ await newElement.performAction();
3862
+ }
3310
3863
  async connectedCallback() {
3311
3864
  try {
3312
3865
  await this.render();
@@ -3319,9 +3872,10 @@ class StreamElement extends HTMLElement {
3319
3872
  async render() {
3320
3873
  var _a;
3321
3874
  return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
3322
- if (this.dispatchEvent(this.beforeRenderEvent)) {
3875
+ const event = this.beforeRenderEvent;
3876
+ if (this.dispatchEvent(event)) {
3323
3877
  await nextAnimationFrame();
3324
- this.performAction();
3878
+ await event.detail.render(this);
3325
3879
  }
3326
3880
  })();
3327
3881
  }
@@ -3336,7 +3890,7 @@ class StreamElement extends HTMLElement {
3336
3890
  get duplicateChildren() {
3337
3891
  var _a;
3338
3892
  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));
3893
+ const newChildrenIds = [ ...((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [] ].filter((c => !!c.id)).map((c => c.id));
3340
3894
  return existingChildren.filter((c => newChildrenIds.includes(c.id)));
3341
3895
  }
3342
3896
  get performAction() {
@@ -3362,7 +3916,11 @@ class StreamElement extends HTMLElement {
3362
3916
  return this.templateElement.content.cloneNode(true);
3363
3917
  }
3364
3918
  get templateElement() {
3365
- if (this.firstElementChild instanceof HTMLTemplateElement) {
3919
+ if (this.firstElementChild === null) {
3920
+ const template = this.ownerDocument.createElement("template");
3921
+ this.appendChild(template);
3922
+ return template;
3923
+ } else if (this.firstElementChild instanceof HTMLTemplateElement) {
3366
3924
  return this.firstElementChild;
3367
3925
  }
3368
3926
  this.raise("first child element must be a <template> element");
@@ -3386,7 +3944,11 @@ class StreamElement extends HTMLElement {
3386
3944
  get beforeRenderEvent() {
3387
3945
  return new CustomEvent("turbo:before-stream-render", {
3388
3946
  bubbles: true,
3389
- cancelable: true
3947
+ cancelable: true,
3948
+ detail: {
3949
+ newStream: this,
3950
+ render: StreamElement.renderElement
3951
+ }
3390
3952
  });
3391
3953
  }
3392
3954
  get targetElementsById() {
@@ -3409,17 +3971,45 @@ class StreamElement extends HTMLElement {
3409
3971
  }
3410
3972
  }
3411
3973
 
3974
+ class StreamSourceElement extends HTMLElement {
3975
+ constructor() {
3976
+ super(...arguments);
3977
+ this.streamSource = null;
3978
+ }
3979
+ connectedCallback() {
3980
+ this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
3981
+ connectStreamSource(this.streamSource);
3982
+ }
3983
+ disconnectedCallback() {
3984
+ if (this.streamSource) {
3985
+ disconnectStreamSource(this.streamSource);
3986
+ }
3987
+ }
3988
+ get src() {
3989
+ return this.getAttribute("src") || "";
3990
+ }
3991
+ }
3992
+
3412
3993
  FrameElement.delegateConstructor = FrameController;
3413
3994
 
3414
- customElements.define("turbo-frame", FrameElement);
3995
+ if (customElements.get("turbo-frame") === undefined) {
3996
+ customElements.define("turbo-frame", FrameElement);
3997
+ }
3998
+
3999
+ if (customElements.get("turbo-stream") === undefined) {
4000
+ customElements.define("turbo-stream", StreamElement);
4001
+ }
3415
4002
 
3416
- customElements.define("turbo-stream", StreamElement);
4003
+ if (customElements.get("turbo-stream-source") === undefined) {
4004
+ customElements.define("turbo-stream-source", StreamSourceElement);
4005
+ }
3417
4006
 
3418
4007
  (() => {
3419
4008
  let element = document.currentScript;
3420
4009
  if (!element) return;
3421
4010
  if (element.hasAttribute("data-turbo-suppress-warning")) return;
3422
- while (element = element.parentElement) {
4011
+ element = element.parentElement;
4012
+ while (element) {
3423
4013
  if (element == document.body) {
3424
4014
  return console.warn(unindent`
3425
4015
  You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
@@ -3432,6 +4022,7 @@ customElements.define("turbo-stream", StreamElement);
3432
4022
  Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
3433
4023
  `, element.outerHTML);
3434
4024
  }
4025
+ element = element.parentElement;
3435
4026
  }
3436
4027
  })();
3437
4028
 
@@ -3441,8 +4032,17 @@ start();
3441
4032
 
3442
4033
  var turbo_es2017Esm = Object.freeze({
3443
4034
  __proto__: null,
4035
+ FrameElement: FrameElement,
4036
+ get FrameLoadingStyle() {
4037
+ return FrameLoadingStyle;
4038
+ },
4039
+ FrameRenderer: FrameRenderer,
3444
4040
  PageRenderer: PageRenderer,
3445
4041
  PageSnapshot: PageSnapshot,
4042
+ StreamActions: StreamActions,
4043
+ StreamElement: StreamElement,
4044
+ StreamSourceElement: StreamSourceElement,
4045
+ cache: cache,
3446
4046
  clearCache: clearCache,
3447
4047
  connectStreamSource: connectStreamSource,
3448
4048
  disconnectStreamSource: disconnectStreamSource,
@@ -3451,6 +4051,7 @@ var turbo_es2017Esm = Object.freeze({
3451
4051
  renderStreamMessage: renderStreamMessage,
3452
4052
  session: session,
3453
4053
  setConfirmMethod: setConfirmMethod,
4054
+ setFormMode: setFormMode,
3454
4055
  setProgressBarDelay: setProgressBarDelay,
3455
4056
  start: start,
3456
4057
  visit: visit
@@ -3486,11 +4087,26 @@ var cable = Object.freeze({
3486
4087
  subscribeTo: subscribeTo
3487
4088
  });
3488
4089
 
4090
+ function walk(obj) {
4091
+ if (!obj || typeof obj !== "object") return obj;
4092
+ if (obj instanceof Date || obj instanceof RegExp) return obj;
4093
+ if (Array.isArray(obj)) return obj.map(walk);
4094
+ return Object.keys(obj).reduce((function(acc, key) {
4095
+ var camel = key[0].toLowerCase() + key.slice(1).replace(/([A-Z]+)/g, (function(m, x) {
4096
+ return "_" + x.toLowerCase();
4097
+ }));
4098
+ acc[camel] = walk(obj[key]);
4099
+ return acc;
4100
+ }), {});
4101
+ }
4102
+
3489
4103
  class TurboCableStreamSourceElement extends HTMLElement {
3490
4104
  async connectedCallback() {
3491
4105
  connectStreamSource(this);
3492
4106
  this.subscription = await subscribeTo(this.channel, {
3493
- received: this.dispatchMessageEvent.bind(this)
4107
+ received: this.dispatchMessageEvent.bind(this),
4108
+ connected: this.subscriptionConnected.bind(this),
4109
+ disconnected: this.subscriptionDisconnected.bind(this)
3494
4110
  });
3495
4111
  }
3496
4112
  disconnectedCallback() {
@@ -3503,17 +4119,79 @@ class TurboCableStreamSourceElement extends HTMLElement {
3503
4119
  });
3504
4120
  return this.dispatchEvent(event);
3505
4121
  }
4122
+ subscriptionConnected() {
4123
+ this.setAttribute("connected", "");
4124
+ }
4125
+ subscriptionDisconnected() {
4126
+ this.removeAttribute("connected");
4127
+ }
3506
4128
  get channel() {
3507
4129
  const channel = this.getAttribute("channel");
3508
4130
  const signed_stream_name = this.getAttribute("signed-stream-name");
3509
4131
  return {
3510
4132
  channel: channel,
3511
- signed_stream_name: signed_stream_name
4133
+ signed_stream_name: signed_stream_name,
4134
+ ...walk({
4135
+ ...this.dataset
4136
+ })
3512
4137
  };
3513
4138
  }
3514
4139
  }
3515
4140
 
3516
- customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement);
4141
+ if (customElements.get("turbo-cable-stream-source") === undefined) {
4142
+ customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement);
4143
+ }
4144
+
4145
+ function encodeMethodIntoRequestBody(event) {
4146
+ if (event.target instanceof HTMLFormElement) {
4147
+ const {target: form, detail: {fetchOptions: fetchOptions}} = event;
4148
+ form.addEventListener("turbo:submit-start", (({detail: {formSubmission: {submitter: submitter}}}) => {
4149
+ const body = isBodyInit(fetchOptions.body) ? fetchOptions.body : new URLSearchParams;
4150
+ const method = determineFetchMethod(submitter, body, form);
4151
+ if (!/get/i.test(method)) {
4152
+ if (/post/i.test(method)) {
4153
+ body.delete("_method");
4154
+ } else {
4155
+ body.set("_method", method);
4156
+ }
4157
+ fetchOptions.method = "post";
4158
+ }
4159
+ }), {
4160
+ once: true
4161
+ });
4162
+ }
4163
+ }
4164
+
4165
+ function determineFetchMethod(submitter, body, form) {
4166
+ const formMethod = determineFormMethod(submitter);
4167
+ const overrideMethod = body.get("_method");
4168
+ const method = form.getAttribute("method") || "get";
4169
+ if (typeof formMethod == "string") {
4170
+ return formMethod;
4171
+ } else if (typeof overrideMethod == "string") {
4172
+ return overrideMethod;
4173
+ } else {
4174
+ return method;
4175
+ }
4176
+ }
4177
+
4178
+ function determineFormMethod(submitter) {
4179
+ if (submitter instanceof HTMLButtonElement || submitter instanceof HTMLInputElement) {
4180
+ if (submitter.hasAttribute("formmethod")) {
4181
+ return submitter.formMethod;
4182
+ } else {
4183
+ return null;
4184
+ }
4185
+ } else {
4186
+ return null;
4187
+ }
4188
+ }
4189
+
4190
+ function isBodyInit(body) {
4191
+ return body instanceof FormData || body instanceof URLSearchParams;
4192
+ }
4193
+
4194
+ addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
3517
4195
 
3518
4196
  var adapters = {
3519
4197
  logger: self.console,