turbo-rails 1.5.0 → 2.0.4

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.3
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,112 @@ 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
+ morphStyle: morphStyle,
3822
+ callbacks: {
3823
+ beforeNodeAdded: this.#shouldAddElement,
3824
+ beforeNodeMorphed: this.#shouldMorphElement,
3825
+ beforeAttributeUpdated: this.#shouldUpdateAttribute,
3826
+ beforeNodeRemoved: this.#shouldRemoveElement,
3827
+ afterNodeMorphed: this.#didMorphElement
3828
+ }
3829
+ });
3830
+ }
3831
+ #shouldAddElement=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
3832
+ #shouldMorphElement=(oldNode, newNode) => {
3833
+ if (oldNode instanceof HTMLElement) {
3834
+ if (!oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode))) {
3835
+ const event = dispatch("turbo:before-morph-element", {
3836
+ cancelable: true,
3837
+ target: oldNode,
3838
+ detail: {
3839
+ newElement: newNode
3840
+ }
3841
+ });
3842
+ return !event.defaultPrevented;
3843
+ } else {
3844
+ return false;
3845
+ }
3846
+ }
3847
+ };
3848
+ #shouldUpdateAttribute=(attributeName, target, mutationType) => {
3849
+ const event = dispatch("turbo:before-morph-attribute", {
3850
+ cancelable: true,
3851
+ target: target,
3852
+ detail: {
3853
+ attributeName: attributeName,
3854
+ mutationType: mutationType
3855
+ }
3856
+ });
3857
+ return !event.defaultPrevented;
3858
+ };
3859
+ #didMorphElement=(oldNode, newNode) => {
3860
+ if (newNode instanceof HTMLElement) {
3861
+ dispatch("turbo:morph-element", {
3862
+ target: oldNode,
3863
+ detail: {
3864
+ newElement: newNode
3865
+ }
3866
+ });
3867
+ }
3868
+ };
3869
+ #shouldRemoveElement=node => this.#shouldMorphElement(node);
3870
+ #reloadRemoteFrames() {
3871
+ this.#remoteFrames().forEach((frame => {
3872
+ if (this.#isFrameReloadedWithMorph(frame)) {
3873
+ this.#renderFrameWithMorph(frame);
3874
+ frame.reload();
3875
+ }
3876
+ }));
3877
+ }
3878
+ #renderFrameWithMorph(frame) {
3879
+ frame.addEventListener("turbo:before-frame-render", (event => {
3880
+ event.detail.render = this.#morphFrameUpdate;
3881
+ }), {
3882
+ once: true
3883
+ });
3884
+ }
3885
+ #morphFrameUpdate=(currentElement, newElement) => {
3886
+ dispatch("turbo:before-frame-morph", {
3887
+ target: currentElement,
3888
+ detail: {
3889
+ currentElement: currentElement,
3890
+ newElement: newElement
3891
+ }
3892
+ });
3893
+ this.#morphElements(currentElement, newElement.children, "innerHTML");
3894
+ };
3895
+ #isFrameReloadedWithMorph(element) {
3896
+ return element.src && element.refresh === "morph";
3897
+ }
3898
+ #remoteFrames() {
3899
+ return Array.from(document.querySelectorAll("turbo-frame[src]")).filter((frame => !frame.closest("[data-turbo-permanent]")));
3900
+ }
3901
+ }
3902
+
2825
3903
  class SnapshotCache {
3904
+ keys=[];
3905
+ snapshots={};
2826
3906
  constructor(size) {
2827
- this.keys = [];
2828
- this.snapshots = {};
2829
3907
  this.size = size;
2830
3908
  }
2831
3909
  has(location) {
@@ -2867,23 +3945,25 @@ class SnapshotCache {
2867
3945
  }
2868
3946
 
2869
3947
  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;
3948
+ snapshotCache=new SnapshotCache(10);
3949
+ lastRenderedLocation=new URL(location.href);
3950
+ forceReloaded=false;
3951
+ shouldTransitionTo(newSnapshot) {
3952
+ return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions;
2875
3953
  }
2876
3954
  renderPage(snapshot, isPreview = false, willRender = true, visit) {
2877
- const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
3955
+ const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
3956
+ const rendererClass = shouldMorphPage ? MorphRenderer : PageRenderer;
3957
+ const renderer = new rendererClass(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
2878
3958
  if (!renderer.shouldRender) {
2879
3959
  this.forceReloaded = true;
2880
3960
  } else {
2881
- visit === null || visit === void 0 ? void 0 : visit.changeHistory();
3961
+ visit?.changeHistory();
2882
3962
  }
2883
3963
  return this.render(renderer);
2884
3964
  }
2885
3965
  renderError(snapshot, visit) {
2886
- visit === null || visit === void 0 ? void 0 : visit.changeHistory();
3966
+ visit?.changeHistory();
2887
3967
  const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
2888
3968
  return this.render(renderer);
2889
3969
  }
@@ -2903,31 +3983,38 @@ class PageView extends View {
2903
3983
  getCachedSnapshotForLocation(location) {
2904
3984
  return this.snapshotCache.get(location);
2905
3985
  }
3986
+ isPageRefresh(visit) {
3987
+ return !visit || this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === "replace";
3988
+ }
3989
+ shouldPreserveScrollPosition(visit) {
3990
+ return this.isPageRefresh(visit) && this.snapshot.shouldPreserveScrollPosition;
3991
+ }
2906
3992
  get snapshot() {
2907
3993
  return PageSnapshot.fromElement(this.element);
2908
3994
  }
2909
3995
  }
2910
3996
 
2911
3997
  class Preloader {
2912
- constructor(delegate) {
2913
- this.selector = "a[data-turbo-preload]";
3998
+ selector="a[data-turbo-preload]";
3999
+ constructor(delegate, snapshotCache) {
2914
4000
  this.delegate = delegate;
2915
- }
2916
- get snapshotCache() {
2917
- return this.delegate.navigator.view.snapshotCache;
4001
+ this.snapshotCache = snapshotCache;
2918
4002
  }
2919
4003
  start() {
2920
4004
  if (document.readyState === "loading") {
2921
- return document.addEventListener("DOMContentLoaded", (() => {
2922
- this.preloadOnLoadLinksForView(document.body);
2923
- }));
4005
+ document.addEventListener("DOMContentLoaded", this.#preloadAll);
2924
4006
  } else {
2925
4007
  this.preloadOnLoadLinksForView(document.body);
2926
4008
  }
2927
4009
  }
4010
+ stop() {
4011
+ document.removeEventListener("DOMContentLoaded", this.#preloadAll);
4012
+ }
2928
4013
  preloadOnLoadLinksForView(element) {
2929
4014
  for (const link of element.querySelectorAll(this.selector)) {
2930
- this.preloadURL(link);
4015
+ if (this.delegate.shouldPreloadLink(link)) {
4016
+ this.preloadURL(link);
4017
+ }
2931
4018
  }
2932
4019
  }
2933
4020
  async preloadURL(link) {
@@ -2935,46 +4022,83 @@ class Preloader {
2935
4022
  if (this.snapshotCache.has(location)) {
2936
4023
  return;
2937
4024
  }
4025
+ const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, link);
4026
+ await fetchRequest.perform();
4027
+ }
4028
+ prepareRequest(fetchRequest) {
4029
+ fetchRequest.headers["X-Sec-Purpose"] = "prefetch";
4030
+ }
4031
+ async requestSucceededWithResponse(fetchRequest, fetchResponse) {
2938
4032
  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);
4033
+ const responseHTML = await fetchResponse.responseHTML;
4034
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
4035
+ this.snapshotCache.put(fetchRequest.url, snapshot);
2948
4036
  } catch (_) {}
2949
4037
  }
4038
+ requestStarted(fetchRequest) {}
4039
+ requestErrored(fetchRequest) {}
4040
+ requestFinished(fetchRequest) {}
4041
+ requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
4042
+ requestFailedWithResponse(fetchRequest, fetchResponse) {}
4043
+ #preloadAll=() => {
4044
+ this.preloadOnLoadLinksForView(document.body);
4045
+ };
4046
+ }
4047
+
4048
+ class Cache {
4049
+ constructor(session) {
4050
+ this.session = session;
4051
+ }
4052
+ clear() {
4053
+ this.session.clearCache();
4054
+ }
4055
+ resetCacheControl() {
4056
+ this.#setCacheControl("");
4057
+ }
4058
+ exemptPageFromCache() {
4059
+ this.#setCacheControl("no-cache");
4060
+ }
4061
+ exemptPageFromPreview() {
4062
+ this.#setCacheControl("no-preview");
4063
+ }
4064
+ #setCacheControl(value) {
4065
+ setMetaContent("turbo-cache-control", value);
4066
+ }
2950
4067
  }
2951
4068
 
2952
4069
  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";
4070
+ navigator=new Navigator(this);
4071
+ history=new History(this);
4072
+ view=new PageView(this, document.documentElement);
4073
+ adapter=new BrowserAdapter(this);
4074
+ pageObserver=new PageObserver(this);
4075
+ cacheObserver=new CacheObserver;
4076
+ linkPrefetchObserver=new LinkPrefetchObserver(this, document);
4077
+ linkClickObserver=new LinkClickObserver(this, window);
4078
+ formSubmitObserver=new FormSubmitObserver(this, document);
4079
+ scrollObserver=new ScrollObserver(this);
4080
+ streamObserver=new StreamObserver(this);
4081
+ formLinkClickObserver=new FormLinkClickObserver(this, document.documentElement);
4082
+ frameRedirector=new FrameRedirector(this, document.documentElement);
4083
+ streamMessageRenderer=new StreamMessageRenderer;
4084
+ cache=new Cache(this);
4085
+ drive=true;
4086
+ enabled=true;
4087
+ progressBarDelay=500;
4088
+ started=false;
4089
+ formMode="on";
4090
+ #pageRefreshDebouncePeriod=150;
4091
+ constructor(recentRequests) {
4092
+ this.recentRequests = recentRequests;
4093
+ this.preloader = new Preloader(this, this.view.snapshotCache);
4094
+ this.debouncedRefresh = this.refresh;
4095
+ this.pageRefreshDebouncePeriod = this.pageRefreshDebouncePeriod;
2973
4096
  }
2974
4097
  start() {
2975
4098
  if (!this.started) {
2976
4099
  this.pageObserver.start();
2977
4100
  this.cacheObserver.start();
4101
+ this.linkPrefetchObserver.start();
2978
4102
  this.formLinkClickObserver.start();
2979
4103
  this.linkClickObserver.start();
2980
4104
  this.formSubmitObserver.start();
@@ -2994,6 +4118,7 @@ class Session {
2994
4118
  if (this.started) {
2995
4119
  this.pageObserver.stop();
2996
4120
  this.cacheObserver.stop();
4121
+ this.linkPrefetchObserver.stop();
2997
4122
  this.formLinkClickObserver.stop();
2998
4123
  this.linkClickObserver.stop();
2999
4124
  this.formSubmitObserver.stop();
@@ -3001,6 +4126,7 @@ class Session {
3001
4126
  this.streamObserver.stop();
3002
4127
  this.frameRedirector.stop();
3003
4128
  this.history.stop();
4129
+ this.preloader.stop();
3004
4130
  this.started = false;
3005
4131
  }
3006
4132
  }
@@ -3010,12 +4136,22 @@ class Session {
3010
4136
  visit(location, options = {}) {
3011
4137
  const frameElement = options.frame ? document.getElementById(options.frame) : null;
3012
4138
  if (frameElement instanceof FrameElement) {
4139
+ const action = options.action || getVisitAction(frameElement);
4140
+ frameElement.delegate.proposeVisitIfNavigatedWithAction(frameElement, action);
3013
4141
  frameElement.src = location.toString();
3014
- frameElement.loaded;
3015
4142
  } else {
3016
4143
  this.navigator.proposeVisit(expandURL(location), options);
3017
4144
  }
3018
4145
  }
4146
+ refresh(url, requestId) {
4147
+ const isRecentRequest = requestId && this.recentRequests.has(requestId);
4148
+ if (!isRecentRequest) {
4149
+ this.visit(url, {
4150
+ action: "replace",
4151
+ shouldCacheSnapshot: false
4152
+ });
4153
+ }
4154
+ }
3019
4155
  connectStreamSource(source) {
3020
4156
  this.streamObserver.connectStreamSource(source);
3021
4157
  }
@@ -3040,11 +4176,31 @@ class Session {
3040
4176
  get restorationIdentifier() {
3041
4177
  return this.history.restorationIdentifier;
3042
4178
  }
3043
- historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
4179
+ get pageRefreshDebouncePeriod() {
4180
+ return this.#pageRefreshDebouncePeriod;
4181
+ }
4182
+ set pageRefreshDebouncePeriod(value) {
4183
+ this.refresh = debounce(this.debouncedRefresh.bind(this), value);
4184
+ this.#pageRefreshDebouncePeriod = value;
4185
+ }
4186
+ shouldPreloadLink(element) {
4187
+ const isUnsafe = element.hasAttribute("data-turbo-method");
4188
+ const isStream = element.hasAttribute("data-turbo-stream");
4189
+ const frameTarget = element.getAttribute("data-turbo-frame");
4190
+ const frame = frameTarget == "_top" ? null : document.getElementById(frameTarget) || findClosestRecursively(element, "turbo-frame:not([disabled])");
4191
+ if (isUnsafe || isStream || frame instanceof FrameElement) {
4192
+ return false;
4193
+ } else {
4194
+ const location = new URL(element.href);
4195
+ return this.elementIsNavigatable(element) && locationIsVisitable(location, this.snapshot.rootLocation);
4196
+ }
4197
+ }
4198
+ historyPoppedToLocationWithRestorationIdentifierAndDirection(location, restorationIdentifier, direction) {
3044
4199
  if (this.enabled) {
3045
4200
  this.navigator.startVisit(location, restorationIdentifier, {
3046
4201
  action: "restore",
3047
- historyChanged: true
4202
+ historyChanged: true,
4203
+ direction: direction
3048
4204
  });
3049
4205
  } else {
3050
4206
  this.adapter.pageInvalidated({
@@ -3061,6 +4217,9 @@ class Session {
3061
4217
  return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
3062
4218
  }
3063
4219
  submittedFormLinkToLocation() {}
4220
+ canPrefetchRequestToLocation(link, location) {
4221
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
4222
+ }
3064
4223
  willFollowLinkToLocation(link, location, event) {
3065
4224
  return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
3066
4225
  }
@@ -3082,6 +4241,7 @@ class Session {
3082
4241
  visitStarted(visit) {
3083
4242
  if (!visit.acceptsStreamResponse) {
3084
4243
  markAsBusy(document.documentElement);
4244
+ this.view.markVisitDirection(visit.direction);
3085
4245
  }
3086
4246
  extendURLWithDeprecatedProperties(visit.location);
3087
4247
  if (!visit.silent) {
@@ -3089,6 +4249,7 @@ class Session {
3089
4249
  }
3090
4250
  }
3091
4251
  visitCompleted(visit) {
4252
+ this.view.unmarkVisitDirection();
3092
4253
  clearBusyState(document.documentElement);
3093
4254
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
3094
4255
  }
@@ -3099,7 +4260,7 @@ class Session {
3099
4260
  this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
3100
4261
  }
3101
4262
  willSubmitForm(form, submitter) {
3102
- const action = getAction(form, submitter);
4263
+ const action = getAction$1(form, submitter);
3103
4264
  return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
3104
4265
  }
3105
4266
  formSubmitted(form, submitter) {
@@ -3119,8 +4280,7 @@ class Session {
3119
4280
  this.renderStreamMessage(message);
3120
4281
  }
3121
4282
  viewWillCacheSnapshot() {
3122
- var _a;
3123
- if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) {
4283
+ if (!this.navigator.currentVisit?.silent) {
3124
4284
  this.notifyApplicationBeforeCachingSnapshot();
3125
4285
  }
3126
4286
  }
@@ -3132,9 +4292,9 @@ class Session {
3132
4292
  }
3133
4293
  return !defaultPrevented;
3134
4294
  }
3135
- viewRenderedSnapshot(_snapshot, _isPreview) {
4295
+ viewRenderedSnapshot(_snapshot, _isPreview, renderMethod) {
3136
4296
  this.view.lastRenderedLocation = this.history.location;
3137
- this.notifyApplicationAfterRender();
4297
+ this.notifyApplicationAfterRender(renderMethod);
3138
4298
  }
3139
4299
  preloadOnLoadLinksForView(element) {
3140
4300
  this.preloader.preloadOnLoadLinksForView(element);
@@ -3187,14 +4347,19 @@ class Session {
3187
4347
  }
3188
4348
  notifyApplicationBeforeRender(newBody, options) {
3189
4349
  return dispatch("turbo:before-render", {
3190
- detail: Object.assign({
3191
- newBody: newBody
3192
- }, options),
4350
+ detail: {
4351
+ newBody: newBody,
4352
+ ...options
4353
+ },
3193
4354
  cancelable: true
3194
4355
  });
3195
4356
  }
3196
- notifyApplicationAfterRender() {
3197
- return dispatch("turbo:render");
4357
+ notifyApplicationAfterRender(renderMethod) {
4358
+ return dispatch("turbo:render", {
4359
+ detail: {
4360
+ renderMethod: renderMethod
4361
+ }
4362
+ });
3198
4363
  }
3199
4364
  notifyApplicationAfterPageLoad(timing = {}) {
3200
4365
  return dispatch("turbo:load", {
@@ -3273,67 +4438,9 @@ const deprecatedLocationPropertyDescriptors = {
3273
4438
  }
3274
4439
  };
3275
4440
 
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;
4441
+ const session = new Session(recentRequests);
3333
4442
 
3334
- const cache = new Cache(session);
3335
-
3336
- const {navigator: navigator$1} = session;
4443
+ const {cache: cache, navigator: navigator$1} = session;
3337
4444
 
3338
4445
  function start() {
3339
4446
  session.start();
@@ -3384,6 +4491,7 @@ var Turbo = Object.freeze({
3384
4491
  PageRenderer: PageRenderer,
3385
4492
  PageSnapshot: PageSnapshot,
3386
4493
  FrameRenderer: FrameRenderer,
4494
+ fetch: fetchWithTurboHeaders,
3387
4495
  start: start,
3388
4496
  registerAdapter: registerAdapter,
3389
4497
  visit: visit,
@@ -3393,28 +4501,20 @@ var Turbo = Object.freeze({
3393
4501
  clearCache: clearCache,
3394
4502
  setProgressBarDelay: setProgressBarDelay,
3395
4503
  setConfirmMethod: setConfirmMethod,
3396
- setFormMode: setFormMode,
3397
- StreamActions: StreamActions
4504
+ setFormMode: setFormMode
3398
4505
  });
3399
4506
 
3400
4507
  class TurboFrameMissingError extends Error {}
3401
4508
 
3402
4509
  class FrameController {
4510
+ fetchResponseLoaded=_fetchResponse => Promise.resolve();
4511
+ #currentFetchRequest=null;
4512
+ #resolveVisitPromise=() => {};
4513
+ #connected=false;
4514
+ #hasBeenLoaded=false;
4515
+ #ignoredAttributes=new Set;
4516
+ action=null;
3403
4517
  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
4518
  this.element = element;
3419
4519
  this.view = new FrameView(this, this.element);
3420
4520
  this.appearanceObserver = new AppearanceObserver(this, this.element);
@@ -3424,12 +4524,12 @@ class FrameController {
3424
4524
  this.formSubmitObserver = new FormSubmitObserver(this, this.element);
3425
4525
  }
3426
4526
  connect() {
3427
- if (!this.connected) {
3428
- this.connected = true;
4527
+ if (!this.#connected) {
4528
+ this.#connected = true;
3429
4529
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
3430
4530
  this.appearanceObserver.start();
3431
4531
  } else {
3432
- this.loadSourceURL();
4532
+ this.#loadSourceURL();
3433
4533
  }
3434
4534
  this.formLinkClickObserver.start();
3435
4535
  this.linkInterceptor.start();
@@ -3437,8 +4537,8 @@ class FrameController {
3437
4537
  }
3438
4538
  }
3439
4539
  disconnect() {
3440
- if (this.connected) {
3441
- this.connected = false;
4540
+ if (this.#connected) {
4541
+ this.#connected = false;
3442
4542
  this.appearanceObserver.stop();
3443
4543
  this.formLinkClickObserver.stop();
3444
4544
  this.linkInterceptor.stop();
@@ -3447,21 +4547,21 @@ class FrameController {
3447
4547
  }
3448
4548
  disabledChanged() {
3449
4549
  if (this.loadingStyle == FrameLoadingStyle.eager) {
3450
- this.loadSourceURL();
4550
+ this.#loadSourceURL();
3451
4551
  }
3452
4552
  }
3453
4553
  sourceURLChanged() {
3454
- if (this.isIgnoringChangesTo("src")) return;
4554
+ if (this.#isIgnoringChangesTo("src")) return;
3455
4555
  if (this.element.isConnected) {
3456
4556
  this.complete = false;
3457
4557
  }
3458
- if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
3459
- this.loadSourceURL();
4558
+ if (this.loadingStyle == FrameLoadingStyle.eager || this.#hasBeenLoaded) {
4559
+ this.#loadSourceURL();
3460
4560
  }
3461
4561
  }
3462
4562
  sourceURLReloaded() {
3463
4563
  const {src: src} = this.element;
3464
- this.ignoringChangesToAttribute("complete", (() => {
4564
+ this.#ignoringChangesToAttribute("complete", (() => {
3465
4565
  this.element.removeAttribute("complete");
3466
4566
  }));
3467
4567
  this.element.src = null;
@@ -3469,23 +4569,23 @@ class FrameController {
3469
4569
  return this.element.loaded;
3470
4570
  }
3471
4571
  completeChanged() {
3472
- if (this.isIgnoringChangesTo("complete")) return;
3473
- this.loadSourceURL();
4572
+ if (this.#isIgnoringChangesTo("complete")) return;
4573
+ this.#loadSourceURL();
3474
4574
  }
3475
4575
  loadingStyleChanged() {
3476
4576
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
3477
4577
  this.appearanceObserver.start();
3478
4578
  } else {
3479
4579
  this.appearanceObserver.stop();
3480
- this.loadSourceURL();
4580
+ this.#loadSourceURL();
3481
4581
  }
3482
4582
  }
3483
- async loadSourceURL() {
4583
+ async #loadSourceURL() {
3484
4584
  if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
3485
- this.element.loaded = this.visit(expandURL(this.sourceURL));
4585
+ this.element.loaded = this.#visit(expandURL(this.sourceURL));
3486
4586
  this.appearanceObserver.stop();
3487
4587
  await this.element.loaded;
3488
- this.hasBeenLoaded = true;
4588
+ this.#hasBeenLoaded = true;
3489
4589
  }
3490
4590
  }
3491
4591
  async loadResponse(fetchResponse) {
@@ -3498,34 +4598,34 @@ class FrameController {
3498
4598
  const document = parseHTMLDocument(html);
3499
4599
  const pageSnapshot = PageSnapshot.fromDocument(document);
3500
4600
  if (pageSnapshot.isVisitable) {
3501
- await this.loadFrameResponse(fetchResponse, document);
4601
+ await this.#loadFrameResponse(fetchResponse, document);
3502
4602
  } else {
3503
- await this.handleUnvisitableFrameResponse(fetchResponse);
4603
+ await this.#handleUnvisitableFrameResponse(fetchResponse);
3504
4604
  }
3505
4605
  }
3506
4606
  } finally {
3507
- this.fetchResponseLoaded = () => {};
4607
+ this.fetchResponseLoaded = () => Promise.resolve();
3508
4608
  }
3509
4609
  }
3510
4610
  elementAppearedInViewport(element) {
3511
- this.proposeVisitIfNavigatedWithAction(element, element);
3512
- this.loadSourceURL();
4611
+ this.proposeVisitIfNavigatedWithAction(element, getVisitAction(element));
4612
+ this.#loadSourceURL();
3513
4613
  }
3514
4614
  willSubmitFormLinkToLocation(link) {
3515
- return this.shouldInterceptNavigation(link);
4615
+ return this.#shouldInterceptNavigation(link);
3516
4616
  }
3517
4617
  submittedFormLinkToLocation(link, _location, form) {
3518
- const frame = this.findFrameElement(link);
4618
+ const frame = this.#findFrameElement(link);
3519
4619
  if (frame) form.setAttribute("data-turbo-frame", frame.id);
3520
4620
  }
3521
4621
  shouldInterceptLinkClick(element, _location, _event) {
3522
- return this.shouldInterceptNavigation(element);
4622
+ return this.#shouldInterceptNavigation(element);
3523
4623
  }
3524
4624
  linkClickIntercepted(element, location) {
3525
- this.navigateFrame(element, location);
4625
+ this.#navigateFrame(element, location);
3526
4626
  }
3527
4627
  willSubmitForm(element, submitter) {
3528
- return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
4628
+ return element.closest("turbo-frame") == this.element && this.#shouldInterceptNavigation(element, submitter);
3529
4629
  }
3530
4630
  formSubmitted(element, submitter) {
3531
4631
  if (this.formSubmission) {
@@ -3537,9 +4637,8 @@ class FrameController {
3537
4637
  this.formSubmission.start();
3538
4638
  }
3539
4639
  prepareRequest(request) {
3540
- var _a;
3541
4640
  request.headers["Turbo-Frame"] = this.id;
3542
- if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
4641
+ if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
3543
4642
  request.acceptResponseType(StreamMessage.contentType);
3544
4643
  }
3545
4644
  }
@@ -3547,29 +4646,29 @@ class FrameController {
3547
4646
  markAsBusy(this.element);
3548
4647
  }
3549
4648
  requestPreventedHandlingResponse(_request, _response) {
3550
- this.resolveVisitPromise();
4649
+ this.#resolveVisitPromise();
3551
4650
  }
3552
4651
  async requestSucceededWithResponse(request, response) {
3553
4652
  await this.loadResponse(response);
3554
- this.resolveVisitPromise();
4653
+ this.#resolveVisitPromise();
3555
4654
  }
3556
4655
  async requestFailedWithResponse(request, response) {
3557
4656
  await this.loadResponse(response);
3558
- this.resolveVisitPromise();
4657
+ this.#resolveVisitPromise();
3559
4658
  }
3560
4659
  requestErrored(request, error) {
3561
4660
  console.error(error);
3562
- this.resolveVisitPromise();
4661
+ this.#resolveVisitPromise();
3563
4662
  }
3564
4663
  requestFinished(_request) {
3565
4664
  clearBusyState(this.element);
3566
4665
  }
3567
4666
  formSubmissionStarted({formElement: formElement}) {
3568
- markAsBusy(formElement, this.findFrameElement(formElement));
4667
+ markAsBusy(formElement, this.#findFrameElement(formElement));
3569
4668
  }
3570
4669
  formSubmissionSucceededWithResponse(formSubmission, response) {
3571
- const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
3572
- frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
4670
+ const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter);
4671
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame));
3573
4672
  frame.delegate.loadResponse(response);
3574
4673
  if (!formSubmission.isSafe) {
3575
4674
  session.clearCache();
@@ -3583,14 +4682,15 @@ class FrameController {
3583
4682
  console.error(error);
3584
4683
  }
3585
4684
  formSubmissionFinished({formElement: formElement}) {
3586
- clearBusyState(formElement, this.findFrameElement(formElement));
4685
+ clearBusyState(formElement, this.#findFrameElement(formElement));
3587
4686
  }
3588
4687
  allowsImmediateRender({element: newFrame}, options) {
3589
4688
  const event = dispatch("turbo:before-frame-render", {
3590
4689
  target: this.element,
3591
- detail: Object.assign({
3592
- newFrame: newFrame
3593
- }, options),
4690
+ detail: {
4691
+ newFrame: newFrame,
4692
+ ...options
4693
+ },
3594
4694
  cancelable: true
3595
4695
  });
3596
4696
  const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
@@ -3599,7 +4699,7 @@ class FrameController {
3599
4699
  }
3600
4700
  return !defaultPrevented;
3601
4701
  }
3602
- viewRenderedSnapshot(_snapshot, _isPreview) {}
4702
+ viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {}
3603
4703
  preloadOnLoadLinksForView(element) {
3604
4704
  session.preloadOnLoadLinksForView(element);
3605
4705
  }
@@ -3607,7 +4707,14 @@ class FrameController {
3607
4707
  willRenderFrame(currentElement, _newElement) {
3608
4708
  this.previousFrameElement = currentElement.cloneNode(true);
3609
4709
  }
3610
- async loadFrameResponse(fetchResponse, document) {
4710
+ visitCachedSnapshot=({element: element}) => {
4711
+ const frame = element.querySelector("#" + this.element.id);
4712
+ if (frame && this.previousFrameElement) {
4713
+ frame.replaceChildren(...this.previousFrameElement.children);
4714
+ }
4715
+ delete this.previousFrameElement;
4716
+ };
4717
+ async #loadFrameResponse(fetchResponse, document) {
3611
4718
  const newFrameElement = await this.extractForeignFrameElement(document.body);
3612
4719
  if (newFrameElement) {
3613
4720
  const snapshot = new Snapshot(newFrameElement);
@@ -3618,41 +4725,40 @@ class FrameController {
3618
4725
  this.complete = true;
3619
4726
  session.frameRendered(fetchResponse, this.element);
3620
4727
  session.frameLoaded(this.element);
3621
- this.fetchResponseLoaded(fetchResponse);
3622
- } else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3623
- this.handleFrameMissingFromResponse(fetchResponse);
4728
+ await this.fetchResponseLoaded(fetchResponse);
4729
+ } else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) {
4730
+ this.#handleFrameMissingFromResponse(fetchResponse);
3624
4731
  }
3625
4732
  }
3626
- async visit(url) {
3627
- var _a;
4733
+ async #visit(url) {
3628
4734
  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;
4735
+ this.#currentFetchRequest?.cancel();
4736
+ this.#currentFetchRequest = request;
3631
4737
  return new Promise((resolve => {
3632
- this.resolveVisitPromise = () => {
3633
- this.resolveVisitPromise = () => {};
3634
- this.currentFetchRequest = null;
4738
+ this.#resolveVisitPromise = () => {
4739
+ this.#resolveVisitPromise = () => {};
4740
+ this.#currentFetchRequest = null;
3635
4741
  resolve();
3636
4742
  };
3637
4743
  request.perform();
3638
4744
  }));
3639
4745
  }
3640
- navigateFrame(element, url, submitter) {
3641
- const frame = this.findFrameElement(element, submitter);
3642
- frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3643
- this.withCurrentNavigationElement(element, (() => {
4746
+ #navigateFrame(element, url, submitter) {
4747
+ const frame = this.#findFrameElement(element, submitter);
4748
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(submitter, element, frame));
4749
+ this.#withCurrentNavigationElement(element, (() => {
3644
4750
  frame.src = url;
3645
4751
  }));
3646
4752
  }
3647
- proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3648
- this.action = getVisitAction(submitter, element, frame);
4753
+ proposeVisitIfNavigatedWithAction(frame, action = null) {
4754
+ this.action = action;
3649
4755
  if (this.action) {
3650
4756
  const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3651
4757
  const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
3652
- frame.delegate.fetchResponseLoaded = fetchResponse => {
4758
+ frame.delegate.fetchResponseLoaded = async fetchResponse => {
3653
4759
  if (frame.src) {
3654
4760
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
3655
- const responseHTML = frame.ownerDocument.documentElement.outerHTML;
4761
+ const responseHTML = await fetchResponse.responseHTML;
3656
4762
  const response = {
3657
4763
  statusCode: statusCode,
3658
4764
  redirected: redirected,
@@ -3678,16 +4784,16 @@ class FrameController {
3678
4784
  session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
3679
4785
  }
3680
4786
  }
3681
- async handleUnvisitableFrameResponse(fetchResponse) {
4787
+ async #handleUnvisitableFrameResponse(fetchResponse) {
3682
4788
  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);
4789
+ await this.#visitResponse(fetchResponse.response);
3684
4790
  }
3685
- willHandleFrameMissingFromResponse(fetchResponse) {
4791
+ #willHandleFrameMissingFromResponse(fetchResponse) {
3686
4792
  this.element.setAttribute("complete", "");
3687
4793
  const response = fetchResponse.response;
3688
- const visit = async (url, options = {}) => {
4794
+ const visit = async (url, options) => {
3689
4795
  if (url instanceof Response) {
3690
- this.visitResponse(url);
4796
+ this.#visitResponse(url);
3691
4797
  } else {
3692
4798
  session.visit(url, options);
3693
4799
  }
@@ -3702,15 +4808,15 @@ class FrameController {
3702
4808
  });
3703
4809
  return !event.defaultPrevented;
3704
4810
  }
3705
- handleFrameMissingFromResponse(fetchResponse) {
4811
+ #handleFrameMissingFromResponse(fetchResponse) {
3706
4812
  this.view.missing();
3707
- this.throwFrameMissingError(fetchResponse);
4813
+ this.#throwFrameMissingError(fetchResponse);
3708
4814
  }
3709
- throwFrameMissingError(fetchResponse) {
4815
+ #throwFrameMissingError(fetchResponse) {
3710
4816
  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
4817
  throw new TurboFrameMissingError(message);
3712
4818
  }
3713
- async visitResponse(response) {
4819
+ async #visitResponse(response) {
3714
4820
  const wrapped = new FetchResponse(response);
3715
4821
  const responseHTML = await wrapped.responseHTML;
3716
4822
  const {location: location, redirected: redirected, statusCode: statusCode} = wrapped;
@@ -3722,10 +4828,9 @@ class FrameController {
3722
4828
  }
3723
4829
  });
3724
4830
  }
3725
- findFrameElement(element, submitter) {
3726
- var _a;
4831
+ #findFrameElement(element, submitter) {
3727
4832
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
3728
- return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
4833
+ return getFrameElementById(id) ?? this.element;
3729
4834
  }
3730
4835
  async extractForeignFrameElement(container) {
3731
4836
  let element;
@@ -3746,13 +4851,13 @@ class FrameController {
3746
4851
  }
3747
4852
  return null;
3748
4853
  }
3749
- formActionIsVisitable(form, submitter) {
3750
- const action = getAction(form, submitter);
4854
+ #formActionIsVisitable(form, submitter) {
4855
+ const action = getAction$1(form, submitter);
3751
4856
  return locationIsVisitable(expandURL(action), this.rootLocation);
3752
4857
  }
3753
- shouldInterceptNavigation(element, submitter) {
4858
+ #shouldInterceptNavigation(element, submitter) {
3754
4859
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
3755
- if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {
4860
+ if (element instanceof HTMLFormElement && !this.#formActionIsVisitable(element, submitter)) {
3756
4861
  return false;
3757
4862
  }
3758
4863
  if (!this.enabled || id == "_top") {
@@ -3784,21 +4889,21 @@ class FrameController {
3784
4889
  }
3785
4890
  }
3786
4891
  set sourceURL(sourceURL) {
3787
- this.ignoringChangesToAttribute("src", (() => {
3788
- this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
4892
+ this.#ignoringChangesToAttribute("src", (() => {
4893
+ this.element.src = sourceURL ?? null;
3789
4894
  }));
3790
4895
  }
3791
4896
  get loadingStyle() {
3792
4897
  return this.element.loading;
3793
4898
  }
3794
4899
  get isLoading() {
3795
- return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
4900
+ return this.formSubmission !== undefined || this.#resolveVisitPromise() !== undefined;
3796
4901
  }
3797
4902
  get complete() {
3798
4903
  return this.element.hasAttribute("complete");
3799
4904
  }
3800
4905
  set complete(value) {
3801
- this.ignoringChangesToAttribute("complete", (() => {
4906
+ this.#ignoringChangesToAttribute("complete", (() => {
3802
4907
  if (value) {
3803
4908
  this.element.setAttribute("complete", "");
3804
4909
  } else {
@@ -3807,23 +4912,22 @@ class FrameController {
3807
4912
  }));
3808
4913
  }
3809
4914
  get isActive() {
3810
- return this.element.isActive && this.connected;
4915
+ return this.element.isActive && this.#connected;
3811
4916
  }
3812
4917
  get rootLocation() {
3813
- var _a;
3814
4918
  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 : "/";
4919
+ const root = meta?.content ?? "/";
3816
4920
  return expandURL(root);
3817
4921
  }
3818
- isIgnoringChangesTo(attributeName) {
3819
- return this.ignoredAttributes.has(attributeName);
4922
+ #isIgnoringChangesTo(attributeName) {
4923
+ return this.#ignoredAttributes.has(attributeName);
3820
4924
  }
3821
- ignoringChangesToAttribute(attributeName, callback) {
3822
- this.ignoredAttributes.add(attributeName);
4925
+ #ignoringChangesToAttribute(attributeName, callback) {
4926
+ this.#ignoredAttributes.add(attributeName);
3823
4927
  callback();
3824
- this.ignoredAttributes.delete(attributeName);
4928
+ this.#ignoredAttributes.delete(attributeName);
3825
4929
  }
3826
- withCurrentNavigationElement(element, callback) {
4930
+ #withCurrentNavigationElement(element, callback) {
3827
4931
  this.currentNavigationElement = element;
3828
4932
  callback();
3829
4933
  delete this.currentNavigationElement;
@@ -3856,6 +4960,38 @@ function activateElement(element, currentURL) {
3856
4960
  }
3857
4961
  }
3858
4962
 
4963
+ const StreamActions = {
4964
+ after() {
4965
+ this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e.nextSibling)));
4966
+ },
4967
+ append() {
4968
+ this.removeDuplicateTargetChildren();
4969
+ this.targetElements.forEach((e => e.append(this.templateContent)));
4970
+ },
4971
+ before() {
4972
+ this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e)));
4973
+ },
4974
+ prepend() {
4975
+ this.removeDuplicateTargetChildren();
4976
+ this.targetElements.forEach((e => e.prepend(this.templateContent)));
4977
+ },
4978
+ remove() {
4979
+ this.targetElements.forEach((e => e.remove()));
4980
+ },
4981
+ replace() {
4982
+ this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
4983
+ },
4984
+ update() {
4985
+ this.targetElements.forEach((targetElement => {
4986
+ targetElement.innerHTML = "";
4987
+ targetElement.append(this.templateContent);
4988
+ }));
4989
+ },
4990
+ refresh() {
4991
+ session.refresh(this.baseURI, this.requestId);
4992
+ }
4993
+ };
4994
+
3859
4995
  class StreamElement extends HTMLElement {
3860
4996
  static async renderElement(newElement) {
3861
4997
  await newElement.performAction();
@@ -3870,11 +5006,10 @@ class StreamElement extends HTMLElement {
3870
5006
  }
3871
5007
  }
3872
5008
  async render() {
3873
- var _a;
3874
- return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
5009
+ return this.renderPromise ??= (async () => {
3875
5010
  const event = this.beforeRenderEvent;
3876
5011
  if (this.dispatchEvent(event)) {
3877
- await nextAnimationFrame();
5012
+ await nextRepaint();
3878
5013
  await event.detail.render(this);
3879
5014
  }
3880
5015
  })();
@@ -3882,15 +5017,14 @@ class StreamElement extends HTMLElement {
3882
5017
  disconnect() {
3883
5018
  try {
3884
5019
  this.remove();
3885
- } catch (_a) {}
5020
+ } catch {}
3886
5021
  }
3887
5022
  removeDuplicateTargetChildren() {
3888
5023
  this.duplicateChildren.forEach((c => c.remove()));
3889
5024
  }
3890
5025
  get duplicateChildren() {
3891
- var _a;
3892
5026
  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));
5027
+ const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
3894
5028
  return existingChildren.filter((c => newChildrenIds.includes(c.id)));
3895
5029
  }
3896
5030
  get performAction() {
@@ -3899,9 +5033,9 @@ class StreamElement extends HTMLElement {
3899
5033
  if (actionFunction) {
3900
5034
  return actionFunction;
3901
5035
  }
3902
- this.raise("unknown action");
5036
+ this.#raise("unknown action");
3903
5037
  }
3904
- this.raise("action attribute is missing");
5038
+ this.#raise("action attribute is missing");
3905
5039
  }
3906
5040
  get targetElements() {
3907
5041
  if (this.target) {
@@ -3909,7 +5043,7 @@ class StreamElement extends HTMLElement {
3909
5043
  } else if (this.targets) {
3910
5044
  return this.targetElementsByQuery;
3911
5045
  } else {
3912
- this.raise("target or targets attribute is missing");
5046
+ this.#raise("target or targets attribute is missing");
3913
5047
  }
3914
5048
  }
3915
5049
  get templateContent() {
@@ -3923,7 +5057,7 @@ class StreamElement extends HTMLElement {
3923
5057
  } else if (this.firstElementChild instanceof HTMLTemplateElement) {
3924
5058
  return this.firstElementChild;
3925
5059
  }
3926
- this.raise("first child element must be a <template> element");
5060
+ this.#raise("first child element must be a <template> element");
3927
5061
  }
3928
5062
  get action() {
3929
5063
  return this.getAttribute("action");
@@ -3934,12 +5068,14 @@ class StreamElement extends HTMLElement {
3934
5068
  get targets() {
3935
5069
  return this.getAttribute("targets");
3936
5070
  }
3937
- raise(message) {
5071
+ get requestId() {
5072
+ return this.getAttribute("request-id");
5073
+ }
5074
+ #raise(message) {
3938
5075
  throw new Error(`${this.description}: ${message}`);
3939
5076
  }
3940
5077
  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>";
5078
+ return (this.outerHTML.match(/<[^>]+>/) ?? [])[0] ?? "<turbo-stream>";
3943
5079
  }
3944
5080
  get beforeRenderEvent() {
3945
5081
  return new CustomEvent("turbo:before-stream-render", {
@@ -3952,8 +5088,7 @@ class StreamElement extends HTMLElement {
3952
5088
  });
3953
5089
  }
3954
5090
  get targetElementsById() {
3955
- var _a;
3956
- const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
5091
+ const element = this.ownerDocument?.getElementById(this.target);
3957
5092
  if (element !== null) {
3958
5093
  return [ element ];
3959
5094
  } else {
@@ -3961,8 +5096,7 @@ class StreamElement extends HTMLElement {
3961
5096
  }
3962
5097
  }
3963
5098
  get targetElementsByQuery() {
3964
- var _a;
3965
- const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);
5099
+ const elements = this.ownerDocument?.querySelectorAll(this.targets);
3966
5100
  if (elements.length !== 0) {
3967
5101
  return Array.prototype.slice.call(elements);
3968
5102
  } else {
@@ -3972,16 +5106,14 @@ class StreamElement extends HTMLElement {
3972
5106
  }
3973
5107
 
3974
5108
  class StreamSourceElement extends HTMLElement {
3975
- constructor() {
3976
- super(...arguments);
3977
- this.streamSource = null;
3978
- }
5109
+ streamSource=null;
3979
5110
  connectedCallback() {
3980
5111
  this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
3981
5112
  connectStreamSource(this.streamSource);
3982
5113
  }
3983
5114
  disconnectedCallback() {
3984
5115
  if (this.streamSource) {
5116
+ this.streamSource.close();
3985
5117
  disconnectStreamSource(this.streamSource);
3986
5118
  }
3987
5119
  }
@@ -4026,16 +5158,21 @@ if (customElements.get("turbo-stream-source") === undefined) {
4026
5158
  }
4027
5159
  })();
4028
5160
 
4029
- window.Turbo = Turbo;
5161
+ window.Turbo = {
5162
+ ...Turbo,
5163
+ StreamActions: StreamActions
5164
+ };
4030
5165
 
4031
5166
  start();
4032
5167
 
4033
- var turbo_es2017Esm = Object.freeze({
5168
+ var Turbo$1 = Object.freeze({
4034
5169
  __proto__: null,
5170
+ FetchEnctype: FetchEnctype,
5171
+ FetchMethod: FetchMethod,
5172
+ FetchRequest: FetchRequest,
5173
+ FetchResponse: FetchResponse,
4035
5174
  FrameElement: FrameElement,
4036
- get FrameLoadingStyle() {
4037
- return FrameLoadingStyle;
4038
- },
5175
+ FrameLoadingStyle: FrameLoadingStyle,
4039
5176
  FrameRenderer: FrameRenderer,
4040
5177
  PageRenderer: PageRenderer,
4041
5178
  PageSnapshot: PageSnapshot,
@@ -4046,6 +5183,10 @@ var turbo_es2017Esm = Object.freeze({
4046
5183
  clearCache: clearCache,
4047
5184
  connectStreamSource: connectStreamSource,
4048
5185
  disconnectStreamSource: disconnectStreamSource,
5186
+ fetch: fetchWithTurboHeaders,
5187
+ fetchEnctypeFromString: fetchEnctypeFromString,
5188
+ fetchMethodFromString: fetchMethodFromString,
5189
+ isSafe: isSafe,
4049
5190
  navigator: navigator$1,
4050
5191
  registerAdapter: registerAdapter,
4051
5192
  renderStreamMessage: renderStreamMessage,
@@ -4060,14 +5201,14 @@ var turbo_es2017Esm = Object.freeze({
4060
5201
  let consumer;
4061
5202
 
4062
5203
  async function getConsumer() {
4063
- return consumer || setConsumer(createConsumer().then(setConsumer));
5204
+ return consumer || setConsumer(createConsumer$1().then(setConsumer));
4064
5205
  }
4065
5206
 
4066
5207
  function setConsumer(newConsumer) {
4067
5208
  return consumer = newConsumer;
4068
5209
  }
4069
5210
 
4070
- async function createConsumer() {
5211
+ async function createConsumer$1() {
4071
5212
  const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
4072
5213
  return index;
4073
5214
  }));
@@ -4083,7 +5224,7 @@ var cable = Object.freeze({
4083
5224
  __proto__: null,
4084
5225
  getConsumer: getConsumer,
4085
5226
  setConsumer: setConsumer,
4086
- createConsumer: createConsumer,
5227
+ createConsumer: createConsumer$1,
4087
5228
  subscribeTo: subscribeTo
4088
5229
  });
4089
5230
 
@@ -4193,6 +5334,8 @@ function isBodyInit(body) {
4193
5334
  return body instanceof FormData || body instanceof URLSearchParams;
4194
5335
  }
4195
5336
 
5337
+ window.Turbo = Turbo$1;
5338
+
4196
5339
  addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
4197
5340
 
4198
5341
  var adapters = {
@@ -4309,6 +5452,8 @@ ConnectionMonitor.staleThreshold = 6;
4309
5452
 
4310
5453
  ConnectionMonitor.reconnectionBackoffRate = .15;
4311
5454
 
5455
+ var ConnectionMonitor$1 = ConnectionMonitor;
5456
+
4312
5457
  var INTERNAL = {
4313
5458
  message_types: {
4314
5459
  welcome: "welcome",
@@ -4320,7 +5465,8 @@ var INTERNAL = {
4320
5465
  disconnect_reasons: {
4321
5466
  unauthorized: "unauthorized",
4322
5467
  invalid_request: "invalid_request",
4323
- server_restart: "server_restart"
5468
+ server_restart: "server_restart",
5469
+ remote: "remote"
4324
5470
  },
4325
5471
  default_mount_path: "/cable",
4326
5472
  protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
@@ -4337,7 +5483,7 @@ class Connection {
4337
5483
  this.open = this.open.bind(this);
4338
5484
  this.consumer = consumer;
4339
5485
  this.subscriptions = this.consumer.subscriptions;
4340
- this.monitor = new ConnectionMonitor(this);
5486
+ this.monitor = new ConnectionMonitor$1(this);
4341
5487
  this.disconnected = true;
4342
5488
  }
4343
5489
  send(data) {
@@ -4353,11 +5499,12 @@ class Connection {
4353
5499
  logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
4354
5500
  return false;
4355
5501
  } else {
4356
- logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${protocols}`);
5502
+ const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
5503
+ logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
4357
5504
  if (this.webSocket) {
4358
5505
  this.uninstallEventHandlers();
4359
5506
  }
4360
- this.webSocket = new adapters.WebSocket(this.consumer.url, protocols);
5507
+ this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
4361
5508
  this.installEventHandlers();
4362
5509
  this.monitor.start();
4363
5510
  return true;
@@ -4369,7 +5516,7 @@ class Connection {
4369
5516
  if (!allowReconnect) {
4370
5517
  this.monitor.stop();
4371
5518
  }
4372
- if (this.isActive()) {
5519
+ if (this.isOpen()) {
4373
5520
  return this.webSocket.close();
4374
5521
  }
4375
5522
  }
@@ -4399,6 +5546,9 @@ class Connection {
4399
5546
  isActive() {
4400
5547
  return this.isState("open", "connecting");
4401
5548
  }
5549
+ triedToReconnect() {
5550
+ return this.monitor.reconnectAttempts > 0;
5551
+ }
4402
5552
  isProtocolSupported() {
4403
5553
  return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
4404
5554
  }
@@ -4438,6 +5588,9 @@ Connection.prototype.events = {
4438
5588
  const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
4439
5589
  switch (type) {
4440
5590
  case message_types.welcome:
5591
+ if (this.triedToReconnect()) {
5592
+ this.reconnectAttempted = true;
5593
+ }
4441
5594
  this.monitor.recordConnect();
4442
5595
  return this.subscriptions.reload();
4443
5596
 
@@ -4452,7 +5605,16 @@ Connection.prototype.events = {
4452
5605
 
4453
5606
  case message_types.confirmation:
4454
5607
  this.subscriptions.confirmSubscription(identifier);
4455
- return this.subscriptions.notify(identifier, "connected");
5608
+ if (this.reconnectAttempted) {
5609
+ this.reconnectAttempted = false;
5610
+ return this.subscriptions.notify(identifier, "connected", {
5611
+ reconnected: true
5612
+ });
5613
+ } else {
5614
+ return this.subscriptions.notify(identifier, "connected", {
5615
+ reconnected: false
5616
+ });
5617
+ }
4456
5618
 
4457
5619
  case message_types.rejection:
4458
5620
  return this.subscriptions.reject(identifier);
@@ -4487,6 +5649,8 @@ Connection.prototype.events = {
4487
5649
  }
4488
5650
  };
4489
5651
 
5652
+ var Connection$1 = Connection;
5653
+
4490
5654
  const extend = function(object, properties) {
4491
5655
  if (properties != null) {
4492
5656
  for (let key in properties) {
@@ -4556,10 +5720,12 @@ class SubscriptionGuarantor {
4556
5720
  }
4557
5721
  }
4558
5722
 
5723
+ var SubscriptionGuarantor$1 = SubscriptionGuarantor;
5724
+
4559
5725
  class Subscriptions {
4560
5726
  constructor(consumer) {
4561
5727
  this.consumer = consumer;
4562
- this.guarantor = new SubscriptionGuarantor(this);
5728
+ this.guarantor = new SubscriptionGuarantor$1(this);
4563
5729
  this.subscriptions = [];
4564
5730
  }
4565
5731
  create(channelName, mixin) {
@@ -4636,7 +5802,8 @@ class Consumer {
4636
5802
  constructor(url) {
4637
5803
  this._url = url;
4638
5804
  this.subscriptions = new Subscriptions(this);
4639
- this.connection = new Connection(this);
5805
+ this.connection = new Connection$1(this);
5806
+ this.subprotocols = [];
4640
5807
  }
4641
5808
  get url() {
4642
5809
  return createWebSocketURL(this._url);
@@ -4657,6 +5824,9 @@ class Consumer {
4657
5824
  return this.connection.open();
4658
5825
  }
4659
5826
  }
5827
+ addSubProtocol(subprotocol) {
5828
+ this.subprotocols = [ ...this.subprotocols, subprotocol ];
5829
+ }
4660
5830
  }
4661
5831
 
4662
5832
  function createWebSocketURL(url) {
@@ -4674,7 +5844,7 @@ function createWebSocketURL(url) {
4674
5844
  }
4675
5845
  }
4676
5846
 
4677
- function createConsumer$1(url = getConfig("url") || INTERNAL.default_mount_path) {
5847
+ function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
4678
5848
  return new Consumer(url);
4679
5849
  }
4680
5850
 
@@ -4687,18 +5857,18 @@ function getConfig(name) {
4687
5857
 
4688
5858
  var index = Object.freeze({
4689
5859
  __proto__: null,
4690
- Connection: Connection,
4691
- ConnectionMonitor: ConnectionMonitor,
5860
+ Connection: Connection$1,
5861
+ ConnectionMonitor: ConnectionMonitor$1,
4692
5862
  Consumer: Consumer,
4693
5863
  INTERNAL: INTERNAL,
4694
5864
  Subscription: Subscription,
4695
5865
  Subscriptions: Subscriptions,
4696
- SubscriptionGuarantor: SubscriptionGuarantor,
5866
+ SubscriptionGuarantor: SubscriptionGuarantor$1,
4697
5867
  adapters: adapters,
4698
5868
  createWebSocketURL: createWebSocketURL,
4699
5869
  logger: logger,
4700
- createConsumer: createConsumer$1,
5870
+ createConsumer: createConsumer,
4701
5871
  getConfig: getConfig
4702
5872
  });
4703
5873
 
4704
- export { turbo_es2017Esm as Turbo, cable };
5874
+ export { Turbo$1 as Turbo, cable };