turbo-rails 1.5.0 → 2.0.7

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +67 -16
  3. data/app/assets/javascripts/turbo.js +1974 -785
  4. data/app/assets/javascripts/turbo.min.js +9 -5
  5. data/app/assets/javascripts/turbo.min.js.map +1 -1
  6. data/app/channels/turbo/streams/broadcasts.rb +33 -7
  7. data/app/channels/turbo/streams_channel.rb +15 -15
  8. data/app/controllers/concerns/turbo/request_id_tracking.rb +12 -0
  9. data/app/controllers/turbo/frames/frame_request.rb +2 -2
  10. data/app/controllers/turbo/native/navigation.rb +6 -3
  11. data/app/helpers/turbo/drive_helper.rb +72 -14
  12. data/app/helpers/turbo/frames_helper.rb +8 -8
  13. data/app/helpers/turbo/streams/action_helper.rb +12 -4
  14. data/app/helpers/turbo/streams_helper.rb +5 -0
  15. data/app/javascript/turbo/index.js +2 -0
  16. data/app/jobs/turbo/streams/action_broadcast_job.rb +2 -2
  17. data/app/jobs/turbo/streams/broadcast_job.rb +1 -1
  18. data/app/jobs/turbo/streams/broadcast_stream_job.rb +7 -0
  19. data/app/models/concerns/turbo/broadcastable.rb +184 -35
  20. data/app/models/turbo/debouncer.rb +24 -0
  21. data/app/models/turbo/streams/tag_builder.rb +20 -0
  22. data/app/models/turbo/thread_debouncer.rb +28 -0
  23. data/config/routes.rb +3 -4
  24. data/lib/install/turbo_with_importmap.rb +1 -1
  25. data/lib/tasks/turbo_tasks.rake +0 -22
  26. data/lib/turbo/broadcastable/test_helper.rb +5 -5
  27. data/lib/turbo/engine.rb +34 -8
  28. data/lib/turbo/test_assertions/integration_test_assertions.rb +2 -2
  29. data/lib/turbo/test_assertions.rb +2 -2
  30. data/lib/turbo/version.rb +1 -1
  31. data/lib/turbo-rails.rb +10 -0
  32. metadata +9 -5
  33. data/lib/install/turbo_needs_redis.rb +0 -20
