turbo-rails 1.5.0 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,7 @@
1
- (function() {
2
- if (window.Reflect === undefined || window.customElements === undefined || window.customElements.polyfillWrapFlushCallback) {
3
- return;
4
- }
5
- const BuiltInHTMLElement = HTMLElement;
6
- const wrapperForTheName = {
7
- HTMLElement: function HTMLElement() {
8
- return Reflect.construct(BuiltInHTMLElement, [], this.constructor);
9
- }
10
- };
11
- window.HTMLElement = wrapperForTheName["HTMLElement"];
12
- HTMLElement.prototype = BuiltInHTMLElement.prototype;
13
- HTMLElement.prototype.constructor = HTMLElement;
14
- Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);
15
- })();
16
-
1
+ /*!
2
+ Turbo 8.0.4
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,83 @@ 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(anchor) {
487
+ if (anchor.hasAttribute("target")) {
488
+ for (const element of document.getElementsByName(anchor.target)) {
489
+ if (element instanceof HTMLIFrameElement) return false;
490
+ }
491
+ }
492
+ return true;
493
+ }
494
+
495
+ function findLinkFromClickTarget(target) {
496
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
497
+ }
498
+
499
+ function getLocationForLink(link) {
500
+ return expandURL(link.getAttribute("href") || "");
501
+ }
502
+
503
+ function debounce(fn, delay) {
504
+ let timeoutId = null;
505
+ return (...args) => {
506
+ const callback = () => fn.apply(this, args);
507
+ clearTimeout(timeoutId);
508
+ timeoutId = setTimeout(callback, delay);
509
+ };
510
+ }
511
+
512
+ class LimitedSet extends Set {
513
+ constructor(maxSize) {
514
+ super();
515
+ this.maxSize = maxSize;
516
+ }
517
+ add(value) {
518
+ if (this.size >= this.maxSize) {
519
+ const iterator = this.values();
520
+ const oldestValue = iterator.next().value;
521
+ this.delete(oldestValue);
522
+ }
523
+ super.add(value);
462
524
  }
463
525
  }
464
526
 
465
- var FetchMethod;
527
+ const recentRequests = new LimitedSet(20);
528
+
529
+ const nativeFetch = window.fetch;
466
530
 
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 = {}));
531
+ function fetchWithTurboHeaders(url, options = {}) {
532
+ const modifiedHeaders = new Headers(options.headers || {});
533
+ const requestUID = uuid();
534
+ recentRequests.add(requestUID);
535
+ modifiedHeaders.append("X-Turbo-Request-Id", requestUID);
536
+ return nativeFetch(url, {
537
+ ...options,
538
+ headers: modifiedHeaders
539
+ });
540
+ }
474
541
 
475
542
  function fetchMethodFromString(method) {
476
543
  switch (method.toLowerCase()) {
@@ -491,16 +558,81 @@ function fetchMethodFromString(method) {
491
558
  }
492
559
  }
493
560
 
561
+ const FetchMethod = {
562
+ get: "get",
563
+ post: "post",
564
+ put: "put",
565
+ patch: "patch",
566
+ delete: "delete"
567
+ };
568
+
569
+ function fetchEnctypeFromString(encoding) {
570
+ switch (encoding.toLowerCase()) {
571
+ case FetchEnctype.multipart:
572
+ return FetchEnctype.multipart;
573
+
574
+ case FetchEnctype.plain:
575
+ return FetchEnctype.plain;
576
+
577
+ default:
578
+ return FetchEnctype.urlEncoded;
579
+ }
580
+ }
581
+
582
+ const FetchEnctype = {
583
+ urlEncoded: "application/x-www-form-urlencoded",
584
+ multipart: "multipart/form-data",
585
+ plain: "text/plain"
586
+ };
587
+
494
588
  class FetchRequest {
495
- constructor(delegate, method, location, body = new URLSearchParams, target = null) {
496
- this.abortController = new AbortController;
497
- this.resolveRequestPromise = _value => {};
589
+ abortController=new AbortController;
590
+ #resolveRequestPromise=_value => {};
591
+ constructor(delegate, method, location, requestBody = new URLSearchParams, target = null, enctype = FetchEnctype.urlEncoded) {
592
+ const [url, body] = buildResourceAndBody(expandURL(location), method, requestBody, enctype);
498
593
  this.delegate = delegate;
499
- this.method = method;
500
- this.headers = this.defaultHeaders;
501
- this.body = body;
502
- this.url = location;
594
+ this.url = url;
503
595
  this.target = target;
596
+ this.fetchOptions = {
597
+ credentials: "same-origin",
598
+ redirect: "follow",
599
+ method: method,
600
+ headers: {
601
+ ...this.defaultHeaders
602
+ },
603
+ body: body,
604
+ signal: this.abortSignal,
605
+ referrer: this.delegate.referrer?.href
606
+ };
607
+ this.enctype = enctype;
608
+ }
609
+ get method() {
610
+ return this.fetchOptions.method;
611
+ }
612
+ set method(value) {
613
+ const fetchBody = this.isSafe ? this.url.searchParams : this.fetchOptions.body || new FormData;
614
+ const fetchMethod = fetchMethodFromString(value) || FetchMethod.get;
615
+ this.url.search = "";
616
+ const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype);
617
+ this.url = url;
618
+ this.fetchOptions.body = body;
619
+ this.fetchOptions.method = fetchMethod;
620
+ }
621
+ get headers() {
622
+ return this.fetchOptions.headers;
623
+ }
624
+ set headers(value) {
625
+ this.fetchOptions.headers = value;
626
+ }
627
+ get body() {
628
+ if (this.isSafe) {
629
+ return this.url.searchParams;
630
+ } else {
631
+ return this.fetchOptions.body;
632
+ }
633
+ }
634
+ set body(value) {
635
+ this.fetchOptions.body = value;
504
636
  }
505
637
  get location() {
506
638
  return this.url;
@@ -517,14 +649,19 @@ class FetchRequest {
517
649
  async perform() {
518
650
  const {fetchOptions: fetchOptions} = this;
519
651
  this.delegate.prepareRequest(this);
520
- await this.allowRequestToBeIntercepted(fetchOptions);
652
+ const event = await this.#allowRequestToBeIntercepted(fetchOptions);
521
653
  try {
522
654
  this.delegate.requestStarted(this);
523
- const response = await fetch(this.url.href, fetchOptions);
655
+ if (event.detail.fetchRequest) {
656
+ this.response = event.detail.fetchRequest.response;
657
+ } else {
658
+ this.response = fetchWithTurboHeaders(this.url.href, fetchOptions);
659
+ }
660
+ const response = await this.response;
524
661
  return await this.receive(response);
525
662
  } catch (error) {
526
663
  if (error.name !== "AbortError") {
527
- if (this.willDelegateErrorHandling(error)) {
664
+ if (this.#willDelegateErrorHandling(error)) {
528
665
  this.delegate.requestErrored(this, error);
529
666
  }
530
667
  throw error;
@@ -551,25 +688,13 @@ class FetchRequest {
551
688
  }
552
689
  return fetchResponse;
553
690
  }
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
691
  get defaultHeaders() {
567
692
  return {
568
693
  Accept: "text/html, application/xhtml+xml"
569
694
  };
570
695
  }
571
696
  get isSafe() {
572
- return this.method === FetchMethod.get;
697
+ return isSafe(this.method);
573
698
  }
574
699
  get abortSignal() {
575
700
  return this.abortController.signal;
@@ -577,20 +702,22 @@ class FetchRequest {
577
702
  acceptResponseType(mimeType) {
578
703
  this.headers["Accept"] = [ mimeType, this.headers["Accept"] ].join(", ");
579
704
  }
580
- async allowRequestToBeIntercepted(fetchOptions) {
581
- const requestInterception = new Promise((resolve => this.resolveRequestPromise = resolve));
705
+ async #allowRequestToBeIntercepted(fetchOptions) {
706
+ const requestInterception = new Promise((resolve => this.#resolveRequestPromise = resolve));
582
707
  const event = dispatch("turbo:before-fetch-request", {
583
708
  cancelable: true,
584
709
  detail: {
585
710
  fetchOptions: fetchOptions,
586
711
  url: this.url,
587
- resume: this.resolveRequestPromise
712
+ resume: this.#resolveRequestPromise
588
713
  },
589
714
  target: this.target
590
715
  });
716
+ this.url = event.detail.url;
591
717
  if (event.defaultPrevented) await requestInterception;
718
+ return event;
592
719
  }
593
- willDelegateErrorHandling(error) {
720
+ #willDelegateErrorHandling(error) {
594
721
  const event = dispatch("turbo:fetch-request-error", {
595
722
  target: this.target,
596
723
  cancelable: true,
@@ -603,15 +730,38 @@ class FetchRequest {
603
730
  }
604
731
  }
605
732
 
733
+ function isSafe(fetchMethod) {
734
+ return fetchMethodFromString(fetchMethod) == FetchMethod.get;
735
+ }
736
+
737
+ function buildResourceAndBody(resource, method, requestBody, enctype) {
738
+ const searchParams = Array.from(requestBody).length > 0 ? new URLSearchParams(entriesExcludingFiles(requestBody)) : resource.searchParams;
739
+ if (isSafe(method)) {
740
+ return [ mergeIntoURLSearchParams(resource, searchParams), null ];
741
+ } else if (enctype == FetchEnctype.urlEncoded) {
742
+ return [ resource, searchParams ];
743
+ } else {
744
+ return [ resource, requestBody ];
745
+ }
746
+ }
747
+
748
+ function entriesExcludingFiles(requestBody) {
749
+ const entries = [];
750
+ for (const [name, value] of requestBody) {
751
+ if (value instanceof File) continue; else entries.push([ name, value ]);
752
+ }
753
+ return entries;
754
+ }
755
+
756
+ function mergeIntoURLSearchParams(url, requestBody) {
757
+ const searchParams = new URLSearchParams(entriesExcludingFiles(requestBody));
758
+ url.search = searchParams.toString();
759
+ return url;
760
+ }
761
+
606
762
  class AppearanceObserver {
763
+ started=false;
607
764
  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
765
  this.delegate = delegate;
616
766
  this.element = element;
617
767
  this.intersectionObserver = new IntersectionObserver(this.intersect);
@@ -628,9 +778,16 @@ class AppearanceObserver {
628
778
  this.intersectionObserver.unobserve(this.element);
629
779
  }
630
780
  }
781
+ intersect=entries => {
782
+ const lastEntry = entries.slice(-1)[0];
783
+ if (lastEntry?.isIntersecting) {
784
+ this.delegate.elementAppearedInViewport(this.element);
785
+ }
786
+ };
631
787
  }
632
788
 
633
789
  class StreamMessage {
790
+ static contentType="text/vnd.turbo-stream.html";
634
791
  static wrap(message) {
635
792
  if (typeof message == "string") {
636
793
  return new this(createDocumentFragment(message));
@@ -643,8 +800,6 @@ class StreamMessage {
643
800
  }
644
801
  }
645
802
 
646
- StreamMessage.contentType = "text/vnd.turbo-stream.html";
647
-
648
803
  function importStreamElements(fragment) {
649
804
  for (const element of fragment.querySelectorAll("turbo-stream")) {
650
805
  const streamElement = document.importNode(element, true);
@@ -656,85 +811,89 @@ function importStreamElements(fragment) {
656
811
  return fragment;
657
812
  }
658
813
 
659
- var FormSubmissionState;
660
-
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 = {}));
814
+ const PREFETCH_DELAY = 100;
669
815
 
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 = {}));
816
+ class PrefetchCache {
817
+ #prefetchTimeout=null;
818
+ #prefetched=null;
819
+ get(url) {
820
+ if (this.#prefetched && this.#prefetched.url === url && this.#prefetched.expire > Date.now()) {
821
+ return this.#prefetched.request;
822
+ }
823
+ }
824
+ setLater(url, request, ttl) {
825
+ this.clear();
826
+ this.#prefetchTimeout = setTimeout((() => {
827
+ request.perform();
828
+ this.set(url, request, ttl);
829
+ this.#prefetchTimeout = null;
830
+ }), PREFETCH_DELAY);
831
+ }
832
+ set(url, request, ttl) {
833
+ this.#prefetched = {
834
+ url: url,
835
+ request: request,
836
+ expire: new Date((new Date).getTime() + ttl)
837
+ };
838
+ }
839
+ clear() {
840
+ if (this.#prefetchTimeout) clearTimeout(this.#prefetchTimeout);
841
+ this.#prefetched = null;
842
+ }
843
+ }
677
844
 
678
- function formEnctypeFromString(encoding) {
679
- switch (encoding.toLowerCase()) {
680
- case FormEnctype.multipart:
681
- return FormEnctype.multipart;
845
+ const cacheTtl = 10 * 1e3;
682
846
 
683
- case FormEnctype.plain:
684
- return FormEnctype.plain;
847
+ const prefetchCache = new PrefetchCache;
685
848
 
686
- default:
687
- return FormEnctype.urlEncoded;
688
- }
689
- }
849
+ const FormSubmissionState = {
850
+ initialized: "initialized",
851
+ requesting: "requesting",
852
+ waiting: "waiting",
853
+ receiving: "receiving",
854
+ stopping: "stopping",
855
+ stopped: "stopped"
856
+ };
690
857
 
691
858
  class FormSubmission {
859
+ state=FormSubmissionState.initialized;
692
860
  static confirmMethod(message, _element, _submitter) {
693
861
  return Promise.resolve(confirm(message));
694
862
  }
695
863
  constructor(delegate, formElement, submitter, mustRedirect = false) {
696
- this.state = FormSubmissionState.initialized;
864
+ const method = getMethod(formElement, submitter);
865
+ const action = getAction(getFormAction(formElement, submitter), method);
866
+ const body = buildFormData(formElement, submitter);
867
+ const enctype = getEnctype(formElement, submitter);
697
868
  this.delegate = delegate;
698
869
  this.formElement = formElement;
699
870
  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);
871
+ this.fetchRequest = new FetchRequest(this, method, action, body, formElement, enctype);
706
872
  this.mustRedirect = mustRedirect;
707
873
  }
708
874
  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;
875
+ return this.fetchRequest.method;
876
+ }
877
+ set method(value) {
878
+ this.fetchRequest.method = value;
712
879
  }
713
880
  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
- }
881
+ return this.fetchRequest.url.toString();
882
+ }
883
+ set action(value) {
884
+ this.fetchRequest.url = expandURL(value);
721
885
  }
722
886
  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
- }
887
+ return this.fetchRequest.body;
728
888
  }
729
889
  get enctype() {
730
- var _a;
731
- return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
890
+ return this.fetchRequest.enctype;
732
891
  }
733
892
  get isSafe() {
734
893
  return this.fetchRequest.isSafe;
735
894
  }
736
- get stringFormData() {
737
- return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
895
+ get location() {
896
+ return this.fetchRequest.url;
738
897
  }
739
898
  async start() {
740
899
  const {initialized: initialized, requesting: requesting} = FormSubmissionState;
@@ -770,10 +929,10 @@ class FormSubmission {
770
929
  }
771
930
  }
772
931
  requestStarted(_request) {
773
- var _a;
774
932
  this.state = FormSubmissionState.waiting;
775
- (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
933
+ this.submitter?.setAttribute("disabled", "");
776
934
  this.setSubmitsWith();
935
+ markAsBusy(this.formElement);
777
936
  dispatch("turbo:submit-start", {
778
937
  target: this.formElement,
779
938
  detail: {
@@ -783,6 +942,7 @@ class FormSubmission {
783
942
  this.delegate.formSubmissionStarted(this);
784
943
  }
785
944
  requestPreventedHandlingResponse(request, response) {
945
+ prefetchCache.clear();
786
946
  this.result = {
787
947
  success: response.succeeded,
788
948
  fetchResponse: response
@@ -791,7 +951,10 @@ class FormSubmission {
791
951
  requestSucceededWithResponse(request, response) {
792
952
  if (response.clientError || response.serverError) {
793
953
  this.delegate.formSubmissionFailedWithResponse(this, response);
794
- } else if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {
954
+ return;
955
+ }
956
+ prefetchCache.clear();
957
+ if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {
795
958
  const error = new Error("Form responses must redirect to another location");
796
959
  this.delegate.formSubmissionErrored(this, error);
797
960
  } else {
@@ -818,15 +981,16 @@ class FormSubmission {
818
981
  this.delegate.formSubmissionErrored(this, error);
819
982
  }
820
983
  requestFinished(_request) {
821
- var _a;
822
984
  this.state = FormSubmissionState.stopped;
823
- (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
985
+ this.submitter?.removeAttribute("disabled");
824
986
  this.resetSubmitterText();
987
+ clearBusyState(this.formElement);
825
988
  dispatch("turbo:submit-end", {
826
989
  target: this.formElement,
827
- detail: Object.assign({
828
- formSubmission: this
829
- }, this.result)
990
+ detail: {
991
+ formSubmission: this,
992
+ ...this.result
993
+ }
830
994
  });
831
995
  this.delegate.formSubmissionFinished(this);
832
996
  }
@@ -857,15 +1021,14 @@ class FormSubmission {
857
1021
  return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
858
1022
  }
859
1023
  get submitsWith() {
860
- var _a;
861
- return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with");
1024
+ return this.submitter?.getAttribute("data-turbo-submits-with");
862
1025
  }
863
1026
  }
864
1027
 
865
1028
  function buildFormData(formElement, submitter) {
866
1029
  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");
1030
+ const name = submitter?.getAttribute("name");
1031
+ const value = submitter?.getAttribute("value");
869
1032
  if (name) {
870
1033
  formData.append(name, value || "");
871
1034
  }
@@ -887,14 +1050,30 @@ function responseSucceededWithoutRedirect(response) {
887
1050
  return response.statusCode == 200 && !response.redirected;
888
1051
  }
889
1052
 
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);
1053
+ function getFormAction(formElement, submitter) {
1054
+ const formElementAction = typeof formElement.action === "string" ? formElement.action : null;
1055
+ if (submitter?.hasAttribute("formaction")) {
1056
+ return submitter.getAttribute("formaction") || "";
1057
+ } else {
1058
+ return formElement.getAttribute("action") || formElementAction || "";
1059
+ }
1060
+ }
1061
+
1062
+ function getAction(formAction, fetchMethod) {
1063
+ const action = expandURL(formAction);
1064
+ if (isSafe(fetchMethod)) {
1065
+ action.search = "";
895
1066
  }
896
- url.search = searchParams.toString();
897
- return url;
1067
+ return action;
1068
+ }
1069
+
1070
+ function getMethod(formElement, submitter) {
1071
+ const method = submitter?.getAttribute("formmethod") || formElement.getAttribute("method") || "";
1072
+ return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
1073
+ }
1074
+
1075
+ function getEnctype(formElement, submitter) {
1076
+ return fetchEnctypeFromString(submitter?.getAttribute("formenctype") || formElement.enctype);
898
1077
  }
899
1078
 
900
1079
  class Snapshot {
@@ -917,11 +1096,7 @@ class Snapshot {
917
1096
  return this.element.isConnected;
918
1097
  }
919
1098
  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;
1099
+ return queryAutofocusableElement(this.element);
925
1100
  }
926
1101
  get permanentElements() {
927
1102
  return queryPermanentElementsAll(this.element);
@@ -951,23 +1126,8 @@ function queryPermanentElementsAll(node) {
951
1126
  }
952
1127
 
953
1128
  class FormSubmitObserver {
1129
+ started=false;
954
1130
  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
1131
  this.delegate = delegate;
972
1132
  this.eventTarget = eventTarget;
973
1133
  }
@@ -983,16 +1143,31 @@ class FormSubmitObserver {
983
1143
  this.started = false;
984
1144
  }
985
1145
  }
1146
+ submitCaptured=() => {
1147
+ this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
1148
+ this.eventTarget.addEventListener("submit", this.submitBubbled, false);
1149
+ };
1150
+ submitBubbled=event => {
1151
+ if (!event.defaultPrevented) {
1152
+ const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1153
+ const submitter = event.submitter || undefined;
1154
+ if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
1155
+ event.preventDefault();
1156
+ event.stopImmediatePropagation();
1157
+ this.delegate.formSubmitted(form, submitter);
1158
+ }
1159
+ }
1160
+ };
986
1161
  }
987
1162
 
988
1163
  function submissionDoesNotDismissDialog(form, submitter) {
989
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
1164
+ const method = submitter?.getAttribute("formmethod") || form.getAttribute("method");
990
1165
  return method != "dialog";
991
1166
  }
992
1167
 
993
1168
  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;
1169
+ if (submitter?.hasAttribute("formtarget") || form.hasAttribute("target")) {
1170
+ const target = submitter?.getAttribute("formtarget") || form.target;
996
1171
  for (const element of document.getElementsByName(target)) {
997
1172
  if (element instanceof HTMLIFrameElement) return false;
998
1173
  }
@@ -1003,9 +1178,9 @@ function submissionDoesNotTargetIFrame(form, submitter) {
1003
1178
  }
1004
1179
 
1005
1180
  class View {
1181
+ #resolveRenderPromise=_value => {};
1182
+ #resolveInterceptionPromise=_value => {};
1006
1183
  constructor(delegate, element) {
1007
- this.resolveRenderPromise = _value => {};
1008
- this.resolveInterceptionPromise = _value => {};
1009
1184
  this.delegate = delegate;
1010
1185
  this.element = element;
1011
1186
  }
@@ -1051,29 +1226,31 @@ class View {
1051
1226
  return window;
1052
1227
  }
1053
1228
  async render(renderer) {
1054
- const {isPreview: isPreview, shouldRender: shouldRender, newSnapshot: snapshot} = renderer;
1229
+ const {isPreview: isPreview, shouldRender: shouldRender, willRender: willRender, newSnapshot: snapshot} = renderer;
1230
+ const shouldInvalidate = willRender;
1055
1231
  if (shouldRender) {
1056
1232
  try {
1057
- this.renderPromise = new Promise((resolve => this.resolveRenderPromise = resolve));
1233
+ this.renderPromise = new Promise((resolve => this.#resolveRenderPromise = resolve));
1058
1234
  this.renderer = renderer;
1059
1235
  await this.prepareToRenderSnapshot(renderer);
1060
- const renderInterception = new Promise((resolve => this.resolveInterceptionPromise = resolve));
1236
+ const renderInterception = new Promise((resolve => this.#resolveInterceptionPromise = resolve));
1061
1237
  const options = {
1062
- resume: this.resolveInterceptionPromise,
1063
- render: this.renderer.renderElement
1238
+ resume: this.#resolveInterceptionPromise,
1239
+ render: this.renderer.renderElement,
1240
+ renderMethod: this.renderer.renderMethod
1064
1241
  };
1065
1242
  const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
1066
1243
  if (!immediateRender) await renderInterception;
1067
1244
  await this.renderSnapshot(renderer);
1068
- this.delegate.viewRenderedSnapshot(snapshot, isPreview);
1245
+ this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod);
1069
1246
  this.delegate.preloadOnLoadLinksForView(this.element);
1070
1247
  this.finishRenderingSnapshot(renderer);
1071
1248
  } finally {
1072
1249
  delete this.renderer;
1073
- this.resolveRenderPromise(undefined);
1250
+ this.#resolveRenderPromise(undefined);
1074
1251
  delete this.renderPromise;
1075
1252
  }
1076
- } else {
1253
+ } else if (shouldInvalidate) {
1077
1254
  this.invalidate(renderer.reloadReason);
1078
1255
  }
1079
1256
  }
@@ -1091,6 +1268,12 @@ class View {
1091
1268
  this.element.removeAttribute("data-turbo-preview");
1092
1269
  }
1093
1270
  }
1271
+ markVisitDirection(direction) {
1272
+ this.element.setAttribute("data-turbo-visit-direction", direction);
1273
+ }
1274
+ unmarkVisitDirection() {
1275
+ this.element.removeAttribute("data-turbo-visit-direction");
1276
+ }
1094
1277
  async renderSnapshot(renderer) {
1095
1278
  await renderer.render();
1096
1279
  }
@@ -1110,26 +1293,6 @@ class FrameView extends View {
1110
1293
 
1111
1294
  class LinkInterceptor {
1112
1295
  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
1296
  this.delegate = delegate;
1134
1297
  this.element = element;
1135
1298
  }
@@ -1143,6 +1306,26 @@ class LinkInterceptor {
1143
1306
  document.removeEventListener("turbo:click", this.linkClicked);
1144
1307
  document.removeEventListener("turbo:before-visit", this.willVisit);
1145
1308
  }
1309
+ clickBubbled=event => {
1310
+ if (this.respondsToEventTarget(event.target)) {
1311
+ this.clickEvent = event;
1312
+ } else {
1313
+ delete this.clickEvent;
1314
+ }
1315
+ };
1316
+ linkClicked=event => {
1317
+ if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
1318
+ if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
1319
+ this.clickEvent.preventDefault();
1320
+ event.preventDefault();
1321
+ this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
1322
+ }
1323
+ }
1324
+ delete this.clickEvent;
1325
+ };
1326
+ willVisit=_event => {
1327
+ delete this.clickEvent;
1328
+ };
1146
1329
  respondsToEventTarget(target) {
1147
1330
  const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
1148
1331
  return element && element.closest("turbo-frame, html") == this.element;
@@ -1150,25 +1333,8 @@ class LinkInterceptor {
1150
1333
  }
1151
1334
 
1152
1335
  class LinkClickObserver {
1336
+ started=false;
1153
1337
  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
1338
  this.delegate = delegate;
1173
1339
  this.eventTarget = eventTarget;
1174
1340
  }
@@ -1184,26 +1350,26 @@ class LinkClickObserver {
1184
1350
  this.started = false;
1185
1351
  }
1186
1352
  }
1353
+ clickCaptured=() => {
1354
+ this.eventTarget.removeEventListener("click", this.clickBubbled, false);
1355
+ this.eventTarget.addEventListener("click", this.clickBubbled, false);
1356
+ };
1357
+ clickBubbled=event => {
1358
+ if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
1359
+ const target = event.composedPath && event.composedPath()[0] || event.target;
1360
+ const link = findLinkFromClickTarget(target);
1361
+ if (link && doesNotTargetIFrame(link)) {
1362
+ const location = getLocationForLink(link);
1363
+ if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1364
+ event.preventDefault();
1365
+ this.delegate.followedLinkToLocation(link, location);
1366
+ }
1367
+ }
1368
+ }
1369
+ };
1187
1370
  clickEventIsSignificant(event) {
1188
1371
  return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
1189
1372
  }
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
1373
  }
1208
1374
 
1209
1375
  class FormLinkClickObserver {
@@ -1217,8 +1383,14 @@ class FormLinkClickObserver {
1217
1383
  stop() {
1218
1384
  this.linkInterceptor.stop();
1219
1385
  }
1386
+ canPrefetchRequestToLocation(link, location) {
1387
+ return false;
1388
+ }
1389
+ prefetchAndCacheRequestToLocation(link, location) {
1390
+ return;
1391
+ }
1220
1392
  willFollowLinkToLocation(link, location, originalEvent) {
1221
- return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
1393
+ return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && (link.hasAttribute("data-turbo-method") || link.hasAttribute("data-turbo-stream"));
1222
1394
  }
1223
1395
  followedLinkToLocation(link, location) {
1224
1396
  const form = document.createElement("form");
@@ -1291,7 +1463,7 @@ class Bardo {
1291
1463
  }
1292
1464
  replacePlaceholderWithPermanentElement(permanentElement) {
1293
1465
  const placeholder = this.getPlaceholderById(permanentElement.id);
1294
- placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
1466
+ placeholder?.replaceWith(permanentElement);
1295
1467
  }
1296
1468
  getPlaceholderById(id) {
1297
1469
  return this.placeholders.find((element => element.content == id));
@@ -1309,8 +1481,8 @@ function createPlaceholderForPermanentElement(permanentElement) {
1309
1481
  }
1310
1482
 
1311
1483
  class Renderer {
1484
+ #activeElement=null;
1312
1485
  constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1313
- this.activeElement = null;
1314
1486
  this.currentSnapshot = currentSnapshot;
1315
1487
  this.newSnapshot = newSnapshot;
1316
1488
  this.isPreview = isPreview;
@@ -1330,6 +1502,7 @@ class Renderer {
1330
1502
  prepareToRender() {
1331
1503
  return;
1332
1504
  }
1505
+ render() {}
1333
1506
  finishRendering() {
1334
1507
  if (this.resolvingFunctions) {
1335
1508
  this.resolvingFunctions.resolve();
@@ -1341,20 +1514,20 @@ class Renderer {
1341
1514
  }
1342
1515
  focusFirstAutofocusableElement() {
1343
1516
  const element = this.connectedSnapshot.firstAutofocusableElement;
1344
- if (elementIsFocusable(element)) {
1517
+ if (element) {
1345
1518
  element.focus();
1346
1519
  }
1347
1520
  }
1348
1521
  enteringBardo(currentPermanentElement) {
1349
- if (this.activeElement) return;
1522
+ if (this.#activeElement) return;
1350
1523
  if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
1351
- this.activeElement = this.currentSnapshot.activeElement;
1524
+ this.#activeElement = this.currentSnapshot.activeElement;
1352
1525
  }
1353
1526
  }
1354
1527
  leavingBardo(currentPermanentElement) {
1355
- if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
1356
- this.activeElement.focus();
1357
- this.activeElement = null;
1528
+ if (currentPermanentElement.contains(this.#activeElement) && this.#activeElement instanceof HTMLElement) {
1529
+ this.#activeElement.focus();
1530
+ this.#activeElement = null;
1358
1531
  }
1359
1532
  }
1360
1533
  get connectedSnapshot() {
@@ -1369,20 +1542,18 @@ class Renderer {
1369
1542
  get permanentElementMap() {
1370
1543
  return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1371
1544
  }
1372
- }
1373
-
1374
- function elementIsFocusable(element) {
1375
- return element && typeof element.focus == "function";
1545
+ get renderMethod() {
1546
+ return "replace";
1547
+ }
1376
1548
  }
1377
1549
 
1378
1550
  class FrameRenderer extends Renderer {
1379
1551
  static renderElement(currentElement, newElement) {
1380
- var _a;
1381
1552
  const destinationRange = document.createRange();
1382
1553
  destinationRange.selectNodeContents(currentElement);
1383
1554
  destinationRange.deleteContents();
1384
1555
  const frameElement = newElement;
1385
- const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1556
+ const sourceRange = frameElement.ownerDocument?.createRange();
1386
1557
  if (sourceRange) {
1387
1558
  sourceRange.selectNodeContents(frameElement);
1388
1559
  currentElement.appendChild(sourceRange.extractContents());
@@ -1396,14 +1567,14 @@ class FrameRenderer extends Renderer {
1396
1567
  return true;
1397
1568
  }
1398
1569
  async render() {
1399
- await nextAnimationFrame();
1570
+ await nextRepaint();
1400
1571
  this.preservingPermanentElements((() => {
1401
1572
  this.loadFrameElement();
1402
1573
  }));
1403
1574
  this.scrollFrameIntoView();
1404
- await nextAnimationFrame();
1575
+ await nextRepaint();
1405
1576
  this.focusFirstAutofocusableElement();
1406
- await nextAnimationFrame();
1577
+ await nextRepaint();
1407
1578
  this.activateScriptElements();
1408
1579
  }
1409
1580
  loadFrameElement() {
@@ -1453,6 +1624,7 @@ function readScrollBehavior(value, defaultValue) {
1453
1624
  }
1454
1625
 
1455
1626
  class ProgressBar {
1627
+ static animationDuration=300;
1456
1628
  static get defaultCSS() {
1457
1629
  return unindent`
1458
1630
  .turbo-progress-bar {
@@ -1470,13 +1642,10 @@ class ProgressBar {
1470
1642
  }
1471
1643
  `;
1472
1644
  }
1645
+ hiding=false;
1646
+ value=0;
1647
+ visible=false;
1473
1648
  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
1649
  this.stylesheetElement = this.createStylesheetElement();
1481
1650
  this.progressElement = this.createProgressElement();
1482
1651
  this.installStylesheetElement();
@@ -1531,6 +1700,9 @@ class ProgressBar {
1531
1700
  window.clearInterval(this.trickleInterval);
1532
1701
  delete this.trickleInterval;
1533
1702
  }
1703
+ trickle=() => {
1704
+ this.setValue(this.value + Math.random() / 100);
1705
+ };
1534
1706
  refresh() {
1535
1707
  requestAnimationFrame((() => {
1536
1708
  this.progressElement.style.width = `${10 + this.value * 90}%`;
@@ -1555,25 +1727,22 @@ class ProgressBar {
1555
1727
  }
1556
1728
  }
1557
1729
 
1558
- ProgressBar.animationDuration = 300;
1559
-
1560
1730
  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
- }
1731
+ detailsByOuterHTML=this.children.filter((element => !elementIsNoscript(element))).map((element => elementWithoutNonce(element))).reduce(((result, element) => {
1732
+ const {outerHTML: outerHTML} = element;
1733
+ const details = outerHTML in result ? result[outerHTML] : {
1734
+ type: elementType(element),
1735
+ tracked: elementIsTracked(element),
1736
+ elements: []
1737
+ };
1738
+ return {
1739
+ ...result,
1740
+ [outerHTML]: {
1741
+ ...details,
1742
+ elements: [ ...details.elements, element ]
1743
+ }
1744
+ };
1745
+ }), {});
1577
1746
  get trackedElementSignature() {
1578
1747
  return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
1579
1748
  }
@@ -1606,7 +1775,7 @@ class HeadSnapshot extends Snapshot {
1606
1775
  return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
1607
1776
  const {elements: [element]} = this.detailsByOuterHTML[outerHTML];
1608
1777
  return elementIsMetaElementWithName(element, name) ? element : result;
1609
- }), undefined);
1778
+ }), undefined | undefined);
1610
1779
  }
1611
1780
  }
1612
1781
 
@@ -1656,11 +1825,12 @@ class PageSnapshot extends Snapshot {
1656
1825
  static fromElement(element) {
1657
1826
  return this.fromDocument(element.ownerDocument);
1658
1827
  }
1659
- static fromDocument({head: head, body: body}) {
1660
- return new this(body, new HeadSnapshot(head));
1828
+ static fromDocument({documentElement: documentElement, body: body, head: head}) {
1829
+ return new this(documentElement, body, new HeadSnapshot(head));
1661
1830
  }
1662
- constructor(element, headSnapshot) {
1663
- super(element);
1831
+ constructor(documentElement, body, headSnapshot) {
1832
+ super(body);
1833
+ this.documentElement = documentElement;
1664
1834
  this.headSnapshot = headSnapshot;
1665
1835
  }
1666
1836
  clone() {
@@ -1675,14 +1845,16 @@ class PageSnapshot extends Snapshot {
1675
1845
  for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
1676
1846
  clonedPasswordInput.value = "";
1677
1847
  }
1678
- return new PageSnapshot(clonedElement, this.headSnapshot);
1848
+ return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot);
1849
+ }
1850
+ get lang() {
1851
+ return this.documentElement.getAttribute("lang");
1679
1852
  }
1680
1853
  get headElement() {
1681
1854
  return this.headSnapshot.element;
1682
1855
  }
1683
1856
  get rootLocation() {
1684
- var _a;
1685
- const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
1857
+ const root = this.getSetting("root") ?? "/";
1686
1858
  return expandURL(root);
1687
1859
  }
1688
1860
  get cacheControlValue() {
@@ -1697,29 +1869,38 @@ class PageSnapshot extends Snapshot {
1697
1869
  get isVisitable() {
1698
1870
  return this.getSetting("visit-control") != "reload";
1699
1871
  }
1872
+ get prefersViewTransitions() {
1873
+ return this.headSnapshot.getMetaValue("view-transition") === "same-origin";
1874
+ }
1875
+ get shouldMorphPage() {
1876
+ return this.getSetting("refresh-method") === "morph";
1877
+ }
1878
+ get shouldPreserveScrollPosition() {
1879
+ return this.getSetting("refresh-scroll") === "preserve";
1880
+ }
1700
1881
  getSetting(name) {
1701
1882
  return this.headSnapshot.getMetaValue(`turbo-${name}`);
1702
1883
  }
1703
1884
  }
1704
1885
 
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 = {}));
1886
+ class ViewTransitioner {
1887
+ #viewTransitionStarted=false;
1888
+ #lastOperation=Promise.resolve();
1889
+ renderChange(useViewTransition, render) {
1890
+ if (useViewTransition && this.viewTransitionsAvailable && !this.#viewTransitionStarted) {
1891
+ this.#viewTransitionStarted = true;
1892
+ this.#lastOperation = this.#lastOperation.then((async () => {
1893
+ await document.startViewTransition(render).finished;
1894
+ }));
1895
+ } else {
1896
+ this.#lastOperation = this.#lastOperation.then(render);
1897
+ }
1898
+ return this.#lastOperation;
1899
+ }
1900
+ get viewTransitionsAvailable() {
1901
+ return document.startViewTransition;
1902
+ }
1903
+ }
1723
1904
 
1724
1905
  const defaultOptions = {
1725
1906
  action: "advance",
@@ -1731,29 +1912,52 @@ const defaultOptions = {
1731
1912
  acceptsStreamResponse: false
1732
1913
  };
1733
1914
 
1734
- var SystemStatusCode;
1915
+ const TimingMetric = {
1916
+ visitStart: "visitStart",
1917
+ requestStart: "requestStart",
1918
+ requestEnd: "requestEnd",
1919
+ visitEnd: "visitEnd"
1920
+ };
1921
+
1922
+ const VisitState = {
1923
+ initialized: "initialized",
1924
+ started: "started",
1925
+ canceled: "canceled",
1926
+ failed: "failed",
1927
+ completed: "completed"
1928
+ };
1929
+
1930
+ const SystemStatusCode = {
1931
+ networkFailure: 0,
1932
+ timeoutFailure: -1,
1933
+ contentTypeMismatch: -2
1934
+ };
1735
1935
 
1736
- (function(SystemStatusCode) {
1737
- SystemStatusCode[SystemStatusCode["networkFailure"] = 0] = "networkFailure";
1738
- SystemStatusCode[SystemStatusCode["timeoutFailure"] = -1] = "timeoutFailure";
1739
- SystemStatusCode[SystemStatusCode["contentTypeMismatch"] = -2] = "contentTypeMismatch";
1740
- })(SystemStatusCode || (SystemStatusCode = {}));
1936
+ const Direction = {
1937
+ advance: "forward",
1938
+ restore: "back",
1939
+ replace: "none"
1940
+ };
1741
1941
 
1742
1942
  class Visit {
1943
+ identifier=uuid();
1944
+ timingMetrics={};
1945
+ followedRedirect=false;
1946
+ historyChanged=false;
1947
+ scrolled=false;
1948
+ shouldCacheSnapshot=true;
1949
+ acceptsStreamResponse=false;
1950
+ snapshotCached=false;
1951
+ state=VisitState.initialized;
1952
+ viewTransitioner=new ViewTransitioner;
1743
1953
  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
1954
  this.delegate = delegate;
1754
1955
  this.location = location;
1755
1956
  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);
1957
+ 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} = {
1958
+ ...defaultOptions,
1959
+ ...options
1960
+ };
1757
1961
  this.action = action;
1758
1962
  this.historyChanged = historyChanged;
1759
1963
  this.referrer = referrer;
@@ -1761,12 +1965,14 @@ class Visit {
1761
1965
  this.snapshotHTML = snapshotHTML;
1762
1966
  this.response = response;
1763
1967
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
1968
+ this.isPageRefresh = this.view.isPageRefresh(this);
1764
1969
  this.visitCachedSnapshot = visitCachedSnapshot;
1765
1970
  this.willRender = willRender;
1766
1971
  this.updateHistory = updateHistory;
1767
1972
  this.scrolled = !willRender;
1768
1973
  this.shouldCacheSnapshot = shouldCacheSnapshot;
1769
1974
  this.acceptsStreamResponse = acceptsStreamResponse;
1975
+ this.direction = direction || Direction[action];
1770
1976
  }
1771
1977
  get adapter() {
1772
1978
  return this.delegate.adapter;
@@ -1803,10 +2009,10 @@ class Visit {
1803
2009
  complete() {
1804
2010
  if (this.state == VisitState.started) {
1805
2011
  this.recordTimingMetric(TimingMetric.visitEnd);
2012
+ this.adapter.visitCompleted(this);
1806
2013
  this.state = VisitState.completed;
1807
2014
  this.followRedirect();
1808
2015
  if (!this.followedRedirect) {
1809
- this.adapter.visitCompleted(this);
1810
2016
  this.delegate.visitCompleted(this);
1811
2017
  }
1812
2018
  }
@@ -1815,12 +2021,12 @@ class Visit {
1815
2021
  if (this.state == VisitState.started) {
1816
2022
  this.state = VisitState.failed;
1817
2023
  this.adapter.visitFailed(this);
2024
+ this.delegate.visitCompleted(this);
1818
2025
  }
1819
2026
  }
1820
2027
  changeHistory() {
1821
- var _a;
1822
2028
  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;
2029
+ const actionForHistory = this.location.href === this.referrer?.href ? "replace" : this.action;
1824
2030
  const method = getHistoryMethodForAction(actionForHistory);
1825
2031
  this.history.update(method, this.location, this.restorationIdentifier);
1826
2032
  this.historyChanged = true;
@@ -1867,8 +2073,8 @@ class Visit {
1867
2073
  if (this.shouldCacheSnapshot) this.cacheSnapshot();
1868
2074
  if (this.view.renderPromise) await this.view.renderPromise;
1869
2075
  if (isSuccessful(statusCode) && responseHTML != null) {
1870
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
1871
- this.performScroll();
2076
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2077
+ await this.renderPageSnapshot(snapshot, false);
1872
2078
  this.adapter.visitRendered(this);
1873
2079
  this.complete();
1874
2080
  } else {
@@ -1901,12 +2107,11 @@ class Visit {
1901
2107
  const isPreview = this.shouldIssueRequest();
1902
2108
  this.render((async () => {
1903
2109
  this.cacheSnapshot();
1904
- if (this.isSamePage) {
2110
+ if (this.isSamePage || this.isPageRefresh) {
1905
2111
  this.adapter.visitRendered(this);
1906
2112
  } else {
1907
2113
  if (this.view.renderPromise) await this.view.renderPromise;
1908
- await this.view.renderPage(snapshot, isPreview, this.willRender, this);
1909
- this.performScroll();
2114
+ await this.renderPageSnapshot(snapshot, isPreview);
1910
2115
  this.adapter.visitRendered(this);
1911
2116
  if (!isPreview) {
1912
2117
  this.complete();
@@ -1916,8 +2121,7 @@ class Visit {
1916
2121
  }
1917
2122
  }
1918
2123
  followRedirect() {
1919
- var _a;
1920
- if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
2124
+ if (this.redirectedToLocation && !this.followedRedirect && this.response?.redirected) {
1921
2125
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1922
2126
  action: "replace",
1923
2127
  response: this.response,
@@ -1989,7 +2193,7 @@ class Visit {
1989
2193
  this.finishRequest();
1990
2194
  }
1991
2195
  performScroll() {
1992
- if (!this.scrolled && !this.view.forceReloaded) {
2196
+ if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) {
1993
2197
  if (this.action == "restore") {
1994
2198
  this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
1995
2199
  } else {
@@ -2019,7 +2223,9 @@ class Visit {
2019
2223
  this.timingMetrics[metric] = (new Date).getTime();
2020
2224
  }
2021
2225
  getTimingMetrics() {
2022
- return Object.assign({}, this.timingMetrics);
2226
+ return {
2227
+ ...this.timingMetrics
2228
+ };
2023
2229
  }
2024
2230
  getHistoryMethodForAction(action) {
2025
2231
  switch (action) {
@@ -2051,12 +2257,16 @@ class Visit {
2051
2257
  }
2052
2258
  async render(callback) {
2053
2259
  this.cancelRender();
2054
- await new Promise((resolve => {
2055
- this.frame = requestAnimationFrame((() => resolve()));
2056
- }));
2260
+ this.frame = await nextRepaint();
2057
2261
  await callback();
2058
2262
  delete this.frame;
2059
2263
  }
2264
+ async renderPageSnapshot(snapshot, isPreview) {
2265
+ await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(snapshot), (async () => {
2266
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
2267
+ this.performScroll();
2268
+ }));
2269
+ }
2060
2270
  cancelRender() {
2061
2271
  if (this.frame) {
2062
2272
  cancelAnimationFrame(this.frame);
@@ -2070,15 +2280,16 @@ function isSuccessful(statusCode) {
2070
2280
  }
2071
2281
 
2072
2282
  class BrowserAdapter {
2283
+ progressBar=new ProgressBar;
2073
2284
  constructor(session) {
2074
- this.progressBar = new ProgressBar;
2075
- this.showProgressBar = () => {
2076
- this.progressBar.show();
2077
- };
2078
2285
  this.session = session;
2079
2286
  }
2080
2287
  visitProposedToLocation(location, options) {
2081
- this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
2288
+ if (locationIsVisitable(location, this.navigator.rootLocation)) {
2289
+ this.navigator.startVisit(location, options?.restorationIdentifier || uuid(), options);
2290
+ } else {
2291
+ window.location.href = location.toString();
2292
+ }
2082
2293
  }
2083
2294
  visitStarted(visit) {
2084
2295
  this.location = visit.location;
@@ -2113,17 +2324,20 @@ class BrowserAdapter {
2113
2324
  return visit.loadResponse();
2114
2325
  }
2115
2326
  }
2116
- visitRequestFinished(_visit) {
2327
+ visitRequestFinished(_visit) {}
2328
+ visitCompleted(_visit) {
2117
2329
  this.progressBar.setValue(1);
2118
2330
  this.hideVisitProgressBar();
2119
2331
  }
2120
- visitCompleted(_visit) {}
2121
2332
  pageInvalidated(reason) {
2122
2333
  this.reload(reason);
2123
2334
  }
2124
- visitFailed(_visit) {}
2125
- visitRendered(_visit) {}
2126
- formSubmissionStarted(_formSubmission) {
2335
+ visitFailed(_visit) {
2336
+ this.progressBar.setValue(1);
2337
+ this.hideVisitProgressBar();
2338
+ }
2339
+ visitRendered(_visit) {}
2340
+ formSubmissionStarted(_formSubmission) {
2127
2341
  this.progressBar.setValue(0);
2128
2342
  this.showFormProgressBarAfterDelay();
2129
2343
  }
@@ -2153,12 +2367,14 @@ class BrowserAdapter {
2153
2367
  delete this.formProgressBarTimeout;
2154
2368
  }
2155
2369
  }
2370
+ showProgressBar=() => {
2371
+ this.progressBar.show();
2372
+ };
2156
2373
  reload(reason) {
2157
- var _a;
2158
2374
  dispatch("turbo:reload", {
2159
2375
  detail: reason
2160
2376
  });
2161
- window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;
2377
+ window.location.href = this.location?.toString() || window.location.href;
2162
2378
  }
2163
2379
  get navigator() {
2164
2380
  return this.session.navigator;
@@ -2166,16 +2382,9 @@ class BrowserAdapter {
2166
2382
  }
2167
2383
 
2168
2384
  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
- }
2385
+ selector="[data-turbo-temporary]";
2386
+ deprecatedSelector="[data-turbo-cache=false]";
2387
+ started=false;
2179
2388
  start() {
2180
2389
  if (!this.started) {
2181
2390
  this.started = true;
@@ -2188,6 +2397,11 @@ class CacheObserver {
2188
2397
  removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
2189
2398
  }
2190
2399
  }
2400
+ removeTemporaryElements=_event => {
2401
+ for (const element of this.temporaryElements) {
2402
+ element.remove();
2403
+ }
2404
+ };
2191
2405
  get temporaryElements() {
2192
2406
  return [ ...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation ];
2193
2407
  }
@@ -2216,41 +2430,40 @@ class FrameRedirector {
2216
2430
  this.formSubmitObserver.stop();
2217
2431
  }
2218
2432
  shouldInterceptLinkClick(element, _location, _event) {
2219
- return this.shouldRedirect(element);
2433
+ return this.#shouldRedirect(element);
2220
2434
  }
2221
2435
  linkClickIntercepted(element, url, event) {
2222
- const frame = this.findFrameElement(element);
2436
+ const frame = this.#findFrameElement(element);
2223
2437
  if (frame) {
2224
2438
  frame.delegate.linkClickIntercepted(element, url, event);
2225
2439
  }
2226
2440
  }
2227
2441
  willSubmitForm(element, submitter) {
2228
- return element.closest("turbo-frame") == null && this.shouldSubmit(element, submitter) && this.shouldRedirect(element, submitter);
2442
+ return element.closest("turbo-frame") == null && this.#shouldSubmit(element, submitter) && this.#shouldRedirect(element, submitter);
2229
2443
  }
2230
2444
  formSubmitted(element, submitter) {
2231
- const frame = this.findFrameElement(element, submitter);
2445
+ const frame = this.#findFrameElement(element, submitter);
2232
2446
  if (frame) {
2233
2447
  frame.delegate.formSubmitted(element, submitter);
2234
2448
  }
2235
2449
  }
2236
- shouldSubmit(form, submitter) {
2237
- var _a;
2238
- const action = getAction(form, submitter);
2450
+ #shouldSubmit(form, submitter) {
2451
+ const action = getAction$1(form, submitter);
2239
2452
  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);
2453
+ const rootLocation = expandURL(meta?.content ?? "/");
2454
+ return this.#shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
2242
2455
  }
2243
- shouldRedirect(element, submitter) {
2456
+ #shouldRedirect(element, submitter) {
2244
2457
  const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
2245
2458
  if (isNavigatable) {
2246
- const frame = this.findFrameElement(element, submitter);
2459
+ const frame = this.#findFrameElement(element, submitter);
2247
2460
  return frame ? frame != element.closest("turbo-frame") : false;
2248
2461
  } else {
2249
2462
  return false;
2250
2463
  }
2251
2464
  }
2252
- findFrameElement(element, submitter) {
2253
- const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
2465
+ #findFrameElement(element, submitter) {
2466
+ const id = submitter?.getAttribute("data-turbo-frame") || element.getAttribute("data-turbo-frame");
2254
2467
  if (id && id != "_top") {
2255
2468
  const frame = this.element.querySelector(`#${id}:not([disabled])`);
2256
2469
  if (frame instanceof FrameElement) {
@@ -2261,32 +2474,20 @@ class FrameRedirector {
2261
2474
  }
2262
2475
 
2263
2476
  class History {
2477
+ location;
2478
+ restorationIdentifier=uuid();
2479
+ restorationData={};
2480
+ started=false;
2481
+ pageLoaded=false;
2482
+ currentIndex=0;
2264
2483
  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
2484
  this.delegate = delegate;
2285
2485
  }
2286
2486
  start() {
2287
2487
  if (!this.started) {
2288
2488
  addEventListener("popstate", this.onPopState, false);
2289
2489
  addEventListener("load", this.onPageLoad, false);
2490
+ this.currentIndex = history.state?.turbo?.restorationIndex || 0;
2290
2491
  this.started = true;
2291
2492
  this.replace(new URL(window.location.href));
2292
2493
  }
@@ -2305,9 +2506,11 @@ class History {
2305
2506
  this.update(history.replaceState, location, restorationIdentifier);
2306
2507
  }
2307
2508
  update(method, location, restorationIdentifier = uuid()) {
2509
+ if (method === history.pushState) ++this.currentIndex;
2308
2510
  const state = {
2309
2511
  turbo: {
2310
- restorationIdentifier: restorationIdentifier
2512
+ restorationIdentifier: restorationIdentifier,
2513
+ restorationIndex: this.currentIndex
2311
2514
  }
2312
2515
  };
2313
2516
  method.call(history, state, "", location.href);
@@ -2320,12 +2523,14 @@ class History {
2320
2523
  updateRestorationData(additionalData) {
2321
2524
  const {restorationIdentifier: restorationIdentifier} = this;
2322
2525
  const restorationData = this.restorationData[restorationIdentifier];
2323
- this.restorationData[restorationIdentifier] = Object.assign(Object.assign({}, restorationData), additionalData);
2526
+ this.restorationData[restorationIdentifier] = {
2527
+ ...restorationData,
2528
+ ...additionalData
2529
+ };
2324
2530
  }
2325
2531
  assumeControlOfScrollRestoration() {
2326
- var _a;
2327
2532
  if (!this.previousScrollRestoration) {
2328
- this.previousScrollRestoration = (_a = history.scrollRestoration) !== null && _a !== void 0 ? _a : "auto";
2533
+ this.previousScrollRestoration = history.scrollRestoration ?? "auto";
2329
2534
  history.scrollRestoration = "manual";
2330
2535
  }
2331
2536
  }
@@ -2335,6 +2540,23 @@ class History {
2335
2540
  delete this.previousScrollRestoration;
2336
2541
  }
2337
2542
  }
2543
+ onPopState=event => {
2544
+ if (this.shouldHandlePopState()) {
2545
+ const {turbo: turbo} = event.state || {};
2546
+ if (turbo) {
2547
+ this.location = new URL(window.location.href);
2548
+ const {restorationIdentifier: restorationIdentifier, restorationIndex: restorationIndex} = turbo;
2549
+ this.restorationIdentifier = restorationIdentifier;
2550
+ const direction = restorationIndex > this.currentIndex ? "forward" : "back";
2551
+ this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
2552
+ this.currentIndex = restorationIndex;
2553
+ }
2554
+ }
2555
+ };
2556
+ onPageLoad=async _event => {
2557
+ await nextMicrotask();
2558
+ this.pageLoaded = true;
2559
+ };
2338
2560
  shouldHandlePopState() {
2339
2561
  return this.pageIsLoaded();
2340
2562
  }
@@ -2343,24 +2565,154 @@ class History {
2343
2565
  }
2344
2566
  }
2345
2567
 
2568
+ class LinkPrefetchObserver {
2569
+ started=false;
2570
+ #prefetchedLink=null;
2571
+ constructor(delegate, eventTarget) {
2572
+ this.delegate = delegate;
2573
+ this.eventTarget = eventTarget;
2574
+ }
2575
+ start() {
2576
+ if (this.started) return;
2577
+ if (this.eventTarget.readyState === "loading") {
2578
+ this.eventTarget.addEventListener("DOMContentLoaded", this.#enable, {
2579
+ once: true
2580
+ });
2581
+ } else {
2582
+ this.#enable();
2583
+ }
2584
+ }
2585
+ stop() {
2586
+ if (!this.started) return;
2587
+ this.eventTarget.removeEventListener("mouseenter", this.#tryToPrefetchRequest, {
2588
+ capture: true,
2589
+ passive: true
2590
+ });
2591
+ this.eventTarget.removeEventListener("mouseleave", this.#cancelRequestIfObsolete, {
2592
+ capture: true,
2593
+ passive: true
2594
+ });
2595
+ this.eventTarget.removeEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
2596
+ this.started = false;
2597
+ }
2598
+ #enable=() => {
2599
+ this.eventTarget.addEventListener("mouseenter", this.#tryToPrefetchRequest, {
2600
+ capture: true,
2601
+ passive: true
2602
+ });
2603
+ this.eventTarget.addEventListener("mouseleave", this.#cancelRequestIfObsolete, {
2604
+ capture: true,
2605
+ passive: true
2606
+ });
2607
+ this.eventTarget.addEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
2608
+ this.started = true;
2609
+ };
2610
+ #tryToPrefetchRequest=event => {
2611
+ if (getMetaContent("turbo-prefetch") === "false") return;
2612
+ const target = event.target;
2613
+ const isLink = target.matches && target.matches("a[href]:not([target^=_]):not([download])");
2614
+ if (isLink && this.#isPrefetchable(target)) {
2615
+ const link = target;
2616
+ const location = getLocationForLink(link);
2617
+ if (this.delegate.canPrefetchRequestToLocation(link, location)) {
2618
+ this.#prefetchedLink = link;
2619
+ const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, target);
2620
+ prefetchCache.setLater(location.toString(), fetchRequest, this.#cacheTtl);
2621
+ }
2622
+ }
2623
+ };
2624
+ #cancelRequestIfObsolete=event => {
2625
+ if (event.target === this.#prefetchedLink) this.#cancelPrefetchRequest();
2626
+ };
2627
+ #cancelPrefetchRequest=() => {
2628
+ prefetchCache.clear();
2629
+ this.#prefetchedLink = null;
2630
+ };
2631
+ #tryToUsePrefetchedRequest=event => {
2632
+ if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "get") {
2633
+ const cached = prefetchCache.get(event.detail.url.toString());
2634
+ if (cached) {
2635
+ event.detail.fetchRequest = cached;
2636
+ }
2637
+ prefetchCache.clear();
2638
+ }
2639
+ };
2640
+ prepareRequest(request) {
2641
+ const link = request.target;
2642
+ request.headers["X-Sec-Purpose"] = "prefetch";
2643
+ const turboFrame = link.closest("turbo-frame");
2644
+ const turboFrameTarget = link.getAttribute("data-turbo-frame") || turboFrame?.getAttribute("target") || turboFrame?.id;
2645
+ if (turboFrameTarget && turboFrameTarget !== "_top") {
2646
+ request.headers["Turbo-Frame"] = turboFrameTarget;
2647
+ }
2648
+ }
2649
+ requestSucceededWithResponse() {}
2650
+ requestStarted(fetchRequest) {}
2651
+ requestErrored(fetchRequest) {}
2652
+ requestFinished(fetchRequest) {}
2653
+ requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
2654
+ requestFailedWithResponse(fetchRequest, fetchResponse) {}
2655
+ get #cacheTtl() {
2656
+ return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl;
2657
+ }
2658
+ #isPrefetchable(link) {
2659
+ const href = link.getAttribute("href");
2660
+ if (!href) return false;
2661
+ if (unfetchableLink(link)) return false;
2662
+ if (linkToTheSamePage(link)) return false;
2663
+ if (linkOptsOut(link)) return false;
2664
+ if (nonSafeLink(link)) return false;
2665
+ if (eventPrevented(link)) return false;
2666
+ return true;
2667
+ }
2668
+ }
2669
+
2670
+ const unfetchableLink = link => link.origin !== document.location.origin || ![ "http:", "https:" ].includes(link.protocol) || link.hasAttribute("target");
2671
+
2672
+ const linkToTheSamePage = link => link.pathname + link.search === document.location.pathname + document.location.search || link.href.startsWith("#");
2673
+
2674
+ const linkOptsOut = link => {
2675
+ if (link.getAttribute("data-turbo-prefetch") === "false") return true;
2676
+ if (link.getAttribute("data-turbo") === "false") return true;
2677
+ const turboPrefetchParent = findClosestRecursively(link, "[data-turbo-prefetch]");
2678
+ if (turboPrefetchParent && turboPrefetchParent.getAttribute("data-turbo-prefetch") === "false") return true;
2679
+ return false;
2680
+ };
2681
+
2682
+ const nonSafeLink = link => {
2683
+ const turboMethod = link.getAttribute("data-turbo-method");
2684
+ if (turboMethod && turboMethod.toLowerCase() !== "get") return true;
2685
+ if (isUJS(link)) return true;
2686
+ if (link.hasAttribute("data-turbo-confirm")) return true;
2687
+ if (link.hasAttribute("data-turbo-stream")) return true;
2688
+ return false;
2689
+ };
2690
+
2691
+ const isUJS = link => link.hasAttribute("data-remote") || link.hasAttribute("data-behavior") || link.hasAttribute("data-confirm") || link.hasAttribute("data-method");
2692
+
2693
+ const eventPrevented = link => {
2694
+ const event = dispatch("turbo:before-prefetch", {
2695
+ target: link,
2696
+ cancelable: true
2697
+ });
2698
+ return event.defaultPrevented;
2699
+ };
2700
+
2346
2701
  class Navigator {
2347
2702
  constructor(delegate) {
2348
2703
  this.delegate = delegate;
2349
2704
  }
2350
2705
  proposeVisit(location, options = {}) {
2351
2706
  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
- }
2707
+ this.delegate.visitProposedToLocation(location, options);
2357
2708
  }
2358
2709
  }
2359
2710
  startVisit(locatable, restorationIdentifier, options = {}) {
2360
2711
  this.stop();
2361
- this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
2362
- referrer: this.location
2363
- }, options));
2712
+ this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, {
2713
+ referrer: this.location,
2714
+ ...options
2715
+ });
2364
2716
  this.currentVisit.start();
2365
2717
  }
2366
2718
  submitForm(form, submitter) {
@@ -2384,6 +2736,9 @@ class Navigator {
2384
2736
  get view() {
2385
2737
  return this.delegate.view;
2386
2738
  }
2739
+ get rootLocation() {
2740
+ return this.view.snapshot.rootLocation;
2741
+ }
2387
2742
  get history() {
2388
2743
  return this.delegate.history;
2389
2744
  }
@@ -2401,7 +2756,7 @@ class Navigator {
2401
2756
  this.view.clearSnapshotCache();
2402
2757
  }
2403
2758
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
2404
- const action = this.getActionForFormSubmission(formSubmission);
2759
+ const action = this.#getActionForFormSubmission(formSubmission, fetchResponse);
2405
2760
  const visitOptions = {
2406
2761
  action: action,
2407
2762
  shouldCacheSnapshot: shouldCacheSnapshot,
@@ -2424,7 +2779,9 @@ class Navigator {
2424
2779
  } else {
2425
2780
  await this.view.renderPage(snapshot, false, true, this.currentVisit);
2426
2781
  }
2427
- this.view.scrollToTop();
2782
+ if (!snapshot.shouldPreserveScrollPosition) {
2783
+ this.view.scrollToTop();
2784
+ }
2428
2785
  this.view.clearSnapshotCache();
2429
2786
  }
2430
2787
  }
@@ -2457,35 +2814,27 @@ class Navigator {
2457
2814
  get restorationIdentifier() {
2458
2815
  return this.history.restorationIdentifier;
2459
2816
  }
2460
- getActionForFormSubmission({submitter: submitter, formElement: formElement}) {
2461
- return getVisitAction(submitter, formElement) || "advance";
2817
+ #getActionForFormSubmission(formSubmission, fetchResponse) {
2818
+ const {submitter: submitter, formElement: formElement} = formSubmission;
2819
+ return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse);
2820
+ }
2821
+ #getDefaultAction(fetchResponse) {
2822
+ const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;
2823
+ return sameLocationRedirect ? "replace" : "advance";
2462
2824
  }
2463
2825
  }
2464
2826
 
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 = {}));
2827
+ const PageStage = {
2828
+ initial: 0,
2829
+ loading: 1,
2830
+ interactive: 2,
2831
+ complete: 3
2832
+ };
2473
2833
 
2474
2834
  class PageObserver {
2835
+ stage=PageStage.initial;
2836
+ started=false;
2475
2837
  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
2838
  this.delegate = delegate;
2490
2839
  }
2491
2840
  start() {
@@ -2505,6 +2854,14 @@ class PageObserver {
2505
2854
  this.started = false;
2506
2855
  }
2507
2856
  }
2857
+ interpretReadyState=() => {
2858
+ const {readyState: readyState} = this;
2859
+ if (readyState == "interactive") {
2860
+ this.pageIsInteractive();
2861
+ } else if (readyState == "complete") {
2862
+ this.pageIsComplete();
2863
+ }
2864
+ };
2508
2865
  pageIsInteractive() {
2509
2866
  if (this.stage == PageStage.loading) {
2510
2867
  this.stage = PageStage.interactive;
@@ -2518,20 +2875,17 @@ class PageObserver {
2518
2875
  this.delegate.pageLoaded();
2519
2876
  }
2520
2877
  }
2878
+ pageWillUnload=() => {
2879
+ this.delegate.pageWillUnload();
2880
+ };
2521
2881
  get readyState() {
2522
2882
  return document.readyState;
2523
2883
  }
2524
2884
  }
2525
2885
 
2526
2886
  class ScrollObserver {
2887
+ started=false;
2527
2888
  constructor(delegate) {
2528
- this.started = false;
2529
- this.onScroll = () => {
2530
- this.updatePosition({
2531
- x: window.pageXOffset,
2532
- y: window.pageYOffset
2533
- });
2534
- };
2535
2889
  this.delegate = delegate;
2536
2890
  }
2537
2891
  start() {
@@ -2547,6 +2901,12 @@ class ScrollObserver {
2547
2901
  this.started = false;
2548
2902
  }
2549
2903
  }
2904
+ onScroll=() => {
2905
+ this.updatePosition({
2906
+ x: window.pageXOffset,
2907
+ y: window.pageYOffset
2908
+ });
2909
+ };
2550
2910
  updatePosition(position) {
2551
2911
  this.delegate.scrollPositionChanged(position);
2552
2912
  }
@@ -2554,7 +2914,13 @@ class ScrollObserver {
2554
2914
 
2555
2915
  class StreamMessageRenderer {
2556
2916
  render({fragment: fragment}) {
2557
- Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => document.documentElement.appendChild(fragment)));
2917
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => {
2918
+ withAutofocusFromFragment(fragment, (() => {
2919
+ withPreservedFocus((() => {
2920
+ document.documentElement.appendChild(fragment);
2921
+ }));
2922
+ }));
2923
+ }));
2558
2924
  }
2559
2925
  enteringBardo(currentPermanentElement, newPermanentElement) {
2560
2926
  newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
@@ -2577,33 +2943,67 @@ function getPermanentElementMapForFragment(fragment) {
2577
2943
  return permanentElementMap;
2578
2944
  }
2579
2945
 
2946
+ async function withAutofocusFromFragment(fragment, callback) {
2947
+ const generatedID = `turbo-stream-autofocus-${uuid()}`;
2948
+ const turboStreams = fragment.querySelectorAll("turbo-stream");
2949
+ const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
2950
+ let willAutofocusId = null;
2951
+ if (elementWithAutofocus) {
2952
+ if (elementWithAutofocus.id) {
2953
+ willAutofocusId = elementWithAutofocus.id;
2954
+ } else {
2955
+ willAutofocusId = generatedID;
2956
+ }
2957
+ elementWithAutofocus.id = willAutofocusId;
2958
+ }
2959
+ callback();
2960
+ await nextRepaint();
2961
+ const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
2962
+ if (hasNoActiveElement && willAutofocusId) {
2963
+ const elementToAutofocus = document.getElementById(willAutofocusId);
2964
+ if (elementIsFocusable(elementToAutofocus)) {
2965
+ elementToAutofocus.focus();
2966
+ }
2967
+ if (elementToAutofocus && elementToAutofocus.id == generatedID) {
2968
+ elementToAutofocus.removeAttribute("id");
2969
+ }
2970
+ }
2971
+ }
2972
+
2973
+ async function withPreservedFocus(callback) {
2974
+ const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, (() => document.activeElement));
2975
+ const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;
2976
+ if (restoreFocusTo) {
2977
+ const elementToFocus = document.getElementById(restoreFocusTo);
2978
+ if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
2979
+ elementToFocus.focus();
2980
+ }
2981
+ }
2982
+ }
2983
+
2984
+ function firstAutofocusableElementInStreams(nodeListOfStreamElements) {
2985
+ for (const streamElement of nodeListOfStreamElements) {
2986
+ const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);
2987
+ if (elementWithAutofocus) return elementWithAutofocus;
2988
+ }
2989
+ return null;
2990
+ }
2991
+
2580
2992
  class StreamObserver {
2993
+ sources=new Set;
2994
+ #started=false;
2581
2995
  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
2996
  this.delegate = delegate;
2597
2997
  }
2598
2998
  start() {
2599
- if (!this.started) {
2600
- this.started = true;
2999
+ if (!this.#started) {
3000
+ this.#started = true;
2601
3001
  addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2602
3002
  }
2603
3003
  }
2604
3004
  stop() {
2605
- if (this.started) {
2606
- this.started = false;
3005
+ if (this.#started) {
3006
+ this.#started = false;
2607
3007
  removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2608
3008
  }
2609
3009
  }
@@ -2622,6 +3022,18 @@ class StreamObserver {
2622
3022
  streamSourceIsConnected(source) {
2623
3023
  return this.sources.has(source);
2624
3024
  }
3025
+ inspectFetchResponse=event => {
3026
+ const response = fetchResponseFromEvent(event);
3027
+ if (response && fetchResponseIsStream(response)) {
3028
+ event.preventDefault();
3029
+ this.receiveMessageResponse(response);
3030
+ }
3031
+ };
3032
+ receiveMessageEvent=event => {
3033
+ if (this.#started && typeof event.data == "string") {
3034
+ this.receiveMessageHTML(event.data);
3035
+ }
3036
+ };
2625
3037
  async receiveMessageResponse(response) {
2626
3038
  const html = await response.responseHTML;
2627
3039
  if (html) {
@@ -2634,16 +3046,14 @@ class StreamObserver {
2634
3046
  }
2635
3047
 
2636
3048
  function fetchResponseFromEvent(event) {
2637
- var _a;
2638
- const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse;
3049
+ const fetchResponse = event.detail?.fetchResponse;
2639
3050
  if (fetchResponse instanceof FetchResponse) {
2640
3051
  return fetchResponse;
2641
3052
  }
2642
3053
  }
2643
3054
 
2644
3055
  function fetchResponseIsStream(response) {
2645
- var _a;
2646
- const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
3056
+ const contentType = response.contentType ?? "";
2647
3057
  return contentType.startsWith(StreamMessage.contentType);
2648
3058
  }
2649
3059
 
@@ -2678,6 +3088,546 @@ class ErrorRenderer extends Renderer {
2678
3088
  }
2679
3089
  }
2680
3090
 
3091
+ var Idiomorph = function() {
3092
+ let EMPTY_SET = new Set;
3093
+ let defaults = {
3094
+ morphStyle: "outerHTML",
3095
+ callbacks: {
3096
+ beforeNodeAdded: noOp,
3097
+ afterNodeAdded: noOp,
3098
+ beforeNodeMorphed: noOp,
3099
+ afterNodeMorphed: noOp,
3100
+ beforeNodeRemoved: noOp,
3101
+ afterNodeRemoved: noOp,
3102
+ beforeAttributeUpdated: noOp
3103
+ },
3104
+ head: {
3105
+ style: "merge",
3106
+ shouldPreserve: function(elt) {
3107
+ return elt.getAttribute("im-preserve") === "true";
3108
+ },
3109
+ shouldReAppend: function(elt) {
3110
+ return elt.getAttribute("im-re-append") === "true";
3111
+ },
3112
+ shouldRemove: noOp,
3113
+ afterHeadMorphed: noOp
3114
+ }
3115
+ };
3116
+ function morph(oldNode, newContent, config = {}) {
3117
+ if (oldNode instanceof Document) {
3118
+ oldNode = oldNode.documentElement;
3119
+ }
3120
+ if (typeof newContent === "string") {
3121
+ newContent = parseContent(newContent);
3122
+ }
3123
+ let normalizedContent = normalizeContent(newContent);
3124
+ let ctx = createMorphContext(oldNode, normalizedContent, config);
3125
+ return morphNormalizedContent(oldNode, normalizedContent, ctx);
3126
+ }
3127
+ function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
3128
+ if (ctx.head.block) {
3129
+ let oldHead = oldNode.querySelector("head");
3130
+ let newHead = normalizedNewContent.querySelector("head");
3131
+ if (oldHead && newHead) {
3132
+ let promises = handleHeadElement(newHead, oldHead, ctx);
3133
+ Promise.all(promises).then((function() {
3134
+ morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
3135
+ head: {
3136
+ block: false,
3137
+ ignore: true
3138
+ }
3139
+ }));
3140
+ }));
3141
+ return;
3142
+ }
3143
+ }
3144
+ if (ctx.morphStyle === "innerHTML") {
3145
+ morphChildren(normalizedNewContent, oldNode, ctx);
3146
+ return oldNode.children;
3147
+ } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
3148
+ let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
3149
+ let previousSibling = bestMatch?.previousSibling;
3150
+ let nextSibling = bestMatch?.nextSibling;
3151
+ let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
3152
+ if (bestMatch) {
3153
+ return insertSiblings(previousSibling, morphedNode, nextSibling);
3154
+ } else {
3155
+ return [];
3156
+ }
3157
+ } else {
3158
+ throw "Do not understand how to morph style " + ctx.morphStyle;
3159
+ }
3160
+ }
3161
+ function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
3162
+ return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
3163
+ }
3164
+ function morphOldNodeTo(oldNode, newContent, ctx) {
3165
+ if (ctx.ignoreActive && oldNode === document.activeElement) ; else if (newContent == null) {
3166
+ if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
3167
+ oldNode.remove();
3168
+ ctx.callbacks.afterNodeRemoved(oldNode);
3169
+ return null;
3170
+ } else if (!isSoftMatch(oldNode, newContent)) {
3171
+ if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
3172
+ if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;
3173
+ oldNode.parentElement.replaceChild(newContent, oldNode);
3174
+ ctx.callbacks.afterNodeAdded(newContent);
3175
+ ctx.callbacks.afterNodeRemoved(oldNode);
3176
+ return newContent;
3177
+ } else {
3178
+ if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode;
3179
+ if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
3180
+ handleHeadElement(newContent, oldNode, ctx);
3181
+ } else {
3182
+ syncNodeFrom(newContent, oldNode, ctx);
3183
+ if (!ignoreValueOfActiveElement(oldNode, ctx)) {
3184
+ morphChildren(newContent, oldNode, ctx);
3185
+ }
3186
+ }
3187
+ ctx.callbacks.afterNodeMorphed(oldNode, newContent);
3188
+ return oldNode;
3189
+ }
3190
+ }
3191
+ function morphChildren(newParent, oldParent, ctx) {
3192
+ let nextNewChild = newParent.firstChild;
3193
+ let insertionPoint = oldParent.firstChild;
3194
+ let newChild;
3195
+ while (nextNewChild) {
3196
+ newChild = nextNewChild;
3197
+ nextNewChild = newChild.nextSibling;
3198
+ if (insertionPoint == null) {
3199
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
3200
+ oldParent.appendChild(newChild);
3201
+ ctx.callbacks.afterNodeAdded(newChild);
3202
+ removeIdsFromConsideration(ctx, newChild);
3203
+ continue;
3204
+ }
3205
+ if (isIdSetMatch(newChild, insertionPoint, ctx)) {
3206
+ morphOldNodeTo(insertionPoint, newChild, ctx);
3207
+ insertionPoint = insertionPoint.nextSibling;
3208
+ removeIdsFromConsideration(ctx, newChild);
3209
+ continue;
3210
+ }
3211
+ let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
3212
+ if (idSetMatch) {
3213
+ insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
3214
+ morphOldNodeTo(idSetMatch, newChild, ctx);
3215
+ removeIdsFromConsideration(ctx, newChild);
3216
+ continue;
3217
+ }
3218
+ let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
3219
+ if (softMatch) {
3220
+ insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
3221
+ morphOldNodeTo(softMatch, newChild, ctx);
3222
+ removeIdsFromConsideration(ctx, newChild);
3223
+ continue;
3224
+ }
3225
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
3226
+ oldParent.insertBefore(newChild, insertionPoint);
3227
+ ctx.callbacks.afterNodeAdded(newChild);
3228
+ removeIdsFromConsideration(ctx, newChild);
3229
+ }
3230
+ while (insertionPoint !== null) {
3231
+ let tempNode = insertionPoint;
3232
+ insertionPoint = insertionPoint.nextSibling;
3233
+ removeNode(tempNode, ctx);
3234
+ }
3235
+ }
3236
+ function ignoreAttribute(attr, to, updateType, ctx) {
3237
+ if (attr === "value" && ctx.ignoreActiveValue && to === document.activeElement) {
3238
+ return true;
3239
+ }
3240
+ return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
3241
+ }
3242
+ function syncNodeFrom(from, to, ctx) {
3243
+ let type = from.nodeType;
3244
+ if (type === 1) {
3245
+ const fromAttributes = from.attributes;
3246
+ const toAttributes = to.attributes;
3247
+ for (const fromAttribute of fromAttributes) {
3248
+ if (ignoreAttribute(fromAttribute.name, to, "update", ctx)) {
3249
+ continue;
3250
+ }
3251
+ if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
3252
+ to.setAttribute(fromAttribute.name, fromAttribute.value);
3253
+ }
3254
+ }
3255
+ for (let i = toAttributes.length - 1; 0 <= i; i--) {
3256
+ const toAttribute = toAttributes[i];
3257
+ if (ignoreAttribute(toAttribute.name, to, "remove", ctx)) {
3258
+ continue;
3259
+ }
3260
+ if (!from.hasAttribute(toAttribute.name)) {
3261
+ to.removeAttribute(toAttribute.name);
3262
+ }
3263
+ }
3264
+ }
3265
+ if (type === 8 || type === 3) {
3266
+ if (to.nodeValue !== from.nodeValue) {
3267
+ to.nodeValue = from.nodeValue;
3268
+ }
3269
+ }
3270
+ if (!ignoreValueOfActiveElement(to, ctx)) {
3271
+ syncInputValue(from, to, ctx);
3272
+ }
3273
+ }
3274
+ function syncBooleanAttribute(from, to, attributeName, ctx) {
3275
+ if (from[attributeName] !== to[attributeName]) {
3276
+ let ignoreUpdate = ignoreAttribute(attributeName, to, "update", ctx);
3277
+ if (!ignoreUpdate) {
3278
+ to[attributeName] = from[attributeName];
3279
+ }
3280
+ if (from[attributeName]) {
3281
+ if (!ignoreUpdate) {
3282
+ to.setAttribute(attributeName, from[attributeName]);
3283
+ }
3284
+ } else {
3285
+ if (!ignoreAttribute(attributeName, to, "remove", ctx)) {
3286
+ to.removeAttribute(attributeName);
3287
+ }
3288
+ }
3289
+ }
3290
+ }
3291
+ function syncInputValue(from, to, ctx) {
3292
+ if (from instanceof HTMLInputElement && to instanceof HTMLInputElement && from.type !== "file") {
3293
+ let fromValue = from.value;
3294
+ let toValue = to.value;
3295
+ syncBooleanAttribute(from, to, "checked", ctx);
3296
+ syncBooleanAttribute(from, to, "disabled", ctx);
3297
+ if (!from.hasAttribute("value")) {
3298
+ if (!ignoreAttribute("value", to, "remove", ctx)) {
3299
+ to.value = "";
3300
+ to.removeAttribute("value");
3301
+ }
3302
+ } else if (fromValue !== toValue) {
3303
+ if (!ignoreAttribute("value", to, "update", ctx)) {
3304
+ to.setAttribute("value", fromValue);
3305
+ to.value = fromValue;
3306
+ }
3307
+ }
3308
+ } else if (from instanceof HTMLOptionElement) {
3309
+ syncBooleanAttribute(from, to, "selected", ctx);
3310
+ } else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
3311
+ let fromValue = from.value;
3312
+ let toValue = to.value;
3313
+ if (ignoreAttribute("value", to, "update", ctx)) {
3314
+ return;
3315
+ }
3316
+ if (fromValue !== toValue) {
3317
+ to.value = fromValue;
3318
+ }
3319
+ if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
3320
+ to.firstChild.nodeValue = fromValue;
3321
+ }
3322
+ }
3323
+ }
3324
+ function handleHeadElement(newHeadTag, currentHead, ctx) {
3325
+ let added = [];
3326
+ let removed = [];
3327
+ let preserved = [];
3328
+ let nodesToAppend = [];
3329
+ let headMergeStyle = ctx.head.style;
3330
+ let srcToNewHeadNodes = new Map;
3331
+ for (const newHeadChild of newHeadTag.children) {
3332
+ srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
3333
+ }
3334
+ for (const currentHeadElt of currentHead.children) {
3335
+ let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
3336
+ let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
3337
+ let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
3338
+ if (inNewContent || isPreserved) {
3339
+ if (isReAppended) {
3340
+ removed.push(currentHeadElt);
3341
+ } else {
3342
+ srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
3343
+ preserved.push(currentHeadElt);
3344
+ }
3345
+ } else {
3346
+ if (headMergeStyle === "append") {
3347
+ if (isReAppended) {
3348
+ removed.push(currentHeadElt);
3349
+ nodesToAppend.push(currentHeadElt);
3350
+ }
3351
+ } else {
3352
+ if (ctx.head.shouldRemove(currentHeadElt) !== false) {
3353
+ removed.push(currentHeadElt);
3354
+ }
3355
+ }
3356
+ }
3357
+ }
3358
+ nodesToAppend.push(...srcToNewHeadNodes.values());
3359
+ let promises = [];
3360
+ for (const newNode of nodesToAppend) {
3361
+ let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
3362
+ if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
3363
+ if (newElt.href || newElt.src) {
3364
+ let resolve = null;
3365
+ let promise = new Promise((function(_resolve) {
3366
+ resolve = _resolve;
3367
+ }));
3368
+ newElt.addEventListener("load", (function() {
3369
+ resolve();
3370
+ }));
3371
+ promises.push(promise);
3372
+ }
3373
+ currentHead.appendChild(newElt);
3374
+ ctx.callbacks.afterNodeAdded(newElt);
3375
+ added.push(newElt);
3376
+ }
3377
+ }
3378
+ for (const removedElement of removed) {
3379
+ if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
3380
+ currentHead.removeChild(removedElement);
3381
+ ctx.callbacks.afterNodeRemoved(removedElement);
3382
+ }
3383
+ }
3384
+ ctx.head.afterHeadMorphed(currentHead, {
3385
+ added: added,
3386
+ kept: preserved,
3387
+ removed: removed
3388
+ });
3389
+ return promises;
3390
+ }
3391
+ function noOp() {}
3392
+ function mergeDefaults(config) {
3393
+ let finalConfig = {};
3394
+ Object.assign(finalConfig, defaults);
3395
+ Object.assign(finalConfig, config);
3396
+ finalConfig.callbacks = {};
3397
+ Object.assign(finalConfig.callbacks, defaults.callbacks);
3398
+ Object.assign(finalConfig.callbacks, config.callbacks);
3399
+ finalConfig.head = {};
3400
+ Object.assign(finalConfig.head, defaults.head);
3401
+ Object.assign(finalConfig.head, config.head);
3402
+ return finalConfig;
3403
+ }
3404
+ function createMorphContext(oldNode, newContent, config) {
3405
+ config = mergeDefaults(config);
3406
+ return {
3407
+ target: oldNode,
3408
+ newContent: newContent,
3409
+ config: config,
3410
+ morphStyle: config.morphStyle,
3411
+ ignoreActive: config.ignoreActive,
3412
+ ignoreActiveValue: config.ignoreActiveValue,
3413
+ idMap: createIdMap(oldNode, newContent),
3414
+ deadIds: new Set,
3415
+ callbacks: config.callbacks,
3416
+ head: config.head
3417
+ };
3418
+ }
3419
+ function isIdSetMatch(node1, node2, ctx) {
3420
+ if (node1 == null || node2 == null) {
3421
+ return false;
3422
+ }
3423
+ if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
3424
+ if (node1.id !== "" && node1.id === node2.id) {
3425
+ return true;
3426
+ } else {
3427
+ return getIdIntersectionCount(ctx, node1, node2) > 0;
3428
+ }
3429
+ }
3430
+ return false;
3431
+ }
3432
+ function isSoftMatch(node1, node2) {
3433
+ if (node1 == null || node2 == null) {
3434
+ return false;
3435
+ }
3436
+ return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName;
3437
+ }
3438
+ function removeNodesBetween(startInclusive, endExclusive, ctx) {
3439
+ while (startInclusive !== endExclusive) {
3440
+ let tempNode = startInclusive;
3441
+ startInclusive = startInclusive.nextSibling;
3442
+ removeNode(tempNode, ctx);
3443
+ }
3444
+ removeIdsFromConsideration(ctx, endExclusive);
3445
+ return endExclusive.nextSibling;
3446
+ }
3447
+ function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
3448
+ let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
3449
+ let potentialMatch = null;
3450
+ if (newChildPotentialIdCount > 0) {
3451
+ let potentialMatch = insertionPoint;
3452
+ let otherMatchCount = 0;
3453
+ while (potentialMatch != null) {
3454
+ if (isIdSetMatch(newChild, potentialMatch, ctx)) {
3455
+ return potentialMatch;
3456
+ }
3457
+ otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
3458
+ if (otherMatchCount > newChildPotentialIdCount) {
3459
+ return null;
3460
+ }
3461
+ potentialMatch = potentialMatch.nextSibling;
3462
+ }
3463
+ }
3464
+ return potentialMatch;
3465
+ }
3466
+ function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
3467
+ let potentialSoftMatch = insertionPoint;
3468
+ let nextSibling = newChild.nextSibling;
3469
+ let siblingSoftMatchCount = 0;
3470
+ while (potentialSoftMatch != null) {
3471
+ if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
3472
+ return null;
3473
+ }
3474
+ if (isSoftMatch(newChild, potentialSoftMatch)) {
3475
+ return potentialSoftMatch;
3476
+ }
3477
+ if (isSoftMatch(nextSibling, potentialSoftMatch)) {
3478
+ siblingSoftMatchCount++;
3479
+ nextSibling = nextSibling.nextSibling;
3480
+ if (siblingSoftMatchCount >= 2) {
3481
+ return null;
3482
+ }
3483
+ }
3484
+ potentialSoftMatch = potentialSoftMatch.nextSibling;
3485
+ }
3486
+ return potentialSoftMatch;
3487
+ }
3488
+ function parseContent(newContent) {
3489
+ let parser = new DOMParser;
3490
+ let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
3491
+ if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
3492
+ let content = parser.parseFromString(newContent, "text/html");
3493
+ if (contentWithSvgsRemoved.match(/<\/html>/)) {
3494
+ content.generatedByIdiomorph = true;
3495
+ return content;
3496
+ } else {
3497
+ let htmlElement = content.firstChild;
3498
+ if (htmlElement) {
3499
+ htmlElement.generatedByIdiomorph = true;
3500
+ return htmlElement;
3501
+ } else {
3502
+ return null;
3503
+ }
3504
+ }
3505
+ } else {
3506
+ let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
3507
+ let content = responseDoc.body.querySelector("template").content;
3508
+ content.generatedByIdiomorph = true;
3509
+ return content;
3510
+ }
3511
+ }
3512
+ function normalizeContent(newContent) {
3513
+ if (newContent == null) {
3514
+ const dummyParent = document.createElement("div");
3515
+ return dummyParent;
3516
+ } else if (newContent.generatedByIdiomorph) {
3517
+ return newContent;
3518
+ } else if (newContent instanceof Node) {
3519
+ const dummyParent = document.createElement("div");
3520
+ dummyParent.append(newContent);
3521
+ return dummyParent;
3522
+ } else {
3523
+ const dummyParent = document.createElement("div");
3524
+ for (const elt of [ ...newContent ]) {
3525
+ dummyParent.append(elt);
3526
+ }
3527
+ return dummyParent;
3528
+ }
3529
+ }
3530
+ function insertSiblings(previousSibling, morphedNode, nextSibling) {
3531
+ let stack = [];
3532
+ let added = [];
3533
+ while (previousSibling != null) {
3534
+ stack.push(previousSibling);
3535
+ previousSibling = previousSibling.previousSibling;
3536
+ }
3537
+ while (stack.length > 0) {
3538
+ let node = stack.pop();
3539
+ added.push(node);
3540
+ morphedNode.parentElement.insertBefore(node, morphedNode);
3541
+ }
3542
+ added.push(morphedNode);
3543
+ while (nextSibling != null) {
3544
+ stack.push(nextSibling);
3545
+ added.push(nextSibling);
3546
+ nextSibling = nextSibling.nextSibling;
3547
+ }
3548
+ while (stack.length > 0) {
3549
+ morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
3550
+ }
3551
+ return added;
3552
+ }
3553
+ function findBestNodeMatch(newContent, oldNode, ctx) {
3554
+ let currentElement;
3555
+ currentElement = newContent.firstChild;
3556
+ let bestElement = currentElement;
3557
+ let score = 0;
3558
+ while (currentElement) {
3559
+ let newScore = scoreElement(currentElement, oldNode, ctx);
3560
+ if (newScore > score) {
3561
+ bestElement = currentElement;
3562
+ score = newScore;
3563
+ }
3564
+ currentElement = currentElement.nextSibling;
3565
+ }
3566
+ return bestElement;
3567
+ }
3568
+ function scoreElement(node1, node2, ctx) {
3569
+ if (isSoftMatch(node1, node2)) {
3570
+ return .5 + getIdIntersectionCount(ctx, node1, node2);
3571
+ }
3572
+ return 0;
3573
+ }
3574
+ function removeNode(tempNode, ctx) {
3575
+ removeIdsFromConsideration(ctx, tempNode);
3576
+ if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
3577
+ tempNode.remove();
3578
+ ctx.callbacks.afterNodeRemoved(tempNode);
3579
+ }
3580
+ function isIdInConsideration(ctx, id) {
3581
+ return !ctx.deadIds.has(id);
3582
+ }
3583
+ function idIsWithinNode(ctx, id, targetNode) {
3584
+ let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
3585
+ return idSet.has(id);
3586
+ }
3587
+ function removeIdsFromConsideration(ctx, node) {
3588
+ let idSet = ctx.idMap.get(node) || EMPTY_SET;
3589
+ for (const id of idSet) {
3590
+ ctx.deadIds.add(id);
3591
+ }
3592
+ }
3593
+ function getIdIntersectionCount(ctx, node1, node2) {
3594
+ let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
3595
+ let matchCount = 0;
3596
+ for (const id of sourceSet) {
3597
+ if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
3598
+ ++matchCount;
3599
+ }
3600
+ }
3601
+ return matchCount;
3602
+ }
3603
+ function populateIdMapForNode(node, idMap) {
3604
+ let nodeParent = node.parentElement;
3605
+ let idElements = node.querySelectorAll("[id]");
3606
+ for (const elt of idElements) {
3607
+ let current = elt;
3608
+ while (current !== nodeParent && current != null) {
3609
+ let idSet = idMap.get(current);
3610
+ if (idSet == null) {
3611
+ idSet = new Set;
3612
+ idMap.set(current, idSet);
3613
+ }
3614
+ idSet.add(elt.id);
3615
+ current = current.parentElement;
3616
+ }
3617
+ }
3618
+ }
3619
+ function createIdMap(oldContent, newContent) {
3620
+ let idMap = new Map;
3621
+ populateIdMapForNode(oldContent, idMap);
3622
+ populateIdMapForNode(newContent, idMap);
3623
+ return idMap;
3624
+ }
3625
+ return {
3626
+ morph: morph,
3627
+ defaults: defaults
3628
+ };
3629
+ }();
3630
+
2681
3631
  class PageRenderer extends Renderer {
2682
3632
  static renderElement(currentElement, newElement) {
2683
3633
  if (document.body && newElement instanceof HTMLBodyElement) {
@@ -2702,6 +3652,7 @@ class PageRenderer extends Renderer {
2702
3652
  }
2703
3653
  }
2704
3654
  async prepareToRender() {
3655
+ this.#setLanguage();
2705
3656
  await this.mergeHead();
2706
3657
  }
2707
3658
  async render() {
@@ -2724,12 +3675,24 @@ class PageRenderer extends Renderer {
2724
3675
  get newElement() {
2725
3676
  return this.newSnapshot.element;
2726
3677
  }
3678
+ #setLanguage() {
3679
+ const {documentElement: documentElement} = this.currentSnapshot;
3680
+ const {lang: lang} = this.newSnapshot;
3681
+ if (lang) {
3682
+ documentElement.setAttribute("lang", lang);
3683
+ } else {
3684
+ documentElement.removeAttribute("lang");
3685
+ }
3686
+ }
2727
3687
  async mergeHead() {
2728
3688
  const mergedHeadElements = this.mergeProvisionalElements();
2729
3689
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
2730
3690
  this.copyNewHeadScriptElements();
2731
3691
  await mergedHeadElements;
2732
3692
  await newStylesheetElements;
3693
+ if (this.willRender) {
3694
+ this.removeUnusedDynamicStylesheetElements();
3695
+ }
2733
3696
  }
2734
3697
  async replaceBody() {
2735
3698
  await this.preservingPermanentElements((async () => {
@@ -2753,6 +3716,11 @@ class PageRenderer extends Renderer {
2753
3716
  document.head.appendChild(activateScriptElement(element));
2754
3717
  }
2755
3718
  }
3719
+ removeUnusedDynamicStylesheetElements() {
3720
+ for (const element of this.unusedDynamicStylesheetElements) {
3721
+ document.head.removeChild(element);
3722
+ }
3723
+ }
2756
3724
  async mergeProvisionalElements() {
2757
3725
  const newHeadElements = [ ...this.newHeadProvisionalElements ];
2758
3726
  for (const element of this.currentHeadProvisionalElements) {
@@ -2805,6 +3773,12 @@ class PageRenderer extends Renderer {
2805
3773
  async assignNewBody() {
2806
3774
  await this.renderElement(this.currentElement, this.newElement);
2807
3775
  }
3776
+ get unusedDynamicStylesheetElements() {
3777
+ return this.oldHeadStylesheetElements.filter((element => element.getAttribute("data-turbo-track") === "dynamic"));
3778
+ }
3779
+ get oldHeadStylesheetElements() {
3780
+ return this.currentHeadSnapshot.getStylesheetElementsNotInSnapshot(this.newHeadSnapshot);
3781
+ }
2808
3782
  get newHeadStylesheetElements() {
2809
3783
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
2810
3784
  }
@@ -2822,10 +3796,112 @@ class PageRenderer extends Renderer {
2822
3796
  }
2823
3797
  }
2824
3798
 
3799
+ class MorphRenderer extends PageRenderer {
3800
+ async render() {
3801
+ if (this.willRender) await this.#morphBody();
3802
+ }
3803
+ get renderMethod() {
3804
+ return "morph";
3805
+ }
3806
+ async #morphBody() {
3807
+ this.#morphElements(this.currentElement, this.newElement);
3808
+ this.#reloadRemoteFrames();
3809
+ dispatch("turbo:morph", {
3810
+ detail: {
3811
+ currentElement: this.currentElement,
3812
+ newElement: this.newElement
3813
+ }
3814
+ });
3815
+ }
3816
+ #morphElements(currentElement, newElement, morphStyle = "outerHTML") {
3817
+ this.isMorphingTurboFrame = this.#isFrameReloadedWithMorph(currentElement);
3818
+ Idiomorph.morph(currentElement, newElement, {
3819
+ morphStyle: morphStyle,
3820
+ callbacks: {
3821
+ beforeNodeAdded: this.#shouldAddElement,
3822
+ beforeNodeMorphed: this.#shouldMorphElement,
3823
+ beforeAttributeUpdated: this.#shouldUpdateAttribute,
3824
+ beforeNodeRemoved: this.#shouldRemoveElement,
3825
+ afterNodeMorphed: this.#didMorphElement
3826
+ }
3827
+ });
3828
+ }
3829
+ #shouldAddElement=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
3830
+ #shouldMorphElement=(oldNode, newNode) => {
3831
+ if (oldNode instanceof HTMLElement) {
3832
+ if (!oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode))) {
3833
+ const event = dispatch("turbo:before-morph-element", {
3834
+ cancelable: true,
3835
+ target: oldNode,
3836
+ detail: {
3837
+ newElement: newNode
3838
+ }
3839
+ });
3840
+ return !event.defaultPrevented;
3841
+ } else {
3842
+ return false;
3843
+ }
3844
+ }
3845
+ };
3846
+ #shouldUpdateAttribute=(attributeName, target, mutationType) => {
3847
+ const event = dispatch("turbo:before-morph-attribute", {
3848
+ cancelable: true,
3849
+ target: target,
3850
+ detail: {
3851
+ attributeName: attributeName,
3852
+ mutationType: mutationType
3853
+ }
3854
+ });
3855
+ return !event.defaultPrevented;
3856
+ };
3857
+ #didMorphElement=(oldNode, newNode) => {
3858
+ if (newNode instanceof HTMLElement) {
3859
+ dispatch("turbo:morph-element", {
3860
+ target: oldNode,
3861
+ detail: {
3862
+ newElement: newNode
3863
+ }
3864
+ });
3865
+ }
3866
+ };
3867
+ #shouldRemoveElement=node => this.#shouldMorphElement(node);
3868
+ #reloadRemoteFrames() {
3869
+ this.#remoteFrames().forEach((frame => {
3870
+ if (this.#isFrameReloadedWithMorph(frame)) {
3871
+ this.#renderFrameWithMorph(frame);
3872
+ frame.reload();
3873
+ }
3874
+ }));
3875
+ }
3876
+ #renderFrameWithMorph(frame) {
3877
+ frame.addEventListener("turbo:before-frame-render", (event => {
3878
+ event.detail.render = this.#morphFrameUpdate;
3879
+ }), {
3880
+ once: true
3881
+ });
3882
+ }
3883
+ #morphFrameUpdate=(currentElement, newElement) => {
3884
+ dispatch("turbo:before-frame-morph", {
3885
+ target: currentElement,
3886
+ detail: {
3887
+ currentElement: currentElement,
3888
+ newElement: newElement
3889
+ }
3890
+ });
3891
+ this.#morphElements(currentElement, newElement.children, "innerHTML");
3892
+ };
3893
+ #isFrameReloadedWithMorph(element) {
3894
+ return element.src && element.refresh === "morph";
3895
+ }
3896
+ #remoteFrames() {
3897
+ return Array.from(document.querySelectorAll("turbo-frame[src]")).filter((frame => !frame.closest("[data-turbo-permanent]")));
3898
+ }
3899
+ }
3900
+
2825
3901
  class SnapshotCache {
3902
+ keys=[];
3903
+ snapshots={};
2826
3904
  constructor(size) {
2827
- this.keys = [];
2828
- this.snapshots = {};
2829
3905
  this.size = size;
2830
3906
  }
2831
3907
  has(location) {
@@ -2867,23 +3943,25 @@ class SnapshotCache {
2867
3943
  }
2868
3944
 
2869
3945
  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;
3946
+ snapshotCache=new SnapshotCache(10);
3947
+ lastRenderedLocation=new URL(location.href);
3948
+ forceReloaded=false;
3949
+ shouldTransitionTo(newSnapshot) {
3950
+ return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions;
2875
3951
  }
2876
3952
  renderPage(snapshot, isPreview = false, willRender = true, visit) {
2877
- const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
3953
+ const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
3954
+ const rendererClass = shouldMorphPage ? MorphRenderer : PageRenderer;
3955
+ const renderer = new rendererClass(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
2878
3956
  if (!renderer.shouldRender) {
2879
3957
  this.forceReloaded = true;
2880
3958
  } else {
2881
- visit === null || visit === void 0 ? void 0 : visit.changeHistory();
3959
+ visit?.changeHistory();
2882
3960
  }
2883
3961
  return this.render(renderer);
2884
3962
  }
2885
3963
  renderError(snapshot, visit) {
2886
- visit === null || visit === void 0 ? void 0 : visit.changeHistory();
3964
+ visit?.changeHistory();
2887
3965
  const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
2888
3966
  return this.render(renderer);
2889
3967
  }
@@ -2903,31 +3981,38 @@ class PageView extends View {
2903
3981
  getCachedSnapshotForLocation(location) {
2904
3982
  return this.snapshotCache.get(location);
2905
3983
  }
3984
+ isPageRefresh(visit) {
3985
+ return !visit || this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === "replace";
3986
+ }
3987
+ shouldPreserveScrollPosition(visit) {
3988
+ return this.isPageRefresh(visit) && this.snapshot.shouldPreserveScrollPosition;
3989
+ }
2906
3990
  get snapshot() {
2907
3991
  return PageSnapshot.fromElement(this.element);
2908
3992
  }
2909
3993
  }
2910
3994
 
2911
3995
  class Preloader {
2912
- constructor(delegate) {
2913
- this.selector = "a[data-turbo-preload]";
3996
+ selector="a[data-turbo-preload]";
3997
+ constructor(delegate, snapshotCache) {
2914
3998
  this.delegate = delegate;
2915
- }
2916
- get snapshotCache() {
2917
- return this.delegate.navigator.view.snapshotCache;
3999
+ this.snapshotCache = snapshotCache;
2918
4000
  }
2919
4001
  start() {
2920
4002
  if (document.readyState === "loading") {
2921
- return document.addEventListener("DOMContentLoaded", (() => {
2922
- this.preloadOnLoadLinksForView(document.body);
2923
- }));
4003
+ document.addEventListener("DOMContentLoaded", this.#preloadAll);
2924
4004
  } else {
2925
4005
  this.preloadOnLoadLinksForView(document.body);
2926
4006
  }
2927
4007
  }
4008
+ stop() {
4009
+ document.removeEventListener("DOMContentLoaded", this.#preloadAll);
4010
+ }
2928
4011
  preloadOnLoadLinksForView(element) {
2929
4012
  for (const link of element.querySelectorAll(this.selector)) {
2930
- this.preloadURL(link);
4013
+ if (this.delegate.shouldPreloadLink(link)) {
4014
+ this.preloadURL(link);
4015
+ }
2931
4016
  }
2932
4017
  }
2933
4018
  async preloadURL(link) {
@@ -2935,46 +4020,83 @@ class Preloader {
2935
4020
  if (this.snapshotCache.has(location)) {
2936
4021
  return;
2937
4022
  }
4023
+ const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, link);
4024
+ await fetchRequest.perform();
4025
+ }
4026
+ prepareRequest(fetchRequest) {
4027
+ fetchRequest.headers["X-Sec-Purpose"] = "prefetch";
4028
+ }
4029
+ async requestSucceededWithResponse(fetchRequest, fetchResponse) {
2938
4030
  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);
4031
+ const responseHTML = await fetchResponse.responseHTML;
4032
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
4033
+ this.snapshotCache.put(fetchRequest.url, snapshot);
2948
4034
  } catch (_) {}
2949
4035
  }
4036
+ requestStarted(fetchRequest) {}
4037
+ requestErrored(fetchRequest) {}
4038
+ requestFinished(fetchRequest) {}
4039
+ requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
4040
+ requestFailedWithResponse(fetchRequest, fetchResponse) {}
4041
+ #preloadAll=() => {
4042
+ this.preloadOnLoadLinksForView(document.body);
4043
+ };
4044
+ }
4045
+
4046
+ class Cache {
4047
+ constructor(session) {
4048
+ this.session = session;
4049
+ }
4050
+ clear() {
4051
+ this.session.clearCache();
4052
+ }
4053
+ resetCacheControl() {
4054
+ this.#setCacheControl("");
4055
+ }
4056
+ exemptPageFromCache() {
4057
+ this.#setCacheControl("no-cache");
4058
+ }
4059
+ exemptPageFromPreview() {
4060
+ this.#setCacheControl("no-preview");
4061
+ }
4062
+ #setCacheControl(value) {
4063
+ setMetaContent("turbo-cache-control", value);
4064
+ }
2950
4065
  }
2951
4066
 
2952
4067
  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";
4068
+ navigator=new Navigator(this);
4069
+ history=new History(this);
4070
+ view=new PageView(this, document.documentElement);
4071
+ adapter=new BrowserAdapter(this);
4072
+ pageObserver=new PageObserver(this);
4073
+ cacheObserver=new CacheObserver;
4074
+ linkPrefetchObserver=new LinkPrefetchObserver(this, document);
4075
+ linkClickObserver=new LinkClickObserver(this, window);
4076
+ formSubmitObserver=new FormSubmitObserver(this, document);
4077
+ scrollObserver=new ScrollObserver(this);
4078
+ streamObserver=new StreamObserver(this);
4079
+ formLinkClickObserver=new FormLinkClickObserver(this, document.documentElement);
4080
+ frameRedirector=new FrameRedirector(this, document.documentElement);
4081
+ streamMessageRenderer=new StreamMessageRenderer;
4082
+ cache=new Cache(this);
4083
+ drive=true;
4084
+ enabled=true;
4085
+ progressBarDelay=500;
4086
+ started=false;
4087
+ formMode="on";
4088
+ #pageRefreshDebouncePeriod=150;
4089
+ constructor(recentRequests) {
4090
+ this.recentRequests = recentRequests;
4091
+ this.preloader = new Preloader(this, this.view.snapshotCache);
4092
+ this.debouncedRefresh = this.refresh;
4093
+ this.pageRefreshDebouncePeriod = this.pageRefreshDebouncePeriod;
2973
4094
  }
2974
4095
  start() {
2975
4096
  if (!this.started) {
2976
4097
  this.pageObserver.start();
2977
4098
  this.cacheObserver.start();
4099
+ this.linkPrefetchObserver.start();
2978
4100
  this.formLinkClickObserver.start();
2979
4101
  this.linkClickObserver.start();
2980
4102
  this.formSubmitObserver.start();
@@ -2994,6 +4116,7 @@ class Session {
2994
4116
  if (this.started) {
2995
4117
  this.pageObserver.stop();
2996
4118
  this.cacheObserver.stop();
4119
+ this.linkPrefetchObserver.stop();
2997
4120
  this.formLinkClickObserver.stop();
2998
4121
  this.linkClickObserver.stop();
2999
4122
  this.formSubmitObserver.stop();
@@ -3001,6 +4124,7 @@ class Session {
3001
4124
  this.streamObserver.stop();
3002
4125
  this.frameRedirector.stop();
3003
4126
  this.history.stop();
4127
+ this.preloader.stop();
3004
4128
  this.started = false;
3005
4129
  }
3006
4130
  }
@@ -3010,12 +4134,22 @@ class Session {
3010
4134
  visit(location, options = {}) {
3011
4135
  const frameElement = options.frame ? document.getElementById(options.frame) : null;
3012
4136
  if (frameElement instanceof FrameElement) {
4137
+ const action = options.action || getVisitAction(frameElement);
4138
+ frameElement.delegate.proposeVisitIfNavigatedWithAction(frameElement, action);
3013
4139
  frameElement.src = location.toString();
3014
- frameElement.loaded;
3015
4140
  } else {
3016
4141
  this.navigator.proposeVisit(expandURL(location), options);
3017
4142
  }
3018
4143
  }
4144
+ refresh(url, requestId) {
4145
+ const isRecentRequest = requestId && this.recentRequests.has(requestId);
4146
+ if (!isRecentRequest) {
4147
+ this.visit(url, {
4148
+ action: "replace",
4149
+ shouldCacheSnapshot: false
4150
+ });
4151
+ }
4152
+ }
3019
4153
  connectStreamSource(source) {
3020
4154
  this.streamObserver.connectStreamSource(source);
3021
4155
  }
@@ -3040,11 +4174,31 @@ class Session {
3040
4174
  get restorationIdentifier() {
3041
4175
  return this.history.restorationIdentifier;
3042
4176
  }
3043
- historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
4177
+ get pageRefreshDebouncePeriod() {
4178
+ return this.#pageRefreshDebouncePeriod;
4179
+ }
4180
+ set pageRefreshDebouncePeriod(value) {
4181
+ this.refresh = debounce(this.debouncedRefresh.bind(this), value);
4182
+ this.#pageRefreshDebouncePeriod = value;
4183
+ }
4184
+ shouldPreloadLink(element) {
4185
+ const isUnsafe = element.hasAttribute("data-turbo-method");
4186
+ const isStream = element.hasAttribute("data-turbo-stream");
4187
+ const frameTarget = element.getAttribute("data-turbo-frame");
4188
+ const frame = frameTarget == "_top" ? null : document.getElementById(frameTarget) || findClosestRecursively(element, "turbo-frame:not([disabled])");
4189
+ if (isUnsafe || isStream || frame instanceof FrameElement) {
4190
+ return false;
4191
+ } else {
4192
+ const location = new URL(element.href);
4193
+ return this.elementIsNavigatable(element) && locationIsVisitable(location, this.snapshot.rootLocation);
4194
+ }
4195
+ }
4196
+ historyPoppedToLocationWithRestorationIdentifierAndDirection(location, restorationIdentifier, direction) {
3044
4197
  if (this.enabled) {
3045
4198
  this.navigator.startVisit(location, restorationIdentifier, {
3046
4199
  action: "restore",
3047
- historyChanged: true
4200
+ historyChanged: true,
4201
+ direction: direction
3048
4202
  });
3049
4203
  } else {
3050
4204
  this.adapter.pageInvalidated({
@@ -3061,6 +4215,9 @@ class Session {
3061
4215
  return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
3062
4216
  }
3063
4217
  submittedFormLinkToLocation() {}
4218
+ canPrefetchRequestToLocation(link, location) {
4219
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
4220
+ }
3064
4221
  willFollowLinkToLocation(link, location, event) {
3065
4222
  return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
3066
4223
  }
@@ -3082,6 +4239,7 @@ class Session {
3082
4239
  visitStarted(visit) {
3083
4240
  if (!visit.acceptsStreamResponse) {
3084
4241
  markAsBusy(document.documentElement);
4242
+ this.view.markVisitDirection(visit.direction);
3085
4243
  }
3086
4244
  extendURLWithDeprecatedProperties(visit.location);
3087
4245
  if (!visit.silent) {
@@ -3089,6 +4247,7 @@ class Session {
3089
4247
  }
3090
4248
  }
3091
4249
  visitCompleted(visit) {
4250
+ this.view.unmarkVisitDirection();
3092
4251
  clearBusyState(document.documentElement);
3093
4252
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
3094
4253
  }
@@ -3099,7 +4258,7 @@ class Session {
3099
4258
  this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
3100
4259
  }
3101
4260
  willSubmitForm(form, submitter) {
3102
- const action = getAction(form, submitter);
4261
+ const action = getAction$1(form, submitter);
3103
4262
  return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
3104
4263
  }
3105
4264
  formSubmitted(form, submitter) {
@@ -3119,8 +4278,7 @@ class Session {
3119
4278
  this.renderStreamMessage(message);
3120
4279
  }
3121
4280
  viewWillCacheSnapshot() {
3122
- var _a;
3123
- if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) {
4281
+ if (!this.navigator.currentVisit?.silent) {
3124
4282
  this.notifyApplicationBeforeCachingSnapshot();
3125
4283
  }
3126
4284
  }
@@ -3132,9 +4290,9 @@ class Session {
3132
4290
  }
3133
4291
  return !defaultPrevented;
3134
4292
  }
3135
- viewRenderedSnapshot(_snapshot, _isPreview) {
4293
+ viewRenderedSnapshot(_snapshot, _isPreview, renderMethod) {
3136
4294
  this.view.lastRenderedLocation = this.history.location;
3137
- this.notifyApplicationAfterRender();
4295
+ this.notifyApplicationAfterRender(renderMethod);
3138
4296
  }
3139
4297
  preloadOnLoadLinksForView(element) {
3140
4298
  this.preloader.preloadOnLoadLinksForView(element);
@@ -3187,14 +4345,19 @@ class Session {
3187
4345
  }
3188
4346
  notifyApplicationBeforeRender(newBody, options) {
3189
4347
  return dispatch("turbo:before-render", {
3190
- detail: Object.assign({
3191
- newBody: newBody
3192
- }, options),
4348
+ detail: {
4349
+ newBody: newBody,
4350
+ ...options
4351
+ },
3193
4352
  cancelable: true
3194
4353
  });
3195
4354
  }
3196
- notifyApplicationAfterRender() {
3197
- return dispatch("turbo:render");
4355
+ notifyApplicationAfterRender(renderMethod) {
4356
+ return dispatch("turbo:render", {
4357
+ detail: {
4358
+ renderMethod: renderMethod
4359
+ }
4360
+ });
3198
4361
  }
3199
4362
  notifyApplicationAfterPageLoad(timing = {}) {
3200
4363
  return dispatch("turbo:load", {
@@ -3273,67 +4436,9 @@ const deprecatedLocationPropertyDescriptors = {
3273
4436
  }
3274
4437
  };
3275
4438
 
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);
4439
+ const session = new Session(recentRequests);
3335
4440
 
3336
- const {navigator: navigator$1} = session;
4441
+ const {cache: cache, navigator: navigator$1} = session;
3337
4442
 
3338
4443
  function start() {
3339
4444
  session.start();
@@ -3384,6 +4489,7 @@ var Turbo = Object.freeze({
3384
4489
  PageRenderer: PageRenderer,
3385
4490
  PageSnapshot: PageSnapshot,
3386
4491
  FrameRenderer: FrameRenderer,
4492
+ fetch: fetchWithTurboHeaders,
3387
4493
  start: start,
3388
4494
  registerAdapter: registerAdapter,
3389
4495
  visit: visit,
@@ -3393,28 +4499,20 @@ var Turbo = Object.freeze({
3393
4499
  clearCache: clearCache,
3394
4500
  setProgressBarDelay: setProgressBarDelay,
3395
4501
  setConfirmMethod: setConfirmMethod,
3396
- setFormMode: setFormMode,
3397
- StreamActions: StreamActions
4502
+ setFormMode: setFormMode
3398
4503
  });
3399
4504
 
3400
4505
  class TurboFrameMissingError extends Error {}
3401
4506
 
3402
4507
  class FrameController {
4508
+ fetchResponseLoaded=_fetchResponse => Promise.resolve();
4509
+ #currentFetchRequest=null;
4510
+ #resolveVisitPromise=() => {};
4511
+ #connected=false;
4512
+ #hasBeenLoaded=false;
4513
+ #ignoredAttributes=new Set;
4514
+ action=null;
3403
4515
  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
4516
  this.element = element;
3419
4517
  this.view = new FrameView(this, this.element);
3420
4518
  this.appearanceObserver = new AppearanceObserver(this, this.element);
@@ -3424,12 +4522,12 @@ class FrameController {
3424
4522
  this.formSubmitObserver = new FormSubmitObserver(this, this.element);
3425
4523
  }
3426
4524
  connect() {
3427
- if (!this.connected) {
3428
- this.connected = true;
4525
+ if (!this.#connected) {
4526
+ this.#connected = true;
3429
4527
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
3430
4528
  this.appearanceObserver.start();
3431
4529
  } else {
3432
- this.loadSourceURL();
4530
+ this.#loadSourceURL();
3433
4531
  }
3434
4532
  this.formLinkClickObserver.start();
3435
4533
  this.linkInterceptor.start();
@@ -3437,8 +4535,8 @@ class FrameController {
3437
4535
  }
3438
4536
  }
3439
4537
  disconnect() {
3440
- if (this.connected) {
3441
- this.connected = false;
4538
+ if (this.#connected) {
4539
+ this.#connected = false;
3442
4540
  this.appearanceObserver.stop();
3443
4541
  this.formLinkClickObserver.stop();
3444
4542
  this.linkInterceptor.stop();
@@ -3447,45 +4545,39 @@ class FrameController {
3447
4545
  }
3448
4546
  disabledChanged() {
3449
4547
  if (this.loadingStyle == FrameLoadingStyle.eager) {
3450
- this.loadSourceURL();
4548
+ this.#loadSourceURL();
3451
4549
  }
3452
4550
  }
3453
4551
  sourceURLChanged() {
3454
- if (this.isIgnoringChangesTo("src")) return;
4552
+ if (this.#isIgnoringChangesTo("src")) return;
3455
4553
  if (this.element.isConnected) {
3456
4554
  this.complete = false;
3457
4555
  }
3458
- if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
3459
- this.loadSourceURL();
4556
+ if (this.loadingStyle == FrameLoadingStyle.eager || this.#hasBeenLoaded) {
4557
+ this.#loadSourceURL();
3460
4558
  }
3461
4559
  }
3462
4560
  sourceURLReloaded() {
3463
4561
  const {src: src} = this.element;
3464
- this.ignoringChangesToAttribute("complete", (() => {
3465
- this.element.removeAttribute("complete");
3466
- }));
4562
+ this.element.removeAttribute("complete");
3467
4563
  this.element.src = null;
3468
4564
  this.element.src = src;
3469
4565
  return this.element.loaded;
3470
4566
  }
3471
- completeChanged() {
3472
- if (this.isIgnoringChangesTo("complete")) return;
3473
- this.loadSourceURL();
3474
- }
3475
4567
  loadingStyleChanged() {
3476
4568
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
3477
4569
  this.appearanceObserver.start();
3478
4570
  } else {
3479
4571
  this.appearanceObserver.stop();
3480
- this.loadSourceURL();
4572
+ this.#loadSourceURL();
3481
4573
  }
3482
4574
  }
3483
- async loadSourceURL() {
4575
+ async #loadSourceURL() {
3484
4576
  if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
3485
- this.element.loaded = this.visit(expandURL(this.sourceURL));
4577
+ this.element.loaded = this.#visit(expandURL(this.sourceURL));
3486
4578
  this.appearanceObserver.stop();
3487
4579
  await this.element.loaded;
3488
- this.hasBeenLoaded = true;
4580
+ this.#hasBeenLoaded = true;
3489
4581
  }
3490
4582
  }
3491
4583
  async loadResponse(fetchResponse) {
@@ -3498,34 +4590,34 @@ class FrameController {
3498
4590
  const document = parseHTMLDocument(html);
3499
4591
  const pageSnapshot = PageSnapshot.fromDocument(document);
3500
4592
  if (pageSnapshot.isVisitable) {
3501
- await this.loadFrameResponse(fetchResponse, document);
4593
+ await this.#loadFrameResponse(fetchResponse, document);
3502
4594
  } else {
3503
- await this.handleUnvisitableFrameResponse(fetchResponse);
4595
+ await this.#handleUnvisitableFrameResponse(fetchResponse);
3504
4596
  }
3505
4597
  }
3506
4598
  } finally {
3507
- this.fetchResponseLoaded = () => {};
4599
+ this.fetchResponseLoaded = () => Promise.resolve();
3508
4600
  }
3509
4601
  }
3510
4602
  elementAppearedInViewport(element) {
3511
- this.proposeVisitIfNavigatedWithAction(element, element);
3512
- this.loadSourceURL();
4603
+ this.proposeVisitIfNavigatedWithAction(element, getVisitAction(element));
4604
+ this.#loadSourceURL();
3513
4605
  }
3514
4606
  willSubmitFormLinkToLocation(link) {
3515
- return this.shouldInterceptNavigation(link);
4607
+ return this.#shouldInterceptNavigation(link);
3516
4608
  }
3517
4609
  submittedFormLinkToLocation(link, _location, form) {
3518
- const frame = this.findFrameElement(link);
4610
+ const frame = this.#findFrameElement(link);
3519
4611
  if (frame) form.setAttribute("data-turbo-frame", frame.id);
3520
4612
  }
3521
4613
  shouldInterceptLinkClick(element, _location, _event) {
3522
- return this.shouldInterceptNavigation(element);
4614
+ return this.#shouldInterceptNavigation(element);
3523
4615
  }
3524
4616
  linkClickIntercepted(element, location) {
3525
- this.navigateFrame(element, location);
4617
+ this.#navigateFrame(element, location);
3526
4618
  }
3527
4619
  willSubmitForm(element, submitter) {
3528
- return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
4620
+ return element.closest("turbo-frame") == this.element && this.#shouldInterceptNavigation(element, submitter);
3529
4621
  }
3530
4622
  formSubmitted(element, submitter) {
3531
4623
  if (this.formSubmission) {
@@ -3537,9 +4629,8 @@ class FrameController {
3537
4629
  this.formSubmission.start();
3538
4630
  }
3539
4631
  prepareRequest(request) {
3540
- var _a;
3541
4632
  request.headers["Turbo-Frame"] = this.id;
3542
- if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
4633
+ if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
3543
4634
  request.acceptResponseType(StreamMessage.contentType);
3544
4635
  }
3545
4636
  }
@@ -3547,29 +4638,29 @@ class FrameController {
3547
4638
  markAsBusy(this.element);
3548
4639
  }
3549
4640
  requestPreventedHandlingResponse(_request, _response) {
3550
- this.resolveVisitPromise();
4641
+ this.#resolveVisitPromise();
3551
4642
  }
3552
4643
  async requestSucceededWithResponse(request, response) {
3553
4644
  await this.loadResponse(response);
3554
- this.resolveVisitPromise();
4645
+ this.#resolveVisitPromise();
3555
4646
  }
3556
4647
  async requestFailedWithResponse(request, response) {
3557
4648
  await this.loadResponse(response);
3558
- this.resolveVisitPromise();
4649
+ this.#resolveVisitPromise();
3559
4650
  }
3560
4651
  requestErrored(request, error) {
3561
4652
  console.error(error);
3562
- this.resolveVisitPromise();
4653
+ this.#resolveVisitPromise();
3563
4654
  }
3564
4655
  requestFinished(_request) {
3565
4656
  clearBusyState(this.element);
3566
4657
  }
3567
4658
  formSubmissionStarted({formElement: formElement}) {
3568
- markAsBusy(formElement, this.findFrameElement(formElement));
4659
+ markAsBusy(formElement, this.#findFrameElement(formElement));
3569
4660
  }
3570
4661
  formSubmissionSucceededWithResponse(formSubmission, response) {
3571
- const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
3572
- frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
4662
+ const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter);
4663
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame));
3573
4664
  frame.delegate.loadResponse(response);
3574
4665
  if (!formSubmission.isSafe) {
3575
4666
  session.clearCache();
@@ -3583,14 +4674,15 @@ class FrameController {
3583
4674
  console.error(error);
3584
4675
  }
3585
4676
  formSubmissionFinished({formElement: formElement}) {
3586
- clearBusyState(formElement, this.findFrameElement(formElement));
4677
+ clearBusyState(formElement, this.#findFrameElement(formElement));
3587
4678
  }
3588
4679
  allowsImmediateRender({element: newFrame}, options) {
3589
4680
  const event = dispatch("turbo:before-frame-render", {
3590
4681
  target: this.element,
3591
- detail: Object.assign({
3592
- newFrame: newFrame
3593
- }, options),
4682
+ detail: {
4683
+ newFrame: newFrame,
4684
+ ...options
4685
+ },
3594
4686
  cancelable: true
3595
4687
  });
3596
4688
  const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
@@ -3599,7 +4691,7 @@ class FrameController {
3599
4691
  }
3600
4692
  return !defaultPrevented;
3601
4693
  }
3602
- viewRenderedSnapshot(_snapshot, _isPreview) {}
4694
+ viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {}
3603
4695
  preloadOnLoadLinksForView(element) {
3604
4696
  session.preloadOnLoadLinksForView(element);
3605
4697
  }
@@ -3607,7 +4699,14 @@ class FrameController {
3607
4699
  willRenderFrame(currentElement, _newElement) {
3608
4700
  this.previousFrameElement = currentElement.cloneNode(true);
3609
4701
  }
3610
- async loadFrameResponse(fetchResponse, document) {
4702
+ visitCachedSnapshot=({element: element}) => {
4703
+ const frame = element.querySelector("#" + this.element.id);
4704
+ if (frame && this.previousFrameElement) {
4705
+ frame.replaceChildren(...this.previousFrameElement.children);
4706
+ }
4707
+ delete this.previousFrameElement;
4708
+ };
4709
+ async #loadFrameResponse(fetchResponse, document) {
3611
4710
  const newFrameElement = await this.extractForeignFrameElement(document.body);
3612
4711
  if (newFrameElement) {
3613
4712
  const snapshot = new Snapshot(newFrameElement);
@@ -3618,41 +4717,40 @@ class FrameController {
3618
4717
  this.complete = true;
3619
4718
  session.frameRendered(fetchResponse, this.element);
3620
4719
  session.frameLoaded(this.element);
3621
- this.fetchResponseLoaded(fetchResponse);
3622
- } else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3623
- this.handleFrameMissingFromResponse(fetchResponse);
4720
+ await this.fetchResponseLoaded(fetchResponse);
4721
+ } else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) {
4722
+ this.#handleFrameMissingFromResponse(fetchResponse);
3624
4723
  }
3625
4724
  }
3626
- async visit(url) {
3627
- var _a;
4725
+ async #visit(url) {
3628
4726
  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;
4727
+ this.#currentFetchRequest?.cancel();
4728
+ this.#currentFetchRequest = request;
3631
4729
  return new Promise((resolve => {
3632
- this.resolveVisitPromise = () => {
3633
- this.resolveVisitPromise = () => {};
3634
- this.currentFetchRequest = null;
4730
+ this.#resolveVisitPromise = () => {
4731
+ this.#resolveVisitPromise = () => {};
4732
+ this.#currentFetchRequest = null;
3635
4733
  resolve();
3636
4734
  };
3637
4735
  request.perform();
3638
4736
  }));
3639
4737
  }
3640
- navigateFrame(element, url, submitter) {
3641
- const frame = this.findFrameElement(element, submitter);
3642
- frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3643
- this.withCurrentNavigationElement(element, (() => {
4738
+ #navigateFrame(element, url, submitter) {
4739
+ const frame = this.#findFrameElement(element, submitter);
4740
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(submitter, element, frame));
4741
+ this.#withCurrentNavigationElement(element, (() => {
3644
4742
  frame.src = url;
3645
4743
  }));
3646
4744
  }
3647
- proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3648
- this.action = getVisitAction(submitter, element, frame);
4745
+ proposeVisitIfNavigatedWithAction(frame, action = null) {
4746
+ this.action = action;
3649
4747
  if (this.action) {
3650
4748
  const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3651
4749
  const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
3652
- frame.delegate.fetchResponseLoaded = fetchResponse => {
4750
+ frame.delegate.fetchResponseLoaded = async fetchResponse => {
3653
4751
  if (frame.src) {
3654
4752
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
3655
- const responseHTML = frame.ownerDocument.documentElement.outerHTML;
4753
+ const responseHTML = await fetchResponse.responseHTML;
3656
4754
  const response = {
3657
4755
  statusCode: statusCode,
3658
4756
  redirected: redirected,
@@ -3678,16 +4776,16 @@ class FrameController {
3678
4776
  session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
3679
4777
  }
3680
4778
  }
3681
- async handleUnvisitableFrameResponse(fetchResponse) {
4779
+ async #handleUnvisitableFrameResponse(fetchResponse) {
3682
4780
  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);
4781
+ await this.#visitResponse(fetchResponse.response);
3684
4782
  }
3685
- willHandleFrameMissingFromResponse(fetchResponse) {
4783
+ #willHandleFrameMissingFromResponse(fetchResponse) {
3686
4784
  this.element.setAttribute("complete", "");
3687
4785
  const response = fetchResponse.response;
3688
- const visit = async (url, options = {}) => {
4786
+ const visit = async (url, options) => {
3689
4787
  if (url instanceof Response) {
3690
- this.visitResponse(url);
4788
+ this.#visitResponse(url);
3691
4789
  } else {
3692
4790
  session.visit(url, options);
3693
4791
  }
@@ -3702,15 +4800,15 @@ class FrameController {
3702
4800
  });
3703
4801
  return !event.defaultPrevented;
3704
4802
  }
3705
- handleFrameMissingFromResponse(fetchResponse) {
4803
+ #handleFrameMissingFromResponse(fetchResponse) {
3706
4804
  this.view.missing();
3707
- this.throwFrameMissingError(fetchResponse);
4805
+ this.#throwFrameMissingError(fetchResponse);
3708
4806
  }
3709
- throwFrameMissingError(fetchResponse) {
4807
+ #throwFrameMissingError(fetchResponse) {
3710
4808
  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
4809
  throw new TurboFrameMissingError(message);
3712
4810
  }
3713
- async visitResponse(response) {
4811
+ async #visitResponse(response) {
3714
4812
  const wrapped = new FetchResponse(response);
3715
4813
  const responseHTML = await wrapped.responseHTML;
3716
4814
  const {location: location, redirected: redirected, statusCode: statusCode} = wrapped;
@@ -3722,10 +4820,9 @@ class FrameController {
3722
4820
  }
3723
4821
  });
3724
4822
  }
3725
- findFrameElement(element, submitter) {
3726
- var _a;
4823
+ #findFrameElement(element, submitter) {
3727
4824
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
3728
- return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
4825
+ return getFrameElementById(id) ?? this.element;
3729
4826
  }
3730
4827
  async extractForeignFrameElement(container) {
3731
4828
  let element;
@@ -3746,13 +4843,13 @@ class FrameController {
3746
4843
  }
3747
4844
  return null;
3748
4845
  }
3749
- formActionIsVisitable(form, submitter) {
3750
- const action = getAction(form, submitter);
4846
+ #formActionIsVisitable(form, submitter) {
4847
+ const action = getAction$1(form, submitter);
3751
4848
  return locationIsVisitable(expandURL(action), this.rootLocation);
3752
4849
  }
3753
- shouldInterceptNavigation(element, submitter) {
4850
+ #shouldInterceptNavigation(element, submitter) {
3754
4851
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
3755
- if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {
4852
+ if (element instanceof HTMLFormElement && !this.#formActionIsVisitable(element, submitter)) {
3756
4853
  return false;
3757
4854
  }
3758
4855
  if (!this.enabled || id == "_top") {
@@ -3784,46 +4881,43 @@ class FrameController {
3784
4881
  }
3785
4882
  }
3786
4883
  set sourceURL(sourceURL) {
3787
- this.ignoringChangesToAttribute("src", (() => {
3788
- this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
4884
+ this.#ignoringChangesToAttribute("src", (() => {
4885
+ this.element.src = sourceURL ?? null;
3789
4886
  }));
3790
4887
  }
3791
4888
  get loadingStyle() {
3792
4889
  return this.element.loading;
3793
4890
  }
3794
4891
  get isLoading() {
3795
- return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
4892
+ return this.formSubmission !== undefined || this.#resolveVisitPromise() !== undefined;
3796
4893
  }
3797
4894
  get complete() {
3798
4895
  return this.element.hasAttribute("complete");
3799
4896
  }
3800
4897
  set complete(value) {
3801
- this.ignoringChangesToAttribute("complete", (() => {
3802
- if (value) {
3803
- this.element.setAttribute("complete", "");
3804
- } else {
3805
- this.element.removeAttribute("complete");
3806
- }
3807
- }));
4898
+ if (value) {
4899
+ this.element.setAttribute("complete", "");
4900
+ } else {
4901
+ this.element.removeAttribute("complete");
4902
+ }
3808
4903
  }
3809
4904
  get isActive() {
3810
- return this.element.isActive && this.connected;
4905
+ return this.element.isActive && this.#connected;
3811
4906
  }
3812
4907
  get rootLocation() {
3813
- var _a;
3814
4908
  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 : "/";
4909
+ const root = meta?.content ?? "/";
3816
4910
  return expandURL(root);
3817
4911
  }
3818
- isIgnoringChangesTo(attributeName) {
3819
- return this.ignoredAttributes.has(attributeName);
4912
+ #isIgnoringChangesTo(attributeName) {
4913
+ return this.#ignoredAttributes.has(attributeName);
3820
4914
  }
3821
- ignoringChangesToAttribute(attributeName, callback) {
3822
- this.ignoredAttributes.add(attributeName);
4915
+ #ignoringChangesToAttribute(attributeName, callback) {
4916
+ this.#ignoredAttributes.add(attributeName);
3823
4917
  callback();
3824
- this.ignoredAttributes.delete(attributeName);
4918
+ this.#ignoredAttributes.delete(attributeName);
3825
4919
  }
3826
- withCurrentNavigationElement(element, callback) {
4920
+ #withCurrentNavigationElement(element, callback) {
3827
4921
  this.currentNavigationElement = element;
3828
4922
  callback();
3829
4923
  delete this.currentNavigationElement;
@@ -3856,6 +4950,38 @@ function activateElement(element, currentURL) {
3856
4950
  }
3857
4951
  }
3858
4952
 
4953
+ const StreamActions = {
4954
+ after() {
4955
+ this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e.nextSibling)));
4956
+ },
4957
+ append() {
4958
+ this.removeDuplicateTargetChildren();
4959
+ this.targetElements.forEach((e => e.append(this.templateContent)));
4960
+ },
4961
+ before() {
4962
+ this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e)));
4963
+ },
4964
+ prepend() {
4965
+ this.removeDuplicateTargetChildren();
4966
+ this.targetElements.forEach((e => e.prepend(this.templateContent)));
4967
+ },
4968
+ remove() {
4969
+ this.targetElements.forEach((e => e.remove()));
4970
+ },
4971
+ replace() {
4972
+ this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
4973
+ },
4974
+ update() {
4975
+ this.targetElements.forEach((targetElement => {
4976
+ targetElement.innerHTML = "";
4977
+ targetElement.append(this.templateContent);
4978
+ }));
4979
+ },
4980
+ refresh() {
4981
+ session.refresh(this.baseURI, this.requestId);
4982
+ }
4983
+ };
4984
+
3859
4985
  class StreamElement extends HTMLElement {
3860
4986
  static async renderElement(newElement) {
3861
4987
  await newElement.performAction();
@@ -3870,11 +4996,10 @@ class StreamElement extends HTMLElement {
3870
4996
  }
3871
4997
  }
3872
4998
  async render() {
3873
- var _a;
3874
- return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
4999
+ return this.renderPromise ??= (async () => {
3875
5000
  const event = this.beforeRenderEvent;
3876
5001
  if (this.dispatchEvent(event)) {
3877
- await nextAnimationFrame();
5002
+ await nextRepaint();
3878
5003
  await event.detail.render(this);
3879
5004
  }
3880
5005
  })();
@@ -3882,15 +5007,14 @@ class StreamElement extends HTMLElement {
3882
5007
  disconnect() {
3883
5008
  try {
3884
5009
  this.remove();
3885
- } catch (_a) {}
5010
+ } catch {}
3886
5011
  }
3887
5012
  removeDuplicateTargetChildren() {
3888
5013
  this.duplicateChildren.forEach((c => c.remove()));
3889
5014
  }
3890
5015
  get duplicateChildren() {
3891
- var _a;
3892
5016
  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));
5017
+ const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
3894
5018
  return existingChildren.filter((c => newChildrenIds.includes(c.id)));
3895
5019
  }
3896
5020
  get performAction() {
@@ -3899,9 +5023,9 @@ class StreamElement extends HTMLElement {
3899
5023
  if (actionFunction) {
3900
5024
  return actionFunction;
3901
5025
  }
3902
- this.raise("unknown action");
5026
+ this.#raise("unknown action");
3903
5027
  }
3904
- this.raise("action attribute is missing");
5028
+ this.#raise("action attribute is missing");
3905
5029
  }
3906
5030
  get targetElements() {
3907
5031
  if (this.target) {
@@ -3909,7 +5033,7 @@ class StreamElement extends HTMLElement {
3909
5033
  } else if (this.targets) {
3910
5034
  return this.targetElementsByQuery;
3911
5035
  } else {
3912
- this.raise("target or targets attribute is missing");
5036
+ this.#raise("target or targets attribute is missing");
3913
5037
  }
3914
5038
  }
3915
5039
  get templateContent() {
@@ -3923,7 +5047,7 @@ class StreamElement extends HTMLElement {
3923
5047
  } else if (this.firstElementChild instanceof HTMLTemplateElement) {
3924
5048
  return this.firstElementChild;
3925
5049
  }
3926
- this.raise("first child element must be a <template> element");
5050
+ this.#raise("first child element must be a <template> element");
3927
5051
  }
3928
5052
  get action() {
3929
5053
  return this.getAttribute("action");
@@ -3934,12 +5058,14 @@ class StreamElement extends HTMLElement {
3934
5058
  get targets() {
3935
5059
  return this.getAttribute("targets");
3936
5060
  }
3937
- raise(message) {
5061
+ get requestId() {
5062
+ return this.getAttribute("request-id");
5063
+ }
5064
+ #raise(message) {
3938
5065
  throw new Error(`${this.description}: ${message}`);
3939
5066
  }
3940
5067
  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>";
5068
+ return (this.outerHTML.match(/<[^>]+>/) ?? [])[0] ?? "<turbo-stream>";
3943
5069
  }
3944
5070
  get beforeRenderEvent() {
3945
5071
  return new CustomEvent("turbo:before-stream-render", {
@@ -3952,8 +5078,7 @@ class StreamElement extends HTMLElement {
3952
5078
  });
3953
5079
  }
3954
5080
  get targetElementsById() {
3955
- var _a;
3956
- const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
5081
+ const element = this.ownerDocument?.getElementById(this.target);
3957
5082
  if (element !== null) {
3958
5083
  return [ element ];
3959
5084
  } else {
@@ -3961,8 +5086,7 @@ class StreamElement extends HTMLElement {
3961
5086
  }
3962
5087
  }
3963
5088
  get targetElementsByQuery() {
3964
- var _a;
3965
- const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);
5089
+ const elements = this.ownerDocument?.querySelectorAll(this.targets);
3966
5090
  if (elements.length !== 0) {
3967
5091
  return Array.prototype.slice.call(elements);
3968
5092
  } else {
@@ -3972,16 +5096,14 @@ class StreamElement extends HTMLElement {
3972
5096
  }
3973
5097
 
3974
5098
  class StreamSourceElement extends HTMLElement {
3975
- constructor() {
3976
- super(...arguments);
3977
- this.streamSource = null;
3978
- }
5099
+ streamSource=null;
3979
5100
  connectedCallback() {
3980
5101
  this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
3981
5102
  connectStreamSource(this.streamSource);
3982
5103
  }
3983
5104
  disconnectedCallback() {
3984
5105
  if (this.streamSource) {
5106
+ this.streamSource.close();
3985
5107
  disconnectStreamSource(this.streamSource);
3986
5108
  }
3987
5109
  }
@@ -4026,16 +5148,21 @@ if (customElements.get("turbo-stream-source") === undefined) {
4026
5148
  }
4027
5149
  })();
4028
5150
 
4029
- window.Turbo = Turbo;
5151
+ window.Turbo = {
5152
+ ...Turbo,
5153
+ StreamActions: StreamActions
5154
+ };
4030
5155
 
4031
5156
  start();
4032
5157
 
4033
- var turbo_es2017Esm = Object.freeze({
5158
+ var Turbo$1 = Object.freeze({
4034
5159
  __proto__: null,
5160
+ FetchEnctype: FetchEnctype,
5161
+ FetchMethod: FetchMethod,
5162
+ FetchRequest: FetchRequest,
5163
+ FetchResponse: FetchResponse,
4035
5164
  FrameElement: FrameElement,
4036
- get FrameLoadingStyle() {
4037
- return FrameLoadingStyle;
4038
- },
5165
+ FrameLoadingStyle: FrameLoadingStyle,
4039
5166
  FrameRenderer: FrameRenderer,
4040
5167
  PageRenderer: PageRenderer,
4041
5168
  PageSnapshot: PageSnapshot,
@@ -4046,6 +5173,10 @@ var turbo_es2017Esm = Object.freeze({
4046
5173
  clearCache: clearCache,
4047
5174
  connectStreamSource: connectStreamSource,
4048
5175
  disconnectStreamSource: disconnectStreamSource,
5176
+ fetch: fetchWithTurboHeaders,
5177
+ fetchEnctypeFromString: fetchEnctypeFromString,
5178
+ fetchMethodFromString: fetchMethodFromString,
5179
+ isSafe: isSafe,
4049
5180
  navigator: navigator$1,
4050
5181
  registerAdapter: registerAdapter,
4051
5182
  renderStreamMessage: renderStreamMessage,
@@ -4060,14 +5191,14 @@ var turbo_es2017Esm = Object.freeze({
4060
5191
  let consumer;
4061
5192
 
4062
5193
  async function getConsumer() {
4063
- return consumer || setConsumer(createConsumer().then(setConsumer));
5194
+ return consumer || setConsumer(createConsumer$1().then(setConsumer));
4064
5195
  }
4065
5196
 
4066
5197
  function setConsumer(newConsumer) {
4067
5198
  return consumer = newConsumer;
4068
5199
  }
4069
5200
 
4070
- async function createConsumer() {
5201
+ async function createConsumer$1() {
4071
5202
  const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
4072
5203
  return index;
4073
5204
  }));
@@ -4083,7 +5214,7 @@ var cable = Object.freeze({
4083
5214
  __proto__: null,
4084
5215
  getConsumer: getConsumer,
4085
5216
  setConsumer: setConsumer,
4086
- createConsumer: createConsumer,
5217
+ createConsumer: createConsumer$1,
4087
5218
  subscribeTo: subscribeTo
4088
5219
  });
4089
5220
 
@@ -4193,6 +5324,8 @@ function isBodyInit(body) {
4193
5324
  return body instanceof FormData || body instanceof URLSearchParams;
4194
5325
  }
4195
5326
 
5327
+ window.Turbo = Turbo$1;
5328
+
4196
5329
  addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
4197
5330
 
4198
5331
  var adapters = {
@@ -4309,6 +5442,8 @@ ConnectionMonitor.staleThreshold = 6;
4309
5442
 
4310
5443
  ConnectionMonitor.reconnectionBackoffRate = .15;
4311
5444
 
5445
+ var ConnectionMonitor$1 = ConnectionMonitor;
5446
+
4312
5447
  var INTERNAL = {
4313
5448
  message_types: {
4314
5449
  welcome: "welcome",
@@ -4320,7 +5455,8 @@ var INTERNAL = {
4320
5455
  disconnect_reasons: {
4321
5456
  unauthorized: "unauthorized",
4322
5457
  invalid_request: "invalid_request",
4323
- server_restart: "server_restart"
5458
+ server_restart: "server_restart",
5459
+ remote: "remote"
4324
5460
  },
4325
5461
  default_mount_path: "/cable",
4326
5462
  protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
@@ -4337,7 +5473,7 @@ class Connection {
4337
5473
  this.open = this.open.bind(this);
4338
5474
  this.consumer = consumer;
4339
5475
  this.subscriptions = this.consumer.subscriptions;
4340
- this.monitor = new ConnectionMonitor(this);
5476
+ this.monitor = new ConnectionMonitor$1(this);
4341
5477
  this.disconnected = true;
4342
5478
  }
4343
5479
  send(data) {
@@ -4353,11 +5489,12 @@ class Connection {
4353
5489
  logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
4354
5490
  return false;
4355
5491
  } else {
4356
- logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${protocols}`);
5492
+ const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
5493
+ logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
4357
5494
  if (this.webSocket) {
4358
5495
  this.uninstallEventHandlers();
4359
5496
  }
4360
- this.webSocket = new adapters.WebSocket(this.consumer.url, protocols);
5497
+ this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
4361
5498
  this.installEventHandlers();
4362
5499
  this.monitor.start();
4363
5500
  return true;
@@ -4369,7 +5506,7 @@ class Connection {
4369
5506
  if (!allowReconnect) {
4370
5507
  this.monitor.stop();
4371
5508
  }
4372
- if (this.isActive()) {
5509
+ if (this.isOpen()) {
4373
5510
  return this.webSocket.close();
4374
5511
  }
4375
5512
  }
@@ -4399,6 +5536,9 @@ class Connection {
4399
5536
  isActive() {
4400
5537
  return this.isState("open", "connecting");
4401
5538
  }
5539
+ triedToReconnect() {
5540
+ return this.monitor.reconnectAttempts > 0;
5541
+ }
4402
5542
  isProtocolSupported() {
4403
5543
  return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
4404
5544
  }
@@ -4438,6 +5578,9 @@ Connection.prototype.events = {
4438
5578
  const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
4439
5579
  switch (type) {
4440
5580
  case message_types.welcome:
5581
+ if (this.triedToReconnect()) {
5582
+ this.reconnectAttempted = true;
5583
+ }
4441
5584
  this.monitor.recordConnect();
4442
5585
  return this.subscriptions.reload();
4443
5586
 
@@ -4452,7 +5595,16 @@ Connection.prototype.events = {
4452
5595
 
4453
5596
  case message_types.confirmation:
4454
5597
  this.subscriptions.confirmSubscription(identifier);
4455
- return this.subscriptions.notify(identifier, "connected");
5598
+ if (this.reconnectAttempted) {
5599
+ this.reconnectAttempted = false;
5600
+ return this.subscriptions.notify(identifier, "connected", {
5601
+ reconnected: true
5602
+ });
5603
+ } else {
5604
+ return this.subscriptions.notify(identifier, "connected", {
5605
+ reconnected: false
5606
+ });
5607
+ }
4456
5608
 
4457
5609
  case message_types.rejection:
4458
5610
  return this.subscriptions.reject(identifier);
@@ -4487,6 +5639,8 @@ Connection.prototype.events = {
4487
5639
  }
4488
5640
  };
4489
5641
 
5642
+ var Connection$1 = Connection;
5643
+
4490
5644
  const extend = function(object, properties) {
4491
5645
  if (properties != null) {
4492
5646
  for (let key in properties) {
@@ -4556,10 +5710,12 @@ class SubscriptionGuarantor {
4556
5710
  }
4557
5711
  }
4558
5712
 
5713
+ var SubscriptionGuarantor$1 = SubscriptionGuarantor;
5714
+
4559
5715
  class Subscriptions {
4560
5716
  constructor(consumer) {
4561
5717
  this.consumer = consumer;
4562
- this.guarantor = new SubscriptionGuarantor(this);
5718
+ this.guarantor = new SubscriptionGuarantor$1(this);
4563
5719
  this.subscriptions = [];
4564
5720
  }
4565
5721
  create(channelName, mixin) {
@@ -4636,7 +5792,8 @@ class Consumer {
4636
5792
  constructor(url) {
4637
5793
  this._url = url;
4638
5794
  this.subscriptions = new Subscriptions(this);
4639
- this.connection = new Connection(this);
5795
+ this.connection = new Connection$1(this);
5796
+ this.subprotocols = [];
4640
5797
  }
4641
5798
  get url() {
4642
5799
  return createWebSocketURL(this._url);
@@ -4657,6 +5814,9 @@ class Consumer {
4657
5814
  return this.connection.open();
4658
5815
  }
4659
5816
  }
5817
+ addSubProtocol(subprotocol) {
5818
+ this.subprotocols = [ ...this.subprotocols, subprotocol ];
5819
+ }
4660
5820
  }
4661
5821
 
4662
5822
  function createWebSocketURL(url) {
@@ -4674,7 +5834,7 @@ function createWebSocketURL(url) {
4674
5834
  }
4675
5835
  }
4676
5836
 
4677
- function createConsumer$1(url = getConfig("url") || INTERNAL.default_mount_path) {
5837
+ function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
4678
5838
  return new Consumer(url);
4679
5839
  }
4680
5840
 
@@ -4687,18 +5847,18 @@ function getConfig(name) {
4687
5847
 
4688
5848
  var index = Object.freeze({
4689
5849
  __proto__: null,
4690
- Connection: Connection,
4691
- ConnectionMonitor: ConnectionMonitor,
5850
+ Connection: Connection$1,
5851
+ ConnectionMonitor: ConnectionMonitor$1,
4692
5852
  Consumer: Consumer,
4693
5853
  INTERNAL: INTERNAL,
4694
5854
  Subscription: Subscription,
4695
5855
  Subscriptions: Subscriptions,
4696
- SubscriptionGuarantor: SubscriptionGuarantor,
5856
+ SubscriptionGuarantor: SubscriptionGuarantor$1,
4697
5857
  adapters: adapters,
4698
5858
  createWebSocketURL: createWebSocketURL,
4699
5859
  logger: logger,
4700
- createConsumer: createConsumer$1,
5860
+ createConsumer: createConsumer,
4701
5861
  getConfig: getConfig
4702
5862
  });
4703
5863
 
4704
- export { turbo_es2017Esm as Turbo, cable };
5864
+ export { Turbo$1 as Turbo, cable };