turbo-rails 1.5.0 → 2.0.2

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