@@ -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.5
3
+ Copyright © 2024 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) {
@@ -57,10 +45,13 @@ function clickCaptured(event) {
57
45
  (function() {
58
46
  if ("submitter" in Event.prototype) return;
59
47
  let prototype = window.Event.prototype;
60
- if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
61
- prototype = window.SubmitEvent.prototype;
62
- } else if ("SubmitEvent" in window) {
63
- return;
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
+ }
64
55
  }
65
56
  addEventListener("click", clickCaptured, true);
66
57
  Object.defineProperty(prototype, "submitter", {
@@ -72,20 +63,19 @@ function clickCaptured(event) {
72
63
  });
73
64
  })();
74
65
 
75
- var FrameLoadingStyle;
76
-
77
- (function(FrameLoadingStyle) {
78
- FrameLoadingStyle["eager"] = "eager";
79
- FrameLoadingStyle["lazy"] = "lazy";
80
- })(FrameLoadingStyle || (FrameLoadingStyle = {}));
66
+ const FrameLoadingStyle = {
67
+ eager: "eager",
68
+ lazy: "lazy"
69
+ };
81
70
 
82
71
  class FrameElement extends HTMLElement {
72
+ static delegateConstructor=undefined;
73
+ loaded=Promise.resolve();
83
74
  static get observedAttributes() {
84
- return [ "disabled", "complete", "loading", "src" ];
75
+ return [ "disabled", "loading", "src" ];
85
76
  }
86
77
  constructor() {
87
78
  super();
88
- this.loaded = Promise.resolve();
89
79
  this.delegate = new FrameElement.delegateConstructor(this);
90
80
  }
91
81
  connectedCallback() {
@@ -100,11 +90,9 @@ class FrameElement extends HTMLElement {
100
90
  attributeChangedCallback(name) {
101
91
  if (name == "loading") {
102
92
  this.delegate.loadingStyleChanged();
103
- } else if (name == "complete") {
104
- this.delegate.completeChanged();
105
93
  } else if (name == "src") {
106
94
  this.delegate.sourceURLChanged();
107
- } else {
95
+ } else if (name == "disabled") {
108
96
  this.delegate.disabledChanged();
109
97
  }
110
98
  }
@@ -118,6 +106,16 @@ class FrameElement extends HTMLElement {
118
106
  this.removeAttribute("src");
119
107
  }
120
108
  }
109
+ get refresh() {
110
+ return this.getAttribute("refresh");
111
+ }
112
+ set refresh(value) {
113
+ if (value) {
114
+ this.setAttribute("refresh", value);
115
+ } else {
116
+ this.removeAttribute("refresh");
117
+ }
118
+ }
121
119
  get loading() {
122
120
  return frameLoadingStyleFromString(this.getAttribute("loading") || "");
123
121
  }
@@ -155,8 +153,7 @@ class FrameElement extends HTMLElement {
155
153
  return this.ownerDocument === document && !this.isPreview;
156
154
  }
157
155
  get isPreview() {
158
- var _a, _b;
159
- return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute("data-turbo-preview");
156
+ return this.ownerDocument?.documentElement?.hasAttribute("data-turbo-preview");
160
157
  }
161
158
  }
162
159
 
@@ -183,8 +180,8 @@ function getAnchor(url) {
183
180
  }
184
181
  }
185
182
 
186
- function getAction(form, submitter) {
187
- const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formaction")) || form.getAttribute("action") || form.action;
183
+ function getAction$1(form, submitter) {
184
+ const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action;
188
185
  return expandURL(action);
189
186
  }
190
187
 
@@ -323,6 +320,14 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
323
320
  return event;
324
321
  }
325
322
 
323
+ function nextRepaint() {
324
+ if (document.visibilityState === "hidden") {
325
+ return nextEventLoopTick();
326
+ } else {
327
+ return nextAnimationFrame();
328
+ }
329
+ }
330
+
326
331
  function nextAnimationFrame() {
327
332
  return new Promise((resolve => requestAnimationFrame((() => resolve()))));
328
333
  }
@@ -370,7 +375,7 @@ function uuid() {
370
375
  }
371
376
 
372
377
  function getAttribute(attributeName, ...elements) {
373
- for (const value of elements.map((element => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName)))) {
378
+ for (const value of elements.map((element => element?.getAttribute(attributeName)))) {
374
379
  if (typeof value == "string") return value;
375
380
  }
376
381
  return null;
@@ -456,21 +461,87 @@ function setMetaContent(name, content) {
456
461
  }
457
462
 
458
463
  function findClosestRecursively(element, selector) {
459
- var _a;
460
464
  if (element instanceof Element) {
461
- return element.closest(selector) || findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector);
465
+ return element.closest(selector) || findClosestRecursively(element.assignedSlot || element.getRootNode()?.host, selector);
466
+ }
467
+ }
468
+
469
+ function elementIsFocusable(element) {
470
+ const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
471
+ return !!element && element.closest(inertDisabledOrHidden) == null && typeof element.focus == "function";
472
+ }
473
+
474
+ function queryAutofocusableElement(elementOrDocumentFragment) {
475
+ return Array.from(elementOrDocumentFragment.querySelectorAll("[autofocus]")).find(elementIsFocusable);
476
+ }
477
+
478
+ async function around(callback, reader) {
479
+ const before = reader();
480
+ callback();
481
+ await nextAnimationFrame();
482
+ const after = reader();
483
+ return [ before, after ];
484
+ }
485
+
486
+ function doesNotTargetIFrame(name) {
487
+ if (name === "_blank") {
488
+ return false;
489
+ } else if (name) {
490
+ for (const element of document.getElementsByName(name)) {
491
+ if (element instanceof HTMLIFrameElement) return false;
492
+ }
493
+ return true;
494
+ } else {
495
+ return true;
496
+ }
497
+ }
498
+
499
+ function findLinkFromClickTarget(target) {
500
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
501
+ }
502
+
503
+ function getLocationForLink(link) {
504
+ return expandURL(link.getAttribute("href") || "");
505
+ }
506
+
507
+ function debounce(fn, delay) {
508
+ let timeoutId = null;
509
+ return (...args) => {
510
+ const callback = () => fn.apply(this, args);
511
+ clearTimeout(timeoutId);
512
+ timeoutId = setTimeout(callback, delay);
513
+ };
514
+ }
515
+
516
+ class LimitedSet extends Set {
517
+ constructor(maxSize) {
518
+ super();
519
+ this.maxSize = maxSize;
520
+ }
521
+ add(value) {
522
+ if (this.size >= this.maxSize) {
523
+ const iterator = this.values();
524
+ const oldestValue = iterator.next().value;
525
+ this.delete(oldestValue);
526
+ }
527
+ super.add(value);
462
528
  }
463
529
  }
464
530
 
465
- var FetchMethod;
531
+ const recentRequests = new LimitedSet(20);
466
532
 
467
- (function(FetchMethod) {
468
- FetchMethod[FetchMethod["get"] = 0] = "get";
469
- FetchMethod[FetchMethod["post"] = 1] = "post";
470
- FetchMethod[FetchMethod["put"] = 2] = "put";
471
- FetchMethod[FetchMethod["patch"] = 3] = "patch";
472
- FetchMethod[FetchMethod["delete"] = 4] = "delete";
473
- })(FetchMethod || (FetchMethod = {}));
533
+ const nativeFetch = window.fetch;
534
+
535
+ function fetchWithTurboHeaders(url, options = {}) {
536
+ const modifiedHeaders = new Headers(options.headers || {});
537
+ const requestUID = uuid();
538
+ recentRequests.add(requestUID);
539
+ modifiedHeaders.append("X-Turbo-Request-Id", requestUID);
540
+ return nativeFetch(url, {
541
+ ...options,
542
+ headers: modifiedHeaders
543
+ });
544
+ }
474
545
 
475
546
  function fetchMethodFromString(method) {
476
547
  switch (method.toLowerCase()) {
@@ -491,16 +562,81 @@ function fetchMethodFromString(method) {
491
562
  }
492
563
  }
493
564
 
565
+ const FetchMethod = {
566
+ get: "get",
567
+ post: "post",
568
+ put: "put",
569
+ patch: "patch",
570
+ delete: "delete"
571
+ };
572
+
573
+ function fetchEnctypeFromString(encoding) {
574
+ switch (encoding.toLowerCase()) {
575
+ case FetchEnctype.multipart:
576
+ return FetchEnctype.multipart;
577
+
578
+ case FetchEnctype.plain:
579
+ return FetchEnctype.plain;
580
+
581
+ default:
582
+ return FetchEnctype.urlEncoded;
583
+ }
584
+ }
585
+
586
+ const FetchEnctype = {
587
+ urlEncoded: "application/x-www-form-urlencoded",
588
+ multipart: "multipart/form-data",
589
+ plain: "text/plain"
590
+ };
591
+
494
592
  class FetchRequest {
495
- constructor(delegate, method, location, body = new URLSearchParams, target = null) {
496
- this.abortController = new AbortController;
497
- this.resolveRequestPromise = _value => {};
593
+ abortController=new AbortController;
594
+ #resolveRequestPromise=_value => {};
595
+ constructor(delegate, method, location, requestBody = new URLSearchParams, target = null, enctype = FetchEnctype.urlEncoded) {
596
+ const [url, body] = buildResourceAndBody(expandURL(location), method, requestBody, enctype);
498
597
  this.delegate = delegate;
499
- this.method = method;
500
- this.headers = this.defaultHeaders;
501
- this.body = body;
502
- this.url = location;
598
+ this.url = url;
503
599
  this.target = target;
600
+ this.fetchOptions = {
601
+ credentials: "same-origin",
602
+ redirect: "follow",
603
+ method: method.toUpperCase(),
604
+ headers: {
605
+ ...this.defaultHeaders
606
+ },
607
+ body: body,
608
+ signal: this.abortSignal,
609
+ referrer: this.delegate.referrer?.href
610
+ };
611
+ this.enctype = enctype;
612
+ }
613
+ get method() {
614
+ return this.fetchOptions.method;
615
+ }
616
+ set method(value) {
617
+ const fetchBody = this.isSafe ? this.url.searchParams : this.fetchOptions.body || new FormData;
618
+ const fetchMethod = fetchMethodFromString(value) || FetchMethod.get;
619
+ this.url.search = "";
620
+ const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype);
621
+ this.url = url;
622
+ this.fetchOptions.body = body;
623
+ this.fetchOptions.method = fetchMethod.toUpperCase();
624
+ }
625
+ get headers() {
626
+ return this.fetchOptions.headers;
627
+ }
628
+ set headers(value) {
629
+ this.fetchOptions.headers = value;
630
+ }
631
+ get body() {
632
+ if (this.isSafe) {
633
+ return this.url.searchParams;
634
+ } else {
635
+ return this.fetchOptions.body;
636
+ }
637
+ }
638
+ set body(value) {
639
+ this.fetchOptions.body = value;
504
640
  }
505
641
  get location() {
506
642
  return this.url;
@@ -517,14 +653,19 @@ class FetchRequest {
517
653
  async perform() {
518
654
  const {fetchOptions: fetchOptions} = this;
519
655
  this.delegate.prepareRequest(this);
520
- await this.allowRequestToBeIntercepted(fetchOptions);
656
+ const event = await this.#allowRequestToBeIntercepted(fetchOptions);
521
657
  try {
522
658
  this.delegate.requestStarted(this);
523
- const response = await fetch(this.url.href, fetchOptions);
659
+ if (event.detail.fetchRequest) {
660
+ this.response = event.detail.fetchRequest.response;
661
+ } else {
662
+ this.response = fetchWithTurboHeaders(this.url.href, fetchOptions);
663
+ }
664
+ const response = await this.response;
524
665
  return await this.receive(response);
525
666
  } catch (error) {
526
667
  if (error.name !== "AbortError") {
527
- if (this.willDelegateErrorHandling(error)) {
668
+ if (this.#willDelegateErrorHandling(error)) {
528
669
  this.delegate.requestErrored(this, error);
529
670
  }
530
671
  throw error;
@@ -551,25 +692,13 @@ class FetchRequest {
551
692
  }
552
693
  return fetchResponse;
553
694
  }
554
- get fetchOptions() {
555
- var _a;
556
- return {
557
- method: FetchMethod[this.method].toUpperCase(),
558
- credentials: "same-origin",
559
- headers: this.headers,
560
- redirect: "follow",
561
- body: this.isSafe ? null : this.body,
562
- signal: this.abortSignal,
563
- referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
564
- };
565
- }
566
695
  get defaultHeaders() {
567
696
  return {
568
697
  Accept: "text/html, application/xhtml+xml"
569
698
  };
570
699
  }
571
700
  get isSafe() {
572
- return this.method === FetchMethod.get;
701
+ return isSafe(this.method);
573
702
  }
574
703
  get abortSignal() {
575
704
  return this.abortController.signal;
@@ -577,20 +706,22 @@ class FetchRequest {
577
706
  acceptResponseType(mimeType) {
578
707
  this.headers["Accept"] = [ mimeType, this.headers["Accept"] ].join(", ");
579
708
  }
580
- async allowRequestToBeIntercepted(fetchOptions) {
581
- const requestInterception = new Promise((resolve => this.resolveRequestPromise = resolve));
709
+ async #allowRequestToBeIntercepted(fetchOptions) {
710
+ const requestInterception = new Promise((resolve => this.#resolveRequestPromise = resolve));
582
711
  const event = dispatch("turbo:before-fetch-request", {
583
712
  cancelable: true,
584
713
  detail: {
585
714
  fetchOptions: fetchOptions,
586
715
  url: this.url,
587
- resume: this.resolveRequestPromise
716
+ resume: this.#resolveRequestPromise
588
717
  },
589
718
  target: this.target
590
719
  });
720
+ this.url = event.detail.url;
591
721
  if (event.defaultPrevented) await requestInterception;
722
+ return event;
592
723
  }
593
- willDelegateErrorHandling(error) {
724
+ #willDelegateErrorHandling(error) {
594
725
  const event = dispatch("turbo:fetch-request-error", {
595
726
  target: this.target,
596
727
  cancelable: true,
@@ -603,15 +734,38 @@ class FetchRequest {
603
734
  }
604
735
  }
605
736
 
737
+ function isSafe(fetchMethod) {
738
+ return fetchMethodFromString(fetchMethod) == FetchMethod.get;
739
+ }
740
+
741
+ function buildResourceAndBody(resource, method, requestBody, enctype) {
742
+ const searchParams = Array.from(requestBody).length > 0 ? new URLSearchParams(entriesExcludingFiles(requestBody)) : resource.searchParams;
743
+ if (isSafe(method)) {
744
+ return [ mergeIntoURLSearchParams(resource, searchParams), null ];
745
+ } else if (enctype == FetchEnctype.urlEncoded) {
746
+ return [ resource, searchParams ];
747
+ } else {
748
+ return [ resource, requestBody ];
749
+ }
750
+ }
751
+
752
+ function entriesExcludingFiles(requestBody) {
753
+ const entries = [];
754
+ for (const [name, value] of requestBody) {
755
+ if (value instanceof File) continue; else entries.push([ name, value ]);
756
+ }
757
+ return entries;
758
+ }
759
+
760
+ function mergeIntoURLSearchParams(url, requestBody) {
761
+ const searchParams = new URLSearchParams(entriesExcludingFiles(requestBody));
762
+ url.search = searchParams.toString();
763
+ return url;
764
+ }
765
+
606
766
  class AppearanceObserver {
767
+ started=false;
607
768
  constructor(delegate, element) {
608
- this.started = false;
609
- this.intersect = entries => {
610
- const lastEntry = entries.slice(-1)[0];
611
- if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
612
- this.delegate.elementAppearedInViewport(this.element);
613
- }
614
- };
615
769
  this.delegate = delegate;
616
770
  this.element = element;
617
771
  this.intersectionObserver = new IntersectionObserver(this.intersect);
@@ -628,9 +782,16 @@ class AppearanceObserver {
628
782
  this.intersectionObserver.unobserve(this.element);
629
783
  }
630
784
  }
785
+ intersect=entries => {
786
+ const lastEntry = entries.slice(-1)[0];
787
+ if (lastEntry?.isIntersecting) {
788
+ this.delegate.elementAppearedInViewport(this.element);
789
+ }
790
+ };
631
791
  }
632
792
 
633
793
  class StreamMessage {
794
+ static contentType="text/vnd.turbo-stream.html";
634
795
  static wrap(message) {
635
796
  if (typeof message == "string") {
636
797
  return new this(createDocumentFragment(message));
@@ -643,8 +804,6 @@ class StreamMessage {
643
804
  }
644
805
  }
645
806
 
646
- StreamMessage.contentType = "text/vnd.turbo-stream.html";
647
-
648
807
  function importStreamElements(fragment) {
649
808
  for (const element of fragment.querySelectorAll("turbo-stream")) {
650
809
  const streamElement = document.importNode(element, true);
@@ -656,85 +815,89 @@ function importStreamElements(fragment) {
656
815
  return fragment;
657
816
  }
658
817
 
659
- var FormSubmissionState;
818
+ const PREFETCH_DELAY = 100;
660
819
 
661
- (function(FormSubmissionState) {
662
- FormSubmissionState[FormSubmissionState["initialized"] = 0] = "initialized";
663
- FormSubmissionState[FormSubmissionState["requesting"] = 1] = "requesting";
664
- FormSubmissionState[FormSubmissionState["waiting"] = 2] = "waiting";
665
- FormSubmissionState[FormSubmissionState["receiving"] = 3] = "receiving";
666
- FormSubmissionState[FormSubmissionState["stopping"] = 4] = "stopping";
667
- FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
668
- })(FormSubmissionState || (FormSubmissionState = {}));
669
-
670
- var FormEnctype;
671
-
672
- (function(FormEnctype) {
673
- FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded";
674
- FormEnctype["multipart"] = "multipart/form-data";
675
- FormEnctype["plain"] = "text/plain";
676
- })(FormEnctype || (FormEnctype = {}));
820
+ class PrefetchCache {
821
+ #prefetchTimeout=null;
822
+ #prefetched=null;
823
+ get(url) {
824
+ if (this.#prefetched && this.#prefetched.url === url && this.#prefetched.expire > Date.now()) {
825
+ return this.#prefetched.request;
826
+ }
827
+ }
828
+ setLater(url, request, ttl) {
829
+ this.clear();
830
+ this.#prefetchTimeout = setTimeout((() => {
831
+ request.perform();
832
+ this.set(url, request, ttl);
833
+ this.#prefetchTimeout = null;
834
+ }), PREFETCH_DELAY);
835
+ }
836
+ set(url, request, ttl) {
837
+ this.#prefetched = {
838
+ url: url,
839
+ request: request,
840
+ expire: new Date((new Date).getTime() + ttl)
841
+ };
842
+ }
843
+ clear() {
844
+ if (this.#prefetchTimeout) clearTimeout(this.#prefetchTimeout);
845
+ this.#prefetched = null;
846
+ }
847
+ }
677
848
 
678
- function formEnctypeFromString(encoding) {
679
- switch (encoding.toLowerCase()) {
680
- case FormEnctype.multipart:
681
- return FormEnctype.multipart;
849
+ const cacheTtl = 10 * 1e3;
682
850
 
683
- case FormEnctype.plain:
684
- return FormEnctype.plain;
851
+ const prefetchCache = new PrefetchCache;
685
852
 
686
- default:
687
- return FormEnctype.urlEncoded;
688
- }
689
- }
853
+ const FormSubmissionState = {
854
+ initialized: "initialized",
855
+ requesting: "requesting",
856
+ waiting: "waiting",
857
+ receiving: "receiving",
858
+ stopping: "stopping",
859
+ stopped: "stopped"
860
+ };
690
861
 
691
862
  class FormSubmission {
863
+ state=FormSubmissionState.initialized;
692
864
  static confirmMethod(message, _element, _submitter) {
693
865
  return Promise.resolve(confirm(message));
694
866
  }
695
867
  constructor(delegate, formElement, submitter, mustRedirect = false) {
696
- this.state = FormSubmissionState.initialized;
868
+ const method = getMethod(formElement, submitter);
869
+ const action = getAction(getFormAction(formElement, submitter), method);
870
+ const body = buildFormData(formElement, submitter);
871
+ const enctype = getEnctype(formElement, submitter);
697
872
  this.delegate = delegate;
698
873
  this.formElement = formElement;
699
874
  this.submitter = submitter;
700
- this.formData = buildFormData(formElement, submitter);
701
- this.location = expandURL(this.action);
702
- if (this.method == FetchMethod.get) {
703
- mergeFormDataEntries(this.location, [ ...this.body.entries() ]);
704
- }
705
- this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
875
+ this.fetchRequest = new FetchRequest(this, method, action, body, formElement, enctype);
706
876
  this.mustRedirect = mustRedirect;
707
877
  }
708
878
  get method() {
709
- var _a;
710
- const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
711
- return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
879
+ return this.fetchRequest.method;
880
+ }
881
+ set method(value) {
882
+ this.fetchRequest.method = value;
712
883
  }
713
884
  get action() {
714
- var _a;
715
- const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
716
- if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
717
- return this.submitter.getAttribute("formaction") || "";
718
- } else {
719
- return this.formElement.getAttribute("action") || formElementAction || "";
720
- }
885
+ return this.fetchRequest.url.toString();
886
+ }
887
+ set action(value) {
888
+ this.fetchRequest.url = expandURL(value);
721
889
  }
722
890
  get body() {
723
- if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
724
- return new URLSearchParams(this.stringFormData);
725
- } else {
726
- return this.formData;
727
- }
891
+ return this.fetchRequest.body;
728
892
  }
729
893
  get enctype() {
730
- var _a;
731
- return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
894
+ return this.fetchRequest.enctype;
732
895
  }
733
896
  get isSafe() {
734
897
  return this.fetchRequest.isSafe;
735
898
  }
736
- get stringFormData() {
737
- return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
899
+ get location() {
900
+ return this.fetchRequest.url;
738
901
  }
739
902
  async start() {
740
903
  const {initialized: initialized, requesting: requesting} = FormSubmissionState;
@@ -770,10 +933,10 @@ class FormSubmission {
770
933
  }
771
934
  }
772
935
  requestStarted(_request) {
773
- var _a;
774
936
  this.state = FormSubmissionState.waiting;
775
- (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
937
+ this.submitter?.setAttribute("disabled", "");
776
938
  this.setSubmitsWith();
939
+ markAsBusy(this.formElement);
777
940
  dispatch("turbo:submit-start", {
778
941
  target: this.formElement,
779
942
  detail: {
@@ -783,6 +946,7 @@ class FormSubmission {
783
946
  this.delegate.formSubmissionStarted(this);
784
947
  }
785
948
  requestPreventedHandlingResponse(request, response) {
949
+ prefetchCache.clear();
786
950
  this.result = {
787
951
  success: response.succeeded,
788
952
  fetchResponse: response
@@ -791,7 +955,10 @@ class FormSubmission {
791
955
  requestSucceededWithResponse(request, response) {
792
956
  if (response.clientError || response.serverError) {
793
957
  this.delegate.formSubmissionFailedWithResponse(this, response);
794
- } else if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {
958
+ return;
959
+ }
960
+ prefetchCache.clear();
961
+ if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {
795
962
  const error = new Error("Form responses must redirect to another location");
796
963
  this.delegate.formSubmissionErrored(this, error);
797
964
  } else {
@@ -818,15 +985,16 @@ class FormSubmission {
818
985
  this.delegate.formSubmissionErrored(this, error);
819
986
  }
820
987
  requestFinished(_request) {
821
- var _a;
822
988
  this.state = FormSubmissionState.stopped;
823
- (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
989
+ this.submitter?.removeAttribute("disabled");
824
990
  this.resetSubmitterText();
991
+ clearBusyState(this.formElement);
825
992
  dispatch("turbo:submit-end", {
826
993
  target: this.formElement,
827
- detail: Object.assign({
828
- formSubmission: this
829
- }, this.result)
994
+ detail: {
995
+ formSubmission: this,
996
+ ...this.result
997
+ }
830
998
  });
831
999
  this.delegate.formSubmissionFinished(this);
832
1000
  }
@@ -857,15 +1025,14 @@ class FormSubmission {
857
1025
  return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
858
1026
  }
859
1027
  get submitsWith() {
860
- var _a;
861
- return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with");
1028
+ return this.submitter?.getAttribute("data-turbo-submits-with");
862
1029
  }
863
1030
  }
864
1031
 
865
1032
  function buildFormData(formElement, submitter) {
866
1033
  const formData = new FormData(formElement);
867
- const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
868
- const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
1034
+ const name = submitter?.getAttribute("name");
1035
+ const value = submitter?.getAttribute("value");
869
1036
  if (name) {
870
1037
  formData.append(name, value || "");
871
1038
  }
@@ -887,14 +1054,30 @@ function responseSucceededWithoutRedirect(response) {
887
1054
  return response.statusCode == 200 && !response.redirected;
888
1055
  }
889
1056
 
890
- function mergeFormDataEntries(url, entries) {
891
- const searchParams = new URLSearchParams;
892
- for (const [name, value] of entries) {
893
- if (value instanceof File) continue;
894
- searchParams.append(name, value);
1057
+ function getFormAction(formElement, submitter) {
1058
+ const formElementAction = typeof formElement.action === "string" ? formElement.action : null;
1059
+ if (submitter?.hasAttribute("formaction")) {
1060
+ return submitter.getAttribute("formaction") || "";
1061
+ } else {
1062
+ return formElement.getAttribute("action") || formElementAction || "";
895
1063
  }
896
- url.search = searchParams.toString();
897
- return url;
1064
+ }
1065
+
1066
+ function getAction(formAction, fetchMethod) {
1067
+ const action = expandURL(formAction);
1068
+ if (isSafe(fetchMethod)) {
1069
+ action.search = "";
1070
+ }
1071
+ return action;
1072
+ }
1073
+
1074
+ function getMethod(formElement, submitter) {
1075
+ const method = submitter?.getAttribute("formmethod") || formElement.getAttribute("method") || "";
1076
+ return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
1077
+ }
1078
+
1079
+ function getEnctype(formElement, submitter) {
1080
+ return fetchEnctypeFromString(submitter?.getAttribute("formenctype") || formElement.enctype);
898
1081
  }
899
1082
 
900
1083
  class Snapshot {
@@ -917,11 +1100,7 @@ class Snapshot {
917
1100
  return this.element.isConnected;
918
1101
  }
919
1102
  get firstAutofocusableElement() {
920
- const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
921
- for (const element of this.element.querySelectorAll("[autofocus]")) {
922
- if (element.closest(inertDisabledOrHidden) == null) return element; else continue;
923
- }
924
- return null;
1103
+ return queryAutofocusableElement(this.element);
925
1104
  }
926
1105
  get permanentElements() {
927
1106
  return queryPermanentElementsAll(this.element);
@@ -951,23 +1130,8 @@ function queryPermanentElementsAll(node) {
951
1130
  }
952
1131
 
953
1132
  class FormSubmitObserver {
1133
+ started=false;
954
1134
  constructor(delegate, eventTarget) {
955
- this.started = false;
956
- this.submitCaptured = () => {
957
- this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
958
- this.eventTarget.addEventListener("submit", this.submitBubbled, false);
959
- };
960
- this.submitBubbled = event => {
961
- if (!event.defaultPrevented) {
962
- const form = event.target instanceof HTMLFormElement ? event.target : undefined;
963
- const submitter = event.submitter || undefined;
964
- if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
965
- event.preventDefault();
966
- event.stopImmediatePropagation();
967
- this.delegate.formSubmitted(form, submitter);
968
- }
969
- }
970
- };
971
1135
  this.delegate = delegate;
972
1136
  this.eventTarget = eventTarget;
973
1137
  }
@@ -983,29 +1147,37 @@ class FormSubmitObserver {
983
1147
  this.started = false;
984
1148
  }
985
1149
  }
1150
+ submitCaptured=() => {
1151
+ this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
1152
+ this.eventTarget.addEventListener("submit", this.submitBubbled, false);
1153
+ };
1154
+ submitBubbled=event => {
1155
+ if (!event.defaultPrevented) {
1156
+ const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1157
+ const submitter = event.submitter || undefined;
1158
+ if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
1159
+ event.preventDefault();
1160
+ event.stopImmediatePropagation();
1161
+ this.delegate.formSubmitted(form, submitter);
1162
+ }
1163
+ }
1164
+ };
986
1165
  }
987
1166
 
988
1167
  function submissionDoesNotDismissDialog(form, submitter) {
989
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
1168
+ const method = submitter?.getAttribute("formmethod") || form.getAttribute("method");
990
1169
  return method != "dialog";
991
1170
  }
992
1171
 
993
1172
  function submissionDoesNotTargetIFrame(form, submitter) {
994
- if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
995
- const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
996
- for (const element of document.getElementsByName(target)) {
997
- if (element instanceof HTMLIFrameElement) return false;
998
- }
999
- return true;
1000
- } else {
1001
- return true;
1002
- }
1173
+ const target = submitter?.getAttribute("formtarget") || form.getAttribute("target");
1174
+ return doesNotTargetIFrame(target);
1003
1175
  }
1004
1176
 
1005
1177
  class View {
1178
+ #resolveRenderPromise=_value => {};
1179
+ #resolveInterceptionPromise=_value => {};
1006
1180
  constructor(delegate, element) {
1007
- this.resolveRenderPromise = _value => {};
1008
- this.resolveInterceptionPromise = _value => {};
1009
1181
  this.delegate = delegate;
1010
1182
  this.element = element;
1011
1183
  }
@@ -1051,29 +1223,31 @@ class View {
1051
1223
  return window;
1052
1224
  }
1053
1225
  async render(renderer) {
1054
- const {isPreview: isPreview, shouldRender: shouldRender, newSnapshot: snapshot} = renderer;
1226
+ const {isPreview: isPreview, shouldRender: shouldRender, willRender: willRender, newSnapshot: snapshot} = renderer;
1227
+ const shouldInvalidate = willRender;
1055
1228
  if (shouldRender) {
1056
1229
  try {
1057
- this.renderPromise = new Promise((resolve => this.resolveRenderPromise = resolve));
1230
+ this.renderPromise = new Promise((resolve => this.#resolveRenderPromise = resolve));
1058
1231
  this.renderer = renderer;
1059
1232
  await this.prepareToRenderSnapshot(renderer);
1060
- const renderInterception = new Promise((resolve => this.resolveInterceptionPromise = resolve));
1233
+ const renderInterception = new Promise((resolve => this.#resolveInterceptionPromise = resolve));
1061
1234
  const options = {
1062
- resume: this.resolveInterceptionPromise,
1063
- render: this.renderer.renderElement
1235
+ resume: this.#resolveInterceptionPromise,
1236
+ render: this.renderer.renderElement,
1237
+ renderMethod: this.renderer.renderMethod
1064
1238
  };
1065
1239
  const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
1066
1240
  if (!immediateRender) await renderInterception;
1067
1241
  await this.renderSnapshot(renderer);
1068
- this.delegate.viewRenderedSnapshot(snapshot, isPreview);
1242
+ this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod);
1069
1243
  this.delegate.preloadOnLoadLinksForView(this.element);
1070
1244
  this.finishRenderingSnapshot(renderer);
1071
1245
  } finally {
1072
1246
  delete this.renderer;
1073
- this.resolveRenderPromise(undefined);
1247
+ this.#resolveRenderPromise(undefined);
1074
1248
  delete this.renderPromise;
1075
1249
  }
1076
- } else {
1250
+ } else if (shouldInvalidate) {
1077
1251
  this.invalidate(renderer.reloadReason);
1078
1252
  }
1079
1253
  }
@@ -1091,6 +1265,12 @@ class View {
1091
1265
  this.element.removeAttribute("data-turbo-preview");
1092
1266
  }
1093
1267
  }
1268
+ markVisitDirection(direction) {
1269
+ this.element.setAttribute("data-turbo-visit-direction", direction);
1270
+ }
1271
+ unmarkVisitDirection() {
1272
+ this.element.removeAttribute("data-turbo-visit-direction");
1273
+ }
1094
1274
  async renderSnapshot(renderer) {
1095
1275
  await renderer.render();
1096
1276
  }
@@ -1110,26 +1290,6 @@ class FrameView extends View {
1110
1290
 
1111
1291
  class LinkInterceptor {
1112
1292
  constructor(delegate, element) {
1113
- this.clickBubbled = event => {
1114
- if (this.respondsToEventTarget(event.target)) {
1115
- this.clickEvent = event;
1116
- } else {
1117
- delete this.clickEvent;
1118
- }
1119
- };
1120
- this.linkClicked = event => {
1121
- if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
1122
- if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
1123
- this.clickEvent.preventDefault();
1124
- event.preventDefault();
1125
- this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
1126
- }
1127
- }
1128
- delete this.clickEvent;
1129
- };
1130
- this.willVisit = _event => {
1131
- delete this.clickEvent;
1132
- };
1133
1293
  this.delegate = delegate;
1134
1294
  this.element = element;
1135
1295
  }
@@ -1143,32 +1303,36 @@ class LinkInterceptor {
1143
1303
  document.removeEventListener("turbo:click", this.linkClicked);
1144
1304
  document.removeEventListener("turbo:before-visit", this.willVisit);
1145
1305
  }
1146
- respondsToEventTarget(target) {
1147
- const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
1148
- return element && element.closest("turbo-frame, html") == this.element;
1306
+ clickBubbled=event => {
1307
+ if (this.clickEventIsSignificant(event)) {
1308
+ this.clickEvent = event;
1309
+ } else {
1310
+ delete this.clickEvent;
1311
+ }
1312
+ };
1313
+ linkClicked=event => {
1314
+ if (this.clickEvent && this.clickEventIsSignificant(event)) {
1315
+ if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
1316
+ this.clickEvent.preventDefault();
1317
+ event.preventDefault();
1318
+ this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
1319
+ }
1320
+ }
1321
+ delete this.clickEvent;
1322
+ };
1323
+ willVisit=_event => {
1324
+ delete this.clickEvent;
1325
+ };
1326
+ clickEventIsSignificant(event) {
1327
+ const target = event.composed ? event.target?.parentElement : event.target;
1328
+ const element = findLinkFromClickTarget(target) || target;
1329
+ return element instanceof Element && element.closest("turbo-frame, html") == this.element;
1149
1330
  }
1150
1331
  }
1151
1332
 
1152
1333
  class LinkClickObserver {
1334
+ started=false;
1153
1335
  constructor(delegate, eventTarget) {
1154
- this.started = false;
1155
- this.clickCaptured = () => {
1156
- this.eventTarget.removeEventListener("click", this.clickBubbled, false);
1157
- this.eventTarget.addEventListener("click", this.clickBubbled, false);
1158
- };
1159
- this.clickBubbled = event => {
1160
- if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
1161
- const target = event.composedPath && event.composedPath()[0] || event.target;
1162
- const link = this.findLinkFromClickTarget(target);
1163
- if (link && doesNotTargetIFrame(link)) {
1164
- const location = this.getLocationForLink(link);
1165
- if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1166
- event.preventDefault();
1167
- this.delegate.followedLinkToLocation(link, location);
1168
- }
1169
- }
1170
- }
1171
- };
1172
1336
  this.delegate = delegate;
1173
1337
  this.eventTarget = eventTarget;
1174
1338
  }
@@ -1184,26 +1348,26 @@ class LinkClickObserver {
1184
1348
  this.started = false;
1185
1349
  }
1186
1350
  }
1351
+ clickCaptured=() => {
1352
+ this.eventTarget.removeEventListener("click", this.clickBubbled, false);
1353
+ this.eventTarget.addEventListener("click", this.clickBubbled, false);
1354
+ };
1355
+ clickBubbled=event => {
1356
+ if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
1357
+ const target = event.composedPath && event.composedPath()[0] || event.target;
1358
+ const link = findLinkFromClickTarget(target);
1359
+ if (link && doesNotTargetIFrame(link.target)) {
1360
+ const location = getLocationForLink(link);
1361
+ if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1362
+ event.preventDefault();
1363
+ this.delegate.followedLinkToLocation(link, location);
1364
+ }
1365
+ }
1366
+ }
1367
+ };
1187
1368
  clickEventIsSignificant(event) {
1188
1369
  return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
1189
1370
  }
1190
- findLinkFromClickTarget(target) {
1191
- return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
1192
- }
1193
- getLocationForLink(link) {
1194
- return expandURL(link.getAttribute("href") || "");
1195
- }
1196
- }
1197
-
1198
- function doesNotTargetIFrame(anchor) {
1199
- if (anchor.hasAttribute("target")) {
1200
- for (const element of document.getElementsByName(anchor.target)) {
1201
- if (element instanceof HTMLIFrameElement) return false;
1202
- }
1203
- return true;
1204
- } else {
1205
- return true;
1206
- }
1207
1371
  }
1208
1372
 
1209
1373
  class FormLinkClickObserver {
@@ -1217,8 +1381,14 @@ class FormLinkClickObserver {
1217
1381
  stop() {
1218
1382
  this.linkInterceptor.stop();
1219
1383
  }
1384
+ canPrefetchRequestToLocation(link, location) {
1385
+ return false;
1386
+ }
1387
+ prefetchAndCacheRequestToLocation(link, location) {
1388
+ return;
1389
+ }
1220
1390
  willFollowLinkToLocation(link, location, originalEvent) {
1221
- return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
1391
+ return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && (link.hasAttribute("data-turbo-method") || link.hasAttribute("data-turbo-stream"));
1222
1392
  }
1223
1393
  followedLinkToLocation(link, location) {
1224
1394
  const form = document.createElement("form");
@@ -1291,7 +1461,7 @@ class Bardo {
1291
1461
  }
1292
1462
  replacePlaceholderWithPermanentElement(permanentElement) {
1293
1463
  const placeholder = this.getPlaceholderById(permanentElement.id);
1294
- placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
1464
+ placeholder?.replaceWith(permanentElement);
1295
1465
  }
1296
1466
  getPlaceholderById(id) {
1297
1467
  return this.placeholders.find((element => element.content == id));
@@ -1309,8 +1479,8 @@ function createPlaceholderForPermanentElement(permanentElement) {
1309
1479
  }
1310
1480
 
1311
1481
  class Renderer {
1482
+ #activeElement=null;
1312
1483
  constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1313
- this.activeElement = null;
1314
1484
  this.currentSnapshot = currentSnapshot;
1315
1485
  this.newSnapshot = newSnapshot;
1316
1486
  this.isPreview = isPreview;
@@ -1324,12 +1494,16 @@ class Renderer {
1324
1494
  get shouldRender() {
1325
1495
  return true;
1326
1496
  }
1497
+ get shouldAutofocus() {
1498
+ return true;
1499
+ }
1327
1500
  get reloadReason() {
1328
1501
  return;
1329
1502
  }
1330
1503
  prepareToRender() {
1331
1504
  return;
1332
1505
  }
1506
+ render() {}
1333
1507
  finishRendering() {
1334
1508
  if (this.resolvingFunctions) {
1335
1509
  this.resolvingFunctions.resolve();
@@ -1340,21 +1514,23 @@ class Renderer {
1340
1514
  await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1341
1515
  }
1342
1516
  focusFirstAutofocusableElement() {
1343
- const element = this.connectedSnapshot.firstAutofocusableElement;
1344
- if (elementIsFocusable(element)) {
1345
- element.focus();
1517
+ if (this.shouldAutofocus) {
1518
+ const element = this.connectedSnapshot.firstAutofocusableElement;
1519
+ if (element) {
1520
+ element.focus();
1521
+ }
1346
1522
  }
1347
1523
  }
1348
1524
  enteringBardo(currentPermanentElement) {
1349
- if (this.activeElement) return;
1525
+ if (this.#activeElement) return;
1350
1526
  if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
1351
- this.activeElement = this.currentSnapshot.activeElement;
1527
+ this.#activeElement = this.currentSnapshot.activeElement;
1352
1528
  }
1353
1529
  }
1354
1530
  leavingBardo(currentPermanentElement) {
1355
- if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
1356
- this.activeElement.focus();
1357
- this.activeElement = null;
1531
+ if (currentPermanentElement.contains(this.#activeElement) && this.#activeElement instanceof HTMLElement) {
1532
+ this.#activeElement.focus();
1533
+ this.#activeElement = null;
1358
1534
  }
1359
1535
  }
1360
1536
  get connectedSnapshot() {
@@ -1369,20 +1545,18 @@ class Renderer {
1369
1545
  get permanentElementMap() {
1370
1546
  return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1371
1547
  }
1372
- }
1373
-
1374
- function elementIsFocusable(element) {
1375
- return element && typeof element.focus == "function";
1548
+ get renderMethod() {
1549
+ return "replace";
1550
+ }
1376
1551
  }
1377
1552
 
1378
1553
  class FrameRenderer extends Renderer {
1379
1554
  static renderElement(currentElement, newElement) {
1380
- var _a;
1381
1555
  const destinationRange = document.createRange();
1382
1556
  destinationRange.selectNodeContents(currentElement);
1383
1557
  destinationRange.deleteContents();
1384
1558
  const frameElement = newElement;
1385
- const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1559
+ const sourceRange = frameElement.ownerDocument?.createRange();
1386
1560
  if (sourceRange) {
1387
1561
  sourceRange.selectNodeContents(frameElement);
1388
1562
  currentElement.appendChild(sourceRange.extractContents());
@@ -1396,14 +1570,14 @@ class FrameRenderer extends Renderer {
1396
1570
  return true;
1397
1571
  }
1398
1572
  async render() {
1399
- await nextAnimationFrame();
1573
+ await nextRepaint();
1400
1574
  this.preservingPermanentElements((() => {
1401
1575
  this.loadFrameElement();
1402
1576
  }));
1403
1577
  this.scrollFrameIntoView();
1404
- await nextAnimationFrame();
1578
+ await nextRepaint();
1405
1579
  this.focusFirstAutofocusableElement();
1406
- await nextAnimationFrame();
1580
+ await nextRepaint();
1407
1581
  this.activateScriptElements();
1408
1582
  }
1409
1583
  loadFrameElement() {
@@ -1453,6 +1627,7 @@ function readScrollBehavior(value, defaultValue) {
1453
1627
  }
1454
1628
 
1455
1629
  class ProgressBar {
1630
+ static animationDuration=300;
1456
1631
  static get defaultCSS() {
1457
1632
  return unindent`
1458
1633
  .turbo-progress-bar {
@@ -1470,13 +1645,10 @@ class ProgressBar {
1470
1645
  }
1471
1646
  `;
1472
1647
  }
1648
+ hiding=false;
1649
+ value=0;
1650
+ visible=false;
1473
1651
  constructor() {
1474
- this.hiding = false;
1475
- this.value = 0;
1476
- this.visible = false;
1477
- this.trickle = () => {
1478
- this.setValue(this.value + Math.random() / 100);
1479
- };
1480
1652
  this.stylesheetElement = this.createStylesheetElement();
1481
1653
  this.progressElement = this.createProgressElement();
1482
1654
  this.installStylesheetElement();
@@ -1531,6 +1703,9 @@ class ProgressBar {
1531
1703
  window.clearInterval(this.trickleInterval);
1532
1704
  delete this.trickleInterval;
1533
1705
  }
1706
+ trickle=() => {
1707
+ this.setValue(this.value + Math.random() / 100);
1708
+ };
1534
1709
  refresh() {
1535
1710
  requestAnimationFrame((() => {
1536
1711
  this.progressElement.style.width = `${10 + this.value * 90}%`;
@@ -1555,25 +1730,22 @@ class ProgressBar {
1555
1730
  }
1556
1731
  }
1557
1732
 
1558
- ProgressBar.animationDuration = 300;
1559
-
1560
1733
  class HeadSnapshot extends Snapshot {
1561
- constructor() {
1562
- super(...arguments);
1563
- this.detailsByOuterHTML = this.children.filter((element => !elementIsNoscript(element))).map((element => elementWithoutNonce(element))).reduce(((result, element) => {
1564
- const {outerHTML: outerHTML} = element;
1565
- const details = outerHTML in result ? result[outerHTML] : {
1566
- type: elementType(element),
1567
- tracked: elementIsTracked(element),
1568
- elements: []
1569
- };
1570
- return Object.assign(Object.assign({}, result), {
1571
- [outerHTML]: Object.assign(Object.assign({}, details), {
1572
- elements: [ ...details.elements, element ]
1573
- })
1574
- });
1575
- }), {});
1576
- }
1734
+ detailsByOuterHTML=this.children.filter((element => !elementIsNoscript(element))).map((element => elementWithoutNonce(element))).reduce(((result, element) => {
1735
+ const {outerHTML: outerHTML} = element;
1736
+ const details = outerHTML in result ? result[outerHTML] : {
1737
+ type: elementType(element),
1738
+ tracked: elementIsTracked(element),
1739
+ elements: []
1740
+ };
1741
+ return {
1742
+ ...result,
1743
+ [outerHTML]: {
1744
+ ...details,
1745
+ elements: [ ...details.elements, element ]
1746
+ }
1747
+ };
1748
+ }), {});
1577
1749
  get trackedElementSignature() {
1578
1750
  return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
1579
1751
  }
@@ -1606,7 +1778,7 @@ class HeadSnapshot extends Snapshot {
1606
1778
  return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
1607
1779
  const {elements: [element]} = this.detailsByOuterHTML[outerHTML];
1608
1780
  return elementIsMetaElementWithName(element, name) ? element : result;
1609
- }), undefined);
1781
+ }), undefined | undefined);
1610
1782
  }
1611
1783
  }
1612
1784
 
@@ -1656,11 +1828,12 @@ class PageSnapshot extends Snapshot {
1656
1828
  static fromElement(element) {
1657
1829
  return this.fromDocument(element.ownerDocument);
1658
1830
  }
1659
- static fromDocument({head: head, body: body}) {
1660
- return new this(body, new HeadSnapshot(head));
1831
+ static fromDocument({documentElement: documentElement, body: body, head: head}) {
1832
+ return new this(documentElement, body, new HeadSnapshot(head));
1661
1833
  }
1662
- constructor(element, headSnapshot) {
1663
- super(element);
1834
+ constructor(documentElement, body, headSnapshot) {
1835
+ super(body);
1836
+ this.documentElement = documentElement;
1664
1837
  this.headSnapshot = headSnapshot;
1665
1838
  }
1666
1839
  clone() {
@@ -1675,14 +1848,16 @@ class PageSnapshot extends Snapshot {
1675
1848
  for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
1676
1849
  clonedPasswordInput.value = "";
1677
1850
  }
1678
- return new PageSnapshot(clonedElement, this.headSnapshot);
1851
+ return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot);
1852
+ }
1853
+ get lang() {
1854
+ return this.documentElement.getAttribute("lang");
1679
1855
  }
1680
1856
  get headElement() {
1681
1857
  return this.headSnapshot.element;
1682
1858
  }
1683
1859
  get rootLocation() {
1684
- var _a;
1685
- const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
1860
+ const root = this.getSetting("root") ?? "/";
1686
1861
  return expandURL(root);
1687
1862
  }
1688
1863
  get cacheControlValue() {
@@ -1697,29 +1872,38 @@ class PageSnapshot extends Snapshot {
1697
1872
  get isVisitable() {
1698
1873
  return this.getSetting("visit-control") != "reload";
1699
1874
  }
1875
+ get prefersViewTransitions() {
1876
+ return this.headSnapshot.getMetaValue("view-transition") === "same-origin";
1877
+ }
1878
+ get shouldMorphPage() {
1879
+ return this.getSetting("refresh-method") === "morph";
1880
+ }
1881
+ get shouldPreserveScrollPosition() {
1882
+ return this.getSetting("refresh-scroll") === "preserve";
1883
+ }
1700
1884
  getSetting(name) {
1701
1885
  return this.headSnapshot.getMetaValue(`turbo-${name}`);
1702
1886
  }
1703
1887
  }
1704
1888
 
1705
- var TimingMetric;
1706
-
1707
- (function(TimingMetric) {
1708
- TimingMetric["visitStart"] = "visitStart";
1709
- TimingMetric["requestStart"] = "requestStart";
1710
- TimingMetric["requestEnd"] = "requestEnd";
1711
- TimingMetric["visitEnd"] = "visitEnd";
1712
- })(TimingMetric || (TimingMetric = {}));
1713
-
1714
- var VisitState;
1715
-
1716
- (function(VisitState) {
1717
- VisitState["initialized"] = "initialized";
1718
- VisitState["started"] = "started";
1719
- VisitState["canceled"] = "canceled";
1720
- VisitState["failed"] = "failed";
1721
- VisitState["completed"] = "completed";
1722
- })(VisitState || (VisitState = {}));
1889
+ class ViewTransitioner {
1890
+ #viewTransitionStarted=false;
1891
+ #lastOperation=Promise.resolve();
1892
+ renderChange(useViewTransition, render) {
1893
+ if (useViewTransition && this.viewTransitionsAvailable && !this.#viewTransitionStarted) {
1894
+ this.#viewTransitionStarted = true;
1895
+ this.#lastOperation = this.#lastOperation.then((async () => {
1896
+ await document.startViewTransition(render).finished;
1897
+ }));
1898
+ } else {
1899
+ this.#lastOperation = this.#lastOperation.then(render);
1900
+ }
1901
+ return this.#lastOperation;
1902
+ }
1903
+ get viewTransitionsAvailable() {
1904
+ return document.startViewTransition;
1905
+ }
1906
+ }
1723
1907
 
1724
1908
  const defaultOptions = {
1725
1909
  action: "advance",
@@ -1731,29 +1915,52 @@ const defaultOptions = {
1731
1915
  acceptsStreamResponse: false
1732
1916
  };
1733
1917
 
1734
- var SystemStatusCode;
1918
+ const TimingMetric = {
1919
+ visitStart: "visitStart",
1920
+ requestStart: "requestStart",
1921
+ requestEnd: "requestEnd",
1922
+ visitEnd: "visitEnd"
1923
+ };
1924
+
1925
+ const VisitState = {
1926
+ initialized: "initialized",
1927
+ started: "started",
1928
+ canceled: "canceled",
1929
+ failed: "failed",
1930
+ completed: "completed"
1931
+ };
1932
+
1933
+ const SystemStatusCode = {
1934
+ networkFailure: 0,
1935
+ timeoutFailure: -1,
1936
+ contentTypeMismatch: -2
1937
+ };
1735
1938
 
1736
- (function(SystemStatusCode) {
1737
- SystemStatusCode[SystemStatusCode["networkFailure"] = 0] = "networkFailure";
1738
- SystemStatusCode[SystemStatusCode["timeoutFailure"] = -1] = "timeoutFailure";
1739
- SystemStatusCode[SystemStatusCode["contentTypeMismatch"] = -2] = "contentTypeMismatch";
1740
- })(SystemStatusCode || (SystemStatusCode = {}));
1939
+ const Direction = {
1940
+ advance: "forward",
1941
+ restore: "back",
1942
+ replace: "none"
1943
+ };
1741
1944
 
1742
1945
  class Visit {
1946
+ identifier=uuid();
1947
+ timingMetrics={};
1948
+ followedRedirect=false;
1949
+ historyChanged=false;
1950
+ scrolled=false;
1951
+ shouldCacheSnapshot=true;
1952
+ acceptsStreamResponse=false;
1953
+ snapshotCached=false;
1954
+ state=VisitState.initialized;
1955
+ viewTransitioner=new ViewTransitioner;
1743
1956
  constructor(delegate, location, restorationIdentifier, options = {}) {
1744
- this.identifier = uuid();
1745
- this.timingMetrics = {};
1746
- this.followedRedirect = false;
1747
- this.historyChanged = false;
1748
- this.scrolled = false;
1749
- this.shouldCacheSnapshot = true;
1750
- this.acceptsStreamResponse = false;
1751
- this.snapshotCached = false;
1752
- this.state = VisitState.initialized;
1753
1957
  this.delegate = delegate;
1754
1958
  this.location = location;
1755
1959
  this.restorationIdentifier = restorationIdentifier || uuid();
1756
- const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = Object.assign(Object.assign({}, defaultOptions), options);
1960
+ const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse, direction: direction} = {
1961
+ ...defaultOptions,
1962
+ ...options
1963
+ };
1757
1964
  this.action = action;
1758
1965
  this.historyChanged = historyChanged;
1759
1966
  this.referrer = referrer;
@@ -1761,12 +1968,14 @@ class Visit {
1761
1968
  this.snapshotHTML = snapshotHTML;
1762
1969
  this.response = response;
1763
1970
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
1971
+ this.isPageRefresh = this.view.isPageRefresh(this);
1764
1972
  this.visitCachedSnapshot = visitCachedSnapshot;
1765
1973
  this.willRender = willRender;
1766
1974
  this.updateHistory = updateHistory;
1767
1975
  this.scrolled = !willRender;
1768
1976
  this.shouldCacheSnapshot = shouldCacheSnapshot;
1769
1977
  this.acceptsStreamResponse = acceptsStreamResponse;
1978
+ this.direction = direction || Direction[action];
1770
1979
  }
1771
1980
  get adapter() {
1772
1981
  return this.delegate.adapter;
@@ -1803,10 +2012,10 @@ class Visit {
1803
2012
  complete() {
1804
2013
  if (this.state == VisitState.started) {
1805
2014
  this.recordTimingMetric(TimingMetric.visitEnd);
2015
+ this.adapter.visitCompleted(this);
1806
2016
  this.state = VisitState.completed;
1807
2017
  this.followRedirect();
1808
2018
  if (!this.followedRedirect) {
1809
- this.adapter.visitCompleted(this);
1810
2019
  this.delegate.visitCompleted(this);
1811
2020
  }
1812
2021
  }
@@ -1815,12 +2024,12 @@ class Visit {
1815
2024
  if (this.state == VisitState.started) {
1816
2025
  this.state = VisitState.failed;
1817
2026
  this.adapter.visitFailed(this);
2027
+ this.delegate.visitCompleted(this);
1818
2028
  }
1819
2029
  }
1820
2030
  changeHistory() {
1821
- var _a;
1822
2031
  if (!this.historyChanged && this.updateHistory) {
1823
- const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
2032
+ const actionForHistory = this.location.href === this.referrer?.href ? "replace" : this.action;
1824
2033
  const method = getHistoryMethodForAction(actionForHistory);
1825
2034
  this.history.update(method, this.location, this.restorationIdentifier);
1826
2035
  this.historyChanged = true;
@@ -1867,8 +2076,8 @@ class Visit {
1867
2076
  if (this.shouldCacheSnapshot) this.cacheSnapshot();
1868
2077
  if (this.view.renderPromise) await this.view.renderPromise;
1869
2078
  if (isSuccessful(statusCode) && responseHTML != null) {
1870
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
1871
- this.performScroll();
2079
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2080
+ await this.renderPageSnapshot(snapshot, false);
1872
2081
  this.adapter.visitRendered(this);
1873
2082
  this.complete();
1874
2083
  } else {
@@ -1901,12 +2110,11 @@ class Visit {
1901
2110
  const isPreview = this.shouldIssueRequest();
1902
2111
  this.render((async () => {
1903
2112
  this.cacheSnapshot();
1904
- if (this.isSamePage) {
2113
+ if (this.isSamePage || this.isPageRefresh) {
1905
2114
  this.adapter.visitRendered(this);
1906
2115
  } else {
1907
2116
  if (this.view.renderPromise) await this.view.renderPromise;
1908
- await this.view.renderPage(snapshot, isPreview, this.willRender, this);
1909
- this.performScroll();
2117
+ await this.renderPageSnapshot(snapshot, isPreview);
1910
2118
  this.adapter.visitRendered(this);
1911
2119
  if (!isPreview) {
1912
2120
  this.complete();
@@ -1916,8 +2124,7 @@ class Visit {
1916
2124
  }
1917
2125
  }
1918
2126
  followRedirect() {
1919
- var _a;
1920
- if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
2127
+ if (this.redirectedToLocation && !this.followedRedirect && this.response?.redirected) {
1921
2128
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1922
2129
  action: "replace",
1923
2130
  response: this.response,
@@ -1989,7 +2196,7 @@ class Visit {
1989
2196
  this.finishRequest();
1990
2197
  }
1991
2198
  performScroll() {
1992
- if (!this.scrolled && !this.view.forceReloaded) {
2199
+ if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) {
1993
2200
  if (this.action == "restore") {
1994
2201
  this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
1995
2202
  } else {
@@ -2019,8 +2226,10 @@ class Visit {
2019
2226
  this.timingMetrics[metric] = (new Date).getTime();
2020
2227
  }
2021
2228
  getTimingMetrics() {
2022
- return Object.assign({}, this.timingMetrics);
2023
- }
2229
+ return {
2230
+ ...this.timingMetrics
2231
+ };
2232
+ }
2024
2233
  getHistoryMethodForAction(action) {
2025
2234
  switch (action) {
2026
2235
  case "replace":
@@ -2051,12 +2260,16 @@ class Visit {
2051
2260
  }
2052
2261
  async render(callback) {
2053
2262
  this.cancelRender();
2054
- await new Promise((resolve => {
2055
- this.frame = requestAnimationFrame((() => resolve()));
2056
- }));
2263
+ this.frame = await nextRepaint();
2057
2264
  await callback();
2058
2265
  delete this.frame;
2059
2266
  }
2267
+ async renderPageSnapshot(snapshot, isPreview) {
2268
+ await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(snapshot), (async () => {
2269
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
2270
+ this.performScroll();
2271
+ }));
2272
+ }
2060
2273
  cancelRender() {
2061
2274
  if (this.frame) {
2062
2275
  cancelAnimationFrame(this.frame);
@@ -2070,15 +2283,16 @@ function isSuccessful(statusCode) {
2070
2283
  }
2071
2284
 
2072
2285
  class BrowserAdapter {
2286
+ progressBar=new ProgressBar;
2073
2287
  constructor(session) {
2074
- this.progressBar = new ProgressBar;
2075
- this.showProgressBar = () => {
2076
- this.progressBar.show();
2077
- };
2078
2288
  this.session = session;
2079
2289
  }
2080
2290
  visitProposedToLocation(location, options) {
2081
- this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
2291
+ if (locationIsVisitable(location, this.navigator.rootLocation)) {
2292
+ this.navigator.startVisit(location, options?.restorationIdentifier || uuid(), options);
2293
+ } else {
2294
+ window.location.href = location.toString();
2295
+ }
2082
2296
  }
2083
2297
  visitStarted(visit) {
2084
2298
  this.location = visit.location;
@@ -2113,15 +2327,18 @@ class BrowserAdapter {
2113
2327
  return visit.loadResponse();
2114
2328
  }
2115
2329
  }
2116
- visitRequestFinished(_visit) {
2330
+ visitRequestFinished(_visit) {}
2331
+ visitCompleted(_visit) {
2117
2332
  this.progressBar.setValue(1);
2118
2333
  this.hideVisitProgressBar();
2119
2334
  }
2120
- visitCompleted(_visit) {}
2121
2335
  pageInvalidated(reason) {
2122
2336
  this.reload(reason);
2123
2337
  }
2124
- visitFailed(_visit) {}
2338
+ visitFailed(_visit) {
2339
+ this.progressBar.setValue(1);
2340
+ this.hideVisitProgressBar();
2341
+ }
2125
2342
  visitRendered(_visit) {}
2126
2343
  formSubmissionStarted(_formSubmission) {
2127
2344
  this.progressBar.setValue(0);
@@ -2153,12 +2370,14 @@ class BrowserAdapter {
2153
2370
  delete this.formProgressBarTimeout;
2154
2371
  }
2155
2372
  }
2373
+ showProgressBar=() => {
2374
+ this.progressBar.show();
2375
+ };
2156
2376
  reload(reason) {
2157
- var _a;
2158
2377
  dispatch("turbo:reload", {
2159
2378
  detail: reason
2160
2379
  });
2161
- window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;
2380
+ window.location.href = this.location?.toString() || window.location.href;
2162
2381
  }
2163
2382
  get navigator() {
2164
2383
  return this.session.navigator;
@@ -2166,16 +2385,9 @@ class BrowserAdapter {
2166
2385
  }
2167
2386
 
2168
2387
  class CacheObserver {
2169
- constructor() {
2170
- this.selector = "[data-turbo-temporary]";
2171
- this.deprecatedSelector = "[data-turbo-cache=false]";
2172
- this.started = false;
2173
- this.removeTemporaryElements = _event => {
2174
- for (const element of this.temporaryElements) {
2175
- element.remove();
2176
- }
2177
- };
2178
- }
2388
+ selector="[data-turbo-temporary]";
2389
+ deprecatedSelector="[data-turbo-cache=false]";
2390
+ started=false;
2179
2391
  start() {
2180
2392
  if (!this.started) {
2181
2393
  this.started = true;
@@ -2188,6 +2400,11 @@ class CacheObserver {
2188
2400
  removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
2189
2401
  }
2190
2402
  }
2403
+ removeTemporaryElements=_event => {
2404
+ for (const element of this.temporaryElements) {
2405
+ element.remove();
2406
+ }
2407
+ };
2191
2408
  get temporaryElements() {
2192
2409
  return [ ...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation ];
2193
2410
  }
@@ -2216,41 +2433,40 @@ class FrameRedirector {
2216
2433
  this.formSubmitObserver.stop();
2217
2434
  }
2218
2435
  shouldInterceptLinkClick(element, _location, _event) {
2219
- return this.shouldRedirect(element);
2436
+ return this.#shouldRedirect(element);
2220
2437
  }
2221
2438
  linkClickIntercepted(element, url, event) {
2222
- const frame = this.findFrameElement(element);
2439
+ const frame = this.#findFrameElement(element);
2223
2440
  if (frame) {
2224
2441
  frame.delegate.linkClickIntercepted(element, url, event);
2225
2442
  }
2226
2443
  }
2227
2444
  willSubmitForm(element, submitter) {
2228
- return element.closest("turbo-frame") == null && this.shouldSubmit(element, submitter) && this.shouldRedirect(element, submitter);
2445
+ return element.closest("turbo-frame") == null && this.#shouldSubmit(element, submitter) && this.#shouldRedirect(element, submitter);
2229
2446
  }
2230
2447
  formSubmitted(element, submitter) {
2231
- const frame = this.findFrameElement(element, submitter);
2448
+ const frame = this.#findFrameElement(element, submitter);
2232
2449
  if (frame) {
2233
2450
  frame.delegate.formSubmitted(element, submitter);
2234
2451
  }
2235
2452
  }
2236
- shouldSubmit(form, submitter) {
2237
- var _a;
2238
- const action = getAction(form, submitter);
2453
+ #shouldSubmit(form, submitter) {
2454
+ const action = getAction$1(form, submitter);
2239
2455
  const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
2240
- const rootLocation = expandURL((_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/");
2241
- return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
2456
+ const rootLocation = expandURL(meta?.content ?? "/");
2457
+ return this.#shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
2242
2458
  }
2243
- shouldRedirect(element, submitter) {
2459
+ #shouldRedirect(element, submitter) {
2244
2460
  const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
2245
2461
  if (isNavigatable) {
2246
- const frame = this.findFrameElement(element, submitter);
2462
+ const frame = this.#findFrameElement(element, submitter);
2247
2463
  return frame ? frame != element.closest("turbo-frame") : false;
2248
2464
  } else {
2249
2465
  return false;
2250
2466
  }
2251
2467
  }
2252
- findFrameElement(element, submitter) {
2253
- const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
2468
+ #findFrameElement(element, submitter) {
2469
+ const id = submitter?.getAttribute("data-turbo-frame") || element.getAttribute("data-turbo-frame");
2254
2470
  if (id && id != "_top") {
2255
2471
  const frame = this.element.querySelector(`#${id}:not([disabled])`);
2256
2472
  if (frame instanceof FrameElement) {
@@ -2261,32 +2477,20 @@ class FrameRedirector {
2261
2477
  }
2262
2478
 
2263
2479
  class History {
2480
+ location;
2481
+ restorationIdentifier=uuid();
2482
+ restorationData={};
2483
+ started=false;
2484
+ pageLoaded=false;
2485
+ currentIndex=0;
2264
2486
  constructor(delegate) {
2265
- this.restorationIdentifier = uuid();
2266
- this.restorationData = {};
2267
- this.started = false;
2268
- this.pageLoaded = false;
2269
- this.onPopState = event => {
2270
- if (this.shouldHandlePopState()) {
2271
- const {turbo: turbo} = event.state || {};
2272
- if (turbo) {
2273
- this.location = new URL(window.location.href);
2274
- const {restorationIdentifier: restorationIdentifier} = turbo;
2275
- this.restorationIdentifier = restorationIdentifier;
2276
- this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
2277
- }
2278
- }
2279
- };
2280
- this.onPageLoad = async _event => {
2281
- await nextMicrotask();
2282
- this.pageLoaded = true;
2283
- };
2284
2487
  this.delegate = delegate;
2285
2488
  }
2286
2489
  start() {
2287
2490
  if (!this.started) {
2288
2491
  addEventListener("popstate", this.onPopState, false);
2289
2492
  addEventListener("load", this.onPageLoad, false);
2493
+ this.currentIndex = history.state?.turbo?.restorationIndex || 0;
2290
2494
  this.started = true;
2291
2495
  this.replace(new URL(window.location.href));
2292
2496
  }
@@ -2305,9 +2509,11 @@ class History {
2305
2509
  this.update(history.replaceState, location, restorationIdentifier);
2306
2510
  }
2307
2511
  update(method, location, restorationIdentifier = uuid()) {
2512
+ if (method === history.pushState) ++this.currentIndex;
2308
2513
  const state = {
2309
2514
  turbo: {
2310
- restorationIdentifier: restorationIdentifier
2515
+ restorationIdentifier: restorationIdentifier,
2516
+ restorationIndex: this.currentIndex
2311
2517
  }
2312
2518
  };
2313
2519
  method.call(history, state, "", location.href);
@@ -2320,12 +2526,14 @@ class History {
2320
2526
  updateRestorationData(additionalData) {
2321
2527
  const {restorationIdentifier: restorationIdentifier} = this;
2322
2528
  const restorationData = this.restorationData[restorationIdentifier];
2323
- this.restorationData[restorationIdentifier] = Object.assign(Object.assign({}, restorationData), additionalData);
2529
+ this.restorationData[restorationIdentifier] = {
2530
+ ...restorationData,
2531
+ ...additionalData
2532
+ };
2324
2533
  }
2325
2534
  assumeControlOfScrollRestoration() {
2326
- var _a;
2327
2535
  if (!this.previousScrollRestoration) {
2328
- this.previousScrollRestoration = (_a = history.scrollRestoration) !== null && _a !== void 0 ? _a : "auto";
2536
+ this.previousScrollRestoration = history.scrollRestoration ?? "auto";
2329
2537
  history.scrollRestoration = "manual";
2330
2538
  }
2331
2539
  }
@@ -2335,6 +2543,23 @@ class History {
2335
2543
  delete this.previousScrollRestoration;
2336
2544
  }
2337
2545
  }
2546
+ onPopState=event => {
2547
+ if (this.shouldHandlePopState()) {
2548
+ const {turbo: turbo} = event.state || {};
2549
+ if (turbo) {
2550
+ this.location = new URL(window.location.href);
2551
+ const {restorationIdentifier: restorationIdentifier, restorationIndex: restorationIndex} = turbo;
2552
+ this.restorationIdentifier = restorationIdentifier;
2553
+ const direction = restorationIndex > this.currentIndex ? "forward" : "back";
2554
+ this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
2555
+ this.currentIndex = restorationIndex;
2556
+ }
2557
+ }
2558
+ };
2559
+ onPageLoad=async _event => {
2560
+ await nextMicrotask();
2561
+ this.pageLoaded = true;
2562
+ };
2338
2563
  shouldHandlePopState() {
2339
2564
  return this.pageIsLoaded();
2340
2565
  }
@@ -2343,24 +2568,154 @@ class History {
2343
2568
  }
2344
2569
  }
2345
2570
 
2571
+ class LinkPrefetchObserver {
2572
+ started=false;
2573
+ #prefetchedLink=null;
2574
+ constructor(delegate, eventTarget) {
2575
+ this.delegate = delegate;
2576
+ this.eventTarget = eventTarget;
2577
+ }
2578
+ start() {
2579
+ if (this.started) return;
2580
+ if (this.eventTarget.readyState === "loading") {
2581
+ this.eventTarget.addEventListener("DOMContentLoaded", this.#enable, {
2582
+ once: true
2583
+ });
2584
+ } else {
2585
+ this.#enable();
2586
+ }
2587
+ }
2588
+ stop() {
2589
+ if (!this.started) return;
2590
+ this.eventTarget.removeEventListener("mouseenter", this.#tryToPrefetchRequest, {
2591
+ capture: true,
2592
+ passive: true
2593
+ });
2594
+ this.eventTarget.removeEventListener("mouseleave", this.#cancelRequestIfObsolete, {
2595
+ capture: true,
2596
+ passive: true
2597
+ });
2598
+ this.eventTarget.removeEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
2599
+ this.started = false;
2600
+ }
2601
+ #enable=() => {
2602
+ this.eventTarget.addEventListener("mouseenter", this.#tryToPrefetchRequest, {
2603
+ capture: true,
2604
+ passive: true
2605
+ });
2606
+ this.eventTarget.addEventListener("mouseleave", this.#cancelRequestIfObsolete, {
2607
+ capture: true,
2608
+ passive: true
2609
+ });
2610
+ this.eventTarget.addEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
2611
+ this.started = true;
2612
+ };
2613
+ #tryToPrefetchRequest=event => {
2614
+ if (getMetaContent("turbo-prefetch") === "false") return;
2615
+ const target = event.target;
2616
+ const isLink = target.matches && target.matches("a[href]:not([target^=_]):not([download])");
2617
+ if (isLink && this.#isPrefetchable(target)) {
2618
+ const link = target;
2619
+ const location = getLocationForLink(link);
2620
+ if (this.delegate.canPrefetchRequestToLocation(link, location)) {
2621
+ this.#prefetchedLink = link;
2622
+ const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, target);
2623
+ prefetchCache.setLater(location.toString(), fetchRequest, this.#cacheTtl);
2624
+ }
2625
+ }
2626
+ };
2627
+ #cancelRequestIfObsolete=event => {
2628
+ if (event.target === this.#prefetchedLink) this.#cancelPrefetchRequest();
2629
+ };
2630
+ #cancelPrefetchRequest=() => {
2631
+ prefetchCache.clear();
2632
+ this.#prefetchedLink = null;
2633
+ };
2634
+ #tryToUsePrefetchedRequest=event => {
2635
+ if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
2636
+ const cached = prefetchCache.get(event.detail.url.toString());
2637
+ if (cached) {
2638
+ event.detail.fetchRequest = cached;
2639
+ }
2640
+ prefetchCache.clear();
2641
+ }
2642
+ };
2643
+ prepareRequest(request) {
2644
+ const link = request.target;
2645
+ request.headers["X-Sec-Purpose"] = "prefetch";
2646
+ const turboFrame = link.closest("turbo-frame");
2647
+ const turboFrameTarget = link.getAttribute("data-turbo-frame") || turboFrame?.getAttribute("target") || turboFrame?.id;
2648
+ if (turboFrameTarget && turboFrameTarget !== "_top") {
2649
+ request.headers["Turbo-Frame"] = turboFrameTarget;
2650
+ }
2651
+ }
2652
+ requestSucceededWithResponse() {}
2653
+ requestStarted(fetchRequest) {}
2654
+ requestErrored(fetchRequest) {}
2655
+ requestFinished(fetchRequest) {}
2656
+ requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
2657
+ requestFailedWithResponse(fetchRequest, fetchResponse) {}
2658
+ get #cacheTtl() {
2659
+ return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl;
2660
+ }
2661
+ #isPrefetchable(link) {
2662
+ const href = link.getAttribute("href");
2663
+ if (!href) return false;
2664
+ if (unfetchableLink(link)) return false;
2665
+ if (linkToTheSamePage(link)) return false;
2666
+ if (linkOptsOut(link)) return false;
2667
+ if (nonSafeLink(link)) return false;
2668
+ if (eventPrevented(link)) return false;
2669
+ return true;
2670
+ }
2671
+ }
2672
+
2673
+ const unfetchableLink = link => link.origin !== document.location.origin || ![ "http:", "https:" ].includes(link.protocol) || link.hasAttribute("target");
2674
+
2675
+ const linkToTheSamePage = link => link.pathname + link.search === document.location.pathname + document.location.search || link.href.startsWith("#");
2676
+
2677
+ const linkOptsOut = link => {
2678
+ if (link.getAttribute("data-turbo-prefetch") === "false") return true;
2679
+ if (link.getAttribute("data-turbo") === "false") return true;
2680
+ const turboPrefetchParent = findClosestRecursively(link, "[data-turbo-prefetch]");
2681
+ if (turboPrefetchParent && turboPrefetchParent.getAttribute("data-turbo-prefetch") === "false") return true;
2682
+ return false;
2683
+ };
2684
+
2685
+ const nonSafeLink = link => {
2686
+ const turboMethod = link.getAttribute("data-turbo-method");
2687
+ if (turboMethod && turboMethod.toLowerCase() !== "get") return true;
2688
+ if (isUJS(link)) return true;
2689
+ if (link.hasAttribute("data-turbo-confirm")) return true;
2690
+ if (link.hasAttribute("data-turbo-stream")) return true;
2691
+ return false;
2692
+ };
2693
+
2694
+ const isUJS = link => link.hasAttribute("data-remote") || link.hasAttribute("data-behavior") || link.hasAttribute("data-confirm") || link.hasAttribute("data-method");
2695
+
2696
+ const eventPrevented = link => {
2697
+ const event = dispatch("turbo:before-prefetch", {
2698
+ target: link,
2699
+ cancelable: true
2700
+ });
2701
+ return event.defaultPrevented;
2702
+ };
2703
+
2346
2704
  class Navigator {
2347
2705
  constructor(delegate) {
2348
2706
  this.delegate = delegate;
2349
2707
  }
2350
2708
  proposeVisit(location, options = {}) {
2351
2709
  if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
2352
- if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
2353
- this.delegate.visitProposedToLocation(location, options);
2354
- } else {
2355
- window.location.href = location.toString();
2356
- }
2710
+ this.delegate.visitProposedToLocation(location, options);
2357
2711
  }
2358
2712
  }
2359
2713
  startVisit(locatable, restorationIdentifier, options = {}) {
2360
2714
  this.stop();
2361
- this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
2362
- referrer: this.location
2363
- }, options));
2715
+ this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, {
2716
+ referrer: this.location,
2717
+ ...options
2718
+ });
2364
2719
  this.currentVisit.start();
2365
2720
  }
2366
2721
  submitForm(form, submitter) {
@@ -2384,6 +2739,9 @@ class Navigator {
2384
2739
  get view() {
2385
2740
  return this.delegate.view;
2386
2741
  }
2742
+ get rootLocation() {
2743
+ return this.view.snapshot.rootLocation;
2744
+ }
2387
2745
  get history() {
2388
2746
  return this.delegate.history;
2389
2747
  }
@@ -2401,7 +2759,7 @@ class Navigator {
2401
2759
  this.view.clearSnapshotCache();
2402
2760
  }
2403
2761
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
2404
- const action = this.getActionForFormSubmission(formSubmission);
2762
+ const action = this.#getActionForFormSubmission(formSubmission, fetchResponse);
2405
2763
  const visitOptions = {
2406
2764
  action: action,
2407
2765
  shouldCacheSnapshot: shouldCacheSnapshot,
@@ -2424,7 +2782,9 @@ class Navigator {
2424
2782
  } else {
2425
2783
  await this.view.renderPage(snapshot, false, true, this.currentVisit);
2426
2784
  }
2427
- this.view.scrollToTop();
2785
+ if (!snapshot.shouldPreserveScrollPosition) {
2786
+ this.view.scrollToTop();
2787
+ }
2428
2788
  this.view.clearSnapshotCache();
2429
2789
  }
2430
2790
  }
@@ -2441,6 +2801,7 @@ class Navigator {
2441
2801
  }
2442
2802
  visitCompleted(visit) {
2443
2803
  this.delegate.visitCompleted(visit);
2804
+ delete this.currentVisit;
2444
2805
  }
2445
2806
  locationWithActionIsSamePage(location, action) {
2446
2807
  const anchor = getAnchor(location);
@@ -2457,35 +2818,27 @@ class Navigator {
2457
2818
  get restorationIdentifier() {
2458
2819
  return this.history.restorationIdentifier;
2459
2820
  }
2460
- getActionForFormSubmission({submitter: submitter, formElement: formElement}) {
2461
- return getVisitAction(submitter, formElement) || "advance";
2821
+ #getActionForFormSubmission(formSubmission, fetchResponse) {
2822
+ const {submitter: submitter, formElement: formElement} = formSubmission;
2823
+ return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse);
2824
+ }
2825
+ #getDefaultAction(fetchResponse) {
2826
+ const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;
2827
+ return sameLocationRedirect ? "replace" : "advance";
2462
2828
  }
2463
2829
  }
2464
2830
 
2465
- var PageStage;
2466
-
2467
- (function(PageStage) {
2468
- PageStage[PageStage["initial"] = 0] = "initial";
2469
- PageStage[PageStage["loading"] = 1] = "loading";
2470
- PageStage[PageStage["interactive"] = 2] = "interactive";
2471
- PageStage[PageStage["complete"] = 3] = "complete";
2472
- })(PageStage || (PageStage = {}));
2831
+ const PageStage = {
2832
+ initial: 0,
2833
+ loading: 1,
2834
+ interactive: 2,
2835
+ complete: 3
2836
+ };
2473
2837
 
2474
2838
  class PageObserver {
2839
+ stage=PageStage.initial;
2840
+ started=false;
2475
2841
  constructor(delegate) {
2476
- this.stage = PageStage.initial;
2477
- this.started = false;
2478
- this.interpretReadyState = () => {
2479
- const {readyState: readyState} = this;
2480
- if (readyState == "interactive") {
2481
- this.pageIsInteractive();
2482
- } else if (readyState == "complete") {
2483
- this.pageIsComplete();
2484
- }
2485
- };
2486
- this.pageWillUnload = () => {
2487
- this.delegate.pageWillUnload();
2488
- };
2489
2842
  this.delegate = delegate;
2490
2843
  }
2491
2844
  start() {
@@ -2505,6 +2858,14 @@ class PageObserver {
2505
2858
  this.started = false;
2506
2859
  }
2507
2860
  }
2861
+ interpretReadyState=() => {
2862
+ const {readyState: readyState} = this;
2863
+ if (readyState == "interactive") {
2864
+ this.pageIsInteractive();
2865
+ } else if (readyState == "complete") {
2866
+ this.pageIsComplete();
2867
+ }
2868
+ };
2508
2869
  pageIsInteractive() {
2509
2870
  if (this.stage == PageStage.loading) {
2510
2871
  this.stage = PageStage.interactive;
@@ -2518,20 +2879,17 @@ class PageObserver {
2518
2879
  this.delegate.pageLoaded();
2519
2880
  }
2520
2881
  }
2882
+ pageWillUnload=() => {
2883
+ this.delegate.pageWillUnload();
2884
+ };
2521
2885
  get readyState() {
2522
2886
  return document.readyState;
2523
2887
  }
2524
2888
  }
2525
2889
 
2526
2890
  class ScrollObserver {
2891
+ started=false;
2527
2892
  constructor(delegate) {
2528
- this.started = false;
2529
- this.onScroll = () => {
2530
- this.updatePosition({
2531
- x: window.pageXOffset,
2532
- y: window.pageYOffset
2533
- });
2534
- };
2535
2893
  this.delegate = delegate;
2536
2894
  }
2537
2895
  start() {
@@ -2547,6 +2905,12 @@ class ScrollObserver {
2547
2905
  this.started = false;
2548
2906
  }
2549
2907
  }
2908
+ onScroll=() => {
2909
+ this.updatePosition({
2910
+ x: window.pageXOffset,
2911
+ y: window.pageYOffset
2912
+ });
2913
+ };
2550
2914
  updatePosition(position) {
2551
2915
  this.delegate.scrollPositionChanged(position);
2552
2916
  }
@@ -2554,7 +2918,13 @@ class ScrollObserver {
2554
2918
 
2555
2919
  class StreamMessageRenderer {
2556
2920
  render({fragment: fragment}) {
2557
- Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => document.documentElement.appendChild(fragment)));
2921
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => {
2922
+ withAutofocusFromFragment(fragment, (() => {
2923
+ withPreservedFocus((() => {
2924
+ document.documentElement.appendChild(fragment);
2925
+ }));
2926
+ }));
2927
+ }));
2558
2928
  }
2559
2929
  enteringBardo(currentPermanentElement, newPermanentElement) {
2560
2930
  newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
@@ -2577,33 +2947,67 @@ function getPermanentElementMapForFragment(fragment) {
2577
2947
  return permanentElementMap;
2578
2948
  }
2579
2949
 
2950
+ async function withAutofocusFromFragment(fragment, callback) {
2951
+ const generatedID = `turbo-stream-autofocus-${uuid()}`;
2952
+ const turboStreams = fragment.querySelectorAll("turbo-stream");
2953
+ const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
2954
+ let willAutofocusId = null;
2955
+ if (elementWithAutofocus) {
2956
+ if (elementWithAutofocus.id) {
2957
+ willAutofocusId = elementWithAutofocus.id;
2958
+ } else {
2959
+ willAutofocusId = generatedID;
2960
+ }
2961
+ elementWithAutofocus.id = willAutofocusId;
2962
+ }
2963
+ callback();
2964
+ await nextRepaint();
2965
+ const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
2966
+ if (hasNoActiveElement && willAutofocusId) {
2967
+ const elementToAutofocus = document.getElementById(willAutofocusId);
2968
+ if (elementIsFocusable(elementToAutofocus)) {
2969
+ elementToAutofocus.focus();
2970
+ }
2971
+ if (elementToAutofocus && elementToAutofocus.id == generatedID) {
2972
+ elementToAutofocus.removeAttribute("id");
2973
+ }
2974
+ }
2975
+ }
2976
+
2977
+ async function withPreservedFocus(callback) {
2978
+ const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, (() => document.activeElement));
2979
+ const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;
2980
+ if (restoreFocusTo) {
2981
+ const elementToFocus = document.getElementById(restoreFocusTo);
2982
+ if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
2983
+ elementToFocus.focus();
2984
+ }
2985
+ }
2986
+ }
2987
+
2988
+ function firstAutofocusableElementInStreams(nodeListOfStreamElements) {
2989
+ for (const streamElement of nodeListOfStreamElements) {
2990
+ const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);
2991
+ if (elementWithAutofocus) return elementWithAutofocus;
2992
+ }
2993
+ return null;
2994
+ }
2995
+
2580
2996
  class StreamObserver {
2997
+ sources=new Set;
2998
+ #started=false;
2581
2999
  constructor(delegate) {
2582
- this.sources = new Set;
2583
- this.started = false;
2584
- this.inspectFetchResponse = event => {
2585
- const response = fetchResponseFromEvent(event);
2586
- if (response && fetchResponseIsStream(response)) {
2587
- event.preventDefault();
2588
- this.receiveMessageResponse(response);
2589
- }
2590
- };
2591
- this.receiveMessageEvent = event => {
2592
- if (this.started && typeof event.data == "string") {
2593
- this.receiveMessageHTML(event.data);
2594
- }
2595
- };
2596
3000
  this.delegate = delegate;
2597
3001
  }
2598
3002
  start() {
2599
- if (!this.started) {
2600
- this.started = true;
3003
+ if (!this.#started) {
3004
+ this.#started = true;
2601
3005
  addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2602
3006
  }
2603
3007
  }
2604
3008
  stop() {
2605
- if (this.started) {
2606
- this.started = false;
3009
+ if (this.#started) {
3010
+ this.#started = false;
2607
3011
  removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2608
3012
  }
2609
3013
  }
@@ -2622,6 +3026,18 @@ class StreamObserver {
2622
3026
  streamSourceIsConnected(source) {
2623
3027
  return this.sources.has(source);
2624
3028
  }
3029
+ inspectFetchResponse=event => {
3030
+ const response = fetchResponseFromEvent(event);
3031
+ if (response && fetchResponseIsStream(response)) {
3032
+ event.preventDefault();
3033
+ this.receiveMessageResponse(response);
3034
+ }
3035
+ };
3036
+ receiveMessageEvent=event => {
3037
+ if (this.#started && typeof event.data == "string") {
3038
+ this.receiveMessageHTML(event.data);
3039
+ }
3040
+ };
2625
3041
  async receiveMessageResponse(response) {
2626
3042
  const html = await response.responseHTML;
2627
3043
  if (html) {
@@ -2633,48 +3049,660 @@ class StreamObserver {
2633
3049
  }
2634
3050
  }
2635
3051
 
2636
- function fetchResponseFromEvent(event) {
2637
- var _a;
2638
- const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse;
2639
- if (fetchResponse instanceof FetchResponse) {
2640
- return fetchResponse;
2641
- }
3052
+ function fetchResponseFromEvent(event) {
3053
+ const fetchResponse = event.detail?.fetchResponse;
3054
+ if (fetchResponse instanceof FetchResponse) {
3055
+ return fetchResponse;
3056
+ }
3057
+ }
3058
+
3059
+ function fetchResponseIsStream(response) {
3060
+ const contentType = response.contentType ?? "";
3061
+ return contentType.startsWith(StreamMessage.contentType);
3062
+ }
3063
+
3064
+ class ErrorRenderer extends Renderer {
3065
+ static renderElement(currentElement, newElement) {
3066
+ const {documentElement: documentElement, body: body} = document;
3067
+ documentElement.replaceChild(newElement, body);
3068
+ }
3069
+ async render() {
3070
+ this.replaceHeadAndBody();
3071
+ this.activateScriptElements();
3072
+ }
3073
+ replaceHeadAndBody() {
3074
+ const {documentElement: documentElement, head: head} = document;
3075
+ documentElement.replaceChild(this.newHead, head);
3076
+ this.renderElement(this.currentElement, this.newElement);
3077
+ }
3078
+ activateScriptElements() {
3079
+ for (const replaceableElement of this.scriptElements) {
3080
+ const parentNode = replaceableElement.parentNode;
3081
+ if (parentNode) {
3082
+ const element = activateScriptElement(replaceableElement);
3083
+ parentNode.replaceChild(element, replaceableElement);
3084
+ }
3085
+ }
3086
+ }
3087
+ get newHead() {
3088
+ return this.newSnapshot.headSnapshot.element;
3089
+ }
3090
+ get scriptElements() {
3091
+ return document.documentElement.querySelectorAll("script");
3092
+ }
3093
+ }
3094
+
3095
+ var Idiomorph = function() {
3096
+ let EMPTY_SET = new Set;
3097
+ let defaults = {
3098
+ morphStyle: "outerHTML",
3099
+ callbacks: {
3100
+ beforeNodeAdded: noOp,
3101
+ afterNodeAdded: noOp,
3102
+ beforeNodeMorphed: noOp,
3103
+ afterNodeMorphed: noOp,
3104
+ beforeNodeRemoved: noOp,
3105
+ afterNodeRemoved: noOp,
3106
+ beforeAttributeUpdated: noOp
3107
+ },
3108
+ head: {
3109
+ style: "merge",
3110
+ shouldPreserve: function(elt) {
3111
+ return elt.getAttribute("im-preserve") === "true";
3112
+ },
3113
+ shouldReAppend: function(elt) {
3114
+ return elt.getAttribute("im-re-append") === "true";
3115
+ },
3116
+ shouldRemove: noOp,
3117
+ afterHeadMorphed: noOp
3118
+ }
3119
+ };
3120
+ function morph(oldNode, newContent, config = {}) {
3121
+ if (oldNode instanceof Document) {
3122
+ oldNode = oldNode.documentElement;
3123
+ }
3124
+ if (typeof newContent === "string") {
3125
+ newContent = parseContent(newContent);
3126
+ }
3127
+ let normalizedContent = normalizeContent(newContent);
3128
+ let ctx = createMorphContext(oldNode, normalizedContent, config);
3129
+ return morphNormalizedContent(oldNode, normalizedContent, ctx);
3130
+ }
3131
+ function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
3132
+ if (ctx.head.block) {
3133
+ let oldHead = oldNode.querySelector("head");
3134
+ let newHead = normalizedNewContent.querySelector("head");
3135
+ if (oldHead && newHead) {
3136
+ let promises = handleHeadElement(newHead, oldHead, ctx);
3137
+ Promise.all(promises).then((function() {
3138
+ morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
3139
+ head: {
3140
+ block: false,
3141
+ ignore: true
3142
+ }
3143
+ }));
3144
+ }));
3145
+ return;
3146
+ }
3147
+ }
3148
+ if (ctx.morphStyle === "innerHTML") {
3149
+ morphChildren(normalizedNewContent, oldNode, ctx);
3150
+ return oldNode.children;
3151
+ } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
3152
+ let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
3153
+ let previousSibling = bestMatch?.previousSibling;
3154
+ let nextSibling = bestMatch?.nextSibling;
3155
+ let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
3156
+ if (bestMatch) {
3157
+ return insertSiblings(previousSibling, morphedNode, nextSibling);
3158
+ } else {
3159
+ return [];
3160
+ }
3161
+ } else {
3162
+ throw "Do not understand how to morph style " + ctx.morphStyle;
3163
+ }
3164
+ }
3165
+ function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
3166
+ return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
3167
+ }
3168
+ function morphOldNodeTo(oldNode, newContent, ctx) {
3169
+ if (ctx.ignoreActive && oldNode === document.activeElement) ; else if (newContent == null) {
3170
+ if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
3171
+ oldNode.remove();
3172
+ ctx.callbacks.afterNodeRemoved(oldNode);
3173
+ return null;
3174
+ } else if (!isSoftMatch(oldNode, newContent)) {
3175
+ if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
3176
+ if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;
3177
+ oldNode.parentElement.replaceChild(newContent, oldNode);
3178
+ ctx.callbacks.afterNodeAdded(newContent);
3179
+ ctx.callbacks.afterNodeRemoved(oldNode);
3180
+ return newContent;
3181
+ } else {
3182
+ if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode;
3183
+ if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
3184
+ handleHeadElement(newContent, oldNode, ctx);
3185
+ } else {
3186
+ syncNodeFrom(newContent, oldNode, ctx);
3187
+ if (!ignoreValueOfActiveElement(oldNode, ctx)) {
3188
+ morphChildren(newContent, oldNode, ctx);
3189
+ }
3190
+ }
3191
+ ctx.callbacks.afterNodeMorphed(oldNode, newContent);
3192
+ return oldNode;
3193
+ }
3194
+ }
3195
+ function morphChildren(newParent, oldParent, ctx) {
3196
+ let nextNewChild = newParent.firstChild;
3197
+ let insertionPoint = oldParent.firstChild;
3198
+ let newChild;
3199
+ while (nextNewChild) {
3200
+ newChild = nextNewChild;
3201
+ nextNewChild = newChild.nextSibling;
3202
+ if (insertionPoint == null) {
3203
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
3204
+ oldParent.appendChild(newChild);
3205
+ ctx.callbacks.afterNodeAdded(newChild);
3206
+ removeIdsFromConsideration(ctx, newChild);
3207
+ continue;
3208
+ }
3209
+ if (isIdSetMatch(newChild, insertionPoint, ctx)) {
3210
+ morphOldNodeTo(insertionPoint, newChild, ctx);
3211
+ insertionPoint = insertionPoint.nextSibling;
3212
+ removeIdsFromConsideration(ctx, newChild);
3213
+ continue;
3214
+ }
3215
+ let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
3216
+ if (idSetMatch) {
3217
+ insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
3218
+ morphOldNodeTo(idSetMatch, newChild, ctx);
3219
+ removeIdsFromConsideration(ctx, newChild);
3220
+ continue;
3221
+ }
3222
+ let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
3223
+ if (softMatch) {
3224
+ insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
3225
+ morphOldNodeTo(softMatch, newChild, ctx);
3226
+ removeIdsFromConsideration(ctx, newChild);
3227
+ continue;
3228
+ }
3229
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
3230
+ oldParent.insertBefore(newChild, insertionPoint);
3231
+ ctx.callbacks.afterNodeAdded(newChild);
3232
+ removeIdsFromConsideration(ctx, newChild);
3233
+ }
3234
+ while (insertionPoint !== null) {
3235
+ let tempNode = insertionPoint;
3236
+ insertionPoint = insertionPoint.nextSibling;
3237
+ removeNode(tempNode, ctx);
3238
+ }
3239
+ }
3240
+ function ignoreAttribute(attr, to, updateType, ctx) {
3241
+ if (attr === "value" && ctx.ignoreActiveValue && to === document.activeElement) {
3242
+ return true;
3243
+ }
3244
+ return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
3245
+ }
3246
+ function syncNodeFrom(from, to, ctx) {
3247
+ let type = from.nodeType;
3248
+ if (type === 1) {
3249
+ const fromAttributes = from.attributes;
3250
+ const toAttributes = to.attributes;
3251
+ for (const fromAttribute of fromAttributes) {
3252
+ if (ignoreAttribute(fromAttribute.name, to, "update", ctx)) {
3253
+ continue;
3254
+ }
3255
+ if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
3256
+ to.setAttribute(fromAttribute.name, fromAttribute.value);
3257
+ }
3258
+ }
3259
+ for (let i = toAttributes.length - 1; 0 <= i; i--) {
3260
+ const toAttribute = toAttributes[i];
3261
+ if (ignoreAttribute(toAttribute.name, to, "remove", ctx)) {
3262
+ continue;
3263
+ }
3264
+ if (!from.hasAttribute(toAttribute.name)) {
3265
+ to.removeAttribute(toAttribute.name);
3266
+ }
3267
+ }
3268
+ }
3269
+ if (type === 8 || type === 3) {
3270
+ if (to.nodeValue !== from.nodeValue) {
3271
+ to.nodeValue = from.nodeValue;
3272
+ }
3273
+ }
3274
+ if (!ignoreValueOfActiveElement(to, ctx)) {
3275
+ syncInputValue(from, to, ctx);
3276
+ }
3277
+ }
3278
+ function syncBooleanAttribute(from, to, attributeName, ctx) {
3279
+ if (from[attributeName] !== to[attributeName]) {
3280
+ let ignoreUpdate = ignoreAttribute(attributeName, to, "update", ctx);
3281
+ if (!ignoreUpdate) {
3282
+ to[attributeName] = from[attributeName];
3283
+ }
3284
+ if (from[attributeName]) {
3285
+ if (!ignoreUpdate) {
3286
+ to.setAttribute(attributeName, from[attributeName]);
3287
+ }
3288
+ } else {
3289
+ if (!ignoreAttribute(attributeName, to, "remove", ctx)) {
3290
+ to.removeAttribute(attributeName);
3291
+ }
3292
+ }
3293
+ }
3294
+ }
3295
+ function syncInputValue(from, to, ctx) {
3296
+ if (from instanceof HTMLInputElement && to instanceof HTMLInputElement && from.type !== "file") {
3297
+ let fromValue = from.value;
3298
+ let toValue = to.value;
3299
+ syncBooleanAttribute(from, to, "checked", ctx);
3300
+ syncBooleanAttribute(from, to, "disabled", ctx);
3301
+ if (!from.hasAttribute("value")) {
3302
+ if (!ignoreAttribute("value", to, "remove", ctx)) {
3303
+ to.value = "";
3304
+ to.removeAttribute("value");
3305
+ }
3306
+ } else if (fromValue !== toValue) {
3307
+ if (!ignoreAttribute("value", to, "update", ctx)) {
3308
+ to.setAttribute("value", fromValue);
3309
+ to.value = fromValue;
3310
+ }
3311
+ }
3312
+ } else if (from instanceof HTMLOptionElement) {
3313
+ syncBooleanAttribute(from, to, "selected", ctx);
3314
+ } else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
3315
+ let fromValue = from.value;
3316
+ let toValue = to.value;
3317
+ if (ignoreAttribute("value", to, "update", ctx)) {
3318
+ return;
3319
+ }
3320
+ if (fromValue !== toValue) {
3321
+ to.value = fromValue;
3322
+ }
3323
+ if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
3324
+ to.firstChild.nodeValue = fromValue;
3325
+ }
3326
+ }
3327
+ }
3328
+ function handleHeadElement(newHeadTag, currentHead, ctx) {
3329
+ let added = [];
3330
+ let removed = [];
3331
+ let preserved = [];
3332
+ let nodesToAppend = [];
3333
+ let headMergeStyle = ctx.head.style;
3334
+ let srcToNewHeadNodes = new Map;
3335
+ for (const newHeadChild of newHeadTag.children) {
3336
+ srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
3337
+ }
3338
+ for (const currentHeadElt of currentHead.children) {
3339
+ let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
3340
+ let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
3341
+ let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
3342
+ if (inNewContent || isPreserved) {
3343
+ if (isReAppended) {
3344
+ removed.push(currentHeadElt);
3345
+ } else {
3346
+ srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
3347
+ preserved.push(currentHeadElt);
3348
+ }
3349
+ } else {
3350
+ if (headMergeStyle === "append") {
3351
+ if (isReAppended) {
3352
+ removed.push(currentHeadElt);
3353
+ nodesToAppend.push(currentHeadElt);
3354
+ }
3355
+ } else {
3356
+ if (ctx.head.shouldRemove(currentHeadElt) !== false) {
3357
+ removed.push(currentHeadElt);
3358
+ }
3359
+ }
3360
+ }
3361
+ }
3362
+ nodesToAppend.push(...srcToNewHeadNodes.values());
3363
+ let promises = [];
3364
+ for (const newNode of nodesToAppend) {
3365
+ let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
3366
+ if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
3367
+ if (newElt.href || newElt.src) {
3368
+ let resolve = null;
3369
+ let promise = new Promise((function(_resolve) {
3370
+ resolve = _resolve;
3371
+ }));
3372
+ newElt.addEventListener("load", (function() {
3373
+ resolve();
3374
+ }));
3375
+ promises.push(promise);
3376
+ }
3377
+ currentHead.appendChild(newElt);
3378
+ ctx.callbacks.afterNodeAdded(newElt);
3379
+ added.push(newElt);
3380
+ }
3381
+ }
3382
+ for (const removedElement of removed) {
3383
+ if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
3384
+ currentHead.removeChild(removedElement);
3385
+ ctx.callbacks.afterNodeRemoved(removedElement);
3386
+ }
3387
+ }
3388
+ ctx.head.afterHeadMorphed(currentHead, {
3389
+ added: added,
3390
+ kept: preserved,
3391
+ removed: removed
3392
+ });
3393
+ return promises;
3394
+ }
3395
+ function noOp() {}
3396
+ function mergeDefaults(config) {
3397
+ let finalConfig = {};
3398
+ Object.assign(finalConfig, defaults);
3399
+ Object.assign(finalConfig, config);
3400
+ finalConfig.callbacks = {};
3401
+ Object.assign(finalConfig.callbacks, defaults.callbacks);
3402
+ Object.assign(finalConfig.callbacks, config.callbacks);
3403
+ finalConfig.head = {};
3404
+ Object.assign(finalConfig.head, defaults.head);
3405
+ Object.assign(finalConfig.head, config.head);
3406
+ return finalConfig;
3407
+ }
3408
+ function createMorphContext(oldNode, newContent, config) {
3409
+ config = mergeDefaults(config);
3410
+ return {
3411
+ target: oldNode,
3412
+ newContent: newContent,
3413
+ config: config,
3414
+ morphStyle: config.morphStyle,
3415
+ ignoreActive: config.ignoreActive,
3416
+ ignoreActiveValue: config.ignoreActiveValue,
3417
+ idMap: createIdMap(oldNode, newContent),
3418
+ deadIds: new Set,
3419
+ callbacks: config.callbacks,
3420
+ head: config.head
3421
+ };
3422
+ }
3423
+ function isIdSetMatch(node1, node2, ctx) {
3424
+ if (node1 == null || node2 == null) {
3425
+ return false;
3426
+ }
3427
+ if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
3428
+ if (node1.id !== "" && node1.id === node2.id) {
3429
+ return true;
3430
+ } else {
3431
+ return getIdIntersectionCount(ctx, node1, node2) > 0;
3432
+ }
3433
+ }
3434
+ return false;
3435
+ }
3436
+ function isSoftMatch(node1, node2) {
3437
+ if (node1 == null || node2 == null) {
3438
+ return false;
3439
+ }
3440
+ return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName;
3441
+ }
3442
+ function removeNodesBetween(startInclusive, endExclusive, ctx) {
3443
+ while (startInclusive !== endExclusive) {
3444
+ let tempNode = startInclusive;
3445
+ startInclusive = startInclusive.nextSibling;
3446
+ removeNode(tempNode, ctx);
3447
+ }
3448
+ removeIdsFromConsideration(ctx, endExclusive);
3449
+ return endExclusive.nextSibling;
3450
+ }
3451
+ function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
3452
+ let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
3453
+ let potentialMatch = null;
3454
+ if (newChildPotentialIdCount > 0) {
3455
+ let potentialMatch = insertionPoint;
3456
+ let otherMatchCount = 0;
3457
+ while (potentialMatch != null) {
3458
+ if (isIdSetMatch(newChild, potentialMatch, ctx)) {
3459
+ return potentialMatch;
3460
+ }
3461
+ otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
3462
+ if (otherMatchCount > newChildPotentialIdCount) {
3463
+ return null;
3464
+ }
3465
+ potentialMatch = potentialMatch.nextSibling;
3466
+ }
3467
+ }
3468
+ return potentialMatch;
3469
+ }
3470
+ function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
3471
+ let potentialSoftMatch = insertionPoint;
3472
+ let nextSibling = newChild.nextSibling;
3473
+ let siblingSoftMatchCount = 0;
3474
+ while (potentialSoftMatch != null) {
3475
+ if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
3476
+ return null;
3477
+ }
3478
+ if (isSoftMatch(newChild, potentialSoftMatch)) {
3479
+ return potentialSoftMatch;
3480
+ }
3481
+ if (isSoftMatch(nextSibling, potentialSoftMatch)) {
3482
+ siblingSoftMatchCount++;
3483
+ nextSibling = nextSibling.nextSibling;
3484
+ if (siblingSoftMatchCount >= 2) {
3485
+ return null;
3486
+ }
3487
+ }
3488
+ potentialSoftMatch = potentialSoftMatch.nextSibling;
3489
+ }
3490
+ return potentialSoftMatch;
3491
+ }
3492
+ function parseContent(newContent) {
3493
+ let parser = new DOMParser;
3494
+ let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
3495
+ if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
3496
+ let content = parser.parseFromString(newContent, "text/html");
3497
+ if (contentWithSvgsRemoved.match(/<\/html>/)) {
3498
+ content.generatedByIdiomorph = true;
3499
+ return content;
3500
+ } else {
3501
+ let htmlElement = content.firstChild;
3502
+ if (htmlElement) {
3503
+ htmlElement.generatedByIdiomorph = true;
3504
+ return htmlElement;
3505
+ } else {
3506
+ return null;
3507
+ }
3508
+ }
3509
+ } else {
3510
+ let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
3511
+ let content = responseDoc.body.querySelector("template").content;
3512
+ content.generatedByIdiomorph = true;
3513
+ return content;
3514
+ }
3515
+ }
3516
+ function normalizeContent(newContent) {
3517
+ if (newContent == null) {
3518
+ const dummyParent = document.createElement("div");
3519
+ return dummyParent;
3520
+ } else if (newContent.generatedByIdiomorph) {
3521
+ return newContent;
3522
+ } else if (newContent instanceof Node) {
3523
+ const dummyParent = document.createElement("div");
3524
+ dummyParent.append(newContent);
3525
+ return dummyParent;
3526
+ } else {
3527
+ const dummyParent = document.createElement("div");
3528
+ for (const elt of [ ...newContent ]) {
3529
+ dummyParent.append(elt);
3530
+ }
3531
+ return dummyParent;
3532
+ }
3533
+ }
3534
+ function insertSiblings(previousSibling, morphedNode, nextSibling) {
3535
+ let stack = [];
3536
+ let added = [];
3537
+ while (previousSibling != null) {
3538
+ stack.push(previousSibling);
3539
+ previousSibling = previousSibling.previousSibling;
3540
+ }
3541
+ while (stack.length > 0) {
3542
+ let node = stack.pop();
3543
+ added.push(node);
3544
+ morphedNode.parentElement.insertBefore(node, morphedNode);
3545
+ }
3546
+ added.push(morphedNode);
3547
+ while (nextSibling != null) {
3548
+ stack.push(nextSibling);
3549
+ added.push(nextSibling);
3550
+ nextSibling = nextSibling.nextSibling;
3551
+ }
3552
+ while (stack.length > 0) {
3553
+ morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
3554
+ }
3555
+ return added;
3556
+ }
3557
+ function findBestNodeMatch(newContent, oldNode, ctx) {
3558
+ let currentElement;
3559
+ currentElement = newContent.firstChild;
3560
+ let bestElement = currentElement;
3561
+ let score = 0;
3562
+ while (currentElement) {
3563
+ let newScore = scoreElement(currentElement, oldNode, ctx);
3564
+ if (newScore > score) {
3565
+ bestElement = currentElement;
3566
+ score = newScore;
3567
+ }
3568
+ currentElement = currentElement.nextSibling;
3569
+ }
3570
+ return bestElement;
3571
+ }
3572
+ function scoreElement(node1, node2, ctx) {
3573
+ if (isSoftMatch(node1, node2)) {
3574
+ return .5 + getIdIntersectionCount(ctx, node1, node2);
3575
+ }
3576
+ return 0;
3577
+ }
3578
+ function removeNode(tempNode, ctx) {
3579
+ removeIdsFromConsideration(ctx, tempNode);
3580
+ if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
3581
+ tempNode.remove();
3582
+ ctx.callbacks.afterNodeRemoved(tempNode);
3583
+ }
3584
+ function isIdInConsideration(ctx, id) {
3585
+ return !ctx.deadIds.has(id);
3586
+ }
3587
+ function idIsWithinNode(ctx, id, targetNode) {
3588
+ let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
3589
+ return idSet.has(id);
3590
+ }
3591
+ function removeIdsFromConsideration(ctx, node) {
3592
+ let idSet = ctx.idMap.get(node) || EMPTY_SET;
3593
+ for (const id of idSet) {
3594
+ ctx.deadIds.add(id);
3595
+ }
3596
+ }
3597
+ function getIdIntersectionCount(ctx, node1, node2) {
3598
+ let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
3599
+ let matchCount = 0;
3600
+ for (const id of sourceSet) {
3601
+ if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
3602
+ ++matchCount;
3603
+ }
3604
+ }
3605
+ return matchCount;
3606
+ }
3607
+ function populateIdMapForNode(node, idMap) {
3608
+ let nodeParent = node.parentElement;
3609
+ let idElements = node.querySelectorAll("[id]");
3610
+ for (const elt of idElements) {
3611
+ let current = elt;
3612
+ while (current !== nodeParent && current != null) {
3613
+ let idSet = idMap.get(current);
3614
+ if (idSet == null) {
3615
+ idSet = new Set;
3616
+ idMap.set(current, idSet);
3617
+ }
3618
+ idSet.add(elt.id);
3619
+ current = current.parentElement;
3620
+ }
3621
+ }
3622
+ }
3623
+ function createIdMap(oldContent, newContent) {
3624
+ let idMap = new Map;
3625
+ populateIdMapForNode(oldContent, idMap);
3626
+ populateIdMapForNode(newContent, idMap);
3627
+ return idMap;
3628
+ }
3629
+ return {
3630
+ morph: morph,
3631
+ defaults: defaults
3632
+ };
3633
+ }();
3634
+
3635
+ function morphElements(currentElement, newElement, {callbacks: callbacks, ...options} = {}) {
3636
+ Idiomorph.morph(currentElement, newElement, {
3637
+ ...options,
3638
+ callbacks: new DefaultIdiomorphCallbacks(callbacks)
3639
+ });
3640
+ }
3641
+
3642
+ function morphChildren(currentElement, newElement) {
3643
+ morphElements(currentElement, newElement.children, {
3644
+ morphStyle: "innerHTML"
3645
+ });
2642
3646
  }
2643
3647
 
2644
- function fetchResponseIsStream(response) {
2645
- var _a;
2646
- const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
2647
- return contentType.startsWith(StreamMessage.contentType);
3648
+ class DefaultIdiomorphCallbacks {
3649
+ #beforeNodeMorphed;
3650
+ constructor({beforeNodeMorphed: beforeNodeMorphed} = {}) {
3651
+ this.#beforeNodeMorphed = beforeNodeMorphed || (() => true);
3652
+ }
3653
+ beforeNodeAdded=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
3654
+ beforeNodeMorphed=(currentElement, newElement) => {
3655
+ if (currentElement instanceof Element) {
3656
+ if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) {
3657
+ const event = dispatch("turbo:before-morph-element", {
3658
+ cancelable: true,
3659
+ target: currentElement,
3660
+ detail: {
3661
+ currentElement: currentElement,
3662
+ newElement: newElement
3663
+ }
3664
+ });
3665
+ return !event.defaultPrevented;
3666
+ } else {
3667
+ return false;
3668
+ }
3669
+ }
3670
+ };
3671
+ beforeAttributeUpdated=(attributeName, target, mutationType) => {
3672
+ const event = dispatch("turbo:before-morph-attribute", {
3673
+ cancelable: true,
3674
+ target: target,
3675
+ detail: {
3676
+ attributeName: attributeName,
3677
+ mutationType: mutationType
3678
+ }
3679
+ });
3680
+ return !event.defaultPrevented;
3681
+ };
3682
+ beforeNodeRemoved=node => this.beforeNodeMorphed(node);
3683
+ afterNodeMorphed=(currentElement, newElement) => {
3684
+ if (currentElement instanceof Element) {
3685
+ dispatch("turbo:morph-element", {
3686
+ target: currentElement,
3687
+ detail: {
3688
+ currentElement: currentElement,
3689
+ newElement: newElement
3690
+ }
3691
+ });
3692
+ }
3693
+ };
2648
3694
  }
2649
3695
 
2650
- class ErrorRenderer extends Renderer {
3696
+ class MorphingFrameRenderer extends FrameRenderer {
2651
3697
  static renderElement(currentElement, newElement) {
2652
- const {documentElement: documentElement, body: body} = document;
2653
- documentElement.replaceChild(newElement, body);
2654
- }
2655
- async render() {
2656
- this.replaceHeadAndBody();
2657
- this.activateScriptElements();
2658
- }
2659
- replaceHeadAndBody() {
2660
- const {documentElement: documentElement, head: head} = document;
2661
- documentElement.replaceChild(this.newHead, head);
2662
- this.renderElement(this.currentElement, this.newElement);
2663
- }
2664
- activateScriptElements() {
2665
- for (const replaceableElement of this.scriptElements) {
2666
- const parentNode = replaceableElement.parentNode;
2667
- if (parentNode) {
2668
- const element = activateScriptElement(replaceableElement);
2669
- parentNode.replaceChild(element, replaceableElement);
3698
+ dispatch("turbo:before-frame-morph", {
3699
+ target: currentElement,
3700
+ detail: {
3701
+ currentElement: currentElement,
3702
+ newElement: newElement
2670
3703
  }
2671
- }
2672
- }
2673
- get newHead() {
2674
- return this.newSnapshot.headSnapshot.element;
2675
- }
2676
- get scriptElements() {
2677
- return document.documentElement.querySelectorAll("script");
3704
+ });
3705
+ morphChildren(currentElement, newElement);
2678
3706
  }
2679
3707
  }
2680
3708
 
@@ -2702,6 +3730,7 @@ class PageRenderer extends Renderer {
2702
3730
  }
2703
3731
  }
2704
3732
  async prepareToRender() {
3733
+ this.#setLanguage();
2705
3734
  await this.mergeHead();
2706
3735
  }
2707
3736
  async render() {
@@ -2724,12 +3753,24 @@ class PageRenderer extends Renderer {
2724
3753
  get newElement() {
2725
3754
  return this.newSnapshot.element;
2726
3755
  }
3756
+ #setLanguage() {
3757
+ const {documentElement: documentElement} = this.currentSnapshot;
3758
+ const {lang: lang} = this.newSnapshot;
3759
+ if (lang) {
3760
+ documentElement.setAttribute("lang", lang);
3761
+ } else {
3762
+ documentElement.removeAttribute("lang");
3763
+ }
3764
+ }
2727
3765
  async mergeHead() {
2728
3766
  const mergedHeadElements = this.mergeProvisionalElements();
2729
3767
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
2730
3768
  this.copyNewHeadScriptElements();
2731
3769
  await mergedHeadElements;
2732
3770
  await newStylesheetElements;
3771
+ if (this.willRender) {
3772
+ this.removeUnusedDynamicStylesheetElements();
3773
+ }
2733
3774
  }
2734
3775
  async replaceBody() {
2735
3776
  await this.preservingPermanentElements((async () => {
@@ -2753,6 +3794,11 @@ class PageRenderer extends Renderer {
2753
3794
  document.head.appendChild(activateScriptElement(element));
2754
3795
  }
2755
3796
  }
3797
+ removeUnusedDynamicStylesheetElements() {
3798
+ for (const element of this.unusedDynamicStylesheetElements) {
3799
+ document.head.removeChild(element);
3800
+ }
3801
+ }
2756
3802
  async mergeProvisionalElements() {
2757
3803
  const newHeadElements = [ ...this.newHeadProvisionalElements ];
2758
3804
  for (const element of this.currentHeadProvisionalElements) {
@@ -2805,6 +3851,12 @@ class PageRenderer extends Renderer {
2805
3851
  async assignNewBody() {
2806
3852
  await this.renderElement(this.currentElement, this.newElement);
2807
3853
  }
3854
+ get unusedDynamicStylesheetElements() {
3855
+ return this.oldHeadStylesheetElements.filter((element => element.getAttribute("data-turbo-track") === "dynamic"));
3856
+ }
3857
+ get oldHeadStylesheetElements() {
3858
+ return this.currentHeadSnapshot.getStylesheetElementsNotInSnapshot(this.newHeadSnapshot);
3859
+ }
2808
3860
  get newHeadStylesheetElements() {
2809
3861
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
2810
3862
  }
@@ -2822,10 +3874,51 @@ class PageRenderer extends Renderer {
2822
3874
  }
2823
3875
  }
2824
3876
 
3877
+ class MorphingPageRenderer extends PageRenderer {
3878
+ static renderElement(currentElement, newElement) {
3879
+ morphElements(currentElement, newElement, {
3880
+ callbacks: {
3881
+ beforeNodeMorphed: element => !canRefreshFrame(element)
3882
+ }
3883
+ });
3884
+ for (const frame of currentElement.querySelectorAll("turbo-frame")) {
3885
+ if (canRefreshFrame(frame)) refreshFrame(frame);
3886
+ }
3887
+ dispatch("turbo:morph", {
3888
+ detail: {
3889
+ currentElement: currentElement,
3890
+ newElement: newElement
3891
+ }
3892
+ });
3893
+ }
3894
+ async preservingPermanentElements(callback) {
3895
+ return await callback();
3896
+ }
3897
+ get renderMethod() {
3898
+ return "morph";
3899
+ }
3900
+ get shouldAutofocus() {
3901
+ return false;
3902
+ }
3903
+ }
3904
+
3905
+ function canRefreshFrame(frame) {
3906
+ return frame instanceof FrameElement && frame.src && frame.refresh === "morph" && !frame.closest("[data-turbo-permanent]");
3907
+ }
3908
+
3909
+ function refreshFrame(frame) {
3910
+ frame.addEventListener("turbo:before-frame-render", (({detail: detail}) => {
3911
+ detail.render = MorphingFrameRenderer.renderElement;
3912
+ }), {
3913
+ once: true
3914
+ });
3915
+ frame.reload();
3916
+ }
3917
+
2825
3918
  class SnapshotCache {
3919
+ keys=[];
3920
+ snapshots={};
2826
3921
  constructor(size) {
2827
- this.keys = [];
2828
- this.snapshots = {};
2829
3922
  this.size = size;
2830
3923
  }
2831
3924
  has(location) {
@@ -2867,23 +3960,25 @@ class SnapshotCache {
2867
3960
  }
2868
3961
 
2869
3962
  class PageView extends View {
2870
- constructor() {
2871
- super(...arguments);
2872
- this.snapshotCache = new SnapshotCache(10);
2873
- this.lastRenderedLocation = new URL(location.href);
2874
- this.forceReloaded = false;
3963
+ snapshotCache=new SnapshotCache(10);
3964
+ lastRenderedLocation=new URL(location.href);
3965
+ forceReloaded=false;
3966
+ shouldTransitionTo(newSnapshot) {
3967
+ return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions;
2875
3968
  }
2876
3969
  renderPage(snapshot, isPreview = false, willRender = true, visit) {
2877
- const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
3970
+ const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
3971
+ const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
3972
+ const renderer = new rendererClass(this.snapshot, snapshot, rendererClass.renderElement, isPreview, willRender);
2878
3973
  if (!renderer.shouldRender) {
2879
3974
  this.forceReloaded = true;
2880
3975
  } else {
2881
- visit === null || visit === void 0 ? void 0 : visit.changeHistory();
3976
+ visit?.changeHistory();
2882
3977
  }
2883
3978
  return this.render(renderer);
2884
3979
  }
2885
3980
  renderError(snapshot, visit) {
2886
- visit === null || visit === void 0 ? void 0 : visit.changeHistory();
3981
+ visit?.changeHistory();
2887
3982
  const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
2888
3983
  return this.render(renderer);
2889
3984
  }
@@ -2903,31 +3998,38 @@ class PageView extends View {
2903
3998
  getCachedSnapshotForLocation(location) {
2904
3999
  return this.snapshotCache.get(location);
2905
4000
  }
4001
+ isPageRefresh(visit) {
4002
+ return !visit || this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === "replace";
4003
+ }
4004
+ shouldPreserveScrollPosition(visit) {
4005
+ return this.isPageRefresh(visit) && this.snapshot.shouldPreserveScrollPosition;
4006
+ }
2906
4007
  get snapshot() {
2907
4008
  return PageSnapshot.fromElement(this.element);
2908
4009
  }
2909
4010
  }
2910
4011
 
2911
4012
  class Preloader {
2912
- constructor(delegate) {
2913
- this.selector = "a[data-turbo-preload]";
4013
+ selector="a[data-turbo-preload]";
4014
+ constructor(delegate, snapshotCache) {
2914
4015
  this.delegate = delegate;
2915
- }
2916
- get snapshotCache() {
2917
- return this.delegate.navigator.view.snapshotCache;
4016
+ this.snapshotCache = snapshotCache;
2918
4017
  }
2919
4018
  start() {
2920
4019
  if (document.readyState === "loading") {
2921
- return document.addEventListener("DOMContentLoaded", (() => {
2922
- this.preloadOnLoadLinksForView(document.body);
2923
- }));
4020
+ document.addEventListener("DOMContentLoaded", this.#preloadAll);
2924
4021
  } else {
2925
4022
  this.preloadOnLoadLinksForView(document.body);
2926
4023
  }
2927
4024
  }
4025
+ stop() {
4026
+ document.removeEventListener("DOMContentLoaded", this.#preloadAll);
4027
+ }
2928
4028
  preloadOnLoadLinksForView(element) {
2929
4029
  for (const link of element.querySelectorAll(this.selector)) {
2930
- this.preloadURL(link);
4030
+ if (this.delegate.shouldPreloadLink(link)) {
4031
+ this.preloadURL(link);
4032
+ }
2931
4033
  }
2932
4034
  }
2933
4035
  async preloadURL(link) {
@@ -2935,46 +4037,83 @@ class Preloader {
2935
4037
  if (this.snapshotCache.has(location)) {
2936
4038
  return;
2937
4039
  }
4040
+ const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, link);
4041
+ await fetchRequest.perform();
4042
+ }
4043
+ prepareRequest(fetchRequest) {
4044
+ fetchRequest.headers["X-Sec-Purpose"] = "prefetch";
4045
+ }
4046
+ async requestSucceededWithResponse(fetchRequest, fetchResponse) {
2938
4047
  try {
2939
- const response = await fetch(location.toString(), {
2940
- headers: {
2941
- "VND.PREFETCH": "true",
2942
- Accept: "text/html"
2943
- }
2944
- });
2945
- const responseText = await response.text();
2946
- const snapshot = PageSnapshot.fromHTMLString(responseText);
2947
- this.snapshotCache.put(location, snapshot);
4048
+ const responseHTML = await fetchResponse.responseHTML;
4049
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
4050
+ this.snapshotCache.put(fetchRequest.url, snapshot);
2948
4051
  } catch (_) {}
2949
4052
  }
4053
+ requestStarted(fetchRequest) {}
4054
+ requestErrored(fetchRequest) {}
4055
+ requestFinished(fetchRequest) {}
4056
+ requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
4057
+ requestFailedWithResponse(fetchRequest, fetchResponse) {}
4058
+ #preloadAll=() => {
4059
+ this.preloadOnLoadLinksForView(document.body);
4060
+ };
4061
+ }
4062
+
4063
+ class Cache {
4064
+ constructor(session) {
4065
+ this.session = session;
4066
+ }
4067
+ clear() {
4068
+ this.session.clearCache();
4069
+ }
4070
+ resetCacheControl() {
4071
+ this.#setCacheControl("");
4072
+ }
4073
+ exemptPageFromCache() {
4074
+ this.#setCacheControl("no-cache");
4075
+ }
4076
+ exemptPageFromPreview() {
4077
+ this.#setCacheControl("no-preview");
4078
+ }
4079
+ #setCacheControl(value) {
4080
+ setMetaContent("turbo-cache-control", value);
4081
+ }
2950
4082
  }
2951
4083
 
2952
4084
  class Session {
2953
- constructor() {
2954
- this.navigator = new Navigator(this);
2955
- this.history = new History(this);
2956
- this.preloader = new Preloader(this);
2957
- this.view = new PageView(this, document.documentElement);
2958
- this.adapter = new BrowserAdapter(this);
2959
- this.pageObserver = new PageObserver(this);
2960
- this.cacheObserver = new CacheObserver;
2961
- this.linkClickObserver = new LinkClickObserver(this, window);
2962
- this.formSubmitObserver = new FormSubmitObserver(this, document);
2963
- this.scrollObserver = new ScrollObserver(this);
2964
- this.streamObserver = new StreamObserver(this);
2965
- this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
2966
- this.frameRedirector = new FrameRedirector(this, document.documentElement);
2967
- this.streamMessageRenderer = new StreamMessageRenderer;
2968
- this.drive = true;
2969
- this.enabled = true;
2970
- this.progressBarDelay = 500;
2971
- this.started = false;
2972
- this.formMode = "on";
4085
+ navigator=new Navigator(this);
4086
+ history=new History(this);
4087
+ view=new PageView(this, document.documentElement);
4088
+ adapter=new BrowserAdapter(this);
4089
+ pageObserver=new PageObserver(this);
4090
+ cacheObserver=new CacheObserver;
4091
+ linkPrefetchObserver=new LinkPrefetchObserver(this, document);
4092
+ linkClickObserver=new LinkClickObserver(this, window);
4093
+ formSubmitObserver=new FormSubmitObserver(this, document);
4094
+ scrollObserver=new ScrollObserver(this);
4095
+ streamObserver=new StreamObserver(this);
4096
+ formLinkClickObserver=new FormLinkClickObserver(this, document.documentElement);
4097
+ frameRedirector=new FrameRedirector(this, document.documentElement);
4098
+ streamMessageRenderer=new StreamMessageRenderer;
4099
+ cache=new Cache(this);
4100
+ drive=true;
4101
+ enabled=true;
4102
+ progressBarDelay=500;
4103
+ started=false;
4104
+ formMode="on";
4105
+ #pageRefreshDebouncePeriod=150;
4106
+ constructor(recentRequests) {
4107
+ this.recentRequests = recentRequests;
4108
+ this.preloader = new Preloader(this, this.view.snapshotCache);
4109
+ this.debouncedRefresh = this.refresh;
4110
+ this.pageRefreshDebouncePeriod = this.pageRefreshDebouncePeriod;
2973
4111
  }
2974
4112
  start() {
2975
4113
  if (!this.started) {
2976
4114
  this.pageObserver.start();
2977
4115
  this.cacheObserver.start();
4116
+ this.linkPrefetchObserver.start();
2978
4117
  this.formLinkClickObserver.start();
2979
4118
  this.linkClickObserver.start();
2980
4119
  this.formSubmitObserver.start();
@@ -2994,6 +4133,7 @@ class Session {
2994
4133
  if (this.started) {
2995
4134
  this.pageObserver.stop();
2996
4135
  this.cacheObserver.stop();
4136
+ this.linkPrefetchObserver.stop();
2997
4137
  this.formLinkClickObserver.stop();
2998
4138
  this.linkClickObserver.stop();
2999
4139
  this.formSubmitObserver.stop();
@@ -3001,6 +4141,7 @@ class Session {
3001
4141
  this.streamObserver.stop();
3002
4142
  this.frameRedirector.stop();
3003
4143
  this.history.stop();
4144
+ this.preloader.stop();
3004
4145
  this.started = false;
3005
4146
  }
3006
4147
  }
@@ -3010,12 +4151,22 @@ class Session {
3010
4151
  visit(location, options = {}) {
3011
4152
  const frameElement = options.frame ? document.getElementById(options.frame) : null;
3012
4153
  if (frameElement instanceof FrameElement) {
4154
+ const action = options.action || getVisitAction(frameElement);
4155
+ frameElement.delegate.proposeVisitIfNavigatedWithAction(frameElement, action);
3013
4156
  frameElement.src = location.toString();
3014
- frameElement.loaded;
3015
4157
  } else {
3016
4158
  this.navigator.proposeVisit(expandURL(location), options);
3017
4159
  }
3018
4160
  }
4161
+ refresh(url, requestId) {
4162
+ const isRecentRequest = requestId && this.recentRequests.has(requestId);
4163
+ if (!isRecentRequest && !this.navigator.currentVisit) {
4164
+ this.visit(url, {
4165
+ action: "replace",
4166
+ shouldCacheSnapshot: false
4167
+ });
4168
+ }
4169
+ }
3019
4170
  connectStreamSource(source) {
3020
4171
  this.streamObserver.connectStreamSource(source);
3021
4172
  }
@@ -3040,11 +4191,31 @@ class Session {
3040
4191
  get restorationIdentifier() {
3041
4192
  return this.history.restorationIdentifier;
3042
4193
  }
3043
- historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
4194
+ get pageRefreshDebouncePeriod() {
4195
+ return this.#pageRefreshDebouncePeriod;
4196
+ }
4197
+ set pageRefreshDebouncePeriod(value) {
4198
+ this.refresh = debounce(this.debouncedRefresh.bind(this), value);
4199
+ this.#pageRefreshDebouncePeriod = value;
4200
+ }
4201
+ shouldPreloadLink(element) {
4202
+ const isUnsafe = element.hasAttribute("data-turbo-method");
4203
+ const isStream = element.hasAttribute("data-turbo-stream");
4204
+ const frameTarget = element.getAttribute("data-turbo-frame");
4205
+ const frame = frameTarget == "_top" ? null : document.getElementById(frameTarget) || findClosestRecursively(element, "turbo-frame:not([disabled])");
4206
+ if (isUnsafe || isStream || frame instanceof FrameElement) {
4207
+ return false;
4208
+ } else {
4209
+ const location = new URL(element.href);
4210
+ return this.elementIsNavigatable(element) && locationIsVisitable(location, this.snapshot.rootLocation);
4211
+ }
4212
+ }
4213
+ historyPoppedToLocationWithRestorationIdentifierAndDirection(location, restorationIdentifier, direction) {
3044
4214
  if (this.enabled) {
3045
4215
  this.navigator.startVisit(location, restorationIdentifier, {
3046
4216
  action: "restore",
3047
- historyChanged: true
4217
+ historyChanged: true,
4218
+ direction: direction
3048
4219
  });
3049
4220
  } else {
3050
4221
  this.adapter.pageInvalidated({
@@ -3061,6 +4232,9 @@ class Session {
3061
4232
  return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
3062
4233
  }
3063
4234
  submittedFormLinkToLocation() {}
4235
+ canPrefetchRequestToLocation(link, location) {
4236
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
4237
+ }
3064
4238
  willFollowLinkToLocation(link, location, event) {
3065
4239
  return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
3066
4240
  }
@@ -3082,6 +4256,7 @@ class Session {
3082
4256
  visitStarted(visit) {
3083
4257
  if (!visit.acceptsStreamResponse) {
3084
4258
  markAsBusy(document.documentElement);
4259
+ this.view.markVisitDirection(visit.direction);
3085
4260
  }
3086
4261
  extendURLWithDeprecatedProperties(visit.location);
3087
4262
  if (!visit.silent) {
@@ -3089,6 +4264,7 @@ class Session {
3089
4264
  }
3090
4265
  }
3091
4266
  visitCompleted(visit) {
4267
+ this.view.unmarkVisitDirection();
3092
4268
  clearBusyState(document.documentElement);
3093
4269
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
3094
4270
  }
@@ -3099,7 +4275,7 @@ class Session {
3099
4275
  this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
3100
4276
  }
3101
4277
  willSubmitForm(form, submitter) {
3102
- const action = getAction(form, submitter);
4278
+ const action = getAction$1(form, submitter);
3103
4279
  return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
3104
4280
  }
3105
4281
  formSubmitted(form, submitter) {
@@ -3119,8 +4295,7 @@ class Session {
3119
4295
  this.renderStreamMessage(message);
3120
4296
  }
3121
4297
  viewWillCacheSnapshot() {
3122
- var _a;
3123
- if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) {
4298
+ if (!this.navigator.currentVisit?.silent) {
3124
4299
  this.notifyApplicationBeforeCachingSnapshot();
3125
4300
  }
3126
4301
  }
@@ -3132,9 +4307,9 @@ class Session {
3132
4307
  }
3133
4308
  return !defaultPrevented;
3134
4309
  }
3135
- viewRenderedSnapshot(_snapshot, _isPreview) {
4310
+ viewRenderedSnapshot(_snapshot, _isPreview, renderMethod) {
3136
4311
  this.view.lastRenderedLocation = this.history.location;
3137
- this.notifyApplicationAfterRender();
4312
+ this.notifyApplicationAfterRender(renderMethod);
3138
4313
  }
3139
4314
  preloadOnLoadLinksForView(element) {
3140
4315
  this.preloader.preloadOnLoadLinksForView(element);
@@ -3187,14 +4362,19 @@ class Session {
3187
4362
  }
3188
4363
  notifyApplicationBeforeRender(newBody, options) {
3189
4364
  return dispatch("turbo:before-render", {
3190
- detail: Object.assign({
3191
- newBody: newBody
3192
- }, options),
4365
+ detail: {
4366
+ newBody: newBody,
4367
+ ...options
4368
+ },
3193
4369
  cancelable: true
3194
4370
  });
3195
4371
  }
3196
- notifyApplicationAfterRender() {
3197
- return dispatch("turbo:render");
4372
+ notifyApplicationAfterRender(renderMethod) {
4373
+ return dispatch("turbo:render", {
4374
+ detail: {
4375
+ renderMethod: renderMethod
4376
+ }
4377
+ });
3198
4378
  }
3199
4379
  notifyApplicationAfterPageLoad(timing = {}) {
3200
4380
  return dispatch("turbo:load", {
@@ -3273,67 +4453,9 @@ const deprecatedLocationPropertyDescriptors = {
3273
4453
  }
3274
4454
  };
3275
4455
 
3276
- class Cache {
3277
- constructor(session) {
3278
- this.session = session;
3279
- }
3280
- clear() {
3281
- this.session.clearCache();
3282
- }
3283
- resetCacheControl() {
3284
- this.setCacheControl("");
3285
- }
3286
- exemptPageFromCache() {
3287
- this.setCacheControl("no-cache");
3288
- }
3289
- exemptPageFromPreview() {
3290
- this.setCacheControl("no-preview");
3291
- }
3292
- setCacheControl(value) {
3293
- setMetaContent("turbo-cache-control", value);
3294
- }
3295
- }
3296
-
3297
- const StreamActions = {
3298
- after() {
3299
- this.targetElements.forEach((e => {
3300
- var _a;
3301
- return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
3302
- }));
3303
- },
3304
- append() {
3305
- this.removeDuplicateTargetChildren();
3306
- this.targetElements.forEach((e => e.append(this.templateContent)));
3307
- },
3308
- before() {
3309
- this.targetElements.forEach((e => {
3310
- var _a;
3311
- return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
3312
- }));
3313
- },
3314
- prepend() {
3315
- this.removeDuplicateTargetChildren();
3316
- this.targetElements.forEach((e => e.prepend(this.templateContent)));
3317
- },
3318
- remove() {
3319
- this.targetElements.forEach((e => e.remove()));
3320
- },
3321
- replace() {
3322
- this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3323
- },
3324
- update() {
3325
- this.targetElements.forEach((targetElement => {
3326
- targetElement.innerHTML = "";
3327
- targetElement.append(this.templateContent);
3328
- }));
3329
- }
3330
- };
3331
-
3332
- const session = new Session;
3333
-
3334
- const cache = new Cache(session);
4456
+ const session = new Session(recentRequests);
3335
4457
 
3336
- const {navigator: navigator$1} = session;
4458
+ const {cache: cache, navigator: navigator$1} = session;
3337
4459
 
3338
4460
  function start() {
3339
4461
  session.start();
@@ -3384,6 +4506,7 @@ var Turbo = Object.freeze({
3384
4506
  PageRenderer: PageRenderer,
3385
4507
  PageSnapshot: PageSnapshot,
3386
4508
  FrameRenderer: FrameRenderer,
4509
+ fetch: fetchWithTurboHeaders,
3387
4510
  start: start,
3388
4511
  registerAdapter: registerAdapter,
3389
4512
  visit: visit,
@@ -3393,28 +4516,20 @@ var Turbo = Object.freeze({
3393
4516
  clearCache: clearCache,
3394
4517
  setProgressBarDelay: setProgressBarDelay,
3395
4518
  setConfirmMethod: setConfirmMethod,
3396
- setFormMode: setFormMode,
3397
- StreamActions: StreamActions
4519
+ setFormMode: setFormMode
3398
4520
  });
3399
4521
 
3400
4522
  class TurboFrameMissingError extends Error {}
3401
4523
 
3402
4524
  class FrameController {
4525
+ fetchResponseLoaded=_fetchResponse => Promise.resolve();
4526
+ #currentFetchRequest=null;
4527
+ #resolveVisitPromise=() => {};
4528
+ #connected=false;
4529
+ #hasBeenLoaded=false;
4530
+ #ignoredAttributes=new Set;
4531
+ action=null;
3403
4532
  constructor(element) {
3404
- this.fetchResponseLoaded = _fetchResponse => {};
3405
- this.currentFetchRequest = null;
3406
- this.resolveVisitPromise = () => {};
3407
- this.connected = false;
3408
- this.hasBeenLoaded = false;
3409
- this.ignoredAttributes = new Set;
3410
- this.action = null;
3411
- this.visitCachedSnapshot = ({element: element}) => {
3412
- const frame = element.querySelector("#" + this.element.id);
3413
- if (frame && this.previousFrameElement) {
3414
- frame.replaceChildren(...this.previousFrameElement.children);
3415
- }
3416
- delete this.previousFrameElement;
3417
- };
3418
4533
  this.element = element;
3419
4534
  this.view = new FrameView(this, this.element);
3420
4535
  this.appearanceObserver = new AppearanceObserver(this, this.element);
@@ -3424,12 +4539,12 @@ class FrameController {
3424
4539
  this.formSubmitObserver = new FormSubmitObserver(this, this.element);
3425
4540
  }
3426
4541
  connect() {
3427
- if (!this.connected) {
3428
- this.connected = true;
4542
+ if (!this.#connected) {
4543
+ this.#connected = true;
3429
4544
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
3430
4545
  this.appearanceObserver.start();
3431
4546
  } else {
3432
- this.loadSourceURL();
4547
+ this.#loadSourceURL();
3433
4548
  }
3434
4549
  this.formLinkClickObserver.start();
3435
4550
  this.linkInterceptor.start();
@@ -3437,8 +4552,8 @@ class FrameController {
3437
4552
  }
3438
4553
  }
3439
4554
  disconnect() {
3440
- if (this.connected) {
3441
- this.connected = false;
4555
+ if (this.#connected) {
4556
+ this.#connected = false;
3442
4557
  this.appearanceObserver.stop();
3443
4558
  this.formLinkClickObserver.stop();
3444
4559
  this.linkInterceptor.stop();
@@ -3447,45 +4562,39 @@ class FrameController {
3447
4562
  }
3448
4563
  disabledChanged() {
3449
4564
  if (this.loadingStyle == FrameLoadingStyle.eager) {
3450
- this.loadSourceURL();
4565
+ this.#loadSourceURL();
3451
4566
  }
3452
4567
  }
3453
4568
  sourceURLChanged() {
3454
- if (this.isIgnoringChangesTo("src")) return;
4569
+ if (this.#isIgnoringChangesTo("src")) return;
3455
4570
  if (this.element.isConnected) {
3456
4571
  this.complete = false;
3457
4572
  }
3458
- if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
3459
- this.loadSourceURL();
4573
+ if (this.loadingStyle == FrameLoadingStyle.eager || this.#hasBeenLoaded) {
4574
+ this.#loadSourceURL();
3460
4575
  }
3461
4576
  }
3462
4577
  sourceURLReloaded() {
3463
4578
  const {src: src} = this.element;
3464
- this.ignoringChangesToAttribute("complete", (() => {
3465
- this.element.removeAttribute("complete");
3466
- }));
4579
+ this.element.removeAttribute("complete");
3467
4580
  this.element.src = null;
3468
4581
  this.element.src = src;
3469
4582
  return this.element.loaded;
3470
4583
  }
3471
- completeChanged() {
3472
- if (this.isIgnoringChangesTo("complete")) return;
3473
- this.loadSourceURL();
3474
- }
3475
4584
  loadingStyleChanged() {
3476
4585
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
3477
4586
  this.appearanceObserver.start();
3478
4587
  } else {
3479
4588
  this.appearanceObserver.stop();
3480
- this.loadSourceURL();
4589
+ this.#loadSourceURL();
3481
4590
  }
3482
4591
  }
3483
- async loadSourceURL() {
4592
+ async #loadSourceURL() {
3484
4593
  if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
3485
- this.element.loaded = this.visit(expandURL(this.sourceURL));
4594
+ this.element.loaded = this.#visit(expandURL(this.sourceURL));
3486
4595
  this.appearanceObserver.stop();
3487
4596
  await this.element.loaded;
3488
- this.hasBeenLoaded = true;
4597
+ this.#hasBeenLoaded = true;
3489
4598
  }
3490
4599
  }
3491
4600
  async loadResponse(fetchResponse) {
@@ -3498,34 +4607,34 @@ class FrameController {
3498
4607
  const document = parseHTMLDocument(html);
3499
4608
  const pageSnapshot = PageSnapshot.fromDocument(document);
3500
4609
  if (pageSnapshot.isVisitable) {
3501
- await this.loadFrameResponse(fetchResponse, document);
4610
+ await this.#loadFrameResponse(fetchResponse, document);
3502
4611
  } else {
3503
- await this.handleUnvisitableFrameResponse(fetchResponse);
4612
+ await this.#handleUnvisitableFrameResponse(fetchResponse);
3504
4613
  }
3505
4614
  }
3506
4615
  } finally {
3507
- this.fetchResponseLoaded = () => {};
4616
+ this.fetchResponseLoaded = () => Promise.resolve();
3508
4617
  }
3509
4618
  }
3510
4619
  elementAppearedInViewport(element) {
3511
- this.proposeVisitIfNavigatedWithAction(element, element);
3512
- this.loadSourceURL();
4620
+ this.proposeVisitIfNavigatedWithAction(element, getVisitAction(element));
4621
+ this.#loadSourceURL();
3513
4622
  }
3514
4623
  willSubmitFormLinkToLocation(link) {
3515
- return this.shouldInterceptNavigation(link);
4624
+ return this.#shouldInterceptNavigation(link);
3516
4625
  }
3517
4626
  submittedFormLinkToLocation(link, _location, form) {
3518
- const frame = this.findFrameElement(link);
4627
+ const frame = this.#findFrameElement(link);
3519
4628
  if (frame) form.setAttribute("data-turbo-frame", frame.id);
3520
4629
  }
3521
4630
  shouldInterceptLinkClick(element, _location, _event) {
3522
- return this.shouldInterceptNavigation(element);
4631
+ return this.#shouldInterceptNavigation(element);
3523
4632
  }
3524
4633
  linkClickIntercepted(element, location) {
3525
- this.navigateFrame(element, location);
4634
+ this.#navigateFrame(element, location);
3526
4635
  }
3527
4636
  willSubmitForm(element, submitter) {
3528
- return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
4637
+ return element.closest("turbo-frame") == this.element && this.#shouldInterceptNavigation(element, submitter);
3529
4638
  }
3530
4639
  formSubmitted(element, submitter) {
3531
4640
  if (this.formSubmission) {
@@ -3537,9 +4646,8 @@ class FrameController {
3537
4646
  this.formSubmission.start();
3538
4647
  }
3539
4648
  prepareRequest(request) {
3540
- var _a;
3541
4649
  request.headers["Turbo-Frame"] = this.id;
3542
- if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
4650
+ if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
3543
4651
  request.acceptResponseType(StreamMessage.contentType);
3544
4652
  }
3545
4653
  }
@@ -3547,29 +4655,29 @@ class FrameController {
3547
4655
  markAsBusy(this.element);
3548
4656
  }
3549
4657
  requestPreventedHandlingResponse(_request, _response) {
3550
- this.resolveVisitPromise();
4658
+ this.#resolveVisitPromise();
3551
4659
  }
3552
4660
  async requestSucceededWithResponse(request, response) {
3553
4661
  await this.loadResponse(response);
3554
- this.resolveVisitPromise();
4662
+ this.#resolveVisitPromise();
3555
4663
  }
3556
4664
  async requestFailedWithResponse(request, response) {
3557
4665
  await this.loadResponse(response);
3558
- this.resolveVisitPromise();
4666
+ this.#resolveVisitPromise();
3559
4667
  }
3560
4668
  requestErrored(request, error) {
3561
4669
  console.error(error);
3562
- this.resolveVisitPromise();
4670
+ this.#resolveVisitPromise();
3563
4671
  }
3564
4672
  requestFinished(_request) {
3565
4673
  clearBusyState(this.element);
3566
4674
  }
3567
4675
  formSubmissionStarted({formElement: formElement}) {
3568
- markAsBusy(formElement, this.findFrameElement(formElement));
4676
+ markAsBusy(formElement, this.#findFrameElement(formElement));
3569
4677
  }
3570
4678
  formSubmissionSucceededWithResponse(formSubmission, response) {
3571
- const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
3572
- frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
4679
+ const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter);
4680
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame));
3573
4681
  frame.delegate.loadResponse(response);
3574
4682
  if (!formSubmission.isSafe) {
3575
4683
  session.clearCache();
@@ -3583,14 +4691,15 @@ class FrameController {
3583
4691
  console.error(error);
3584
4692
  }
3585
4693
  formSubmissionFinished({formElement: formElement}) {
3586
- clearBusyState(formElement, this.findFrameElement(formElement));
4694
+ clearBusyState(formElement, this.#findFrameElement(formElement));
3587
4695
  }
3588
4696
  allowsImmediateRender({element: newFrame}, options) {
3589
4697
  const event = dispatch("turbo:before-frame-render", {
3590
4698
  target: this.element,
3591
- detail: Object.assign({
3592
- newFrame: newFrame
3593
- }, options),
4699
+ detail: {
4700
+ newFrame: newFrame,
4701
+ ...options
4702
+ },
3594
4703
  cancelable: true
3595
4704
  });
3596
4705
  const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
@@ -3599,7 +4708,7 @@ class FrameController {
3599
4708
  }
3600
4709
  return !defaultPrevented;
3601
4710
  }
3602
- viewRenderedSnapshot(_snapshot, _isPreview) {}
4711
+ viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {}
3603
4712
  preloadOnLoadLinksForView(element) {
3604
4713
  session.preloadOnLoadLinksForView(element);
3605
4714
  }
@@ -3607,7 +4716,14 @@ class FrameController {
3607
4716
  willRenderFrame(currentElement, _newElement) {
3608
4717
  this.previousFrameElement = currentElement.cloneNode(true);
3609
4718
  }
3610
- async loadFrameResponse(fetchResponse, document) {
4719
+ visitCachedSnapshot=({element: element}) => {
4720
+ const frame = element.querySelector("#" + this.element.id);
4721
+ if (frame && this.previousFrameElement) {
4722
+ frame.replaceChildren(...this.previousFrameElement.children);
4723
+ }
4724
+ delete this.previousFrameElement;
4725
+ };
4726
+ async #loadFrameResponse(fetchResponse, document) {
3611
4727
  const newFrameElement = await this.extractForeignFrameElement(document.body);
3612
4728
  if (newFrameElement) {
3613
4729
  const snapshot = new Snapshot(newFrameElement);
@@ -3618,41 +4734,40 @@ class FrameController {
3618
4734
  this.complete = true;
3619
4735
  session.frameRendered(fetchResponse, this.element);
3620
4736
  session.frameLoaded(this.element);
3621
- this.fetchResponseLoaded(fetchResponse);
3622
- } else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3623
- this.handleFrameMissingFromResponse(fetchResponse);
4737
+ await this.fetchResponseLoaded(fetchResponse);
4738
+ } else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) {
4739
+ this.#handleFrameMissingFromResponse(fetchResponse);
3624
4740
  }
3625
4741
  }
3626
- async visit(url) {
3627
- var _a;
4742
+ async #visit(url) {
3628
4743
  const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
3629
- (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
3630
- this.currentFetchRequest = request;
4744
+ this.#currentFetchRequest?.cancel();
4745
+ this.#currentFetchRequest = request;
3631
4746
  return new Promise((resolve => {
3632
- this.resolveVisitPromise = () => {
3633
- this.resolveVisitPromise = () => {};
3634
- this.currentFetchRequest = null;
4747
+ this.#resolveVisitPromise = () => {
4748
+ this.#resolveVisitPromise = () => {};
4749
+ this.#currentFetchRequest = null;
3635
4750
  resolve();
3636
4751
  };
3637
4752
  request.perform();
3638
4753
  }));
3639
4754
  }
3640
- navigateFrame(element, url, submitter) {
3641
- const frame = this.findFrameElement(element, submitter);
3642
- frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3643
- this.withCurrentNavigationElement(element, (() => {
4755
+ #navigateFrame(element, url, submitter) {
4756
+ const frame = this.#findFrameElement(element, submitter);
4757
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(submitter, element, frame));
4758
+ this.#withCurrentNavigationElement(element, (() => {
3644
4759
  frame.src = url;
3645
4760
  }));
3646
4761
  }
3647
- proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3648
- this.action = getVisitAction(submitter, element, frame);
4762
+ proposeVisitIfNavigatedWithAction(frame, action = null) {
4763
+ this.action = action;
3649
4764
  if (this.action) {
3650
4765
  const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3651
4766
  const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
3652
- frame.delegate.fetchResponseLoaded = fetchResponse => {
4767
+ frame.delegate.fetchResponseLoaded = async fetchResponse => {
3653
4768
  if (frame.src) {
3654
4769
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
3655
- const responseHTML = frame.ownerDocument.documentElement.outerHTML;
4770
+ const responseHTML = await fetchResponse.responseHTML;
3656
4771
  const response = {
3657
4772
  statusCode: statusCode,
3658
4773
  redirected: redirected,
@@ -3678,16 +4793,16 @@ class FrameController {
3678
4793
  session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
3679
4794
  }
3680
4795
  }
3681
- async handleUnvisitableFrameResponse(fetchResponse) {
4796
+ async #handleUnvisitableFrameResponse(fetchResponse) {
3682
4797
  console.warn(`The response (${fetchResponse.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.`);
3683
- await this.visitResponse(fetchResponse.response);
4798
+ await this.#visitResponse(fetchResponse.response);
3684
4799
  }
3685
- willHandleFrameMissingFromResponse(fetchResponse) {
4800
+ #willHandleFrameMissingFromResponse(fetchResponse) {
3686
4801
  this.element.setAttribute("complete", "");
3687
4802
  const response = fetchResponse.response;
3688
- const visit = async (url, options = {}) => {
4803
+ const visit = async (url, options) => {
3689
4804
  if (url instanceof Response) {
3690
- this.visitResponse(url);
4805
+ this.#visitResponse(url);
3691
4806
  } else {
3692
4807
  session.visit(url, options);
3693
4808
  }
@@ -3702,15 +4817,15 @@ class FrameController {
3702
4817
  });
3703
4818
  return !event.defaultPrevented;
3704
4819
  }
3705
- handleFrameMissingFromResponse(fetchResponse) {
4820
+ #handleFrameMissingFromResponse(fetchResponse) {
3706
4821
  this.view.missing();
3707
- this.throwFrameMissingError(fetchResponse);
4822
+ this.#throwFrameMissingError(fetchResponse);
3708
4823
  }
3709
- throwFrameMissingError(fetchResponse) {
4824
+ #throwFrameMissingError(fetchResponse) {
3710
4825
  const message = `The response (${fetchResponse.statusCode}) did not contain the expected <turbo-frame id="${this.element.id}"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;
3711
4826
  throw new TurboFrameMissingError(message);
3712
4827
  }
3713
- async visitResponse(response) {
4828
+ async #visitResponse(response) {
3714
4829
  const wrapped = new FetchResponse(response);
3715
4830
  const responseHTML = await wrapped.responseHTML;
3716
4831
  const {location: location, redirected: redirected, statusCode: statusCode} = wrapped;
@@ -3722,10 +4837,9 @@ class FrameController {
3722
4837
  }
3723
4838
  });
3724
4839
  }
3725
- findFrameElement(element, submitter) {
3726
- var _a;
4840
+ #findFrameElement(element, submitter) {
3727
4841
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
3728
- return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
4842
+ return getFrameElementById(id) ?? this.element;
3729
4843
  }
3730
4844
  async extractForeignFrameElement(container) {
3731
4845
  let element;
@@ -3746,13 +4860,13 @@ class FrameController {
3746
4860
  }
3747
4861
  return null;
3748
4862
  }
3749
- formActionIsVisitable(form, submitter) {
3750
- const action = getAction(form, submitter);
4863
+ #formActionIsVisitable(form, submitter) {
4864
+ const action = getAction$1(form, submitter);
3751
4865
  return locationIsVisitable(expandURL(action), this.rootLocation);
3752
4866
  }
3753
- shouldInterceptNavigation(element, submitter) {
4867
+ #shouldInterceptNavigation(element, submitter) {
3754
4868
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
3755
- if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {
4869
+ if (element instanceof HTMLFormElement && !this.#formActionIsVisitable(element, submitter)) {
3756
4870
  return false;
3757
4871
  }
3758
4872
  if (!this.enabled || id == "_top") {
@@ -3784,46 +4898,43 @@ class FrameController {
3784
4898
  }
3785
4899
  }
3786
4900
  set sourceURL(sourceURL) {
3787
- this.ignoringChangesToAttribute("src", (() => {
3788
- this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
4901
+ this.#ignoringChangesToAttribute("src", (() => {
4902
+ this.element.src = sourceURL ?? null;
3789
4903
  }));
3790
4904
  }
3791
4905
  get loadingStyle() {
3792
4906
  return this.element.loading;
3793
4907
  }
3794
4908
  get isLoading() {
3795
- return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
4909
+ return this.formSubmission !== undefined || this.#resolveVisitPromise() !== undefined;
3796
4910
  }
3797
4911
  get complete() {
3798
4912
  return this.element.hasAttribute("complete");
3799
4913
  }
3800
4914
  set complete(value) {
3801
- this.ignoringChangesToAttribute("complete", (() => {
3802
- if (value) {
3803
- this.element.setAttribute("complete", "");
3804
- } else {
3805
- this.element.removeAttribute("complete");
3806
- }
3807
- }));
4915
+ if (value) {
4916
+ this.element.setAttribute("complete", "");
4917
+ } else {
4918
+ this.element.removeAttribute("complete");
4919
+ }
3808
4920
  }
3809
4921
  get isActive() {
3810
- return this.element.isActive && this.connected;
4922
+ return this.element.isActive && this.#connected;
3811
4923
  }
3812
4924
  get rootLocation() {
3813
- var _a;
3814
4925
  const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
3815
- const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
4926
+ const root = meta?.content ?? "/";
3816
4927
  return expandURL(root);
3817
4928
  }
3818
- isIgnoringChangesTo(attributeName) {
3819
- return this.ignoredAttributes.has(attributeName);
4929
+ #isIgnoringChangesTo(attributeName) {
4930
+ return this.#ignoredAttributes.has(attributeName);
3820
4931
  }
3821
- ignoringChangesToAttribute(attributeName, callback) {
3822
- this.ignoredAttributes.add(attributeName);
4932
+ #ignoringChangesToAttribute(attributeName, callback) {
4933
+ this.#ignoredAttributes.add(attributeName);
3823
4934
  callback();
3824
- this.ignoredAttributes.delete(attributeName);
4935
+ this.#ignoredAttributes.delete(attributeName);
3825
4936
  }
3826
- withCurrentNavigationElement(element, callback) {
4937
+ #withCurrentNavigationElement(element, callback) {
3827
4938
  this.currentNavigationElement = element;
3828
4939
  callback();
3829
4940
  delete this.currentNavigationElement;
@@ -3856,6 +4967,50 @@ function activateElement(element, currentURL) {
3856
4967
  }
3857
4968
  }
3858
4969
 
4970
+ const StreamActions = {
4971
+ after() {
4972
+ this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e.nextSibling)));
4973
+ },
4974
+ append() {
4975
+ this.removeDuplicateTargetChildren();
4976
+ this.targetElements.forEach((e => e.append(this.templateContent)));
4977
+ },
4978
+ before() {
4979
+ this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e)));
4980
+ },
4981
+ prepend() {
4982
+ this.removeDuplicateTargetChildren();
4983
+ this.targetElements.forEach((e => e.prepend(this.templateContent)));
4984
+ },
4985
+ remove() {
4986
+ this.targetElements.forEach((e => e.remove()));
4987
+ },
4988
+ replace() {
4989
+ const method = this.getAttribute("method");
4990
+ this.targetElements.forEach((targetElement => {
4991
+ if (method === "morph") {
4992
+ morphElements(targetElement, this.templateContent);
4993
+ } else {
4994
+ targetElement.replaceWith(this.templateContent);
4995
+ }
4996
+ }));
4997
+ },
4998
+ update() {
4999
+ const method = this.getAttribute("method");
5000
+ this.targetElements.forEach((targetElement => {
5001
+ if (method === "morph") {
5002
+ morphChildren(targetElement, this.templateContent);
5003
+ } else {
5004
+ targetElement.innerHTML = "";
5005
+ targetElement.append(this.templateContent);
5006
+ }
5007
+ }));
5008
+ },
5009
+ refresh() {
5010
+ session.refresh(this.baseURI, this.requestId);
5011
+ }
5012
+ };
5013
+
3859
5014
  class StreamElement extends HTMLElement {
3860
5015
  static async renderElement(newElement) {
3861
5016
  await newElement.performAction();
@@ -3870,11 +5025,10 @@ class StreamElement extends HTMLElement {
3870
5025
  }
3871
5026
  }
3872
5027
  async render() {
3873
- var _a;
3874
- return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
5028
+ return this.renderPromise ??= (async () => {
3875
5029
  const event = this.beforeRenderEvent;
3876
5030
  if (this.dispatchEvent(event)) {
3877
- await nextAnimationFrame();
5031
+ await nextRepaint();
3878
5032
  await event.detail.render(this);
3879
5033
  }
3880
5034
  })();
@@ -3882,15 +5036,14 @@ class StreamElement extends HTMLElement {
3882
5036
  disconnect() {
3883
5037
  try {
3884
5038
  this.remove();
3885
- } catch (_a) {}
5039
+ } catch {}
3886
5040
  }
3887
5041
  removeDuplicateTargetChildren() {
3888
5042
  this.duplicateChildren.forEach((c => c.remove()));
3889
5043
  }
3890
5044
  get duplicateChildren() {
3891
- var _a;
3892
5045
  const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
3893
- const newChildrenIds = [ ...((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [] ].filter((c => !!c.id)).map((c => c.id));
5046
+ const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
3894
5047
  return existingChildren.filter((c => newChildrenIds.includes(c.id)));
3895
5048
  }
3896
5049
  get performAction() {
@@ -3899,9 +5052,9 @@ class StreamElement extends HTMLElement {
3899
5052
  if (actionFunction) {
3900
5053
  return actionFunction;
3901
5054
  }
3902
- this.raise("unknown action");
5055
+ this.#raise("unknown action");
3903
5056
  }
3904
- this.raise("action attribute is missing");
5057
+ this.#raise("action attribute is missing");
3905
5058
  }
3906
5059
  get targetElements() {
3907
5060
  if (this.target) {
@@ -3909,7 +5062,7 @@ class StreamElement extends HTMLElement {
3909
5062
  } else if (this.targets) {
3910
5063
  return this.targetElementsByQuery;
3911
5064
  } else {
3912
- this.raise("target or targets attribute is missing");
5065
+ this.#raise("target or targets attribute is missing");
3913
5066
  }
3914
5067
  }
3915
5068
  get templateContent() {
@@ -3923,7 +5076,7 @@ class StreamElement extends HTMLElement {
3923
5076
  } else if (this.firstElementChild instanceof HTMLTemplateElement) {
3924
5077
  return this.firstElementChild;
3925
5078
  }
3926
- this.raise("first child element must be a <template> element");
5079
+ this.#raise("first child element must be a <template> element");
3927
5080
  }
3928
5081
  get action() {
3929
5082
  return this.getAttribute("action");
@@ -3934,12 +5087,14 @@ class StreamElement extends HTMLElement {
3934
5087
  get targets() {
3935
5088
  return this.getAttribute("targets");
3936
5089
  }
3937
- raise(message) {
5090
+ get requestId() {
5091
+ return this.getAttribute("request-id");
5092
+ }
5093
+ #raise(message) {
3938
5094
  throw new Error(`${this.description}: ${message}`);
3939
5095
  }
3940
5096
  get description() {
3941
- var _a, _b;
3942
- return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
5097
+ return (this.outerHTML.match(/<[^>]+>/) ?? [])[0] ?? "<turbo-stream>";
3943
5098
  }
3944
5099
  get beforeRenderEvent() {
3945
5100
  return new CustomEvent("turbo:before-stream-render", {
@@ -3952,8 +5107,7 @@ class StreamElement extends HTMLElement {
3952
5107
  });
3953
5108
  }
3954
5109
  get targetElementsById() {
3955
- var _a;
3956
- const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
5110
+ const element = this.ownerDocument?.getElementById(this.target);
3957
5111
  if (element !== null) {
3958
5112
  return [ element ];
3959
5113
  } else {
@@ -3961,8 +5115,7 @@ class StreamElement extends HTMLElement {
3961
5115
  }
3962
5116
  }
3963
5117
  get targetElementsByQuery() {
3964
- var _a;
3965
- const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);
5118
+ const elements = this.ownerDocument?.querySelectorAll(this.targets);
3966
5119
  if (elements.length !== 0) {
3967
5120
  return Array.prototype.slice.call(elements);
3968
5121
  } else {
@@ -3972,16 +5125,14 @@ class StreamElement extends HTMLElement {
3972
5125
  }
3973
5126
 
3974
5127
  class StreamSourceElement extends HTMLElement {
3975
- constructor() {
3976
- super(...arguments);
3977
- this.streamSource = null;
3978
- }
5128
+ streamSource=null;
3979
5129
  connectedCallback() {
3980
5130
  this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
3981
5131
  connectStreamSource(this.streamSource);
3982
5132
  }
3983
5133
  disconnectedCallback() {
3984
5134
  if (this.streamSource) {
5135
+ this.streamSource.close();
3985
5136
  disconnectStreamSource(this.streamSource);
3986
5137
  }
3987
5138
  }
@@ -4026,16 +5177,21 @@ if (customElements.get("turbo-stream-source") === undefined) {
4026
5177
  }
4027
5178
  })();
4028
5179
 
4029
- window.Turbo = Turbo;
5180
+ window.Turbo = {
5181
+ ...Turbo,
5182
+ StreamActions: StreamActions
5183
+ };
4030
5184
 
4031
5185
  start();
4032
5186
 
4033
- var turbo_es2017Esm = Object.freeze({
5187
+ var Turbo$1 = Object.freeze({
4034
5188
  __proto__: null,
5189
+ FetchEnctype: FetchEnctype,
5190
+ FetchMethod: FetchMethod,
5191
+ FetchRequest: FetchRequest,
5192
+ FetchResponse: FetchResponse,
4035
5193
  FrameElement: FrameElement,
4036
- get FrameLoadingStyle() {
4037
- return FrameLoadingStyle;
4038
- },
5194
+ FrameLoadingStyle: FrameLoadingStyle,
4039
5195
  FrameRenderer: FrameRenderer,
4040
5196
  PageRenderer: PageRenderer,
4041
5197
  PageSnapshot: PageSnapshot,
@@ -4046,6 +5202,10 @@ var turbo_es2017Esm = Object.freeze({
4046
5202
  clearCache: clearCache,
4047
5203
  connectStreamSource: connectStreamSource,
4048
5204
  disconnectStreamSource: disconnectStreamSource,
5205
+ fetch: fetchWithTurboHeaders,
5206
+ fetchEnctypeFromString: fetchEnctypeFromString,
5207
+ fetchMethodFromString: fetchMethodFromString,
5208
+ isSafe: isSafe,
4049
5209
  navigator: navigator$1,
4050
5210
  registerAdapter: registerAdapter,
4051
5211
  renderStreamMessage: renderStreamMessage,
@@ -4060,14 +5220,14 @@ var turbo_es2017Esm = Object.freeze({
4060
5220
  let consumer;
4061
5221
 
4062
5222
  async function getConsumer() {
4063
- return consumer || setConsumer(createConsumer().then(setConsumer));
5223
+ return consumer || setConsumer(createConsumer$1().then(setConsumer));
4064
5224
  }
4065
5225
 
4066
5226
  function setConsumer(newConsumer) {
4067
5227
  return consumer = newConsumer;
4068
5228
  }
4069
5229
 
4070
- async function createConsumer() {
5230
+ async function createConsumer$1() {
4071
5231
  const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
4072
5232
  return index;
4073
5233
  }));
@@ -4083,7 +5243,7 @@ var cable = Object.freeze({
4083
5243
  __proto__: null,
4084
5244
  getConsumer: getConsumer,
4085
5245
  setConsumer: setConsumer,
4086
- createConsumer: createConsumer,
5246
+ createConsumer: createConsumer$1,
4087
5247
  subscribeTo: subscribeTo
4088
5248
  });
4089
5249
 
@@ -4193,6 +5353,8 @@ function isBodyInit(body) {
4193
5353
  return body instanceof FormData || body instanceof URLSearchParams;
4194
5354
  }
4195
5355
 
5356
+ window.Turbo = Turbo$1;
5357
+
4196
5358
  addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
4197
5359
 
4198
5360
  var adapters = {
@@ -4309,6 +5471,8 @@ ConnectionMonitor.staleThreshold = 6;
4309
5471
 
4310
5472
  ConnectionMonitor.reconnectionBackoffRate = .15;
4311
5473
 
5474
+ var ConnectionMonitor$1 = ConnectionMonitor;
5475
+
4312
5476
  var INTERNAL = {
4313
5477
  message_types: {
4314
5478
  welcome: "welcome",
@@ -4320,7 +5484,8 @@ var INTERNAL = {
4320
5484
  disconnect_reasons: {
4321
5485
  unauthorized: "unauthorized",
4322
5486
  invalid_request: "invalid_request",
4323
- server_restart: "server_restart"
5487
+ server_restart: "server_restart",
5488
+ remote: "remote"
4324
5489
  },
4325
5490
  default_mount_path: "/cable",
4326
5491
  protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
@@ -4337,7 +5502,7 @@ class Connection {
4337
5502
  this.open = this.open.bind(this);
4338
5503
  this.consumer = consumer;
4339
5504
  this.subscriptions = this.consumer.subscriptions;
4340
- this.monitor = new ConnectionMonitor(this);
5505
+ this.monitor = new ConnectionMonitor$1(this);
4341
5506
  this.disconnected = true;
4342
5507
  }
4343
5508
  send(data) {
@@ -4353,11 +5518,12 @@ class Connection {
4353
5518
  logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
4354
5519
  return false;
4355
5520
  } else {
4356
- logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${protocols}`);
5521
+ const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
5522
+ logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
4357
5523
  if (this.webSocket) {
4358
5524
  this.uninstallEventHandlers();
4359
5525
  }
4360
- this.webSocket = new adapters.WebSocket(this.consumer.url, protocols);
5526
+ this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
4361
5527
  this.installEventHandlers();
4362
5528
  this.monitor.start();
4363
5529
  return true;
@@ -4369,7 +5535,7 @@ class Connection {
4369
5535
  if (!allowReconnect) {
4370
5536
  this.monitor.stop();
4371
5537
  }
4372
- if (this.isActive()) {
5538
+ if (this.isOpen()) {
4373
5539
  return this.webSocket.close();
4374
5540
  }
4375
5541
  }
@@ -4399,6 +5565,9 @@ class Connection {
4399
5565
  isActive() {
4400
5566
  return this.isState("open", "connecting");
4401
5567
  }
5568
+ triedToReconnect() {
5569
+ return this.monitor.reconnectAttempts > 0;
5570
+ }
4402
5571
  isProtocolSupported() {
4403
5572
  return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
4404
5573
  }
@@ -4438,6 +5607,9 @@ Connection.prototype.events = {
4438
5607
  const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
4439
5608
  switch (type) {
4440
5609
  case message_types.welcome:
5610
+ if (this.triedToReconnect()) {
5611
+ this.reconnectAttempted = true;
5612
+ }
4441
5613
  this.monitor.recordConnect();
4442
5614
  return this.subscriptions.reload();
4443
5615
 
@@ -4452,7 +5624,16 @@ Connection.prototype.events = {
4452
5624
 
4453
5625
  case message_types.confirmation:
4454
5626
  this.subscriptions.confirmSubscription(identifier);
4455
- return this.subscriptions.notify(identifier, "connected");
5627
+ if (this.reconnectAttempted) {
5628
+ this.reconnectAttempted = false;
5629
+ return this.subscriptions.notify(identifier, "connected", {
5630
+ reconnected: true
5631
+ });
5632
+ } else {
5633
+ return this.subscriptions.notify(identifier, "connected", {
5634
+ reconnected: false
5635
+ });
5636
+ }
4456
5637
 
4457
5638
  case message_types.rejection:
4458
5639
  return this.subscriptions.reject(identifier);
@@ -4487,6 +5668,8 @@ Connection.prototype.events = {
4487
5668
  }
4488
5669
  };
4489
5670
 
5671
+ var Connection$1 = Connection;
5672
+
4490
5673
  const extend = function(object, properties) {
4491
5674
  if (properties != null) {
4492
5675
  for (let key in properties) {
@@ -4556,10 +5739,12 @@ class SubscriptionGuarantor {
4556
5739
  }
4557
5740
  }
4558
5741
 
5742
+ var SubscriptionGuarantor$1 = SubscriptionGuarantor;
5743
+
4559
5744
  class Subscriptions {
4560
5745
  constructor(consumer) {
4561
5746
  this.consumer = consumer;
4562
- this.guarantor = new SubscriptionGuarantor(this);
5747
+ this.guarantor = new SubscriptionGuarantor$1(this);
4563
5748
  this.subscriptions = [];
4564
5749
  }
4565
5750
  create(channelName, mixin) {
@@ -4636,7 +5821,8 @@ class Consumer {
4636
5821
  constructor(url) {
4637
5822
  this._url = url;
4638
5823
  this.subscriptions = new Subscriptions(this);
4639
- this.connection = new Connection(this);
5824
+ this.connection = new Connection$1(this);
5825
+ this.subprotocols = [];
4640
5826
  }
4641
5827
  get url() {
4642
5828
  return createWebSocketURL(this._url);
@@ -4657,6 +5843,9 @@ class Consumer {
4657
5843
  return this.connection.open();
4658
5844
  }
4659
5845
  }
5846
+ addSubProtocol(subprotocol) {
5847
+ this.subprotocols = [ ...this.subprotocols, subprotocol ];
5848
+ }
4660
5849
  }
4661
5850
 
4662
5851
  function createWebSocketURL(url) {
@@ -4674,7 +5863,7 @@ function createWebSocketURL(url) {
4674
5863
  }
4675
5864
  }
4676
5865
 
4677
- function createConsumer$1(url = getConfig("url") || INTERNAL.default_mount_path) {
5866
+ function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
4678
5867
  return new Consumer(url);
4679
5868
  }
4680
5869
 
@@ -4687,18 +5876,18 @@ function getConfig(name) {
4687
5876
 
4688
5877
  var index = Object.freeze({
4689
5878
  __proto__: null,
4690
- Connection: Connection,
4691
- ConnectionMonitor: ConnectionMonitor,
5879
+ Connection: Connection$1,
5880
+ ConnectionMonitor: ConnectionMonitor$1,
4692
5881
  Consumer: Consumer,
4693
5882
  INTERNAL: INTERNAL,
4694
5883
  Subscription: Subscription,
4695
5884
  Subscriptions: Subscriptions,
4696
- SubscriptionGuarantor: SubscriptionGuarantor,
5885
+ SubscriptionGuarantor: SubscriptionGuarantor$1,
4697
5886
  adapters: adapters,
4698
5887
  createWebSocketURL: createWebSocketURL,
4699
5888
  logger: logger,
4700
- createConsumer: createConsumer$1,
5889
+ createConsumer: createConsumer,
4701
5890
  getConfig: getConfig
4702
5891
  });
4703
5892
 
4704
- export { turbo_es2017Esm as Turbo, cable };
5893
+ export { Turbo$1 as Turbo, cable };