@hotwired/turbo-rails 7.2.5 → 8.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,7 @@
1
- (function() {
2
- if (window.Reflect === undefined || window.customElements === undefined || window.customElements.polyfillWrapFlushCallback) {
3
- return;
4
- }
5
- const BuiltInHTMLElement = HTMLElement;
6
- const wrapperForTheName = {
7
- HTMLElement: function HTMLElement() {
8
- return Reflect.construct(BuiltInHTMLElement, [], this.constructor);
9
- }
10
- };
11
- window.HTMLElement = wrapperForTheName["HTMLElement"];
12
- HTMLElement.prototype = BuiltInHTMLElement.prototype;
13
- HTMLElement.prototype.constructor = HTMLElement;
14
- Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);
15
- })();
16
-
1
+ /*!
2
+ Turbo 8.0.0-beta.1
3
+ Copyright © 2023 37signals LLC
4
+ */
17
5
  (function(prototype) {
18
6
  if (typeof prototype.requestSubmit == "function") return;
19
7
  prototype.requestSubmit = function(submitter) {
@@ -44,7 +32,7 @@ const submittersByForm = new WeakMap;
44
32
  function findSubmitterFromClickTarget(target) {
45
33
  const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
46
34
  const candidate = element ? element.closest("input, button") : null;
47
- return (candidate === null || candidate === void 0 ? void 0 : candidate.type) == "submit" ? candidate : null;
35
+ return candidate?.type == "submit" ? candidate : null;
48
36
  }
49
37
 
50
38
  function clickCaptured(event) {
@@ -56,13 +44,14 @@ function clickCaptured(event) {
56
44
 
57
45
  (function() {
58
46
  if ("submitter" in Event.prototype) return;
59
- let prototype;
60
- if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
61
- prototype = window.SubmitEvent.prototype;
62
- } else if ("SubmitEvent" in window) {
63
- return;
64
- } else {
65
- prototype = window.Event.prototype;
47
+ let prototype = window.Event.prototype;
48
+ if ("SubmitEvent" in window) {
49
+ const prototypeOfSubmitEvent = window.SubmitEvent.prototype;
50
+ if (/Apple Computer/.test(navigator.vendor) && !("submitter" in prototypeOfSubmitEvent)) {
51
+ prototype = prototypeOfSubmitEvent;
52
+ } else {
53
+ return;
54
+ }
66
55
  }
67
56
  addEventListener("click", clickCaptured, true);
68
57
  Object.defineProperty(prototype, "submitter", {
@@ -74,22 +63,21 @@ function clickCaptured(event) {
74
63
  });
75
64
  })();
76
65
 
77
- var FrameLoadingStyle;
78
-
79
- (function(FrameLoadingStyle) {
80
- FrameLoadingStyle["eager"] = "eager";
81
- FrameLoadingStyle["lazy"] = "lazy";
82
- })(FrameLoadingStyle || (FrameLoadingStyle = {}));
66
+ const FrameLoadingStyle = {
67
+ eager: "eager",
68
+ lazy: "lazy"
69
+ };
83
70
 
84
71
  class FrameElement extends HTMLElement {
72
+ static delegateConstructor=undefined;
73
+ loaded=Promise.resolve();
74
+ static get observedAttributes() {
75
+ return [ "disabled", "complete", "loading", "src" ];
76
+ }
85
77
  constructor() {
86
78
  super();
87
- this.loaded = Promise.resolve();
88
79
  this.delegate = new FrameElement.delegateConstructor(this);
89
80
  }
90
- static get observedAttributes() {
91
- return [ "disabled", "complete", "loading", "src" ];
92
- }
93
81
  connectedCallback() {
94
82
  this.delegate.connect();
95
83
  }
@@ -120,6 +108,16 @@ class FrameElement extends HTMLElement {
120
108
  this.removeAttribute("src");
121
109
  }
122
110
  }
111
+ get refresh() {
112
+ return this.getAttribute("refresh");
113
+ }
114
+ set refresh(value) {
115
+ if (value) {
116
+ this.setAttribute("refresh", value);
117
+ } else {
118
+ this.removeAttribute("refresh");
119
+ }
120
+ }
123
121
  get loading() {
124
122
  return frameLoadingStyleFromString(this.getAttribute("loading") || "");
125
123
  }
@@ -157,8 +155,7 @@ class FrameElement extends HTMLElement {
157
155
  return this.ownerDocument === document && !this.isPreview;
158
156
  }
159
157
  get isPreview() {
160
- var _a, _b;
161
- return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute("data-turbo-preview");
158
+ return this.ownerDocument?.documentElement?.hasAttribute("data-turbo-preview");
162
159
  }
163
160
  }
164
161
 
@@ -185,8 +182,8 @@ function getAnchor(url) {
185
182
  }
186
183
  }
187
184
 
188
- function getAction(form, submitter) {
189
- const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formaction")) || form.getAttribute("action") || form.action;
185
+ function getAction$1(form, submitter) {
186
+ const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action;
190
187
  return expandURL(action);
191
188
  }
192
189
 
@@ -325,6 +322,14 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
325
322
  return event;
326
323
  }
327
324
 
325
+ function nextRepaint() {
326
+ if (document.visibilityState === "hidden") {
327
+ return nextEventLoopTick();
328
+ } else {
329
+ return nextAnimationFrame();
330
+ }
331
+ }
332
+
328
333
  function nextAnimationFrame() {
329
334
  return new Promise((resolve => requestAnimationFrame((() => resolve()))));
330
335
  }
@@ -372,7 +377,7 @@ function uuid() {
372
377
  }
373
378
 
374
379
  function getAttribute(attributeName, ...elements) {
375
- for (const value of elements.map((element => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName)))) {
380
+ for (const value of elements.map((element => element?.getAttribute(attributeName)))) {
376
381
  if (typeof value == "string") return value;
377
382
  }
378
383
  return null;
@@ -458,21 +463,38 @@ function setMetaContent(name, content) {
458
463
  }
459
464
 
460
465
  function findClosestRecursively(element, selector) {
461
- var _a;
462
466
  if (element instanceof Element) {
463
- return element.closest(selector) || findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector);
467
+ return element.closest(selector) || findClosestRecursively(element.assignedSlot || element.getRootNode()?.host, selector);
464
468
  }
465
469
  }
466
470
 
467
- var FetchMethod;
471
+ function elementIsFocusable(element) {
472
+ const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
473
+ return !!element && element.closest(inertDisabledOrHidden) == null && typeof element.focus == "function";
474
+ }
475
+
476
+ function queryAutofocusableElement(elementOrDocumentFragment) {
477
+ return Array.from(elementOrDocumentFragment.querySelectorAll("[autofocus]")).find(elementIsFocusable);
478
+ }
479
+
480
+ async function around(callback, reader) {
481
+ const before = reader();
482
+ callback();
483
+ await nextAnimationFrame();
484
+ const after = reader();
485
+ return [ before, after ];
486
+ }
468
487
 
469
- (function(FetchMethod) {
470
- FetchMethod[FetchMethod["get"] = 0] = "get";
471
- FetchMethod[FetchMethod["post"] = 1] = "post";
472
- FetchMethod[FetchMethod["put"] = 2] = "put";
473
- FetchMethod[FetchMethod["patch"] = 3] = "patch";
474
- FetchMethod[FetchMethod["delete"] = 4] = "delete";
475
- })(FetchMethod || (FetchMethod = {}));
488
+ function fetch(url, options = {}) {
489
+ const modifiedHeaders = new Headers(options.headers || {});
490
+ const requestUID = uuid();
491
+ window.Turbo.session.recentRequests.add(requestUID);
492
+ modifiedHeaders.append("X-Turbo-Request-Id", requestUID);
493
+ return window.fetch(url, {
494
+ ...options,
495
+ headers: modifiedHeaders
496
+ });
497
+ }
476
498
 
477
499
  function fetchMethodFromString(method) {
478
500
  switch (method.toLowerCase()) {
@@ -493,16 +515,81 @@ function fetchMethodFromString(method) {
493
515
  }
494
516
  }
495
517
 
518
+ const FetchMethod = {
519
+ get: "get",
520
+ post: "post",
521
+ put: "put",
522
+ patch: "patch",
523
+ delete: "delete"
524
+ };
525
+
526
+ function fetchEnctypeFromString(encoding) {
527
+ switch (encoding.toLowerCase()) {
528
+ case FetchEnctype.multipart:
529
+ return FetchEnctype.multipart;
530
+
531
+ case FetchEnctype.plain:
532
+ return FetchEnctype.plain;
533
+
534
+ default:
535
+ return FetchEnctype.urlEncoded;
536
+ }
537
+ }
538
+
539
+ const FetchEnctype = {
540
+ urlEncoded: "application/x-www-form-urlencoded",
541
+ multipart: "multipart/form-data",
542
+ plain: "text/plain"
543
+ };
544
+
496
545
  class FetchRequest {
497
- constructor(delegate, method, location, body = new URLSearchParams, target = null) {
498
- this.abortController = new AbortController;
499
- this.resolveRequestPromise = _value => {};
546
+ abortController=new AbortController;
547
+ #resolveRequestPromise=_value => {};
548
+ constructor(delegate, method, location, requestBody = new URLSearchParams, target = null, enctype = FetchEnctype.urlEncoded) {
549
+ const [url, body] = buildResourceAndBody(expandURL(location), method, requestBody, enctype);
500
550
  this.delegate = delegate;
501
- this.method = method;
502
- this.headers = this.defaultHeaders;
503
- this.body = body;
504
- this.url = location;
551
+ this.url = url;
505
552
  this.target = target;
553
+ this.fetchOptions = {
554
+ credentials: "same-origin",
555
+ redirect: "follow",
556
+ method: method,
557
+ headers: {
558
+ ...this.defaultHeaders
559
+ },
560
+ body: body,
561
+ signal: this.abortSignal,
562
+ referrer: this.delegate.referrer?.href
563
+ };
564
+ this.enctype = enctype;
565
+ }
566
+ get method() {
567
+ return this.fetchOptions.method;
568
+ }
569
+ set method(value) {
570
+ const fetchBody = this.isSafe ? this.url.searchParams : this.fetchOptions.body || new FormData;
571
+ const fetchMethod = fetchMethodFromString(value) || FetchMethod.get;
572
+ this.url.search = "";
573
+ const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype);
574
+ this.url = url;
575
+ this.fetchOptions.body = body;
576
+ this.fetchOptions.method = fetchMethod;
577
+ }
578
+ get headers() {
579
+ return this.fetchOptions.headers;
580
+ }
581
+ set headers(value) {
582
+ this.fetchOptions.headers = value;
583
+ }
584
+ get body() {
585
+ if (this.isSafe) {
586
+ return this.url.searchParams;
587
+ } else {
588
+ return this.fetchOptions.body;
589
+ }
590
+ }
591
+ set body(value) {
592
+ this.fetchOptions.body = value;
506
593
  }
507
594
  get location() {
508
595
  return this.url;
@@ -519,14 +606,14 @@ class FetchRequest {
519
606
  async perform() {
520
607
  const {fetchOptions: fetchOptions} = this;
521
608
  this.delegate.prepareRequest(this);
522
- await this.allowRequestToBeIntercepted(fetchOptions);
609
+ await this.#allowRequestToBeIntercepted(fetchOptions);
523
610
  try {
524
611
  this.delegate.requestStarted(this);
525
612
  const response = await fetch(this.url.href, fetchOptions);
526
613
  return await this.receive(response);
527
614
  } catch (error) {
528
615
  if (error.name !== "AbortError") {
529
- if (this.willDelegateErrorHandling(error)) {
616
+ if (this.#willDelegateErrorHandling(error)) {
530
617
  this.delegate.requestErrored(this, error);
531
618
  }
532
619
  throw error;
@@ -553,25 +640,13 @@ class FetchRequest {
553
640
  }
554
641
  return fetchResponse;
555
642
  }
556
- get fetchOptions() {
557
- var _a;
558
- return {
559
- method: FetchMethod[this.method].toUpperCase(),
560
- credentials: "same-origin",
561
- headers: this.headers,
562
- redirect: "follow",
563
- body: this.isIdempotent ? null : this.body,
564
- signal: this.abortSignal,
565
- referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
566
- };
567
- }
568
643
  get defaultHeaders() {
569
644
  return {
570
645
  Accept: "text/html, application/xhtml+xml"
571
646
  };
572
647
  }
573
- get isIdempotent() {
574
- return this.method == FetchMethod.get;
648
+ get isSafe() {
649
+ return isSafe(this.method);
575
650
  }
576
651
  get abortSignal() {
577
652
  return this.abortController.signal;
@@ -579,20 +654,21 @@ class FetchRequest {
579
654
  acceptResponseType(mimeType) {
580
655
  this.headers["Accept"] = [ mimeType, this.headers["Accept"] ].join(", ");
581
656
  }
582
- async allowRequestToBeIntercepted(fetchOptions) {
583
- const requestInterception = new Promise((resolve => this.resolveRequestPromise = resolve));
657
+ async #allowRequestToBeIntercepted(fetchOptions) {
658
+ const requestInterception = new Promise((resolve => this.#resolveRequestPromise = resolve));
584
659
  const event = dispatch("turbo:before-fetch-request", {
585
660
  cancelable: true,
586
661
  detail: {
587
662
  fetchOptions: fetchOptions,
588
663
  url: this.url,
589
- resume: this.resolveRequestPromise
664
+ resume: this.#resolveRequestPromise
590
665
  },
591
666
  target: this.target
592
667
  });
668
+ this.url = event.detail.url;
593
669
  if (event.defaultPrevented) await requestInterception;
594
670
  }
595
- willDelegateErrorHandling(error) {
671
+ #willDelegateErrorHandling(error) {
596
672
  const event = dispatch("turbo:fetch-request-error", {
597
673
  target: this.target,
598
674
  cancelable: true,
@@ -605,15 +681,38 @@ class FetchRequest {
605
681
  }
606
682
  }
607
683
 
684
+ function isSafe(fetchMethod) {
685
+ return fetchMethodFromString(fetchMethod) == FetchMethod.get;
686
+ }
687
+
688
+ function buildResourceAndBody(resource, method, requestBody, enctype) {
689
+ const searchParams = Array.from(requestBody).length > 0 ? new URLSearchParams(entriesExcludingFiles(requestBody)) : resource.searchParams;
690
+ if (isSafe(method)) {
691
+ return [ mergeIntoURLSearchParams(resource, searchParams), null ];
692
+ } else if (enctype == FetchEnctype.urlEncoded) {
693
+ return [ resource, searchParams ];
694
+ } else {
695
+ return [ resource, requestBody ];
696
+ }
697
+ }
698
+
699
+ function entriesExcludingFiles(requestBody) {
700
+ const entries = [];
701
+ for (const [name, value] of requestBody) {
702
+ if (value instanceof File) continue; else entries.push([ name, value ]);
703
+ }
704
+ return entries;
705
+ }
706
+
707
+ function mergeIntoURLSearchParams(url, requestBody) {
708
+ const searchParams = new URLSearchParams(entriesExcludingFiles(requestBody));
709
+ url.search = searchParams.toString();
710
+ return url;
711
+ }
712
+
608
713
  class AppearanceObserver {
714
+ started=false;
609
715
  constructor(delegate, element) {
610
- this.started = false;
611
- this.intersect = entries => {
612
- const lastEntry = entries.slice(-1)[0];
613
- if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
614
- this.delegate.elementAppearedInViewport(this.element);
615
- }
616
- };
617
716
  this.delegate = delegate;
618
717
  this.element = element;
619
718
  this.intersectionObserver = new IntersectionObserver(this.intersect);
@@ -630,12 +729,16 @@ class AppearanceObserver {
630
729
  this.intersectionObserver.unobserve(this.element);
631
730
  }
632
731
  }
732
+ intersect=entries => {
733
+ const lastEntry = entries.slice(-1)[0];
734
+ if (lastEntry?.isIntersecting) {
735
+ this.delegate.elementAppearedInViewport(this.element);
736
+ }
737
+ };
633
738
  }
634
739
 
635
740
  class StreamMessage {
636
- constructor(fragment) {
637
- this.fragment = importStreamElements(fragment);
638
- }
741
+ static contentType="text/vnd.turbo-stream.html";
639
742
  static wrap(message) {
640
743
  if (typeof message == "string") {
641
744
  return new this(createDocumentFragment(message));
@@ -643,10 +746,11 @@ class StreamMessage {
643
746
  return message;
644
747
  }
645
748
  }
749
+ constructor(fragment) {
750
+ this.fragment = importStreamElements(fragment);
751
+ }
646
752
  }
647
753
 
648
- StreamMessage.contentType = "text/vnd.turbo-stream.html";
649
-
650
754
  function importStreamElements(fragment) {
651
755
  for (const element of fragment.querySelectorAll("turbo-stream")) {
652
756
  const streamElement = document.importNode(element, true);
@@ -658,85 +762,54 @@ function importStreamElements(fragment) {
658
762
  return fragment;
659
763
  }
660
764
 
661
- var FormSubmissionState;
662
-
663
- (function(FormSubmissionState) {
664
- FormSubmissionState[FormSubmissionState["initialized"] = 0] = "initialized";
665
- FormSubmissionState[FormSubmissionState["requesting"] = 1] = "requesting";
666
- FormSubmissionState[FormSubmissionState["waiting"] = 2] = "waiting";
667
- FormSubmissionState[FormSubmissionState["receiving"] = 3] = "receiving";
668
- FormSubmissionState[FormSubmissionState["stopping"] = 4] = "stopping";
669
- FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
670
- })(FormSubmissionState || (FormSubmissionState = {}));
671
-
672
- var FormEnctype;
673
-
674
- (function(FormEnctype) {
675
- FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded";
676
- FormEnctype["multipart"] = "multipart/form-data";
677
- FormEnctype["plain"] = "text/plain";
678
- })(FormEnctype || (FormEnctype = {}));
679
-
680
- function formEnctypeFromString(encoding) {
681
- switch (encoding.toLowerCase()) {
682
- case FormEnctype.multipart:
683
- return FormEnctype.multipart;
684
-
685
- case FormEnctype.plain:
686
- return FormEnctype.plain;
687
-
688
- default:
689
- return FormEnctype.urlEncoded;
690
- }
691
- }
765
+ const FormSubmissionState = {
766
+ initialized: "initialized",
767
+ requesting: "requesting",
768
+ waiting: "waiting",
769
+ receiving: "receiving",
770
+ stopping: "stopping",
771
+ stopped: "stopped"
772
+ };
692
773
 
693
774
  class FormSubmission {
775
+ state=FormSubmissionState.initialized;
776
+ static confirmMethod(message, _element, _submitter) {
777
+ return Promise.resolve(confirm(message));
778
+ }
694
779
  constructor(delegate, formElement, submitter, mustRedirect = false) {
695
- this.state = FormSubmissionState.initialized;
780
+ const method = getMethod(formElement, submitter);
781
+ const action = getAction(getFormAction(formElement, submitter), method);
782
+ const body = buildFormData(formElement, submitter);
783
+ const enctype = getEnctype(formElement, submitter);
696
784
  this.delegate = delegate;
697
785
  this.formElement = formElement;
698
786
  this.submitter = submitter;
699
- this.formData = buildFormData(formElement, submitter);
700
- this.location = expandURL(this.action);
701
- if (this.method == FetchMethod.get) {
702
- mergeFormDataEntries(this.location, [ ...this.body.entries() ]);
703
- }
704
- this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
787
+ this.fetchRequest = new FetchRequest(this, method, action, body, formElement, enctype);
705
788
  this.mustRedirect = mustRedirect;
706
789
  }
707
- static confirmMethod(message, _element, _submitter) {
708
- return Promise.resolve(confirm(message));
709
- }
710
790
  get method() {
711
- var _a;
712
- const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
713
- return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
791
+ return this.fetchRequest.method;
792
+ }
793
+ set method(value) {
794
+ this.fetchRequest.method = value;
714
795
  }
715
796
  get action() {
716
- var _a;
717
- const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
718
- if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
719
- return this.submitter.getAttribute("formaction") || "";
720
- } else {
721
- return this.formElement.getAttribute("action") || formElementAction || "";
722
- }
797
+ return this.fetchRequest.url.toString();
798
+ }
799
+ set action(value) {
800
+ this.fetchRequest.url = expandURL(value);
723
801
  }
724
802
  get body() {
725
- if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
726
- return new URLSearchParams(this.stringFormData);
727
- } else {
728
- return this.formData;
729
- }
803
+ return this.fetchRequest.body;
730
804
  }
731
805
  get enctype() {
732
- var _a;
733
- return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
806
+ return this.fetchRequest.enctype;
734
807
  }
735
- get isIdempotent() {
736
- return this.fetchRequest.isIdempotent;
808
+ get isSafe() {
809
+ return this.fetchRequest.isSafe;
737
810
  }
738
- get stringFormData() {
739
- return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
811
+ get location() {
812
+ return this.fetchRequest.url;
740
813
  }
741
814
  async start() {
742
815
  const {initialized: initialized, requesting: requesting} = FormSubmissionState;
@@ -761,7 +834,7 @@ class FormSubmission {
761
834
  }
762
835
  }
763
836
  prepareRequest(request) {
764
- if (!request.isIdempotent) {
837
+ if (!request.isSafe) {
765
838
  const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
766
839
  if (token) {
767
840
  request.headers["X-CSRF-Token"] = token;
@@ -772,9 +845,9 @@ class FormSubmission {
772
845
  }
773
846
  }
774
847
  requestStarted(_request) {
775
- var _a;
776
848
  this.state = FormSubmissionState.waiting;
777
- (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
849
+ this.submitter?.setAttribute("disabled", "");
850
+ this.setSubmitsWith();
778
851
  dispatch("turbo:submit-start", {
779
852
  target: this.formElement,
780
853
  detail: {
@@ -819,29 +892,53 @@ class FormSubmission {
819
892
  this.delegate.formSubmissionErrored(this, error);
820
893
  }
821
894
  requestFinished(_request) {
822
- var _a;
823
895
  this.state = FormSubmissionState.stopped;
824
- (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
896
+ this.submitter?.removeAttribute("disabled");
897
+ this.resetSubmitterText();
825
898
  dispatch("turbo:submit-end", {
826
899
  target: this.formElement,
827
- detail: Object.assign({
828
- formSubmission: this
829
- }, this.result)
900
+ detail: {
901
+ formSubmission: this,
902
+ ...this.result
903
+ }
830
904
  });
831
905
  this.delegate.formSubmissionFinished(this);
832
906
  }
907
+ setSubmitsWith() {
908
+ if (!this.submitter || !this.submitsWith) return;
909
+ if (this.submitter.matches("button")) {
910
+ this.originalSubmitText = this.submitter.innerHTML;
911
+ this.submitter.innerHTML = this.submitsWith;
912
+ } else if (this.submitter.matches("input")) {
913
+ const input = this.submitter;
914
+ this.originalSubmitText = input.value;
915
+ input.value = this.submitsWith;
916
+ }
917
+ }
918
+ resetSubmitterText() {
919
+ if (!this.submitter || !this.originalSubmitText) return;
920
+ if (this.submitter.matches("button")) {
921
+ this.submitter.innerHTML = this.originalSubmitText;
922
+ } else if (this.submitter.matches("input")) {
923
+ const input = this.submitter;
924
+ input.value = this.originalSubmitText;
925
+ }
926
+ }
833
927
  requestMustRedirect(request) {
834
- return !request.isIdempotent && this.mustRedirect;
928
+ return !request.isSafe && this.mustRedirect;
835
929
  }
836
930
  requestAcceptsTurboStreamResponse(request) {
837
- return !request.isIdempotent || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
931
+ return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
932
+ }
933
+ get submitsWith() {
934
+ return this.submitter?.getAttribute("data-turbo-submits-with");
838
935
  }
839
936
  }
840
937
 
841
938
  function buildFormData(formElement, submitter) {
842
939
  const formData = new FormData(formElement);
843
- const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
844
- const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
940
+ const name = submitter?.getAttribute("name");
941
+ const value = submitter?.getAttribute("value");
845
942
  if (name) {
846
943
  formData.append(name, value || "");
847
944
  }
@@ -863,14 +960,30 @@ function responseSucceededWithoutRedirect(response) {
863
960
  return response.statusCode == 200 && !response.redirected;
864
961
  }
865
962
 
866
- function mergeFormDataEntries(url, entries) {
867
- const searchParams = new URLSearchParams;
868
- for (const [name, value] of entries) {
869
- if (value instanceof File) continue;
870
- searchParams.append(name, value);
963
+ function getFormAction(formElement, submitter) {
964
+ const formElementAction = typeof formElement.action === "string" ? formElement.action : null;
965
+ if (submitter?.hasAttribute("formaction")) {
966
+ return submitter.getAttribute("formaction") || "";
967
+ } else {
968
+ return formElement.getAttribute("action") || formElementAction || "";
871
969
  }
872
- url.search = searchParams.toString();
873
- return url;
970
+ }
971
+
972
+ function getAction(formAction, fetchMethod) {
973
+ const action = expandURL(formAction);
974
+ if (isSafe(fetchMethod)) {
975
+ action.search = "";
976
+ }
977
+ return action;
978
+ }
979
+
980
+ function getMethod(formElement, submitter) {
981
+ const method = submitter?.getAttribute("formmethod") || formElement.getAttribute("method") || "";
982
+ return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
983
+ }
984
+
985
+ function getEnctype(formElement, submitter) {
986
+ return fetchEnctypeFromString(submitter?.getAttribute("formenctype") || formElement.enctype);
874
987
  }
875
988
 
876
989
  class Snapshot {
@@ -893,11 +1006,7 @@ class Snapshot {
893
1006
  return this.element.isConnected;
894
1007
  }
895
1008
  get firstAutofocusableElement() {
896
- const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
897
- for (const element of this.element.querySelectorAll("[autofocus]")) {
898
- if (element.closest(inertDisabledOrHidden) == null) return element; else continue;
899
- }
900
- return null;
1009
+ return queryAutofocusableElement(this.element);
901
1010
  }
902
1011
  get permanentElements() {
903
1012
  return queryPermanentElementsAll(this.element);
@@ -927,23 +1036,8 @@ function queryPermanentElementsAll(node) {
927
1036
  }
928
1037
 
929
1038
  class FormSubmitObserver {
1039
+ started=false;
930
1040
  constructor(delegate, eventTarget) {
931
- this.started = false;
932
- this.submitCaptured = () => {
933
- this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
934
- this.eventTarget.addEventListener("submit", this.submitBubbled, false);
935
- };
936
- this.submitBubbled = event => {
937
- if (!event.defaultPrevented) {
938
- const form = event.target instanceof HTMLFormElement ? event.target : undefined;
939
- const submitter = event.submitter || undefined;
940
- if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
941
- event.preventDefault();
942
- event.stopImmediatePropagation();
943
- this.delegate.formSubmitted(form, submitter);
944
- }
945
- }
946
- };
947
1041
  this.delegate = delegate;
948
1042
  this.eventTarget = eventTarget;
949
1043
  }
@@ -959,16 +1053,31 @@ class FormSubmitObserver {
959
1053
  this.started = false;
960
1054
  }
961
1055
  }
1056
+ submitCaptured=() => {
1057
+ this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
1058
+ this.eventTarget.addEventListener("submit", this.submitBubbled, false);
1059
+ };
1060
+ submitBubbled=event => {
1061
+ if (!event.defaultPrevented) {
1062
+ const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1063
+ const submitter = event.submitter || undefined;
1064
+ if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
1065
+ event.preventDefault();
1066
+ event.stopImmediatePropagation();
1067
+ this.delegate.formSubmitted(form, submitter);
1068
+ }
1069
+ }
1070
+ };
962
1071
  }
963
1072
 
964
1073
  function submissionDoesNotDismissDialog(form, submitter) {
965
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
1074
+ const method = submitter?.getAttribute("formmethod") || form.getAttribute("method");
966
1075
  return method != "dialog";
967
1076
  }
968
1077
 
969
1078
  function submissionDoesNotTargetIFrame(form, submitter) {
970
- if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
971
- const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
1079
+ if (submitter?.hasAttribute("formtarget") || form.hasAttribute("target")) {
1080
+ const target = submitter?.getAttribute("formtarget") || form.target;
972
1081
  for (const element of document.getElementsByName(target)) {
973
1082
  if (element instanceof HTMLIFrameElement) return false;
974
1083
  }
@@ -979,9 +1088,9 @@ function submissionDoesNotTargetIFrame(form, submitter) {
979
1088
  }
980
1089
 
981
1090
  class View {
1091
+ #resolveRenderPromise=_value => {};
1092
+ #resolveInterceptionPromise=_value => {};
982
1093
  constructor(delegate, element) {
983
- this.resolveRenderPromise = _value => {};
984
- this.resolveInterceptionPromise = _value => {};
985
1094
  this.delegate = delegate;
986
1095
  this.element = element;
987
1096
  }
@@ -1030,23 +1139,23 @@ class View {
1030
1139
  const {isPreview: isPreview, shouldRender: shouldRender, newSnapshot: snapshot} = renderer;
1031
1140
  if (shouldRender) {
1032
1141
  try {
1033
- this.renderPromise = new Promise((resolve => this.resolveRenderPromise = resolve));
1142
+ this.renderPromise = new Promise((resolve => this.#resolveRenderPromise = resolve));
1034
1143
  this.renderer = renderer;
1035
1144
  await this.prepareToRenderSnapshot(renderer);
1036
- const renderInterception = new Promise((resolve => this.resolveInterceptionPromise = resolve));
1145
+ const renderInterception = new Promise((resolve => this.#resolveInterceptionPromise = resolve));
1037
1146
  const options = {
1038
- resume: this.resolveInterceptionPromise,
1147
+ resume: this.#resolveInterceptionPromise,
1039
1148
  render: this.renderer.renderElement
1040
1149
  };
1041
- const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
1150
+ const immediateRender = this.delegate.allowsImmediateRender(snapshot, isPreview, options);
1042
1151
  if (!immediateRender) await renderInterception;
1043
1152
  await this.renderSnapshot(renderer);
1044
- this.delegate.viewRenderedSnapshot(snapshot, isPreview);
1153
+ this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod);
1045
1154
  this.delegate.preloadOnLoadLinksForView(this.element);
1046
1155
  this.finishRenderingSnapshot(renderer);
1047
1156
  } finally {
1048
1157
  delete this.renderer;
1049
- this.resolveRenderPromise(undefined);
1158
+ this.#resolveRenderPromise(undefined);
1050
1159
  delete this.renderPromise;
1051
1160
  }
1052
1161
  } else {
@@ -1076,8 +1185,8 @@ class View {
1076
1185
  }
1077
1186
 
1078
1187
  class FrameView extends View {
1079
- invalidate() {
1080
- this.element.innerHTML = "";
1188
+ missing() {
1189
+ this.element.innerHTML = `<strong class="turbo-frame-error">Content missing</strong>`;
1081
1190
  }
1082
1191
  get snapshot() {
1083
1192
  return new Snapshot(this.element);
@@ -1086,26 +1195,6 @@ class FrameView extends View {
1086
1195
 
1087
1196
  class LinkInterceptor {
1088
1197
  constructor(delegate, element) {
1089
- this.clickBubbled = event => {
1090
- if (this.respondsToEventTarget(event.target)) {
1091
- this.clickEvent = event;
1092
- } else {
1093
- delete this.clickEvent;
1094
- }
1095
- };
1096
- this.linkClicked = event => {
1097
- if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
1098
- if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
1099
- this.clickEvent.preventDefault();
1100
- event.preventDefault();
1101
- this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
1102
- }
1103
- }
1104
- delete this.clickEvent;
1105
- };
1106
- this.willVisit = _event => {
1107
- delete this.clickEvent;
1108
- };
1109
1198
  this.delegate = delegate;
1110
1199
  this.element = element;
1111
1200
  }
@@ -1119,6 +1208,26 @@ class LinkInterceptor {
1119
1208
  document.removeEventListener("turbo:click", this.linkClicked);
1120
1209
  document.removeEventListener("turbo:before-visit", this.willVisit);
1121
1210
  }
1211
+ clickBubbled=event => {
1212
+ if (this.respondsToEventTarget(event.target)) {
1213
+ this.clickEvent = event;
1214
+ } else {
1215
+ delete this.clickEvent;
1216
+ }
1217
+ };
1218
+ linkClicked=event => {
1219
+ if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
1220
+ if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
1221
+ this.clickEvent.preventDefault();
1222
+ event.preventDefault();
1223
+ this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
1224
+ }
1225
+ }
1226
+ delete this.clickEvent;
1227
+ };
1228
+ willVisit=_event => {
1229
+ delete this.clickEvent;
1230
+ };
1122
1231
  respondsToEventTarget(target) {
1123
1232
  const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
1124
1233
  return element && element.closest("turbo-frame, html") == this.element;
@@ -1126,25 +1235,8 @@ class LinkInterceptor {
1126
1235
  }
1127
1236
 
1128
1237
  class LinkClickObserver {
1238
+ started=false;
1129
1239
  constructor(delegate, eventTarget) {
1130
- this.started = false;
1131
- this.clickCaptured = () => {
1132
- this.eventTarget.removeEventListener("click", this.clickBubbled, false);
1133
- this.eventTarget.addEventListener("click", this.clickBubbled, false);
1134
- };
1135
- this.clickBubbled = event => {
1136
- if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
1137
- const target = event.composedPath && event.composedPath()[0] || event.target;
1138
- const link = this.findLinkFromClickTarget(target);
1139
- if (link && doesNotTargetIFrame(link)) {
1140
- const location = this.getLocationForLink(link);
1141
- if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1142
- event.preventDefault();
1143
- this.delegate.followedLinkToLocation(link, location);
1144
- }
1145
- }
1146
- }
1147
- };
1148
1240
  this.delegate = delegate;
1149
1241
  this.eventTarget = eventTarget;
1150
1242
  }
@@ -1160,6 +1252,23 @@ class LinkClickObserver {
1160
1252
  this.started = false;
1161
1253
  }
1162
1254
  }
1255
+ clickCaptured=() => {
1256
+ this.eventTarget.removeEventListener("click", this.clickBubbled, false);
1257
+ this.eventTarget.addEventListener("click", this.clickBubbled, false);
1258
+ };
1259
+ clickBubbled=event => {
1260
+ if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
1261
+ const target = event.composedPath && event.composedPath()[0] || event.target;
1262
+ const link = this.findLinkFromClickTarget(target);
1263
+ if (link && doesNotTargetIFrame(link)) {
1264
+ const location = this.getLocationForLink(link);
1265
+ if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1266
+ event.preventDefault();
1267
+ this.delegate.followedLinkToLocation(link, location);
1268
+ }
1269
+ }
1270
+ }
1271
+ };
1163
1272
  clickEventIsSignificant(event) {
1164
1273
  return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
1165
1274
  }
@@ -1194,7 +1303,7 @@ class FormLinkClickObserver {
1194
1303
  this.linkInterceptor.stop();
1195
1304
  }
1196
1305
  willFollowLinkToLocation(link, location, originalEvent) {
1197
- return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
1306
+ return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && (link.hasAttribute("data-turbo-method") || link.hasAttribute("data-turbo-stream"));
1198
1307
  }
1199
1308
  followedLinkToLocation(link, location) {
1200
1309
  const form = document.createElement("form");
@@ -1232,16 +1341,16 @@ class FormLinkClickObserver {
1232
1341
  }
1233
1342
 
1234
1343
  class Bardo {
1235
- constructor(delegate, permanentElementMap) {
1236
- this.delegate = delegate;
1237
- this.permanentElementMap = permanentElementMap;
1238
- }
1239
1344
  static async preservingPermanentElements(delegate, permanentElementMap, callback) {
1240
1345
  const bardo = new this(delegate, permanentElementMap);
1241
1346
  bardo.enter();
1242
1347
  await callback();
1243
1348
  bardo.leave();
1244
1349
  }
1350
+ constructor(delegate, permanentElementMap) {
1351
+ this.delegate = delegate;
1352
+ this.permanentElementMap = permanentElementMap;
1353
+ }
1245
1354
  enter() {
1246
1355
  for (const id in this.permanentElementMap) {
1247
1356
  const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
@@ -1267,7 +1376,7 @@ class Bardo {
1267
1376
  }
1268
1377
  replacePlaceholderWithPermanentElement(permanentElement) {
1269
1378
  const placeholder = this.getPlaceholderById(permanentElement.id);
1270
- placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
1379
+ placeholder?.replaceWith(permanentElement);
1271
1380
  }
1272
1381
  getPlaceholderById(id) {
1273
1382
  return this.placeholders.find((element => element.content == id));
@@ -1285,8 +1394,8 @@ function createPlaceholderForPermanentElement(permanentElement) {
1285
1394
  }
1286
1395
 
1287
1396
  class Renderer {
1397
+ #activeElement=null;
1288
1398
  constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1289
- this.activeElement = null;
1290
1399
  this.currentSnapshot = currentSnapshot;
1291
1400
  this.newSnapshot = newSnapshot;
1292
1401
  this.isPreview = isPreview;
@@ -1306,6 +1415,7 @@ class Renderer {
1306
1415
  prepareToRender() {
1307
1416
  return;
1308
1417
  }
1418
+ render() {}
1309
1419
  finishRendering() {
1310
1420
  if (this.resolvingFunctions) {
1311
1421
  this.resolvingFunctions.resolve();
@@ -1317,20 +1427,20 @@ class Renderer {
1317
1427
  }
1318
1428
  focusFirstAutofocusableElement() {
1319
1429
  const element = this.connectedSnapshot.firstAutofocusableElement;
1320
- if (elementIsFocusable(element)) {
1430
+ if (element) {
1321
1431
  element.focus();
1322
1432
  }
1323
1433
  }
1324
1434
  enteringBardo(currentPermanentElement) {
1325
- if (this.activeElement) return;
1435
+ if (this.#activeElement) return;
1326
1436
  if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
1327
- this.activeElement = this.currentSnapshot.activeElement;
1437
+ this.#activeElement = this.currentSnapshot.activeElement;
1328
1438
  }
1329
1439
  }
1330
1440
  leavingBardo(currentPermanentElement) {
1331
- if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
1332
- this.activeElement.focus();
1333
- this.activeElement = null;
1441
+ if (currentPermanentElement.contains(this.#activeElement) && this.#activeElement instanceof HTMLElement) {
1442
+ this.#activeElement.focus();
1443
+ this.#activeElement = null;
1334
1444
  }
1335
1445
  }
1336
1446
  get connectedSnapshot() {
@@ -1345,29 +1455,27 @@ class Renderer {
1345
1455
  get permanentElementMap() {
1346
1456
  return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1347
1457
  }
1348
- }
1349
-
1350
- function elementIsFocusable(element) {
1351
- return element && typeof element.focus == "function";
1458
+ get renderMethod() {
1459
+ return "replace";
1460
+ }
1352
1461
  }
1353
1462
 
1354
1463
  class FrameRenderer extends Renderer {
1355
- constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1356
- super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1357
- this.delegate = delegate;
1358
- }
1359
1464
  static renderElement(currentElement, newElement) {
1360
- var _a;
1361
1465
  const destinationRange = document.createRange();
1362
1466
  destinationRange.selectNodeContents(currentElement);
1363
1467
  destinationRange.deleteContents();
1364
1468
  const frameElement = newElement;
1365
- const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1469
+ const sourceRange = frameElement.ownerDocument?.createRange();
1366
1470
  if (sourceRange) {
1367
1471
  sourceRange.selectNodeContents(frameElement);
1368
1472
  currentElement.appendChild(sourceRange.extractContents());
1369
1473
  }
1370
1474
  }
1475
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1476
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1477
+ this.delegate = delegate;
1478
+ }
1371
1479
  get shouldRender() {
1372
1480
  return true;
1373
1481
  }
@@ -1429,18 +1537,7 @@ function readScrollBehavior(value, defaultValue) {
1429
1537
  }
1430
1538
 
1431
1539
  class ProgressBar {
1432
- constructor() {
1433
- this.hiding = false;
1434
- this.value = 0;
1435
- this.visible = false;
1436
- this.trickle = () => {
1437
- this.setValue(this.value + Math.random() / 100);
1438
- };
1439
- this.stylesheetElement = this.createStylesheetElement();
1440
- this.progressElement = this.createProgressElement();
1441
- this.installStylesheetElement();
1442
- this.setValue(0);
1443
- }
1540
+ static animationDuration=300;
1444
1541
  static get defaultCSS() {
1445
1542
  return unindent`
1446
1543
  .turbo-progress-bar {
@@ -1458,6 +1555,15 @@ class ProgressBar {
1458
1555
  }
1459
1556
  `;
1460
1557
  }
1558
+ hiding=false;
1559
+ value=0;
1560
+ visible=false;
1561
+ constructor() {
1562
+ this.stylesheetElement = this.createStylesheetElement();
1563
+ this.progressElement = this.createProgressElement();
1564
+ this.installStylesheetElement();
1565
+ this.setValue(0);
1566
+ }
1461
1567
  show() {
1462
1568
  if (!this.visible) {
1463
1569
  this.visible = true;
@@ -1507,6 +1613,9 @@ class ProgressBar {
1507
1613
  window.clearInterval(this.trickleInterval);
1508
1614
  delete this.trickleInterval;
1509
1615
  }
1616
+ trickle=() => {
1617
+ this.setValue(this.value + Math.random() / 100);
1618
+ };
1510
1619
  refresh() {
1511
1620
  requestAnimationFrame((() => {
1512
1621
  this.progressElement.style.width = `${10 + this.value * 90}%`;
@@ -1531,25 +1640,22 @@ class ProgressBar {
1531
1640
  }
1532
1641
  }
1533
1642
 
1534
- ProgressBar.animationDuration = 300;
1535
-
1536
1643
  class HeadSnapshot extends Snapshot {
1537
- constructor() {
1538
- super(...arguments);
1539
- this.detailsByOuterHTML = this.children.filter((element => !elementIsNoscript(element))).map((element => elementWithoutNonce(element))).reduce(((result, element) => {
1540
- const {outerHTML: outerHTML} = element;
1541
- const details = outerHTML in result ? result[outerHTML] : {
1542
- type: elementType(element),
1543
- tracked: elementIsTracked(element),
1544
- elements: []
1545
- };
1546
- return Object.assign(Object.assign({}, result), {
1547
- [outerHTML]: Object.assign(Object.assign({}, details), {
1548
- elements: [ ...details.elements, element ]
1549
- })
1550
- });
1551
- }), {});
1552
- }
1644
+ detailsByOuterHTML=this.children.filter((element => !elementIsNoscript(element))).map((element => elementWithoutNonce(element))).reduce(((result, element) => {
1645
+ const {outerHTML: outerHTML} = element;
1646
+ const details = outerHTML in result ? result[outerHTML] : {
1647
+ type: elementType(element),
1648
+ tracked: elementIsTracked(element),
1649
+ elements: []
1650
+ };
1651
+ return {
1652
+ ...result,
1653
+ [outerHTML]: {
1654
+ ...details,
1655
+ elements: [ ...details.elements, element ]
1656
+ }
1657
+ };
1658
+ }), {});
1553
1659
  get trackedElementSignature() {
1554
1660
  return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
1555
1661
  }
@@ -1582,7 +1688,7 @@ class HeadSnapshot extends Snapshot {
1582
1688
  return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
1583
1689
  const {elements: [element]} = this.detailsByOuterHTML[outerHTML];
1584
1690
  return elementIsMetaElementWithName(element, name) ? element : result;
1585
- }), undefined);
1691
+ }), undefined | undefined);
1586
1692
  }
1587
1693
  }
1588
1694
 
@@ -1626,18 +1732,19 @@ function elementWithoutNonce(element) {
1626
1732
  }
1627
1733
 
1628
1734
  class PageSnapshot extends Snapshot {
1629
- constructor(element, headSnapshot) {
1630
- super(element);
1631
- this.headSnapshot = headSnapshot;
1632
- }
1633
1735
  static fromHTMLString(html = "") {
1634
1736
  return this.fromDocument(parseHTMLDocument(html));
1635
1737
  }
1636
1738
  static fromElement(element) {
1637
1739
  return this.fromDocument(element.ownerDocument);
1638
1740
  }
1639
- static fromDocument({head: head, body: body}) {
1640
- return new this(body, new HeadSnapshot(head));
1741
+ static fromDocument({documentElement: documentElement, body: body, head: head}) {
1742
+ return new this(documentElement, body, new HeadSnapshot(head));
1743
+ }
1744
+ constructor(documentElement, body, headSnapshot) {
1745
+ super(body);
1746
+ this.documentElement = documentElement;
1747
+ this.headSnapshot = headSnapshot;
1641
1748
  }
1642
1749
  clone() {
1643
1750
  const clonedElement = this.element.cloneNode(true);
@@ -1651,14 +1758,16 @@ class PageSnapshot extends Snapshot {
1651
1758
  for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
1652
1759
  clonedPasswordInput.value = "";
1653
1760
  }
1654
- return new PageSnapshot(clonedElement, this.headSnapshot);
1761
+ return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot);
1762
+ }
1763
+ get lang() {
1764
+ return this.documentElement.getAttribute("lang");
1655
1765
  }
1656
1766
  get headElement() {
1657
1767
  return this.headSnapshot.element;
1658
1768
  }
1659
1769
  get rootLocation() {
1660
- var _a;
1661
- const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
1770
+ const root = this.getSetting("root") ?? "/";
1662
1771
  return expandURL(root);
1663
1772
  }
1664
1773
  get cacheControlValue() {
@@ -1673,29 +1782,38 @@ class PageSnapshot extends Snapshot {
1673
1782
  get isVisitable() {
1674
1783
  return this.getSetting("visit-control") != "reload";
1675
1784
  }
1785
+ get prefersViewTransitions() {
1786
+ return this.headSnapshot.getMetaValue("view-transition") === "same-origin";
1787
+ }
1788
+ get shouldMorphPage() {
1789
+ return this.getSetting("refresh-method") === "morph";
1790
+ }
1791
+ get shouldPreserveScrollPosition() {
1792
+ return this.getSetting("refresh-scroll") === "preserve";
1793
+ }
1676
1794
  getSetting(name) {
1677
1795
  return this.headSnapshot.getMetaValue(`turbo-${name}`);
1678
1796
  }
1679
1797
  }
1680
1798
 
1681
- var TimingMetric;
1682
-
1683
- (function(TimingMetric) {
1684
- TimingMetric["visitStart"] = "visitStart";
1685
- TimingMetric["requestStart"] = "requestStart";
1686
- TimingMetric["requestEnd"] = "requestEnd";
1687
- TimingMetric["visitEnd"] = "visitEnd";
1688
- })(TimingMetric || (TimingMetric = {}));
1689
-
1690
- var VisitState;
1691
-
1692
- (function(VisitState) {
1693
- VisitState["initialized"] = "initialized";
1694
- VisitState["started"] = "started";
1695
- VisitState["canceled"] = "canceled";
1696
- VisitState["failed"] = "failed";
1697
- VisitState["completed"] = "completed";
1698
- })(VisitState || (VisitState = {}));
1799
+ class ViewTransitioner {
1800
+ #viewTransitionStarted=false;
1801
+ #lastOperation=Promise.resolve();
1802
+ renderChange(useViewTransition, render) {
1803
+ if (useViewTransition && this.viewTransitionsAvailable && !this.#viewTransitionStarted) {
1804
+ this.#viewTransitionStarted = true;
1805
+ this.#lastOperation = this.#lastOperation.then((async () => {
1806
+ await document.startViewTransition(render).finished;
1807
+ }));
1808
+ } else {
1809
+ this.#lastOperation = this.#lastOperation.then(render);
1810
+ }
1811
+ return this.#lastOperation;
1812
+ }
1813
+ get viewTransitionsAvailable() {
1814
+ return document.startViewTransition;
1815
+ }
1816
+ }
1699
1817
 
1700
1818
  const defaultOptions = {
1701
1819
  action: "advance",
@@ -1707,29 +1825,46 @@ const defaultOptions = {
1707
1825
  acceptsStreamResponse: false
1708
1826
  };
1709
1827
 
1710
- var SystemStatusCode;
1828
+ const TimingMetric = {
1829
+ visitStart: "visitStart",
1830
+ requestStart: "requestStart",
1831
+ requestEnd: "requestEnd",
1832
+ visitEnd: "visitEnd"
1833
+ };
1834
+
1835
+ const VisitState = {
1836
+ initialized: "initialized",
1837
+ started: "started",
1838
+ canceled: "canceled",
1839
+ failed: "failed",
1840
+ completed: "completed"
1841
+ };
1711
1842
 
1712
- (function(SystemStatusCode) {
1713
- SystemStatusCode[SystemStatusCode["networkFailure"] = 0] = "networkFailure";
1714
- SystemStatusCode[SystemStatusCode["timeoutFailure"] = -1] = "timeoutFailure";
1715
- SystemStatusCode[SystemStatusCode["contentTypeMismatch"] = -2] = "contentTypeMismatch";
1716
- })(SystemStatusCode || (SystemStatusCode = {}));
1843
+ const SystemStatusCode = {
1844
+ networkFailure: 0,
1845
+ timeoutFailure: -1,
1846
+ contentTypeMismatch: -2
1847
+ };
1717
1848
 
1718
1849
  class Visit {
1850
+ identifier=uuid();
1851
+ timingMetrics={};
1852
+ followedRedirect=false;
1853
+ historyChanged=false;
1854
+ scrolled=false;
1855
+ shouldCacheSnapshot=true;
1856
+ acceptsStreamResponse=false;
1857
+ snapshotCached=false;
1858
+ state=VisitState.initialized;
1859
+ viewTransitioner=new ViewTransitioner;
1719
1860
  constructor(delegate, location, restorationIdentifier, options = {}) {
1720
- this.identifier = uuid();
1721
- this.timingMetrics = {};
1722
- this.followedRedirect = false;
1723
- this.historyChanged = false;
1724
- this.scrolled = false;
1725
- this.shouldCacheSnapshot = true;
1726
- this.acceptsStreamResponse = false;
1727
- this.snapshotCached = false;
1728
- this.state = VisitState.initialized;
1729
1861
  this.delegate = delegate;
1730
1862
  this.location = location;
1731
1863
  this.restorationIdentifier = restorationIdentifier || uuid();
1732
- 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);
1864
+ const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = {
1865
+ ...defaultOptions,
1866
+ ...options
1867
+ };
1733
1868
  this.action = action;
1734
1869
  this.historyChanged = historyChanged;
1735
1870
  this.referrer = referrer;
@@ -1791,12 +1926,12 @@ class Visit {
1791
1926
  if (this.state == VisitState.started) {
1792
1927
  this.state = VisitState.failed;
1793
1928
  this.adapter.visitFailed(this);
1929
+ this.delegate.visitCompleted(this);
1794
1930
  }
1795
1931
  }
1796
1932
  changeHistory() {
1797
- var _a;
1798
1933
  if (!this.historyChanged && this.updateHistory) {
1799
- const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
1934
+ const actionForHistory = this.location.href === this.referrer?.href ? "replace" : this.action;
1800
1935
  const method = getHistoryMethodForAction(actionForHistory);
1801
1936
  this.history.update(method, this.location, this.restorationIdentifier);
1802
1937
  this.historyChanged = true;
@@ -1843,8 +1978,8 @@ class Visit {
1843
1978
  if (this.shouldCacheSnapshot) this.cacheSnapshot();
1844
1979
  if (this.view.renderPromise) await this.view.renderPromise;
1845
1980
  if (isSuccessful(statusCode) && responseHTML != null) {
1846
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
1847
- this.performScroll();
1981
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
1982
+ await this.renderPageSnapshot(snapshot, false);
1848
1983
  this.adapter.visitRendered(this);
1849
1984
  this.complete();
1850
1985
  } else {
@@ -1881,8 +2016,7 @@ class Visit {
1881
2016
  this.adapter.visitRendered(this);
1882
2017
  } else {
1883
2018
  if (this.view.renderPromise) await this.view.renderPromise;
1884
- await this.view.renderPage(snapshot, isPreview, this.willRender, this);
1885
- this.performScroll();
2019
+ await this.renderPageSnapshot(snapshot, isPreview);
1886
2020
  this.adapter.visitRendered(this);
1887
2021
  if (!isPreview) {
1888
2022
  this.complete();
@@ -1892,8 +2026,7 @@ class Visit {
1892
2026
  }
1893
2027
  }
1894
2028
  followRedirect() {
1895
- var _a;
1896
- if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
2029
+ if (this.redirectedToLocation && !this.followedRedirect && this.response?.redirected) {
1897
2030
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1898
2031
  action: "replace",
1899
2032
  response: this.response,
@@ -1965,7 +2098,7 @@ class Visit {
1965
2098
  this.finishRequest();
1966
2099
  }
1967
2100
  performScroll() {
1968
- if (!this.scrolled && !this.view.forceReloaded) {
2101
+ if (!this.scrolled && !this.view.forceReloaded && !this.view.snapshot.shouldPreserveScrollPosition) {
1969
2102
  if (this.action == "restore") {
1970
2103
  this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
1971
2104
  } else {
@@ -1995,7 +2128,9 @@ class Visit {
1995
2128
  this.timingMetrics[metric] = (new Date).getTime();
1996
2129
  }
1997
2130
  getTimingMetrics() {
1998
- return Object.assign({}, this.timingMetrics);
2131
+ return {
2132
+ ...this.timingMetrics
2133
+ };
1999
2134
  }
2000
2135
  getHistoryMethodForAction(action) {
2001
2136
  switch (action) {
@@ -2033,6 +2168,12 @@ class Visit {
2033
2168
  await callback();
2034
2169
  delete this.frame;
2035
2170
  }
2171
+ async renderPageSnapshot(snapshot, isPreview) {
2172
+ await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(snapshot), (async () => {
2173
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
2174
+ this.performScroll();
2175
+ }));
2176
+ }
2036
2177
  cancelRender() {
2037
2178
  if (this.frame) {
2038
2179
  cancelAnimationFrame(this.frame);
@@ -2046,15 +2187,16 @@ function isSuccessful(statusCode) {
2046
2187
  }
2047
2188
 
2048
2189
  class BrowserAdapter {
2190
+ progressBar=new ProgressBar;
2049
2191
  constructor(session) {
2050
- this.progressBar = new ProgressBar;
2051
- this.showProgressBar = () => {
2052
- this.progressBar.show();
2053
- };
2054
2192
  this.session = session;
2055
2193
  }
2056
2194
  visitProposedToLocation(location, options) {
2057
- this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
2195
+ if (locationIsVisitable(location, this.navigator.rootLocation)) {
2196
+ this.navigator.startVisit(location, options?.restorationIdentifier || uuid(), options);
2197
+ } else {
2198
+ window.location.href = location.toString();
2199
+ }
2058
2200
  }
2059
2201
  visitStarted(visit) {
2060
2202
  this.location = visit.location;
@@ -2089,15 +2231,18 @@ class BrowserAdapter {
2089
2231
  return visit.loadResponse();
2090
2232
  }
2091
2233
  }
2092
- visitRequestFinished(_visit) {
2234
+ visitRequestFinished(_visit) {}
2235
+ visitCompleted(_visit) {
2093
2236
  this.progressBar.setValue(1);
2094
2237
  this.hideVisitProgressBar();
2095
2238
  }
2096
- visitCompleted(_visit) {}
2097
2239
  pageInvalidated(reason) {
2098
2240
  this.reload(reason);
2099
2241
  }
2100
- visitFailed(_visit) {}
2242
+ visitFailed(_visit) {
2243
+ this.progressBar.setValue(1);
2244
+ this.hideVisitProgressBar();
2245
+ }
2101
2246
  visitRendered(_visit) {}
2102
2247
  formSubmissionStarted(_formSubmission) {
2103
2248
  this.progressBar.setValue(0);
@@ -2129,12 +2274,14 @@ class BrowserAdapter {
2129
2274
  delete this.formProgressBarTimeout;
2130
2275
  }
2131
2276
  }
2277
+ showProgressBar=() => {
2278
+ this.progressBar.show();
2279
+ };
2132
2280
  reload(reason) {
2133
- var _a;
2134
2281
  dispatch("turbo:reload", {
2135
2282
  detail: reason
2136
2283
  });
2137
- window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;
2284
+ window.location.href = this.location?.toString() || window.location.href;
2138
2285
  }
2139
2286
  get navigator() {
2140
2287
  return this.session.navigator;
@@ -2142,27 +2289,36 @@ class BrowserAdapter {
2142
2289
  }
2143
2290
 
2144
2291
  class CacheObserver {
2145
- constructor() {
2146
- this.started = false;
2147
- this.removeStaleElements = _event => {
2148
- const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
2149
- for (const element of staleElements) {
2150
- element.remove();
2151
- }
2152
- };
2153
- }
2292
+ selector="[data-turbo-temporary]";
2293
+ deprecatedSelector="[data-turbo-cache=false]";
2294
+ started=false;
2154
2295
  start() {
2155
2296
  if (!this.started) {
2156
2297
  this.started = true;
2157
- addEventListener("turbo:before-cache", this.removeStaleElements, false);
2298
+ addEventListener("turbo:before-cache", this.removeTemporaryElements, false);
2158
2299
  }
2159
2300
  }
2160
2301
  stop() {
2161
2302
  if (this.started) {
2162
2303
  this.started = false;
2163
- removeEventListener("turbo:before-cache", this.removeStaleElements, false);
2304
+ removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
2164
2305
  }
2165
2306
  }
2307
+ removeTemporaryElements=_event => {
2308
+ for (const element of this.temporaryElements) {
2309
+ element.remove();
2310
+ }
2311
+ };
2312
+ get temporaryElements() {
2313
+ return [ ...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation ];
2314
+ }
2315
+ get temporaryElementsWithDeprecation() {
2316
+ const elements = document.querySelectorAll(this.deprecatedSelector);
2317
+ if (elements.length) {
2318
+ console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);
2319
+ }
2320
+ return [ ...elements ];
2321
+ }
2166
2322
  }
2167
2323
 
2168
2324
  class FrameRedirector {
@@ -2181,41 +2337,40 @@ class FrameRedirector {
2181
2337
  this.formSubmitObserver.stop();
2182
2338
  }
2183
2339
  shouldInterceptLinkClick(element, _location, _event) {
2184
- return this.shouldRedirect(element);
2340
+ return this.#shouldRedirect(element);
2185
2341
  }
2186
2342
  linkClickIntercepted(element, url, event) {
2187
- const frame = this.findFrameElement(element);
2343
+ const frame = this.#findFrameElement(element);
2188
2344
  if (frame) {
2189
2345
  frame.delegate.linkClickIntercepted(element, url, event);
2190
2346
  }
2191
2347
  }
2192
2348
  willSubmitForm(element, submitter) {
2193
- return element.closest("turbo-frame") == null && this.shouldSubmit(element, submitter) && this.shouldRedirect(element, submitter);
2349
+ return element.closest("turbo-frame") == null && this.#shouldSubmit(element, submitter) && this.#shouldRedirect(element, submitter);
2194
2350
  }
2195
2351
  formSubmitted(element, submitter) {
2196
- const frame = this.findFrameElement(element, submitter);
2352
+ const frame = this.#findFrameElement(element, submitter);
2197
2353
  if (frame) {
2198
2354
  frame.delegate.formSubmitted(element, submitter);
2199
2355
  }
2200
2356
  }
2201
- shouldSubmit(form, submitter) {
2202
- var _a;
2203
- const action = getAction(form, submitter);
2357
+ #shouldSubmit(form, submitter) {
2358
+ const action = getAction$1(form, submitter);
2204
2359
  const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
2205
- const rootLocation = expandURL((_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/");
2206
- return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
2360
+ const rootLocation = expandURL(meta?.content ?? "/");
2361
+ return this.#shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
2207
2362
  }
2208
- shouldRedirect(element, submitter) {
2363
+ #shouldRedirect(element, submitter) {
2209
2364
  const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
2210
2365
  if (isNavigatable) {
2211
- const frame = this.findFrameElement(element, submitter);
2366
+ const frame = this.#findFrameElement(element, submitter);
2212
2367
  return frame ? frame != element.closest("turbo-frame") : false;
2213
2368
  } else {
2214
2369
  return false;
2215
2370
  }
2216
2371
  }
2217
- findFrameElement(element, submitter) {
2218
- const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
2372
+ #findFrameElement(element, submitter) {
2373
+ const id = submitter?.getAttribute("data-turbo-frame") || element.getAttribute("data-turbo-frame");
2219
2374
  if (id && id != "_top") {
2220
2375
  const frame = this.element.querySelector(`#${id}:not([disabled])`);
2221
2376
  if (frame instanceof FrameElement) {
@@ -2226,26 +2381,12 @@ class FrameRedirector {
2226
2381
  }
2227
2382
 
2228
2383
  class History {
2384
+ location;
2385
+ restorationIdentifier=uuid();
2386
+ restorationData={};
2387
+ started=false;
2388
+ pageLoaded=false;
2229
2389
  constructor(delegate) {
2230
- this.restorationIdentifier = uuid();
2231
- this.restorationData = {};
2232
- this.started = false;
2233
- this.pageLoaded = false;
2234
- this.onPopState = event => {
2235
- if (this.shouldHandlePopState()) {
2236
- const {turbo: turbo} = event.state || {};
2237
- if (turbo) {
2238
- this.location = new URL(window.location.href);
2239
- const {restorationIdentifier: restorationIdentifier} = turbo;
2240
- this.restorationIdentifier = restorationIdentifier;
2241
- this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
2242
- }
2243
- }
2244
- };
2245
- this.onPageLoad = async _event => {
2246
- await nextMicrotask();
2247
- this.pageLoaded = true;
2248
- };
2249
2390
  this.delegate = delegate;
2250
2391
  }
2251
2392
  start() {
@@ -2285,12 +2426,14 @@ class History {
2285
2426
  updateRestorationData(additionalData) {
2286
2427
  const {restorationIdentifier: restorationIdentifier} = this;
2287
2428
  const restorationData = this.restorationData[restorationIdentifier];
2288
- this.restorationData[restorationIdentifier] = Object.assign(Object.assign({}, restorationData), additionalData);
2429
+ this.restorationData[restorationIdentifier] = {
2430
+ ...restorationData,
2431
+ ...additionalData
2432
+ };
2289
2433
  }
2290
2434
  assumeControlOfScrollRestoration() {
2291
- var _a;
2292
2435
  if (!this.previousScrollRestoration) {
2293
- this.previousScrollRestoration = (_a = history.scrollRestoration) !== null && _a !== void 0 ? _a : "auto";
2436
+ this.previousScrollRestoration = history.scrollRestoration ?? "auto";
2294
2437
  history.scrollRestoration = "manual";
2295
2438
  }
2296
2439
  }
@@ -2300,6 +2443,21 @@ class History {
2300
2443
  delete this.previousScrollRestoration;
2301
2444
  }
2302
2445
  }
2446
+ onPopState=event => {
2447
+ if (this.shouldHandlePopState()) {
2448
+ const {turbo: turbo} = event.state || {};
2449
+ if (turbo) {
2450
+ this.location = new URL(window.location.href);
2451
+ const {restorationIdentifier: restorationIdentifier} = turbo;
2452
+ this.restorationIdentifier = restorationIdentifier;
2453
+ this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
2454
+ }
2455
+ }
2456
+ };
2457
+ onPageLoad=async _event => {
2458
+ await nextMicrotask();
2459
+ this.pageLoaded = true;
2460
+ };
2303
2461
  shouldHandlePopState() {
2304
2462
  return this.pageIsLoaded();
2305
2463
  }
@@ -2314,18 +2472,15 @@ class Navigator {
2314
2472
  }
2315
2473
  proposeVisit(location, options = {}) {
2316
2474
  if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
2317
- if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
2318
- this.delegate.visitProposedToLocation(location, options);
2319
- } else {
2320
- window.location.href = location.toString();
2321
- }
2475
+ this.delegate.visitProposedToLocation(location, options);
2322
2476
  }
2323
2477
  }
2324
2478
  startVisit(locatable, restorationIdentifier, options = {}) {
2325
2479
  this.stop();
2326
- this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
2327
- referrer: this.location
2328
- }, options));
2480
+ this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, {
2481
+ referrer: this.location,
2482
+ ...options
2483
+ });
2329
2484
  this.currentVisit.start();
2330
2485
  }
2331
2486
  submitForm(form, submitter) {
@@ -2349,6 +2504,9 @@ class Navigator {
2349
2504
  get view() {
2350
2505
  return this.delegate.view;
2351
2506
  }
2507
+ get rootLocation() {
2508
+ return this.view.snapshot.rootLocation;
2509
+ }
2352
2510
  get history() {
2353
2511
  return this.delegate.history;
2354
2512
  }
@@ -2361,12 +2519,12 @@ class Navigator {
2361
2519
  if (formSubmission == this.formSubmission) {
2362
2520
  const responseHTML = await fetchResponse.responseHTML;
2363
2521
  if (responseHTML) {
2364
- const shouldCacheSnapshot = formSubmission.method == FetchMethod.get;
2522
+ const shouldCacheSnapshot = formSubmission.isSafe;
2365
2523
  if (!shouldCacheSnapshot) {
2366
2524
  this.view.clearSnapshotCache();
2367
2525
  }
2368
2526
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
2369
- const action = this.getActionForFormSubmission(formSubmission);
2527
+ const action = this.#getActionForFormSubmission(formSubmission, fetchResponse);
2370
2528
  const visitOptions = {
2371
2529
  action: action,
2372
2530
  shouldCacheSnapshot: shouldCacheSnapshot,
@@ -2389,7 +2547,9 @@ class Navigator {
2389
2547
  } else {
2390
2548
  await this.view.renderPage(snapshot, false, true, this.currentVisit);
2391
2549
  }
2392
- this.view.scrollToTop();
2550
+ if (!snapshot.shouldPreserveScrollPosition) {
2551
+ this.view.scrollToTop();
2552
+ }
2393
2553
  this.view.clearSnapshotCache();
2394
2554
  }
2395
2555
  }
@@ -2422,35 +2582,27 @@ class Navigator {
2422
2582
  get restorationIdentifier() {
2423
2583
  return this.history.restorationIdentifier;
2424
2584
  }
2425
- getActionForFormSubmission({submitter: submitter, formElement: formElement}) {
2426
- return getVisitAction(submitter, formElement) || "advance";
2585
+ #getActionForFormSubmission(formSubmission, fetchResponse) {
2586
+ const {submitter: submitter, formElement: formElement} = formSubmission;
2587
+ return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse);
2588
+ }
2589
+ #getDefaultAction(fetchResponse) {
2590
+ const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;
2591
+ return sameLocationRedirect ? "replace" : "advance";
2427
2592
  }
2428
2593
  }
2429
2594
 
2430
- var PageStage;
2431
-
2432
- (function(PageStage) {
2433
- PageStage[PageStage["initial"] = 0] = "initial";
2434
- PageStage[PageStage["loading"] = 1] = "loading";
2435
- PageStage[PageStage["interactive"] = 2] = "interactive";
2436
- PageStage[PageStage["complete"] = 3] = "complete";
2437
- })(PageStage || (PageStage = {}));
2595
+ const PageStage = {
2596
+ initial: 0,
2597
+ loading: 1,
2598
+ interactive: 2,
2599
+ complete: 3
2600
+ };
2438
2601
 
2439
2602
  class PageObserver {
2603
+ stage=PageStage.initial;
2604
+ started=false;
2440
2605
  constructor(delegate) {
2441
- this.stage = PageStage.initial;
2442
- this.started = false;
2443
- this.interpretReadyState = () => {
2444
- const {readyState: readyState} = this;
2445
- if (readyState == "interactive") {
2446
- this.pageIsInteractive();
2447
- } else if (readyState == "complete") {
2448
- this.pageIsComplete();
2449
- }
2450
- };
2451
- this.pageWillUnload = () => {
2452
- this.delegate.pageWillUnload();
2453
- };
2454
2606
  this.delegate = delegate;
2455
2607
  }
2456
2608
  start() {
@@ -2470,6 +2622,14 @@ class PageObserver {
2470
2622
  this.started = false;
2471
2623
  }
2472
2624
  }
2625
+ interpretReadyState=() => {
2626
+ const {readyState: readyState} = this;
2627
+ if (readyState == "interactive") {
2628
+ this.pageIsInteractive();
2629
+ } else if (readyState == "complete") {
2630
+ this.pageIsComplete();
2631
+ }
2632
+ };
2473
2633
  pageIsInteractive() {
2474
2634
  if (this.stage == PageStage.loading) {
2475
2635
  this.stage = PageStage.interactive;
@@ -2483,20 +2643,17 @@ class PageObserver {
2483
2643
  this.delegate.pageLoaded();
2484
2644
  }
2485
2645
  }
2646
+ pageWillUnload=() => {
2647
+ this.delegate.pageWillUnload();
2648
+ };
2486
2649
  get readyState() {
2487
2650
  return document.readyState;
2488
2651
  }
2489
2652
  }
2490
2653
 
2491
2654
  class ScrollObserver {
2655
+ started=false;
2492
2656
  constructor(delegate) {
2493
- this.started = false;
2494
- this.onScroll = () => {
2495
- this.updatePosition({
2496
- x: window.pageXOffset,
2497
- y: window.pageYOffset
2498
- });
2499
- };
2500
2657
  this.delegate = delegate;
2501
2658
  }
2502
2659
  start() {
@@ -2512,6 +2669,12 @@ class ScrollObserver {
2512
2669
  this.started = false;
2513
2670
  }
2514
2671
  }
2672
+ onScroll=() => {
2673
+ this.updatePosition({
2674
+ x: window.pageXOffset,
2675
+ y: window.pageYOffset
2676
+ });
2677
+ };
2515
2678
  updatePosition(position) {
2516
2679
  this.delegate.scrollPositionChanged(position);
2517
2680
  }
@@ -2519,7 +2682,13 @@ class ScrollObserver {
2519
2682
 
2520
2683
  class StreamMessageRenderer {
2521
2684
  render({fragment: fragment}) {
2522
- Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => document.documentElement.appendChild(fragment)));
2685
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => {
2686
+ withAutofocusFromFragment(fragment, (() => {
2687
+ withPreservedFocus((() => {
2688
+ document.documentElement.appendChild(fragment);
2689
+ }));
2690
+ }));
2691
+ }));
2523
2692
  }
2524
2693
  enteringBardo(currentPermanentElement, newPermanentElement) {
2525
2694
  newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
@@ -2542,33 +2711,67 @@ function getPermanentElementMapForFragment(fragment) {
2542
2711
  return permanentElementMap;
2543
2712
  }
2544
2713
 
2714
+ async function withAutofocusFromFragment(fragment, callback) {
2715
+ const generatedID = `turbo-stream-autofocus-${uuid()}`;
2716
+ const turboStreams = fragment.querySelectorAll("turbo-stream");
2717
+ const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
2718
+ let willAutofocusId = null;
2719
+ if (elementWithAutofocus) {
2720
+ if (elementWithAutofocus.id) {
2721
+ willAutofocusId = elementWithAutofocus.id;
2722
+ } else {
2723
+ willAutofocusId = generatedID;
2724
+ }
2725
+ elementWithAutofocus.id = willAutofocusId;
2726
+ }
2727
+ callback();
2728
+ await nextAnimationFrame();
2729
+ const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
2730
+ if (hasNoActiveElement && willAutofocusId) {
2731
+ const elementToAutofocus = document.getElementById(willAutofocusId);
2732
+ if (elementIsFocusable(elementToAutofocus)) {
2733
+ elementToAutofocus.focus();
2734
+ }
2735
+ if (elementToAutofocus && elementToAutofocus.id == generatedID) {
2736
+ elementToAutofocus.removeAttribute("id");
2737
+ }
2738
+ }
2739
+ }
2740
+
2741
+ async function withPreservedFocus(callback) {
2742
+ const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, (() => document.activeElement));
2743
+ const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;
2744
+ if (restoreFocusTo) {
2745
+ const elementToFocus = document.getElementById(restoreFocusTo);
2746
+ if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
2747
+ elementToFocus.focus();
2748
+ }
2749
+ }
2750
+ }
2751
+
2752
+ function firstAutofocusableElementInStreams(nodeListOfStreamElements) {
2753
+ for (const streamElement of nodeListOfStreamElements) {
2754
+ const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);
2755
+ if (elementWithAutofocus) return elementWithAutofocus;
2756
+ }
2757
+ return null;
2758
+ }
2759
+
2545
2760
  class StreamObserver {
2761
+ sources=new Set;
2762
+ #started=false;
2546
2763
  constructor(delegate) {
2547
- this.sources = new Set;
2548
- this.started = false;
2549
- this.inspectFetchResponse = event => {
2550
- const response = fetchResponseFromEvent(event);
2551
- if (response && fetchResponseIsStream(response)) {
2552
- event.preventDefault();
2553
- this.receiveMessageResponse(response);
2554
- }
2555
- };
2556
- this.receiveMessageEvent = event => {
2557
- if (this.started && typeof event.data == "string") {
2558
- this.receiveMessageHTML(event.data);
2559
- }
2560
- };
2561
2764
  this.delegate = delegate;
2562
2765
  }
2563
2766
  start() {
2564
- if (!this.started) {
2565
- this.started = true;
2767
+ if (!this.#started) {
2768
+ this.#started = true;
2566
2769
  addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2567
2770
  }
2568
2771
  }
2569
2772
  stop() {
2570
- if (this.started) {
2571
- this.started = false;
2773
+ if (this.#started) {
2774
+ this.#started = false;
2572
2775
  removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2573
2776
  }
2574
2777
  }
@@ -2587,6 +2790,18 @@ class StreamObserver {
2587
2790
  streamSourceIsConnected(source) {
2588
2791
  return this.sources.has(source);
2589
2792
  }
2793
+ inspectFetchResponse=event => {
2794
+ const response = fetchResponseFromEvent(event);
2795
+ if (response && fetchResponseIsStream(response)) {
2796
+ event.preventDefault();
2797
+ this.receiveMessageResponse(response);
2798
+ }
2799
+ };
2800
+ receiveMessageEvent=event => {
2801
+ if (this.#started && typeof event.data == "string") {
2802
+ this.receiveMessageHTML(event.data);
2803
+ }
2804
+ };
2590
2805
  async receiveMessageResponse(response) {
2591
2806
  const html = await response.responseHTML;
2592
2807
  if (html) {
@@ -2599,16 +2814,14 @@ class StreamObserver {
2599
2814
  }
2600
2815
 
2601
2816
  function fetchResponseFromEvent(event) {
2602
- var _a;
2603
- const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse;
2817
+ const fetchResponse = event.detail?.fetchResponse;
2604
2818
  if (fetchResponse instanceof FetchResponse) {
2605
2819
  return fetchResponse;
2606
2820
  }
2607
2821
  }
2608
2822
 
2609
2823
  function fetchResponseIsStream(response) {
2610
- var _a;
2611
- const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
2824
+ const contentType = response.contentType ?? "";
2612
2825
  return contentType.startsWith(StreamMessage.contentType);
2613
2826
  }
2614
2827
 
@@ -2643,6 +2856,575 @@ class ErrorRenderer extends Renderer {
2643
2856
  }
2644
2857
  }
2645
2858
 
2859
+ let EMPTY_SET = new Set;
2860
+
2861
+ function morph(oldNode, newContent, config = {}) {
2862
+ if (oldNode instanceof Document) {
2863
+ oldNode = oldNode.documentElement;
2864
+ }
2865
+ if (typeof newContent === "string") {
2866
+ newContent = parseContent(newContent);
2867
+ }
2868
+ let normalizedContent = normalizeContent(newContent);
2869
+ let ctx = createMorphContext(oldNode, normalizedContent, config);
2870
+ return morphNormalizedContent(oldNode, normalizedContent, ctx);
2871
+ }
2872
+
2873
+ function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
2874
+ if (ctx.head.block) {
2875
+ let oldHead = oldNode.querySelector("head");
2876
+ let newHead = normalizedNewContent.querySelector("head");
2877
+ if (oldHead && newHead) {
2878
+ let promises = handleHeadElement(newHead, oldHead, ctx);
2879
+ Promise.all(promises).then((function() {
2880
+ morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
2881
+ head: {
2882
+ block: false,
2883
+ ignore: true
2884
+ }
2885
+ }));
2886
+ }));
2887
+ return;
2888
+ }
2889
+ }
2890
+ if (ctx.morphStyle === "innerHTML") {
2891
+ morphChildren(normalizedNewContent, oldNode, ctx);
2892
+ return oldNode.children;
2893
+ } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
2894
+ let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
2895
+ let previousSibling = bestMatch?.previousSibling;
2896
+ let nextSibling = bestMatch?.nextSibling;
2897
+ let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
2898
+ if (bestMatch) {
2899
+ return insertSiblings(previousSibling, morphedNode, nextSibling);
2900
+ } else {
2901
+ return [];
2902
+ }
2903
+ } else {
2904
+ throw "Do not understand how to morph style " + ctx.morphStyle;
2905
+ }
2906
+ }
2907
+
2908
+ function morphOldNodeTo(oldNode, newContent, ctx) {
2909
+ if (ctx.ignoreActive && oldNode === document.activeElement) ; else if (newContent == null) {
2910
+ if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return;
2911
+ oldNode.remove();
2912
+ ctx.callbacks.afterNodeRemoved(oldNode);
2913
+ return null;
2914
+ } else if (!isSoftMatch(oldNode, newContent)) {
2915
+ if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return;
2916
+ if (ctx.callbacks.beforeNodeAdded(newContent) === false) return;
2917
+ oldNode.parentElement.replaceChild(newContent, oldNode);
2918
+ ctx.callbacks.afterNodeAdded(newContent);
2919
+ ctx.callbacks.afterNodeRemoved(oldNode);
2920
+ return newContent;
2921
+ } else {
2922
+ if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return;
2923
+ if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
2924
+ handleHeadElement(newContent, oldNode, ctx);
2925
+ } else {
2926
+ syncNodeFrom(newContent, oldNode);
2927
+ morphChildren(newContent, oldNode, ctx);
2928
+ }
2929
+ ctx.callbacks.afterNodeMorphed(oldNode, newContent);
2930
+ return oldNode;
2931
+ }
2932
+ }
2933
+
2934
+ function morphChildren(newParent, oldParent, ctx) {
2935
+ let nextNewChild = newParent.firstChild;
2936
+ let insertionPoint = oldParent.firstChild;
2937
+ let newChild;
2938
+ while (nextNewChild) {
2939
+ newChild = nextNewChild;
2940
+ nextNewChild = newChild.nextSibling;
2941
+ if (insertionPoint == null) {
2942
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
2943
+ oldParent.appendChild(newChild);
2944
+ ctx.callbacks.afterNodeAdded(newChild);
2945
+ removeIdsFromConsideration(ctx, newChild);
2946
+ continue;
2947
+ }
2948
+ if (isIdSetMatch(newChild, insertionPoint, ctx)) {
2949
+ morphOldNodeTo(insertionPoint, newChild, ctx);
2950
+ insertionPoint = insertionPoint.nextSibling;
2951
+ removeIdsFromConsideration(ctx, newChild);
2952
+ continue;
2953
+ }
2954
+ let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
2955
+ if (idSetMatch) {
2956
+ insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
2957
+ morphOldNodeTo(idSetMatch, newChild, ctx);
2958
+ removeIdsFromConsideration(ctx, newChild);
2959
+ continue;
2960
+ }
2961
+ let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
2962
+ if (softMatch) {
2963
+ insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
2964
+ morphOldNodeTo(softMatch, newChild, ctx);
2965
+ removeIdsFromConsideration(ctx, newChild);
2966
+ continue;
2967
+ }
2968
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
2969
+ oldParent.insertBefore(newChild, insertionPoint);
2970
+ ctx.callbacks.afterNodeAdded(newChild);
2971
+ removeIdsFromConsideration(ctx, newChild);
2972
+ }
2973
+ while (insertionPoint !== null) {
2974
+ let tempNode = insertionPoint;
2975
+ insertionPoint = insertionPoint.nextSibling;
2976
+ removeNode(tempNode, ctx);
2977
+ }
2978
+ }
2979
+
2980
+ function syncNodeFrom(from, to) {
2981
+ let type = from.nodeType;
2982
+ if (type === 1) {
2983
+ const fromAttributes = from.attributes;
2984
+ const toAttributes = to.attributes;
2985
+ for (const fromAttribute of fromAttributes) {
2986
+ if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
2987
+ to.setAttribute(fromAttribute.name, fromAttribute.value);
2988
+ }
2989
+ }
2990
+ for (const toAttribute of toAttributes) {
2991
+ if (!from.hasAttribute(toAttribute.name)) {
2992
+ to.removeAttribute(toAttribute.name);
2993
+ }
2994
+ }
2995
+ }
2996
+ if (type === 8 || type === 3) {
2997
+ if (to.nodeValue !== from.nodeValue) {
2998
+ to.nodeValue = from.nodeValue;
2999
+ }
3000
+ }
3001
+ if (from instanceof HTMLInputElement && to instanceof HTMLInputElement && from.type !== "file") {
3002
+ to.value = from.value || "";
3003
+ syncAttribute(from, to, "value");
3004
+ syncAttribute(from, to, "checked");
3005
+ syncAttribute(from, to, "disabled");
3006
+ } else if (from instanceof HTMLOptionElement) {
3007
+ syncAttribute(from, to, "selected");
3008
+ } else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
3009
+ let fromValue = from.value;
3010
+ let toValue = to.value;
3011
+ if (fromValue !== toValue) {
3012
+ to.value = fromValue;
3013
+ }
3014
+ if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
3015
+ to.firstChild.nodeValue = fromValue;
3016
+ }
3017
+ }
3018
+ }
3019
+
3020
+ function syncAttribute(from, to, attributeName) {
3021
+ if (from[attributeName] !== to[attributeName]) {
3022
+ if (from[attributeName]) {
3023
+ to.setAttribute(attributeName, from[attributeName]);
3024
+ } else {
3025
+ to.removeAttribute(attributeName);
3026
+ }
3027
+ }
3028
+ }
3029
+
3030
+ function handleHeadElement(newHeadTag, currentHead, ctx) {
3031
+ let added = [];
3032
+ let removed = [];
3033
+ let preserved = [];
3034
+ let nodesToAppend = [];
3035
+ let headMergeStyle = ctx.head.style;
3036
+ let srcToNewHeadNodes = new Map;
3037
+ for (const newHeadChild of newHeadTag.children) {
3038
+ srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
3039
+ }
3040
+ for (const currentHeadElt of currentHead.children) {
3041
+ let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
3042
+ let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
3043
+ let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
3044
+ if (inNewContent || isPreserved) {
3045
+ if (isReAppended) {
3046
+ removed.push(currentHeadElt);
3047
+ } else {
3048
+ srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
3049
+ preserved.push(currentHeadElt);
3050
+ }
3051
+ } else {
3052
+ if (headMergeStyle === "append") {
3053
+ if (isReAppended) {
3054
+ removed.push(currentHeadElt);
3055
+ nodesToAppend.push(currentHeadElt);
3056
+ }
3057
+ } else {
3058
+ if (ctx.head.shouldRemove(currentHeadElt) !== false) {
3059
+ removed.push(currentHeadElt);
3060
+ }
3061
+ }
3062
+ }
3063
+ }
3064
+ nodesToAppend.push(...srcToNewHeadNodes.values());
3065
+ let promises = [];
3066
+ for (const newNode of nodesToAppend) {
3067
+ let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
3068
+ if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
3069
+ if (newElt.href || newElt.src) {
3070
+ let resolve = null;
3071
+ let promise = new Promise((function(_resolve) {
3072
+ resolve = _resolve;
3073
+ }));
3074
+ newElt.addEventListener("load", (function() {
3075
+ resolve();
3076
+ }));
3077
+ promises.push(promise);
3078
+ }
3079
+ currentHead.appendChild(newElt);
3080
+ ctx.callbacks.afterNodeAdded(newElt);
3081
+ added.push(newElt);
3082
+ }
3083
+ }
3084
+ for (const removedElement of removed) {
3085
+ if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
3086
+ currentHead.removeChild(removedElement);
3087
+ ctx.callbacks.afterNodeRemoved(removedElement);
3088
+ }
3089
+ }
3090
+ ctx.head.afterHeadMorphed(currentHead, {
3091
+ added: added,
3092
+ kept: preserved,
3093
+ removed: removed
3094
+ });
3095
+ return promises;
3096
+ }
3097
+
3098
+ function noOp() {}
3099
+
3100
+ function createMorphContext(oldNode, newContent, config) {
3101
+ return {
3102
+ target: oldNode,
3103
+ newContent: newContent,
3104
+ config: config,
3105
+ morphStyle: config.morphStyle,
3106
+ ignoreActive: config.ignoreActive,
3107
+ idMap: createIdMap(oldNode, newContent),
3108
+ deadIds: new Set,
3109
+ callbacks: Object.assign({
3110
+ beforeNodeAdded: noOp,
3111
+ afterNodeAdded: noOp,
3112
+ beforeNodeMorphed: noOp,
3113
+ afterNodeMorphed: noOp,
3114
+ beforeNodeRemoved: noOp,
3115
+ afterNodeRemoved: noOp
3116
+ }, config.callbacks),
3117
+ head: Object.assign({
3118
+ style: "merge",
3119
+ shouldPreserve: function(elt) {
3120
+ return elt.getAttribute("im-preserve") === "true";
3121
+ },
3122
+ shouldReAppend: function(elt) {
3123
+ return elt.getAttribute("im-re-append") === "true";
3124
+ },
3125
+ shouldRemove: noOp,
3126
+ afterHeadMorphed: noOp
3127
+ }, config.head)
3128
+ };
3129
+ }
3130
+
3131
+ function isIdSetMatch(node1, node2, ctx) {
3132
+ if (node1 == null || node2 == null) {
3133
+ return false;
3134
+ }
3135
+ if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
3136
+ if (node1.id !== "" && node1.id === node2.id) {
3137
+ return true;
3138
+ } else {
3139
+ return getIdIntersectionCount(ctx, node1, node2) > 0;
3140
+ }
3141
+ }
3142
+ return false;
3143
+ }
3144
+
3145
+ function isSoftMatch(node1, node2) {
3146
+ if (node1 == null || node2 == null) {
3147
+ return false;
3148
+ }
3149
+ return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName;
3150
+ }
3151
+
3152
+ function removeNodesBetween(startInclusive, endExclusive, ctx) {
3153
+ while (startInclusive !== endExclusive) {
3154
+ let tempNode = startInclusive;
3155
+ startInclusive = startInclusive.nextSibling;
3156
+ removeNode(tempNode, ctx);
3157
+ }
3158
+ removeIdsFromConsideration(ctx, endExclusive);
3159
+ return endExclusive.nextSibling;
3160
+ }
3161
+
3162
+ function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
3163
+ let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
3164
+ let potentialMatch = null;
3165
+ if (newChildPotentialIdCount > 0) {
3166
+ let potentialMatch = insertionPoint;
3167
+ let otherMatchCount = 0;
3168
+ while (potentialMatch != null) {
3169
+ if (isIdSetMatch(newChild, potentialMatch, ctx)) {
3170
+ return potentialMatch;
3171
+ }
3172
+ otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
3173
+ if (otherMatchCount > newChildPotentialIdCount) {
3174
+ return null;
3175
+ }
3176
+ potentialMatch = potentialMatch.nextSibling;
3177
+ }
3178
+ }
3179
+ return potentialMatch;
3180
+ }
3181
+
3182
+ function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
3183
+ let potentialSoftMatch = insertionPoint;
3184
+ let nextSibling = newChild.nextSibling;
3185
+ let siblingSoftMatchCount = 0;
3186
+ while (potentialSoftMatch != null) {
3187
+ if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
3188
+ return null;
3189
+ }
3190
+ if (isSoftMatch(newChild, potentialSoftMatch)) {
3191
+ return potentialSoftMatch;
3192
+ }
3193
+ if (isSoftMatch(nextSibling, potentialSoftMatch)) {
3194
+ siblingSoftMatchCount++;
3195
+ nextSibling = nextSibling.nextSibling;
3196
+ if (siblingSoftMatchCount >= 2) {
3197
+ return null;
3198
+ }
3199
+ }
3200
+ potentialSoftMatch = potentialSoftMatch.nextSibling;
3201
+ }
3202
+ return potentialSoftMatch;
3203
+ }
3204
+
3205
+ function parseContent(newContent) {
3206
+ let parser = new DOMParser;
3207
+ let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
3208
+ if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
3209
+ let content = parser.parseFromString(newContent, "text/html");
3210
+ if (contentWithSvgsRemoved.match(/<\/html>/)) {
3211
+ content.generatedByIdiomorph = true;
3212
+ return content;
3213
+ } else {
3214
+ let htmlElement = content.firstChild;
3215
+ if (htmlElement) {
3216
+ htmlElement.generatedByIdiomorph = true;
3217
+ return htmlElement;
3218
+ } else {
3219
+ return null;
3220
+ }
3221
+ }
3222
+ } else {
3223
+ let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
3224
+ let content = responseDoc.body.querySelector("template").content;
3225
+ content.generatedByIdiomorph = true;
3226
+ return content;
3227
+ }
3228
+ }
3229
+
3230
+ function normalizeContent(newContent) {
3231
+ if (newContent == null) {
3232
+ const dummyParent = document.createElement("div");
3233
+ return dummyParent;
3234
+ } else if (newContent.generatedByIdiomorph) {
3235
+ return newContent;
3236
+ } else if (newContent instanceof Node) {
3237
+ const dummyParent = document.createElement("div");
3238
+ dummyParent.append(newContent);
3239
+ return dummyParent;
3240
+ } else {
3241
+ const dummyParent = document.createElement("div");
3242
+ for (const elt of [ ...newContent ]) {
3243
+ dummyParent.append(elt);
3244
+ }
3245
+ return dummyParent;
3246
+ }
3247
+ }
3248
+
3249
+ function insertSiblings(previousSibling, morphedNode, nextSibling) {
3250
+ let stack = [];
3251
+ let added = [];
3252
+ while (previousSibling != null) {
3253
+ stack.push(previousSibling);
3254
+ previousSibling = previousSibling.previousSibling;
3255
+ }
3256
+ while (stack.length > 0) {
3257
+ let node = stack.pop();
3258
+ added.push(node);
3259
+ morphedNode.parentElement.insertBefore(node, morphedNode);
3260
+ }
3261
+ added.push(morphedNode);
3262
+ while (nextSibling != null) {
3263
+ stack.push(nextSibling);
3264
+ added.push(nextSibling);
3265
+ nextSibling = nextSibling.nextSibling;
3266
+ }
3267
+ while (stack.length > 0) {
3268
+ morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
3269
+ }
3270
+ return added;
3271
+ }
3272
+
3273
+ function findBestNodeMatch(newContent, oldNode, ctx) {
3274
+ let currentElement;
3275
+ currentElement = newContent.firstChild;
3276
+ let bestElement = currentElement;
3277
+ let score = 0;
3278
+ while (currentElement) {
3279
+ let newScore = scoreElement(currentElement, oldNode, ctx);
3280
+ if (newScore > score) {
3281
+ bestElement = currentElement;
3282
+ score = newScore;
3283
+ }
3284
+ currentElement = currentElement.nextSibling;
3285
+ }
3286
+ return bestElement;
3287
+ }
3288
+
3289
+ function scoreElement(node1, node2, ctx) {
3290
+ if (isSoftMatch(node1, node2)) {
3291
+ return .5 + getIdIntersectionCount(ctx, node1, node2);
3292
+ }
3293
+ return 0;
3294
+ }
3295
+
3296
+ function removeNode(tempNode, ctx) {
3297
+ removeIdsFromConsideration(ctx, tempNode);
3298
+ if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
3299
+ tempNode.remove();
3300
+ ctx.callbacks.afterNodeRemoved(tempNode);
3301
+ }
3302
+
3303
+ function isIdInConsideration(ctx, id) {
3304
+ return !ctx.deadIds.has(id);
3305
+ }
3306
+
3307
+ function idIsWithinNode(ctx, id, targetNode) {
3308
+ let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
3309
+ return idSet.has(id);
3310
+ }
3311
+
3312
+ function removeIdsFromConsideration(ctx, node) {
3313
+ let idSet = ctx.idMap.get(node) || EMPTY_SET;
3314
+ for (const id of idSet) {
3315
+ ctx.deadIds.add(id);
3316
+ }
3317
+ }
3318
+
3319
+ function getIdIntersectionCount(ctx, node1, node2) {
3320
+ let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
3321
+ let matchCount = 0;
3322
+ for (const id of sourceSet) {
3323
+ if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
3324
+ ++matchCount;
3325
+ }
3326
+ }
3327
+ return matchCount;
3328
+ }
3329
+
3330
+ function populateIdMapForNode(node, idMap) {
3331
+ let nodeParent = node.parentElement;
3332
+ let idElements = node.querySelectorAll("[id]");
3333
+ for (const elt of idElements) {
3334
+ let current = elt;
3335
+ while (current !== nodeParent && current != null) {
3336
+ let idSet = idMap.get(current);
3337
+ if (idSet == null) {
3338
+ idSet = new Set;
3339
+ idMap.set(current, idSet);
3340
+ }
3341
+ idSet.add(elt.id);
3342
+ current = current.parentElement;
3343
+ }
3344
+ }
3345
+ }
3346
+
3347
+ function createIdMap(oldContent, newContent) {
3348
+ let idMap = new Map;
3349
+ populateIdMapForNode(oldContent, idMap);
3350
+ populateIdMapForNode(newContent, idMap);
3351
+ return idMap;
3352
+ }
3353
+
3354
+ var idiomorph = {
3355
+ morph: morph
3356
+ };
3357
+
3358
+ class MorphRenderer extends Renderer {
3359
+ async render() {
3360
+ if (this.willRender) await this.#morphBody();
3361
+ }
3362
+ get renderMethod() {
3363
+ return "morph";
3364
+ }
3365
+ async #morphBody() {
3366
+ this.#morphElements(this.currentElement, this.newElement);
3367
+ this.#reloadRemoteFrames();
3368
+ dispatch("turbo:morph", {
3369
+ detail: {
3370
+ currentElement: this.currentElement,
3371
+ newElement: this.newElement
3372
+ }
3373
+ });
3374
+ }
3375
+ #morphElements(currentElement, newElement, morphStyle = "outerHTML") {
3376
+ this.isMorphingTurboFrame = this.#isFrameReloadedWithMorph(currentElement);
3377
+ idiomorph.morph(currentElement, newElement, {
3378
+ morphStyle: morphStyle,
3379
+ callbacks: {
3380
+ beforeNodeAdded: this.#shouldAddElement,
3381
+ beforeNodeMorphed: this.#shouldMorphElement,
3382
+ beforeNodeRemoved: this.#shouldRemoveElement
3383
+ }
3384
+ });
3385
+ }
3386
+ #shouldAddElement=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
3387
+ #shouldMorphElement=(oldNode, newNode) => {
3388
+ if (oldNode instanceof HTMLElement) {
3389
+ return !oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode));
3390
+ } else {
3391
+ return true;
3392
+ }
3393
+ };
3394
+ #shouldRemoveElement=node => this.#shouldMorphElement(node);
3395
+ #reloadRemoteFrames() {
3396
+ this.#remoteFrames().forEach((frame => {
3397
+ if (this.#isFrameReloadedWithMorph(frame)) {
3398
+ this.#renderFrameWithMorph(frame);
3399
+ frame.reload();
3400
+ }
3401
+ }));
3402
+ }
3403
+ #renderFrameWithMorph(frame) {
3404
+ frame.addEventListener("turbo:before-frame-render", (event => {
3405
+ event.detail.render = this.#morphFrameUpdate;
3406
+ }), {
3407
+ once: true
3408
+ });
3409
+ }
3410
+ #morphFrameUpdate=(currentElement, newElement) => {
3411
+ dispatch("turbo:before-frame-morph", {
3412
+ target: currentElement,
3413
+ detail: {
3414
+ currentElement: currentElement,
3415
+ newElement: newElement
3416
+ }
3417
+ });
3418
+ this.#morphElements(currentElement, newElement.children, "innerHTML");
3419
+ };
3420
+ #isFrameReloadedWithMorph(element) {
3421
+ return element.src && element.refresh === "morph";
3422
+ }
3423
+ #remoteFrames() {
3424
+ return Array.from(document.querySelectorAll("turbo-frame[src]")).filter((frame => !frame.closest("[data-turbo-permanent]")));
3425
+ }
3426
+ }
3427
+
2646
3428
  class PageRenderer extends Renderer {
2647
3429
  static renderElement(currentElement, newElement) {
2648
3430
  if (document.body && newElement instanceof HTMLBodyElement) {
@@ -2667,6 +3449,7 @@ class PageRenderer extends Renderer {
2667
3449
  }
2668
3450
  }
2669
3451
  async prepareToRender() {
3452
+ this.#setLanguage();
2670
3453
  await this.mergeHead();
2671
3454
  }
2672
3455
  async render() {
@@ -2689,6 +3472,15 @@ class PageRenderer extends Renderer {
2689
3472
  get newElement() {
2690
3473
  return this.newSnapshot.element;
2691
3474
  }
3475
+ #setLanguage() {
3476
+ const {documentElement: documentElement} = this.currentSnapshot;
3477
+ const {lang: lang} = this.newSnapshot;
3478
+ if (lang) {
3479
+ documentElement.setAttribute("lang", lang);
3480
+ } else {
3481
+ documentElement.removeAttribute("lang");
3482
+ }
3483
+ }
2692
3484
  async mergeHead() {
2693
3485
  const mergedHeadElements = this.mergeProvisionalElements();
2694
3486
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
@@ -2788,9 +3580,9 @@ class PageRenderer extends Renderer {
2788
3580
  }
2789
3581
 
2790
3582
  class SnapshotCache {
3583
+ keys=[];
3584
+ snapshots={};
2791
3585
  constructor(size) {
2792
- this.keys = [];
2793
- this.snapshots = {};
2794
3586
  this.size = size;
2795
3587
  }
2796
3588
  has(location) {
@@ -2832,23 +3624,25 @@ class SnapshotCache {
2832
3624
  }
2833
3625
 
2834
3626
  class PageView extends View {
2835
- constructor() {
2836
- super(...arguments);
2837
- this.snapshotCache = new SnapshotCache(10);
2838
- this.lastRenderedLocation = new URL(location.href);
2839
- this.forceReloaded = false;
3627
+ snapshotCache=new SnapshotCache(10);
3628
+ lastRenderedLocation=new URL(location.href);
3629
+ forceReloaded=false;
3630
+ shouldTransitionTo(newSnapshot) {
3631
+ return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions;
2840
3632
  }
2841
3633
  renderPage(snapshot, isPreview = false, willRender = true, visit) {
2842
- const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
3634
+ const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
3635
+ const rendererClass = shouldMorphPage ? MorphRenderer : PageRenderer;
3636
+ const renderer = new rendererClass(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
2843
3637
  if (!renderer.shouldRender) {
2844
3638
  this.forceReloaded = true;
2845
3639
  } else {
2846
- visit === null || visit === void 0 ? void 0 : visit.changeHistory();
3640
+ visit?.changeHistory();
2847
3641
  }
2848
3642
  return this.render(renderer);
2849
3643
  }
2850
3644
  renderError(snapshot, visit) {
2851
- visit === null || visit === void 0 ? void 0 : visit.changeHistory();
3645
+ visit?.changeHistory();
2852
3646
  const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
2853
3647
  return this.render(renderer);
2854
3648
  }
@@ -2868,14 +3662,17 @@ class PageView extends View {
2868
3662
  getCachedSnapshotForLocation(location) {
2869
3663
  return this.snapshotCache.get(location);
2870
3664
  }
3665
+ isPageRefresh(visit) {
3666
+ return !visit || this.lastRenderedLocation.href === visit.location.href && visit.action === "replace";
3667
+ }
2871
3668
  get snapshot() {
2872
3669
  return PageSnapshot.fromElement(this.element);
2873
3670
  }
2874
3671
  }
2875
3672
 
2876
3673
  class Preloader {
3674
+ selector="a[data-turbo-preload]";
2877
3675
  constructor(delegate) {
2878
- this.selector = "a[data-turbo-preload]";
2879
3676
  this.delegate = delegate;
2880
3677
  }
2881
3678
  get snapshotCache() {
@@ -2903,7 +3700,7 @@ class Preloader {
2903
3700
  try {
2904
3701
  const response = await fetch(location.toString(), {
2905
3702
  headers: {
2906
- "VND.PREFETCH": "true",
3703
+ "Sec-Purpose": "prefetch",
2907
3704
  Accept: "text/html"
2908
3705
  }
2909
3706
  });
@@ -2914,28 +3711,64 @@ class Preloader {
2914
3711
  }
2915
3712
  }
2916
3713
 
2917
- class Session {
2918
- constructor() {
2919
- this.navigator = new Navigator(this);
2920
- this.history = new History(this);
2921
- this.preloader = new Preloader(this);
2922
- this.view = new PageView(this, document.documentElement);
2923
- this.adapter = new BrowserAdapter(this);
2924
- this.pageObserver = new PageObserver(this);
2925
- this.cacheObserver = new CacheObserver;
2926
- this.linkClickObserver = new LinkClickObserver(this, window);
2927
- this.formSubmitObserver = new FormSubmitObserver(this, document);
2928
- this.scrollObserver = new ScrollObserver(this);
2929
- this.streamObserver = new StreamObserver(this);
2930
- this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
2931
- this.frameRedirector = new FrameRedirector(this, document.documentElement);
2932
- this.streamMessageRenderer = new StreamMessageRenderer;
2933
- this.drive = true;
2934
- this.enabled = true;
2935
- this.progressBarDelay = 500;
2936
- this.started = false;
2937
- this.formMode = "on";
3714
+ class LimitedSet extends Set {
3715
+ constructor(maxSize) {
3716
+ super();
3717
+ this.maxSize = maxSize;
2938
3718
  }
3719
+ add(value) {
3720
+ if (this.size >= this.maxSize) {
3721
+ const iterator = this.values();
3722
+ const oldestValue = iterator.next().value;
3723
+ this.delete(oldestValue);
3724
+ }
3725
+ super.add(value);
3726
+ }
3727
+ }
3728
+
3729
+ class Cache {
3730
+ constructor(session) {
3731
+ this.session = session;
3732
+ }
3733
+ clear() {
3734
+ this.session.clearCache();
3735
+ }
3736
+ resetCacheControl() {
3737
+ this.#setCacheControl("");
3738
+ }
3739
+ exemptPageFromCache() {
3740
+ this.#setCacheControl("no-cache");
3741
+ }
3742
+ exemptPageFromPreview() {
3743
+ this.#setCacheControl("no-preview");
3744
+ }
3745
+ #setCacheControl(value) {
3746
+ setMetaContent("turbo-cache-control", value);
3747
+ }
3748
+ }
3749
+
3750
+ class Session {
3751
+ navigator=new Navigator(this);
3752
+ history=new History(this);
3753
+ preloader=new Preloader(this);
3754
+ view=new PageView(this, document.documentElement);
3755
+ adapter=new BrowserAdapter(this);
3756
+ pageObserver=new PageObserver(this);
3757
+ cacheObserver=new CacheObserver;
3758
+ linkClickObserver=new LinkClickObserver(this, window);
3759
+ formSubmitObserver=new FormSubmitObserver(this, document);
3760
+ scrollObserver=new ScrollObserver(this);
3761
+ streamObserver=new StreamObserver(this);
3762
+ formLinkClickObserver=new FormLinkClickObserver(this, document.documentElement);
3763
+ frameRedirector=new FrameRedirector(this, document.documentElement);
3764
+ streamMessageRenderer=new StreamMessageRenderer;
3765
+ cache=new Cache(this);
3766
+ recentRequests=new LimitedSet(20);
3767
+ drive=true;
3768
+ enabled=true;
3769
+ progressBarDelay=500;
3770
+ started=false;
3771
+ formMode="on";
2939
3772
  start() {
2940
3773
  if (!this.started) {
2941
3774
  this.pageObserver.start();
@@ -2981,6 +3814,15 @@ class Session {
2981
3814
  this.navigator.proposeVisit(expandURL(location), options);
2982
3815
  }
2983
3816
  }
3817
+ refresh(url, requestId) {
3818
+ const isRecentRequest = requestId && this.recentRequests.has(requestId);
3819
+ if (!isRecentRequest) {
3820
+ this.cache.exemptPageFromPreview();
3821
+ this.visit(url, {
3822
+ action: "replace"
3823
+ });
3824
+ }
3825
+ }
2984
3826
  connectStreamSource(source) {
2985
3827
  this.streamObserver.connectStreamSource(source);
2986
3828
  }
@@ -3064,7 +3906,7 @@ class Session {
3064
3906
  this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
3065
3907
  }
3066
3908
  willSubmitForm(form, submitter) {
3067
- const action = getAction(form, submitter);
3909
+ const action = getAction$1(form, submitter);
3068
3910
  return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
3069
3911
  }
3070
3912
  formSubmitted(form, submitter) {
@@ -3084,22 +3926,21 @@ class Session {
3084
3926
  this.renderStreamMessage(message);
3085
3927
  }
3086
3928
  viewWillCacheSnapshot() {
3087
- var _a;
3088
- if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) {
3929
+ if (!this.navigator.currentVisit?.silent) {
3089
3930
  this.notifyApplicationBeforeCachingSnapshot();
3090
3931
  }
3091
3932
  }
3092
- allowsImmediateRender({element: element}, options) {
3093
- const event = this.notifyApplicationBeforeRender(element, options);
3933
+ allowsImmediateRender({element: element}, isPreview, options) {
3934
+ const event = this.notifyApplicationBeforeRender(element, isPreview, options);
3094
3935
  const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
3095
3936
  if (this.view.renderer && render) {
3096
3937
  this.view.renderer.renderElement = render;
3097
3938
  }
3098
3939
  return !defaultPrevented;
3099
3940
  }
3100
- viewRenderedSnapshot(_snapshot, _isPreview) {
3941
+ viewRenderedSnapshot(_snapshot, isPreview, renderMethod) {
3101
3942
  this.view.lastRenderedLocation = this.history.location;
3102
- this.notifyApplicationAfterRender();
3943
+ this.notifyApplicationAfterRender(isPreview, renderMethod);
3103
3944
  }
3104
3945
  preloadOnLoadLinksForView(element) {
3105
3946
  this.preloader.preloadOnLoadLinksForView(element);
@@ -3150,16 +3991,23 @@ class Session {
3150
3991
  notifyApplicationBeforeCachingSnapshot() {
3151
3992
  return dispatch("turbo:before-cache");
3152
3993
  }
3153
- notifyApplicationBeforeRender(newBody, options) {
3994
+ notifyApplicationBeforeRender(newBody, isPreview, options) {
3154
3995
  return dispatch("turbo:before-render", {
3155
- detail: Object.assign({
3156
- newBody: newBody
3157
- }, options),
3996
+ detail: {
3997
+ newBody: newBody,
3998
+ isPreview: isPreview,
3999
+ ...options
4000
+ },
3158
4001
  cancelable: true
3159
4002
  });
3160
4003
  }
3161
- notifyApplicationAfterRender() {
3162
- return dispatch("turbo:render");
4004
+ notifyApplicationAfterRender(isPreview, renderMethod) {
4005
+ return dispatch("turbo:render", {
4006
+ detail: {
4007
+ isPreview: isPreview,
4008
+ renderMethod: renderMethod
4009
+ }
4010
+ });
3163
4011
  }
3164
4012
  notifyApplicationAfterPageLoad(timing = {}) {
3165
4013
  return dispatch("turbo:load", {
@@ -3238,67 +4086,9 @@ const deprecatedLocationPropertyDescriptors = {
3238
4086
  }
3239
4087
  };
3240
4088
 
3241
- class Cache {
3242
- constructor(session) {
3243
- this.session = session;
3244
- }
3245
- clear() {
3246
- this.session.clearCache();
3247
- }
3248
- resetCacheControl() {
3249
- this.setCacheControl("");
3250
- }
3251
- exemptPageFromCache() {
3252
- this.setCacheControl("no-cache");
3253
- }
3254
- exemptPageFromPreview() {
3255
- this.setCacheControl("no-preview");
3256
- }
3257
- setCacheControl(value) {
3258
- setMetaContent("turbo-cache-control", value);
3259
- }
3260
- }
3261
-
3262
- const StreamActions = {
3263
- after() {
3264
- this.targetElements.forEach((e => {
3265
- var _a;
3266
- return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
3267
- }));
3268
- },
3269
- append() {
3270
- this.removeDuplicateTargetChildren();
3271
- this.targetElements.forEach((e => e.append(this.templateContent)));
3272
- },
3273
- before() {
3274
- this.targetElements.forEach((e => {
3275
- var _a;
3276
- return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
3277
- }));
3278
- },
3279
- prepend() {
3280
- this.removeDuplicateTargetChildren();
3281
- this.targetElements.forEach((e => e.prepend(this.templateContent)));
3282
- },
3283
- remove() {
3284
- this.targetElements.forEach((e => e.remove()));
3285
- },
3286
- replace() {
3287
- this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3288
- },
3289
- update() {
3290
- this.targetElements.forEach((targetElement => {
3291
- targetElement.innerHTML = "";
3292
- targetElement.append(this.templateContent);
3293
- }));
3294
- }
3295
- };
3296
-
3297
4089
  const session = new Session;
3298
4090
 
3299
- const cache = new Cache(session);
3300
-
3301
- const {navigator: navigator$1} = session;
4091
+ const {cache: cache, navigator: navigator$1} = session;
3302
4092
 
3303
4093
  function start() {
3304
4094
  session.start();
@@ -3349,6 +4139,7 @@ var Turbo = Object.freeze({
3349
4139
  PageRenderer: PageRenderer,
3350
4140
  PageSnapshot: PageSnapshot,
3351
4141
  FrameRenderer: FrameRenderer,
4142
+ fetch: fetch,
3352
4143
  start: start,
3353
4144
  registerAdapter: registerAdapter,
3354
4145
  visit: visit,
@@ -3358,26 +4149,20 @@ var Turbo = Object.freeze({
3358
4149
  clearCache: clearCache,
3359
4150
  setProgressBarDelay: setProgressBarDelay,
3360
4151
  setConfirmMethod: setConfirmMethod,
3361
- setFormMode: setFormMode,
3362
- StreamActions: StreamActions
4152
+ setFormMode: setFormMode
3363
4153
  });
3364
4154
 
4155
+ class TurboFrameMissingError extends Error {}
4156
+
3365
4157
  class FrameController {
4158
+ fetchResponseLoaded=_fetchResponse => Promise.resolve();
4159
+ #currentFetchRequest=null;
4160
+ #resolveVisitPromise=() => {};
4161
+ #connected=false;
4162
+ #hasBeenLoaded=false;
4163
+ #ignoredAttributes=new Set;
4164
+ action=null;
3366
4165
  constructor(element) {
3367
- this.fetchResponseLoaded = _fetchResponse => {};
3368
- this.currentFetchRequest = null;
3369
- this.resolveVisitPromise = () => {};
3370
- this.connected = false;
3371
- this.hasBeenLoaded = false;
3372
- this.ignoredAttributes = new Set;
3373
- this.action = null;
3374
- this.visitCachedSnapshot = ({element: element}) => {
3375
- const frame = element.querySelector("#" + this.element.id);
3376
- if (frame && this.previousFrameElement) {
3377
- frame.replaceChildren(...this.previousFrameElement.children);
3378
- }
3379
- delete this.previousFrameElement;
3380
- };
3381
4166
  this.element = element;
3382
4167
  this.view = new FrameView(this, this.element);
3383
4168
  this.appearanceObserver = new AppearanceObserver(this, this.element);
@@ -3387,12 +4172,12 @@ class FrameController {
3387
4172
  this.formSubmitObserver = new FormSubmitObserver(this, this.element);
3388
4173
  }
3389
4174
  connect() {
3390
- if (!this.connected) {
3391
- this.connected = true;
4175
+ if (!this.#connected) {
4176
+ this.#connected = true;
3392
4177
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
3393
4178
  this.appearanceObserver.start();
3394
4179
  } else {
3395
- this.loadSourceURL();
4180
+ this.#loadSourceURL();
3396
4181
  }
3397
4182
  this.formLinkClickObserver.start();
3398
4183
  this.linkInterceptor.start();
@@ -3400,8 +4185,8 @@ class FrameController {
3400
4185
  }
3401
4186
  }
3402
4187
  disconnect() {
3403
- if (this.connected) {
3404
- this.connected = false;
4188
+ if (this.#connected) {
4189
+ this.#connected = false;
3405
4190
  this.appearanceObserver.stop();
3406
4191
  this.formLinkClickObserver.stop();
3407
4192
  this.linkInterceptor.stop();
@@ -3410,21 +4195,21 @@ class FrameController {
3410
4195
  }
3411
4196
  disabledChanged() {
3412
4197
  if (this.loadingStyle == FrameLoadingStyle.eager) {
3413
- this.loadSourceURL();
4198
+ this.#loadSourceURL();
3414
4199
  }
3415
4200
  }
3416
4201
  sourceURLChanged() {
3417
- if (this.isIgnoringChangesTo("src")) return;
4202
+ if (this.#isIgnoringChangesTo("src")) return;
3418
4203
  if (this.element.isConnected) {
3419
4204
  this.complete = false;
3420
4205
  }
3421
- if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
3422
- this.loadSourceURL();
4206
+ if (this.loadingStyle == FrameLoadingStyle.eager || this.#hasBeenLoaded) {
4207
+ this.#loadSourceURL();
3423
4208
  }
3424
4209
  }
3425
4210
  sourceURLReloaded() {
3426
4211
  const {src: src} = this.element;
3427
- this.ignoringChangesToAttribute("complete", (() => {
4212
+ this.#ignoringChangesToAttribute("complete", (() => {
3428
4213
  this.element.removeAttribute("complete");
3429
4214
  }));
3430
4215
  this.element.src = null;
@@ -3432,23 +4217,23 @@ class FrameController {
3432
4217
  return this.element.loaded;
3433
4218
  }
3434
4219
  completeChanged() {
3435
- if (this.isIgnoringChangesTo("complete")) return;
3436
- this.loadSourceURL();
4220
+ if (this.#isIgnoringChangesTo("complete")) return;
4221
+ this.#loadSourceURL();
3437
4222
  }
3438
4223
  loadingStyleChanged() {
3439
4224
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
3440
4225
  this.appearanceObserver.start();
3441
4226
  } else {
3442
4227
  this.appearanceObserver.stop();
3443
- this.loadSourceURL();
4228
+ this.#loadSourceURL();
3444
4229
  }
3445
4230
  }
3446
- async loadSourceURL() {
4231
+ async #loadSourceURL() {
3447
4232
  if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
3448
- this.element.loaded = this.visit(expandURL(this.sourceURL));
4233
+ this.element.loaded = this.#visit(expandURL(this.sourceURL));
3449
4234
  this.appearanceObserver.stop();
3450
4235
  await this.element.loaded;
3451
- this.hasBeenLoaded = true;
4236
+ this.#hasBeenLoaded = true;
3452
4237
  }
3453
4238
  }
3454
4239
  async loadResponse(fetchResponse) {
@@ -3458,49 +4243,37 @@ class FrameController {
3458
4243
  try {
3459
4244
  const html = await fetchResponse.responseHTML;
3460
4245
  if (html) {
3461
- const {body: body} = parseHTMLDocument(html);
3462
- const newFrameElement = await this.extractForeignFrameElement(body);
3463
- if (newFrameElement) {
3464
- const snapshot = new Snapshot(newFrameElement);
3465
- const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
3466
- if (this.view.renderPromise) await this.view.renderPromise;
3467
- this.changeHistory();
3468
- await this.view.render(renderer);
3469
- this.complete = true;
3470
- session.frameRendered(fetchResponse, this.element);
3471
- session.frameLoaded(this.element);
3472
- this.fetchResponseLoaded(fetchResponse);
3473
- } else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3474
- console.warn(`A matching frame for #${this.element.id} was missing from the response, transforming into full-page Visit.`);
3475
- this.visitResponse(fetchResponse.response);
4246
+ const document = parseHTMLDocument(html);
4247
+ const pageSnapshot = PageSnapshot.fromDocument(document);
4248
+ if (pageSnapshot.isVisitable) {
4249
+ await this.#loadFrameResponse(fetchResponse, document);
4250
+ } else {
4251
+ await this.#handleUnvisitableFrameResponse(fetchResponse);
3476
4252
  }
3477
4253
  }
3478
- } catch (error) {
3479
- console.error(error);
3480
- this.view.invalidate();
3481
4254
  } finally {
3482
- this.fetchResponseLoaded = () => {};
4255
+ this.fetchResponseLoaded = () => Promise.resolve();
3483
4256
  }
3484
4257
  }
3485
4258
  elementAppearedInViewport(element) {
3486
4259
  this.proposeVisitIfNavigatedWithAction(element, element);
3487
- this.loadSourceURL();
4260
+ this.#loadSourceURL();
3488
4261
  }
3489
4262
  willSubmitFormLinkToLocation(link) {
3490
- return this.shouldInterceptNavigation(link);
4263
+ return this.#shouldInterceptNavigation(link);
3491
4264
  }
3492
4265
  submittedFormLinkToLocation(link, _location, form) {
3493
- const frame = this.findFrameElement(link);
4266
+ const frame = this.#findFrameElement(link);
3494
4267
  if (frame) form.setAttribute("data-turbo-frame", frame.id);
3495
4268
  }
3496
4269
  shouldInterceptLinkClick(element, _location, _event) {
3497
- return this.shouldInterceptNavigation(element);
4270
+ return this.#shouldInterceptNavigation(element);
3498
4271
  }
3499
4272
  linkClickIntercepted(element, location) {
3500
- this.navigateFrame(element, location);
4273
+ this.#navigateFrame(element, location);
3501
4274
  }
3502
4275
  willSubmitForm(element, submitter) {
3503
- return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
4276
+ return element.closest("turbo-frame") == this.element && this.#shouldInterceptNavigation(element, submitter);
3504
4277
  }
3505
4278
  formSubmitted(element, submitter) {
3506
4279
  if (this.formSubmission) {
@@ -3512,9 +4285,8 @@ class FrameController {
3512
4285
  this.formSubmission.start();
3513
4286
  }
3514
4287
  prepareRequest(request) {
3515
- var _a;
3516
4288
  request.headers["Turbo-Frame"] = this.id;
3517
- if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
4289
+ if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
3518
4290
  request.acceptResponseType(StreamMessage.contentType);
3519
4291
  }
3520
4292
  }
@@ -3522,47 +4294,51 @@ class FrameController {
3522
4294
  markAsBusy(this.element);
3523
4295
  }
3524
4296
  requestPreventedHandlingResponse(_request, _response) {
3525
- this.resolveVisitPromise();
4297
+ this.#resolveVisitPromise();
3526
4298
  }
3527
4299
  async requestSucceededWithResponse(request, response) {
3528
4300
  await this.loadResponse(response);
3529
- this.resolveVisitPromise();
4301
+ this.#resolveVisitPromise();
3530
4302
  }
3531
4303
  async requestFailedWithResponse(request, response) {
3532
- console.error(response);
3533
4304
  await this.loadResponse(response);
3534
- this.resolveVisitPromise();
4305
+ this.#resolveVisitPromise();
3535
4306
  }
3536
4307
  requestErrored(request, error) {
3537
4308
  console.error(error);
3538
- this.resolveVisitPromise();
4309
+ this.#resolveVisitPromise();
3539
4310
  }
3540
4311
  requestFinished(_request) {
3541
4312
  clearBusyState(this.element);
3542
4313
  }
3543
4314
  formSubmissionStarted({formElement: formElement}) {
3544
- markAsBusy(formElement, this.findFrameElement(formElement));
4315
+ markAsBusy(formElement, this.#findFrameElement(formElement));
3545
4316
  }
3546
4317
  formSubmissionSucceededWithResponse(formSubmission, response) {
3547
- const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
4318
+ const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter);
3548
4319
  frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
3549
4320
  frame.delegate.loadResponse(response);
4321
+ if (!formSubmission.isSafe) {
4322
+ session.clearCache();
4323
+ }
3550
4324
  }
3551
4325
  formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
3552
4326
  this.element.delegate.loadResponse(fetchResponse);
4327
+ session.clearCache();
3553
4328
  }
3554
4329
  formSubmissionErrored(formSubmission, error) {
3555
4330
  console.error(error);
3556
4331
  }
3557
4332
  formSubmissionFinished({formElement: formElement}) {
3558
- clearBusyState(formElement, this.findFrameElement(formElement));
4333
+ clearBusyState(formElement, this.#findFrameElement(formElement));
3559
4334
  }
3560
- allowsImmediateRender({element: newFrame}, options) {
4335
+ allowsImmediateRender({element: newFrame}, _isPreview, options) {
3561
4336
  const event = dispatch("turbo:before-frame-render", {
3562
4337
  target: this.element,
3563
- detail: Object.assign({
3564
- newFrame: newFrame
3565
- }, options),
4338
+ detail: {
4339
+ newFrame: newFrame,
4340
+ ...options
4341
+ },
3566
4342
  cancelable: true
3567
4343
  });
3568
4344
  const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
@@ -3571,7 +4347,7 @@ class FrameController {
3571
4347
  }
3572
4348
  return !defaultPrevented;
3573
4349
  }
3574
- viewRenderedSnapshot(_snapshot, _isPreview) {}
4350
+ viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {}
3575
4351
  preloadOnLoadLinksForView(element) {
3576
4352
  session.preloadOnLoadLinksForView(element);
3577
4353
  }
@@ -3579,24 +4355,46 @@ class FrameController {
3579
4355
  willRenderFrame(currentElement, _newElement) {
3580
4356
  this.previousFrameElement = currentElement.cloneNode(true);
3581
4357
  }
3582
- async visit(url) {
3583
- var _a;
4358
+ visitCachedSnapshot=({element: element}) => {
4359
+ const frame = element.querySelector("#" + this.element.id);
4360
+ if (frame && this.previousFrameElement) {
4361
+ frame.replaceChildren(...this.previousFrameElement.children);
4362
+ }
4363
+ delete this.previousFrameElement;
4364
+ };
4365
+ async #loadFrameResponse(fetchResponse, document) {
4366
+ const newFrameElement = await this.extractForeignFrameElement(document.body);
4367
+ if (newFrameElement) {
4368
+ const snapshot = new Snapshot(newFrameElement);
4369
+ const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
4370
+ if (this.view.renderPromise) await this.view.renderPromise;
4371
+ this.changeHistory();
4372
+ await this.view.render(renderer);
4373
+ this.complete = true;
4374
+ session.frameRendered(fetchResponse, this.element);
4375
+ session.frameLoaded(this.element);
4376
+ await this.fetchResponseLoaded(fetchResponse);
4377
+ } else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) {
4378
+ this.#handleFrameMissingFromResponse(fetchResponse);
4379
+ }
4380
+ }
4381
+ async #visit(url) {
3584
4382
  const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
3585
- (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
3586
- this.currentFetchRequest = request;
4383
+ this.#currentFetchRequest?.cancel();
4384
+ this.#currentFetchRequest = request;
3587
4385
  return new Promise((resolve => {
3588
- this.resolveVisitPromise = () => {
3589
- this.resolveVisitPromise = () => {};
3590
- this.currentFetchRequest = null;
4386
+ this.#resolveVisitPromise = () => {
4387
+ this.#resolveVisitPromise = () => {};
4388
+ this.#currentFetchRequest = null;
3591
4389
  resolve();
3592
4390
  };
3593
4391
  request.perform();
3594
4392
  }));
3595
4393
  }
3596
- navigateFrame(element, url, submitter) {
3597
- const frame = this.findFrameElement(element, submitter);
4394
+ #navigateFrame(element, url, submitter) {
4395
+ const frame = this.#findFrameElement(element, submitter);
3598
4396
  frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3599
- this.withCurrentNavigationElement(element, (() => {
4397
+ this.#withCurrentNavigationElement(element, (() => {
3600
4398
  frame.src = url;
3601
4399
  }));
3602
4400
  }
@@ -3605,10 +4403,10 @@ class FrameController {
3605
4403
  if (this.action) {
3606
4404
  const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3607
4405
  const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
3608
- frame.delegate.fetchResponseLoaded = fetchResponse => {
4406
+ frame.delegate.fetchResponseLoaded = async fetchResponse => {
3609
4407
  if (frame.src) {
3610
4408
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
3611
- const responseHTML = frame.ownerDocument.documentElement.outerHTML;
4409
+ const responseHTML = await fetchResponse.responseHTML;
3612
4410
  const response = {
3613
4411
  statusCode: statusCode,
3614
4412
  redirected: redirected,
@@ -3634,12 +4432,16 @@ class FrameController {
3634
4432
  session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
3635
4433
  }
3636
4434
  }
3637
- willHandleFrameMissingFromResponse(fetchResponse) {
4435
+ async #handleUnvisitableFrameResponse(fetchResponse) {
4436
+ console.warn(`The response (${fetchResponse.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.`);
4437
+ await this.#visitResponse(fetchResponse.response);
4438
+ }
4439
+ #willHandleFrameMissingFromResponse(fetchResponse) {
3638
4440
  this.element.setAttribute("complete", "");
3639
4441
  const response = fetchResponse.response;
3640
- const visit = async (url, options = {}) => {
4442
+ const visit = async (url, options) => {
3641
4443
  if (url instanceof Response) {
3642
- this.visitResponse(url);
4444
+ this.#visitResponse(url);
3643
4445
  } else {
3644
4446
  session.visit(url, options);
3645
4447
  }
@@ -3654,7 +4456,15 @@ class FrameController {
3654
4456
  });
3655
4457
  return !event.defaultPrevented;
3656
4458
  }
3657
- async visitResponse(response) {
4459
+ #handleFrameMissingFromResponse(fetchResponse) {
4460
+ this.view.missing();
4461
+ this.#throwFrameMissingError(fetchResponse);
4462
+ }
4463
+ #throwFrameMissingError(fetchResponse) {
4464
+ 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.`;
4465
+ throw new TurboFrameMissingError(message);
4466
+ }
4467
+ async #visitResponse(response) {
3658
4468
  const wrapped = new FetchResponse(response);
3659
4469
  const responseHTML = await wrapped.responseHTML;
3660
4470
  const {location: location, redirected: redirected, statusCode: statusCode} = wrapped;
@@ -3666,10 +4476,9 @@ class FrameController {
3666
4476
  }
3667
4477
  });
3668
4478
  }
3669
- findFrameElement(element, submitter) {
3670
- var _a;
4479
+ #findFrameElement(element, submitter) {
3671
4480
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
3672
- return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
4481
+ return getFrameElementById(id) ?? this.element;
3673
4482
  }
3674
4483
  async extractForeignFrameElement(container) {
3675
4484
  let element;
@@ -3690,13 +4499,13 @@ class FrameController {
3690
4499
  }
3691
4500
  return null;
3692
4501
  }
3693
- formActionIsVisitable(form, submitter) {
3694
- const action = getAction(form, submitter);
4502
+ #formActionIsVisitable(form, submitter) {
4503
+ const action = getAction$1(form, submitter);
3695
4504
  return locationIsVisitable(expandURL(action), this.rootLocation);
3696
4505
  }
3697
- shouldInterceptNavigation(element, submitter) {
4506
+ #shouldInterceptNavigation(element, submitter) {
3698
4507
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
3699
- if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {
4508
+ if (element instanceof HTMLFormElement && !this.#formActionIsVisitable(element, submitter)) {
3700
4509
  return false;
3701
4510
  }
3702
4511
  if (!this.enabled || id == "_top") {
@@ -3728,21 +4537,21 @@ class FrameController {
3728
4537
  }
3729
4538
  }
3730
4539
  set sourceURL(sourceURL) {
3731
- this.ignoringChangesToAttribute("src", (() => {
3732
- this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
4540
+ this.#ignoringChangesToAttribute("src", (() => {
4541
+ this.element.src = sourceURL ?? null;
3733
4542
  }));
3734
4543
  }
3735
4544
  get loadingStyle() {
3736
4545
  return this.element.loading;
3737
4546
  }
3738
4547
  get isLoading() {
3739
- return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
4548
+ return this.formSubmission !== undefined || this.#resolveVisitPromise() !== undefined;
3740
4549
  }
3741
4550
  get complete() {
3742
4551
  return this.element.hasAttribute("complete");
3743
4552
  }
3744
4553
  set complete(value) {
3745
- this.ignoringChangesToAttribute("complete", (() => {
4554
+ this.#ignoringChangesToAttribute("complete", (() => {
3746
4555
  if (value) {
3747
4556
  this.element.setAttribute("complete", "");
3748
4557
  } else {
@@ -3751,23 +4560,22 @@ class FrameController {
3751
4560
  }));
3752
4561
  }
3753
4562
  get isActive() {
3754
- return this.element.isActive && this.connected;
4563
+ return this.element.isActive && this.#connected;
3755
4564
  }
3756
4565
  get rootLocation() {
3757
- var _a;
3758
4566
  const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
3759
- const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
4567
+ const root = meta?.content ?? "/";
3760
4568
  return expandURL(root);
3761
4569
  }
3762
- isIgnoringChangesTo(attributeName) {
3763
- return this.ignoredAttributes.has(attributeName);
4570
+ #isIgnoringChangesTo(attributeName) {
4571
+ return this.#ignoredAttributes.has(attributeName);
3764
4572
  }
3765
- ignoringChangesToAttribute(attributeName, callback) {
3766
- this.ignoredAttributes.add(attributeName);
4573
+ #ignoringChangesToAttribute(attributeName, callback) {
4574
+ this.#ignoredAttributes.add(attributeName);
3767
4575
  callback();
3768
- this.ignoredAttributes.delete(attributeName);
4576
+ this.#ignoredAttributes.delete(attributeName);
3769
4577
  }
3770
- withCurrentNavigationElement(element, callback) {
4578
+ #withCurrentNavigationElement(element, callback) {
3771
4579
  this.currentNavigationElement = element;
3772
4580
  callback();
3773
4581
  delete this.currentNavigationElement;
@@ -3800,6 +4608,38 @@ function activateElement(element, currentURL) {
3800
4608
  }
3801
4609
  }
3802
4610
 
4611
+ const StreamActions = {
4612
+ after() {
4613
+ this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e.nextSibling)));
4614
+ },
4615
+ append() {
4616
+ this.removeDuplicateTargetChildren();
4617
+ this.targetElements.forEach((e => e.append(this.templateContent)));
4618
+ },
4619
+ before() {
4620
+ this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e)));
4621
+ },
4622
+ prepend() {
4623
+ this.removeDuplicateTargetChildren();
4624
+ this.targetElements.forEach((e => e.prepend(this.templateContent)));
4625
+ },
4626
+ remove() {
4627
+ this.targetElements.forEach((e => e.remove()));
4628
+ },
4629
+ replace() {
4630
+ this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
4631
+ },
4632
+ update() {
4633
+ this.targetElements.forEach((targetElement => {
4634
+ targetElement.innerHTML = "";
4635
+ targetElement.append(this.templateContent);
4636
+ }));
4637
+ },
4638
+ refresh() {
4639
+ session.refresh(this.baseURI, this.requestId);
4640
+ }
4641
+ };
4642
+
3803
4643
  class StreamElement extends HTMLElement {
3804
4644
  static async renderElement(newElement) {
3805
4645
  await newElement.performAction();
@@ -3814,11 +4654,10 @@ class StreamElement extends HTMLElement {
3814
4654
  }
3815
4655
  }
3816
4656
  async render() {
3817
- var _a;
3818
- return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
4657
+ return this.renderPromise ??= (async () => {
3819
4658
  const event = this.beforeRenderEvent;
3820
4659
  if (this.dispatchEvent(event)) {
3821
- await nextAnimationFrame();
4660
+ await nextRepaint();
3822
4661
  await event.detail.render(this);
3823
4662
  }
3824
4663
  })();
@@ -3826,15 +4665,14 @@ class StreamElement extends HTMLElement {
3826
4665
  disconnect() {
3827
4666
  try {
3828
4667
  this.remove();
3829
- } catch (_a) {}
4668
+ } catch {}
3830
4669
  }
3831
4670
  removeDuplicateTargetChildren() {
3832
4671
  this.duplicateChildren.forEach((c => c.remove()));
3833
4672
  }
3834
4673
  get duplicateChildren() {
3835
- var _a;
3836
4674
  const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
3837
- const newChildrenIds = [ ...((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [] ].filter((c => !!c.id)).map((c => c.id));
4675
+ const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
3838
4676
  return existingChildren.filter((c => newChildrenIds.includes(c.id)));
3839
4677
  }
3840
4678
  get performAction() {
@@ -3843,9 +4681,9 @@ class StreamElement extends HTMLElement {
3843
4681
  if (actionFunction) {
3844
4682
  return actionFunction;
3845
4683
  }
3846
- this.raise("unknown action");
4684
+ this.#raise("unknown action");
3847
4685
  }
3848
- this.raise("action attribute is missing");
4686
+ this.#raise("action attribute is missing");
3849
4687
  }
3850
4688
  get targetElements() {
3851
4689
  if (this.target) {
@@ -3853,7 +4691,7 @@ class StreamElement extends HTMLElement {
3853
4691
  } else if (this.targets) {
3854
4692
  return this.targetElementsByQuery;
3855
4693
  } else {
3856
- this.raise("target or targets attribute is missing");
4694
+ this.#raise("target or targets attribute is missing");
3857
4695
  }
3858
4696
  }
3859
4697
  get templateContent() {
@@ -3867,7 +4705,7 @@ class StreamElement extends HTMLElement {
3867
4705
  } else if (this.firstElementChild instanceof HTMLTemplateElement) {
3868
4706
  return this.firstElementChild;
3869
4707
  }
3870
- this.raise("first child element must be a <template> element");
4708
+ this.#raise("first child element must be a <template> element");
3871
4709
  }
3872
4710
  get action() {
3873
4711
  return this.getAttribute("action");
@@ -3878,12 +4716,14 @@ class StreamElement extends HTMLElement {
3878
4716
  get targets() {
3879
4717
  return this.getAttribute("targets");
3880
4718
  }
3881
- raise(message) {
4719
+ get requestId() {
4720
+ return this.getAttribute("request-id");
4721
+ }
4722
+ #raise(message) {
3882
4723
  throw new Error(`${this.description}: ${message}`);
3883
4724
  }
3884
4725
  get description() {
3885
- var _a, _b;
3886
- return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
4726
+ return (this.outerHTML.match(/<[^>]+>/) ?? [])[0] ?? "<turbo-stream>";
3887
4727
  }
3888
4728
  get beforeRenderEvent() {
3889
4729
  return new CustomEvent("turbo:before-stream-render", {
@@ -3896,8 +4736,7 @@ class StreamElement extends HTMLElement {
3896
4736
  });
3897
4737
  }
3898
4738
  get targetElementsById() {
3899
- var _a;
3900
- const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
4739
+ const element = this.ownerDocument?.getElementById(this.target);
3901
4740
  if (element !== null) {
3902
4741
  return [ element ];
3903
4742
  } else {
@@ -3905,8 +4744,7 @@ class StreamElement extends HTMLElement {
3905
4744
  }
3906
4745
  }
3907
4746
  get targetElementsByQuery() {
3908
- var _a;
3909
- const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);
4747
+ const elements = this.ownerDocument?.querySelectorAll(this.targets);
3910
4748
  if (elements.length !== 0) {
3911
4749
  return Array.prototype.slice.call(elements);
3912
4750
  } else {
@@ -3916,16 +4754,14 @@ class StreamElement extends HTMLElement {
3916
4754
  }
3917
4755
 
3918
4756
  class StreamSourceElement extends HTMLElement {
3919
- constructor() {
3920
- super(...arguments);
3921
- this.streamSource = null;
3922
- }
4757
+ streamSource=null;
3923
4758
  connectedCallback() {
3924
4759
  this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
3925
4760
  connectStreamSource(this.streamSource);
3926
4761
  }
3927
4762
  disconnectedCallback() {
3928
4763
  if (this.streamSource) {
4764
+ this.streamSource.close();
3929
4765
  disconnectStreamSource(this.streamSource);
3930
4766
  }
3931
4767
  }
@@ -3976,10 +4812,12 @@ start();
3976
4812
 
3977
4813
  var turbo_es2017Esm = Object.freeze({
3978
4814
  __proto__: null,
4815
+ FetchEnctype: FetchEnctype,
4816
+ FetchMethod: FetchMethod,
4817
+ FetchRequest: FetchRequest,
4818
+ FetchResponse: FetchResponse,
3979
4819
  FrameElement: FrameElement,
3980
- get FrameLoadingStyle() {
3981
- return FrameLoadingStyle;
3982
- },
4820
+ FrameLoadingStyle: FrameLoadingStyle,
3983
4821
  FrameRenderer: FrameRenderer,
3984
4822
  PageRenderer: PageRenderer,
3985
4823
  PageSnapshot: PageSnapshot,
@@ -3990,6 +4828,10 @@ var turbo_es2017Esm = Object.freeze({
3990
4828
  clearCache: clearCache,
3991
4829
  connectStreamSource: connectStreamSource,
3992
4830
  disconnectStreamSource: disconnectStreamSource,
4831
+ fetch: fetch,
4832
+ fetchEnctypeFromString: fetchEnctypeFromString,
4833
+ fetchMethodFromString: fetchMethodFromString,
4834
+ isSafe: isSafe,
3993
4835
  navigator: navigator$1,
3994
4836
  registerAdapter: registerAdapter,
3995
4837
  renderStreamMessage: renderStreamMessage,
@@ -4004,14 +4846,14 @@ var turbo_es2017Esm = Object.freeze({
4004
4846
  let consumer;
4005
4847
 
4006
4848
  async function getConsumer() {
4007
- return consumer || setConsumer(createConsumer().then(setConsumer));
4849
+ return consumer || setConsumer(createConsumer$1().then(setConsumer));
4008
4850
  }
4009
4851
 
4010
4852
  function setConsumer(newConsumer) {
4011
4853
  return consumer = newConsumer;
4012
4854
  }
4013
4855
 
4014
- async function createConsumer() {
4856
+ async function createConsumer$1() {
4015
4857
  const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
4016
4858
  return index;
4017
4859
  }));
@@ -4027,7 +4869,7 @@ var cable = Object.freeze({
4027
4869
  __proto__: null,
4028
4870
  getConsumer: getConsumer,
4029
4871
  setConsumer: setConsumer,
4030
- createConsumer: createConsumer,
4872
+ createConsumer: createConsumer$1,
4031
4873
  subscribeTo: subscribeTo
4032
4874
  });
4033
4875
 
@@ -4048,7 +4890,9 @@ class TurboCableStreamSourceElement extends HTMLElement {
4048
4890
  async connectedCallback() {
4049
4891
  connectStreamSource(this);
4050
4892
  this.subscription = await subscribeTo(this.channel, {
4051
- received: this.dispatchMessageEvent.bind(this)
4893
+ received: this.dispatchMessageEvent.bind(this),
4894
+ connected: this.subscriptionConnected.bind(this),
4895
+ disconnected: this.subscriptionDisconnected.bind(this)
4052
4896
  });
4053
4897
  }
4054
4898
  disconnectedCallback() {
@@ -4061,6 +4905,12 @@ class TurboCableStreamSourceElement extends HTMLElement {
4061
4905
  });
4062
4906
  return this.dispatchEvent(event);
4063
4907
  }
4908
+ subscriptionConnected() {
4909
+ this.setAttribute("connected", "");
4910
+ }
4911
+ subscriptionDisconnected() {
4912
+ this.removeAttribute("connected");
4913
+ }
4064
4914
  get channel() {
4065
4915
  const channel = this.getAttribute("channel");
4066
4916
  const signed_stream_name = this.getAttribute("signed-stream-name");
@@ -4113,7 +4963,9 @@ function determineFetchMethod(submitter, body, form) {
4113
4963
 
4114
4964
  function determineFormMethod(submitter) {
4115
4965
  if (submitter instanceof HTMLButtonElement || submitter instanceof HTMLInputElement) {
4116
- if (submitter.hasAttribute("formmethod")) {
4966
+ if (submitter.name === "_method") {
4967
+ return submitter.value;
4968
+ } else if (submitter.hasAttribute("formmethod")) {
4117
4969
  return submitter.formMethod;
4118
4970
  } else {
4119
4971
  return null;
@@ -4243,6 +5095,8 @@ ConnectionMonitor.staleThreshold = 6;
4243
5095
 
4244
5096
  ConnectionMonitor.reconnectionBackoffRate = .15;
4245
5097
 
5098
+ var ConnectionMonitor$1 = ConnectionMonitor;
5099
+
4246
5100
  var INTERNAL = {
4247
5101
  message_types: {
4248
5102
  welcome: "welcome",
@@ -4254,7 +5108,8 @@ var INTERNAL = {
4254
5108
  disconnect_reasons: {
4255
5109
  unauthorized: "unauthorized",
4256
5110
  invalid_request: "invalid_request",
4257
- server_restart: "server_restart"
5111
+ server_restart: "server_restart",
5112
+ remote: "remote"
4258
5113
  },
4259
5114
  default_mount_path: "/cable",
4260
5115
  protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
@@ -4271,7 +5126,7 @@ class Connection {
4271
5126
  this.open = this.open.bind(this);
4272
5127
  this.consumer = consumer;
4273
5128
  this.subscriptions = this.consumer.subscriptions;
4274
- this.monitor = new ConnectionMonitor(this);
5129
+ this.monitor = new ConnectionMonitor$1(this);
4275
5130
  this.disconnected = true;
4276
5131
  }
4277
5132
  send(data) {
@@ -4287,11 +5142,12 @@ class Connection {
4287
5142
  logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
4288
5143
  return false;
4289
5144
  } else {
4290
- logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${protocols}`);
5145
+ const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
5146
+ logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
4291
5147
  if (this.webSocket) {
4292
5148
  this.uninstallEventHandlers();
4293
5149
  }
4294
- this.webSocket = new adapters.WebSocket(this.consumer.url, protocols);
5150
+ this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
4295
5151
  this.installEventHandlers();
4296
5152
  this.monitor.start();
4297
5153
  return true;
@@ -4303,7 +5159,7 @@ class Connection {
4303
5159
  if (!allowReconnect) {
4304
5160
  this.monitor.stop();
4305
5161
  }
4306
- if (this.isActive()) {
5162
+ if (this.isOpen()) {
4307
5163
  return this.webSocket.close();
4308
5164
  }
4309
5165
  }
@@ -4333,6 +5189,9 @@ class Connection {
4333
5189
  isActive() {
4334
5190
  return this.isState("open", "connecting");
4335
5191
  }
5192
+ triedToReconnect() {
5193
+ return this.monitor.reconnectAttempts > 0;
5194
+ }
4336
5195
  isProtocolSupported() {
4337
5196
  return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
4338
5197
  }
@@ -4372,6 +5231,9 @@ Connection.prototype.events = {
4372
5231
  const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
4373
5232
  switch (type) {
4374
5233
  case message_types.welcome:
5234
+ if (this.triedToReconnect()) {
5235
+ this.reconnectAttempted = true;
5236
+ }
4375
5237
  this.monitor.recordConnect();
4376
5238
  return this.subscriptions.reload();
4377
5239
 
@@ -4386,7 +5248,16 @@ Connection.prototype.events = {
4386
5248
 
4387
5249
  case message_types.confirmation:
4388
5250
  this.subscriptions.confirmSubscription(identifier);
4389
- return this.subscriptions.notify(identifier, "connected");
5251
+ if (this.reconnectAttempted) {
5252
+ this.reconnectAttempted = false;
5253
+ return this.subscriptions.notify(identifier, "connected", {
5254
+ reconnected: true
5255
+ });
5256
+ } else {
5257
+ return this.subscriptions.notify(identifier, "connected", {
5258
+ reconnected: false
5259
+ });
5260
+ }
4390
5261
 
4391
5262
  case message_types.rejection:
4392
5263
  return this.subscriptions.reject(identifier);
@@ -4421,6 +5292,8 @@ Connection.prototype.events = {
4421
5292
  }
4422
5293
  };
4423
5294
 
5295
+ var Connection$1 = Connection;
5296
+
4424
5297
  const extend = function(object, properties) {
4425
5298
  if (properties != null) {
4426
5299
  for (let key in properties) {
@@ -4490,10 +5363,12 @@ class SubscriptionGuarantor {
4490
5363
  }
4491
5364
  }
4492
5365
 
5366
+ var SubscriptionGuarantor$1 = SubscriptionGuarantor;
5367
+
4493
5368
  class Subscriptions {
4494
5369
  constructor(consumer) {
4495
5370
  this.consumer = consumer;
4496
- this.guarantor = new SubscriptionGuarantor(this);
5371
+ this.guarantor = new SubscriptionGuarantor$1(this);
4497
5372
  this.subscriptions = [];
4498
5373
  }
4499
5374
  create(channelName, mixin) {
@@ -4570,7 +5445,8 @@ class Consumer {
4570
5445
  constructor(url) {
4571
5446
  this._url = url;
4572
5447
  this.subscriptions = new Subscriptions(this);
4573
- this.connection = new Connection(this);
5448
+ this.connection = new Connection$1(this);
5449
+ this.subprotocols = [];
4574
5450
  }
4575
5451
  get url() {
4576
5452
  return createWebSocketURL(this._url);
@@ -4591,6 +5467,9 @@ class Consumer {
4591
5467
  return this.connection.open();
4592
5468
  }
4593
5469
  }
5470
+ addSubProtocol(subprotocol) {
5471
+ this.subprotocols = [ ...this.subprotocols, subprotocol ];
5472
+ }
4594
5473
  }
4595
5474
 
4596
5475
  function createWebSocketURL(url) {
@@ -4608,7 +5487,7 @@ function createWebSocketURL(url) {
4608
5487
  }
4609
5488
  }
4610
5489
 
4611
- function createConsumer$1(url = getConfig("url") || INTERNAL.default_mount_path) {
5490
+ function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
4612
5491
  return new Consumer(url);
4613
5492
  }
4614
5493
 
@@ -4621,17 +5500,17 @@ function getConfig(name) {
4621
5500
 
4622
5501
  var index = Object.freeze({
4623
5502
  __proto__: null,
4624
- Connection: Connection,
4625
- ConnectionMonitor: ConnectionMonitor,
5503
+ Connection: Connection$1,
5504
+ ConnectionMonitor: ConnectionMonitor$1,
4626
5505
  Consumer: Consumer,
4627
5506
  INTERNAL: INTERNAL,
4628
5507
  Subscription: Subscription,
4629
5508
  Subscriptions: Subscriptions,
4630
- SubscriptionGuarantor: SubscriptionGuarantor,
5509
+ SubscriptionGuarantor: SubscriptionGuarantor$1,
4631
5510
  adapters: adapters,
4632
5511
  createWebSocketURL: createWebSocketURL,
4633
5512
  logger: logger,
4634
- createConsumer: createConsumer$1,
5513
+ createConsumer: createConsumer,
4635
5514
  getConfig: getConfig
4636
5515
  });
4637
5516