@hotwired/turbo 7.1.0 → 7.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/turbo.es2017-esm.js +712 -364
  2. package/dist/turbo.es2017-umd.js +717 -365
  3. package/dist/types/core/bardo.d.ts +7 -2
  4. package/dist/types/core/cache.d.ts +10 -0
  5. package/dist/types/core/drive/error_renderer.d.ts +1 -0
  6. package/dist/types/core/drive/form_submission.d.ts +12 -3
  7. package/dist/types/core/drive/history.d.ts +1 -1
  8. package/dist/types/core/drive/navigator.d.ts +1 -0
  9. package/dist/types/core/drive/page_renderer.d.ts +3 -0
  10. package/dist/types/core/drive/page_view.d.ts +8 -5
  11. package/dist/types/core/drive/preloader.d.ts +14 -0
  12. package/dist/types/core/drive/progress_bar.d.ts +1 -0
  13. package/dist/types/core/drive/visit.d.ts +3 -3
  14. package/dist/types/core/frames/frame_controller.d.ts +26 -16
  15. package/dist/types/core/frames/frame_redirector.d.ts +1 -1
  16. package/dist/types/core/frames/frame_renderer.d.ts +8 -1
  17. package/dist/types/core/frames/frame_view.d.ts +2 -1
  18. package/dist/types/core/frames/link_interceptor.d.ts +1 -1
  19. package/dist/types/core/index.d.ts +11 -2
  20. package/dist/types/core/native/adapter.d.ts +2 -1
  21. package/dist/types/core/native/browser_adapter.d.ts +17 -8
  22. package/dist/types/core/renderer.d.ts +12 -4
  23. package/dist/types/core/session.d.ts +68 -16
  24. package/dist/types/core/snapshot.d.ts +2 -1
  25. package/dist/types/core/view.d.ts +12 -6
  26. package/dist/types/elements/frame_element.d.ts +5 -1
  27. package/dist/types/elements/stream_element.d.ts +1 -0
  28. package/dist/types/elements/stream_source_element.d.ts +7 -0
  29. package/dist/types/http/fetch_request.d.ts +8 -0
  30. package/dist/types/observers/cache_observer.d.ts +1 -1
  31. package/dist/types/observers/form_link_interceptor.d.ts +14 -0
  32. package/dist/types/observers/link_click_observer.d.ts +2 -2
  33. package/dist/types/polyfills/submit-event.d.ts +1 -7
  34. package/dist/types/tests/functional/async_script_tests.d.ts +1 -6
  35. package/dist/types/tests/functional/autofocus_tests.d.ts +1 -9
  36. package/dist/types/tests/functional/cache_observer_tests.d.ts +1 -5
  37. package/dist/types/tests/functional/drive_disabled_tests.d.ts +1 -9
  38. package/dist/types/tests/functional/drive_tests.d.ts +1 -8
  39. package/dist/types/tests/functional/form_submission_tests.d.ts +1 -84
  40. package/dist/types/tests/functional/frame_navigation_tests.d.ts +1 -7
  41. package/dist/types/tests/functional/frame_tests.d.ts +1 -52
  42. package/dist/types/tests/functional/import_tests.d.ts +1 -4
  43. package/dist/types/tests/functional/loading_tests.d.ts +1 -13
  44. package/dist/types/tests/functional/navigation_tests.d.ts +1 -38
  45. package/dist/types/tests/functional/pausable_rendering_tests.d.ts +1 -6
  46. package/dist/types/tests/functional/pausable_requests_tests.d.ts +1 -6
  47. package/dist/types/tests/functional/preloader_tests.d.ts +1 -0
  48. package/dist/types/tests/functional/rendering_tests.d.ts +1 -35
  49. package/dist/types/tests/functional/scroll_restoration_tests.d.ts +1 -6
  50. package/dist/types/tests/functional/stream_tests.d.ts +1 -6
  51. package/dist/types/tests/functional/visit_tests.d.ts +1 -15
  52. package/dist/types/tests/helpers/page.d.ts +44 -0
  53. package/dist/types/tests/unit/deprecated_adapter_support_test.d.ts +10 -10
  54. package/dist/types/util.d.ts +6 -3
  55. package/package.json +22 -8
  56. package/CHANGELOG.md +0 -3
  57. package/dist/types/tests/functional/index.d.ts +0 -17
  58. package/dist/types/tests/helpers/functional_test_case.d.ts +0 -44
  59. package/dist/types/tests/helpers/remote_channel.d.ts +0 -10
  60. package/dist/types/tests/helpers/turbo_drive_test_case.d.ts +0 -21
@@ -1,20 +1,20 @@
1
1
  /*
2
- Turbo 7.1.0
3
- Copyright © 2021 Basecamp, LLC
2
+ Turbo 7.2.0-beta.1
3
+ Copyright © 2022 Basecamp, LLC
4
4
  */
5
5
  (function () {
6
- if (window.Reflect === undefined || window.customElements === undefined ||
6
+ if (window.Reflect === undefined ||
7
+ window.customElements === undefined ||
7
8
  window.customElements.polyfillWrapFlushCallback) {
8
9
  return;
9
10
  }
10
11
  const BuiltInHTMLElement = HTMLElement;
11
12
  const wrapperForTheName = {
12
- 'HTMLElement': function HTMLElement() {
13
+ HTMLElement: function HTMLElement() {
13
14
  return Reflect.construct(BuiltInHTMLElement, [], this.constructor);
14
- }
15
+ },
15
16
  };
16
- window.HTMLElement =
17
- wrapperForTheName['HTMLElement'];
17
+ window.HTMLElement = wrapperForTheName["HTMLElement"];
18
18
  HTMLElement.prototype = BuiltInHTMLElement.prototype;
19
19
  HTMLElement.prototype.constructor = HTMLElement;
20
20
  Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);
@@ -72,7 +72,7 @@ Copyright © 2021 Basecamp, LLC
72
72
  }
73
73
  })(HTMLFormElement.prototype);
74
74
 
75
- const submittersByForm = new WeakMap;
75
+ const submittersByForm = new WeakMap();
76
76
  function findSubmitterFromClickTarget(target) {
77
77
  const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
78
78
  const candidate = element ? element.closest("input, button") : null;
@@ -103,7 +103,7 @@ function clickCaptured(event) {
103
103
  if (this.type == "submit" && this.target instanceof HTMLFormElement) {
104
104
  return submittersByForm.get(this.target);
105
105
  }
106
- }
106
+ },
107
107
  });
108
108
  })();
109
109
 
@@ -119,7 +119,7 @@ class FrameElement extends HTMLElement {
119
119
  this.delegate = new FrameElement.delegateConstructor(this);
120
120
  }
121
121
  static get observedAttributes() {
122
- return ["disabled", "loading", "src"];
122
+ return ["disabled", "complete", "loading", "src"];
123
123
  }
124
124
  connectedCallback() {
125
125
  this.delegate.connect();
@@ -129,6 +129,7 @@ class FrameElement extends HTMLElement {
129
129
  }
130
130
  reload() {
131
131
  const { src } = this;
132
+ this.removeAttribute("complete");
132
133
  this.src = null;
133
134
  this.src = src;
134
135
  }
@@ -136,6 +137,9 @@ class FrameElement extends HTMLElement {
136
137
  if (name == "loading") {
137
138
  this.delegate.loadingStyleChanged();
138
139
  }
140
+ else if (name == "complete") {
141
+ this.delegate.completeChanged();
142
+ }
139
143
  else if (name == "src") {
140
144
  this.delegate.sourceURLChanged();
141
145
  }
@@ -200,8 +204,10 @@ class FrameElement extends HTMLElement {
200
204
  }
201
205
  function frameLoadingStyleFromString(style) {
202
206
  switch (style.toLowerCase()) {
203
- case "lazy": return FrameLoadingStyle.lazy;
204
- default: return FrameLoadingStyle.eager;
207
+ case "lazy":
208
+ return FrameLoadingStyle.lazy;
209
+ default:
210
+ return FrameLoadingStyle.eager;
205
211
  }
206
212
  }
207
213
 
@@ -213,7 +219,7 @@ function getAnchor(url) {
213
219
  if (url.hash) {
214
220
  return url.hash.slice(1);
215
221
  }
216
- else if (anchorMatch = url.href.match(/#(.*)$/)) {
222
+ else if ((anchorMatch = url.href.match(/#(.*)$/))) {
217
223
  return anchorMatch[1];
218
224
  }
219
225
  }
@@ -225,7 +231,7 @@ function getExtension(url) {
225
231
  return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
226
232
  }
227
233
  function isHTML(url) {
228
- return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
234
+ return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
229
235
  }
230
236
  function isPrefixedBy(baseURL, url) {
231
237
  const prefix = getPrefix(url);
@@ -236,9 +242,7 @@ function locationIsVisitable(location, rootLocation) {
236
242
  }
237
243
  function getRequestURL(url) {
238
244
  const anchor = getAnchor(url);
239
- return anchor != null
240
- ? url.href.slice(0, -(anchor.length + 1))
241
- : url.href;
245
+ return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
242
246
  }
243
247
  function toCacheKey(url) {
244
248
  return getRequestURL(url);
@@ -307,7 +311,11 @@ class FetchResponse {
307
311
  }
308
312
 
309
313
  function dispatch(eventName, { target, cancelable, detail } = {}) {
310
- const event = new CustomEvent(eventName, { cancelable, bubbles: true, detail });
314
+ const event = new CustomEvent(eventName, {
315
+ cancelable,
316
+ bubbles: true,
317
+ detail,
318
+ });
311
319
  if (target && target.isConnected) {
312
320
  target.dispatchEvent(event);
313
321
  }
@@ -317,10 +325,10 @@ function dispatch(eventName, { target, cancelable, detail } = {}) {
317
325
  return event;
318
326
  }
319
327
  function nextAnimationFrame() {
320
- return new Promise(resolve => requestAnimationFrame(() => resolve()));
328
+ return new Promise((resolve) => requestAnimationFrame(() => resolve()));
321
329
  }
322
330
  function nextEventLoopTick() {
323
- return new Promise(resolve => setTimeout(() => resolve(), 0));
331
+ return new Promise((resolve) => setTimeout(() => resolve(), 0));
324
332
  }
325
333
  function nextMicrotask() {
326
334
  return Promise.resolve();
@@ -332,7 +340,7 @@ function unindent(strings, ...values) {
332
340
  const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
333
341
  const match = lines[0].match(/^\s+/);
334
342
  const indent = match ? match[0].length : 0;
335
- return lines.map(line => line.slice(indent)).join("\n");
343
+ return lines.map((line) => line.slice(indent)).join("\n");
336
344
  }
337
345
  function interpolate(strings, values) {
338
346
  return strings.reduce((result, string, i) => {
@@ -341,7 +349,8 @@ function interpolate(strings, values) {
341
349
  }, "");
342
350
  }
343
351
  function uuid() {
344
- return Array.apply(null, { length: 36 }).map((_, i) => {
352
+ return Array.from({ length: 36 })
353
+ .map((_, i) => {
345
354
  if (i == 8 || i == 13 || i == 18 || i == 23) {
346
355
  return "-";
347
356
  }
@@ -354,10 +363,11 @@ function uuid() {
354
363
  else {
355
364
  return Math.floor(Math.random() * 15).toString(16);
356
365
  }
357
- }).join("");
366
+ })
367
+ .join("");
358
368
  }
359
369
  function getAttribute(attributeName, ...elements) {
360
- for (const value of elements.map(element => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {
370
+ for (const value of elements.map((element) => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {
361
371
  if (typeof value == "string")
362
372
  return value;
363
373
  }
@@ -379,6 +389,23 @@ function clearBusyState(...elements) {
379
389
  element.removeAttribute("aria-busy");
380
390
  }
381
391
  }
392
+ function getMetaElement(name) {
393
+ return document.querySelector(`meta[name="${name}"]`);
394
+ }
395
+ function getMetaContent(name) {
396
+ const element = getMetaElement(name);
397
+ return element && element.content;
398
+ }
399
+ function setMetaContent(name, content) {
400
+ let element = getMetaElement(name);
401
+ if (!element) {
402
+ element = document.createElement("meta");
403
+ element.setAttribute("name", name);
404
+ document.head.appendChild(element);
405
+ }
406
+ element.setAttribute("content", content);
407
+ return element;
408
+ }
382
409
 
383
410
  var FetchMethod;
384
411
  (function (FetchMethod) {
@@ -390,17 +417,22 @@ var FetchMethod;
390
417
  })(FetchMethod || (FetchMethod = {}));
391
418
  function fetchMethodFromString(method) {
392
419
  switch (method.toLowerCase()) {
393
- case "get": return FetchMethod.get;
394
- case "post": return FetchMethod.post;
395
- case "put": return FetchMethod.put;
396
- case "patch": return FetchMethod.patch;
397
- case "delete": return FetchMethod.delete;
420
+ case "get":
421
+ return FetchMethod.get;
422
+ case "post":
423
+ return FetchMethod.post;
424
+ case "put":
425
+ return FetchMethod.put;
426
+ case "patch":
427
+ return FetchMethod.patch;
428
+ case "delete":
429
+ return FetchMethod.delete;
398
430
  }
399
431
  }
400
432
  class FetchRequest {
401
- constructor(delegate, method, location, body = new URLSearchParams, target = null) {
402
- this.abortController = new AbortController;
403
- this.resolveRequestPromise = (value) => { };
433
+ constructor(delegate, method, location, body = new URLSearchParams(), target = null) {
434
+ this.abortController = new AbortController();
435
+ this.resolveRequestPromise = (_value) => { };
404
436
  this.delegate = delegate;
405
437
  this.method = method;
406
438
  this.headers = this.defaultHeaders;
@@ -431,7 +463,7 @@ class FetchRequest {
431
463
  return await this.receive(response);
432
464
  }
433
465
  catch (error) {
434
- if (error.name !== 'AbortError') {
466
+ if (error.name !== "AbortError") {
435
467
  this.delegate.requestErrored(this, error);
436
468
  throw error;
437
469
  }
@@ -442,7 +474,11 @@ class FetchRequest {
442
474
  }
443
475
  async receive(response) {
444
476
  const fetchResponse = new FetchResponse(response);
445
- const event = dispatch("turbo:before-fetch-response", { cancelable: true, detail: { fetchResponse }, target: this.target });
477
+ const event = dispatch("turbo:before-fetch-response", {
478
+ cancelable: true,
479
+ detail: { fetchResponse },
480
+ target: this.target,
481
+ });
446
482
  if (event.defaultPrevented) {
447
483
  this.delegate.requestPreventedHandlingResponse(this, fetchResponse);
448
484
  }
@@ -463,12 +499,12 @@ class FetchRequest {
463
499
  redirect: "follow",
464
500
  body: this.isIdempotent ? null : this.body,
465
501
  signal: this.abortSignal,
466
- referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
502
+ referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href,
467
503
  };
468
504
  }
469
505
  get defaultHeaders() {
470
506
  return {
471
- "Accept": "text/html, application/xhtml+xml"
507
+ Accept: "text/html, application/xhtml+xml",
472
508
  };
473
509
  }
474
510
  get isIdempotent() {
@@ -478,15 +514,15 @@ class FetchRequest {
478
514
  return this.abortController.signal;
479
515
  }
480
516
  async allowRequestToBeIntercepted(fetchOptions) {
481
- const requestInterception = new Promise(resolve => this.resolveRequestPromise = resolve);
517
+ const requestInterception = new Promise((resolve) => (this.resolveRequestPromise = resolve));
482
518
  const event = dispatch("turbo:before-fetch-request", {
483
519
  cancelable: true,
484
520
  detail: {
485
521
  fetchOptions,
486
522
  url: this.url,
487
- resume: this.resolveRequestPromise
523
+ resume: this.resolveRequestPromise,
488
524
  },
489
- target: this.target
525
+ target: this.target,
490
526
  });
491
527
  if (event.defaultPrevented)
492
528
  await requestInterception;
@@ -496,7 +532,7 @@ class FetchRequest {
496
532
  class AppearanceObserver {
497
533
  constructor(delegate, element) {
498
534
  this.started = false;
499
- this.intersect = entries => {
535
+ this.intersect = (entries) => {
500
536
  const lastEntry = entries.slice(-1)[0];
501
537
  if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
502
538
  this.delegate.elementAppearedInViewport(this.element);
@@ -573,9 +609,12 @@ var FormEnctype;
573
609
  })(FormEnctype || (FormEnctype = {}));
574
610
  function formEnctypeFromString(encoding) {
575
611
  switch (encoding.toLowerCase()) {
576
- case FormEnctype.multipart: return FormEnctype.multipart;
577
- case FormEnctype.plain: return FormEnctype.plain;
578
- default: return FormEnctype.urlEncoded;
612
+ case FormEnctype.multipart:
613
+ return FormEnctype.multipart;
614
+ case FormEnctype.plain:
615
+ return FormEnctype.plain;
616
+ default:
617
+ return FormEnctype.urlEncoded;
579
618
  }
580
619
  }
581
620
  class FormSubmission {
@@ -592,8 +631,8 @@ class FormSubmission {
592
631
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
593
632
  this.mustRedirect = mustRedirect;
594
633
  }
595
- static confirmMethod(message, element) {
596
- return confirm(message);
634
+ static confirmMethod(message, _element) {
635
+ return Promise.resolve(confirm(message));
597
636
  }
598
637
  get method() {
599
638
  var _a;
@@ -602,8 +641,13 @@ class FormSubmission {
602
641
  }
603
642
  get action() {
604
643
  var _a;
605
- const formElementAction = typeof this.formElement.action === 'string' ? this.formElement.action : null;
606
- return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.getAttribute("action") || formElementAction || "";
644
+ const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
645
+ if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
646
+ return this.submitter.getAttribute("formaction") || "";
647
+ }
648
+ else {
649
+ return this.formElement.getAttribute("action") || formElementAction || "";
650
+ }
607
651
  }
608
652
  get body() {
609
653
  if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
@@ -626,7 +670,8 @@ class FormSubmission {
626
670
  }, []);
627
671
  }
628
672
  get confirmationMessage() {
629
- return this.formElement.getAttribute("data-turbo-confirm");
673
+ var _a;
674
+ return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-confirm")) || this.formElement.getAttribute("data-turbo-confirm");
630
675
  }
631
676
  get needsConfirmation() {
632
677
  return this.confirmationMessage !== null;
@@ -634,7 +679,7 @@ class FormSubmission {
634
679
  async start() {
635
680
  const { initialized, requesting } = FormSubmissionState;
636
681
  if (this.needsConfirmation) {
637
- const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
682
+ const answer = await FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
638
683
  if (!answer) {
639
684
  return;
640
685
  }
@@ -658,14 +703,19 @@ class FormSubmission {
658
703
  if (token) {
659
704
  headers["X-CSRF-Token"] = token;
660
705
  }
706
+ }
707
+ if (this.requestAcceptsTurboStreamResponse(request)) {
661
708
  headers["Accept"] = [StreamMessage.contentType, headers["Accept"]].join(", ");
662
709
  }
663
710
  }
664
- requestStarted(request) {
711
+ requestStarted(_request) {
665
712
  var _a;
666
713
  this.state = FormSubmissionState.waiting;
667
714
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
668
- dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } });
715
+ dispatch("turbo:submit-start", {
716
+ target: this.formElement,
717
+ detail: { formSubmission: this },
718
+ });
669
719
  this.delegate.formSubmissionStarted(this);
670
720
  }
671
721
  requestPreventedHandlingResponse(request, response) {
@@ -693,16 +743,22 @@ class FormSubmission {
693
743
  this.result = { success: false, error };
694
744
  this.delegate.formSubmissionErrored(this, error);
695
745
  }
696
- requestFinished(request) {
746
+ requestFinished(_request) {
697
747
  var _a;
698
748
  this.state = FormSubmissionState.stopped;
699
749
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
700
- dispatch("turbo:submit-end", { target: this.formElement, detail: Object.assign({ formSubmission: this }, this.result) });
750
+ dispatch("turbo:submit-end", {
751
+ target: this.formElement,
752
+ detail: Object.assign({ formSubmission: this }, this.result),
753
+ });
701
754
  this.delegate.formSubmissionFinished(this);
702
755
  }
703
756
  requestMustRedirect(request) {
704
757
  return !request.isIdempotent && this.mustRedirect;
705
758
  }
759
+ requestAcceptsTurboStreamResponse(request) {
760
+ return !request.isIdempotent || this.formElement.hasAttribute("data-turbo-stream");
761
+ }
706
762
  }
707
763
  function buildFormData(formElement, submitter) {
708
764
  const formData = new FormData(formElement);
@@ -723,15 +779,11 @@ function getCookieValue(cookieName) {
723
779
  }
724
780
  }
725
781
  }
726
- function getMetaContent(name) {
727
- const element = document.querySelector(`meta[name="${name}"]`);
728
- return element && element.content;
729
- }
730
782
  function responseSucceededWithoutRedirect(response) {
731
783
  return response.statusCode == 200 && !response.redirected;
732
784
  }
733
785
  function mergeFormDataEntries(url, entries) {
734
- const searchParams = new URLSearchParams;
786
+ const searchParams = new URLSearchParams();
735
787
  for (const [name, value] of entries) {
736
788
  if (value instanceof File)
737
789
  continue;
@@ -745,6 +797,9 @@ class Snapshot {
745
797
  constructor(element) {
746
798
  this.element = element;
747
799
  }
800
+ get activeElement() {
801
+ return this.element.ownerDocument.activeElement;
802
+ }
748
803
  get children() {
749
804
  return [...this.element.children];
750
805
  }
@@ -783,7 +838,9 @@ class FormInterceptor {
783
838
  constructor(delegate, element) {
784
839
  this.submitBubbled = ((event) => {
785
840
  const form = event.target;
786
- if (!event.defaultPrevented && form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
841
+ if (!event.defaultPrevented &&
842
+ form instanceof HTMLFormElement &&
843
+ form.closest("turbo-frame, html") == this.element) {
787
844
  const submitter = event.submitter || undefined;
788
845
  const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
789
846
  if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
@@ -806,8 +863,8 @@ class FormInterceptor {
806
863
 
807
864
  class View {
808
865
  constructor(delegate, element) {
809
- this.resolveRenderPromise = (value) => { };
810
- this.resolveInterceptionPromise = (value) => { };
866
+ this.resolveRenderPromise = (_value) => { };
867
+ this.resolveInterceptionPromise = (_value) => { };
811
868
  this.delegate = delegate;
812
869
  this.element = element;
813
870
  }
@@ -852,15 +909,17 @@ class View {
852
909
  const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
853
910
  if (shouldRender) {
854
911
  try {
855
- this.renderPromise = new Promise(resolve => this.resolveRenderPromise = resolve);
912
+ this.renderPromise = new Promise((resolve) => (this.resolveRenderPromise = resolve));
856
913
  this.renderer = renderer;
857
914
  this.prepareToRenderSnapshot(renderer);
858
- const renderInterception = new Promise(resolve => this.resolveInterceptionPromise = resolve);
859
- const immediateRender = this.delegate.allowsImmediateRender(snapshot, this.resolveInterceptionPromise);
915
+ const renderInterception = new Promise((resolve) => (this.resolveInterceptionPromise = resolve));
916
+ const options = { resume: this.resolveInterceptionPromise, render: this.renderer.renderElement };
917
+ const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
860
918
  if (!immediateRender)
861
919
  await renderInterception;
862
920
  await this.renderSnapshot(renderer);
863
921
  this.delegate.viewRenderedSnapshot(snapshot, isPreview);
922
+ this.delegate.preloadOnLoadLinksForView(this.element);
864
923
  this.finishRenderingSnapshot(renderer);
865
924
  }
866
925
  finally {
@@ -870,11 +929,11 @@ class View {
870
929
  }
871
930
  }
872
931
  else {
873
- this.invalidate();
932
+ this.invalidate(renderer.reloadReason);
874
933
  }
875
934
  }
876
- invalidate() {
877
- this.delegate.viewInvalidated();
935
+ invalidate(reason) {
936
+ this.delegate.viewInvalidated(reason);
878
937
  }
879
938
  prepareToRenderSnapshot(renderer) {
880
939
  this.markAsPreview(renderer.isPreview);
@@ -925,9 +984,9 @@ class LinkInterceptor {
925
984
  }
926
985
  delete this.clickEvent;
927
986
  });
928
- this.willVisit = () => {
987
+ this.willVisit = ((_event) => {
929
988
  delete this.clickEvent;
930
- };
989
+ });
931
990
  this.delegate = delegate;
932
991
  this.element = element;
933
992
  }
@@ -942,28 +1001,65 @@ class LinkInterceptor {
942
1001
  document.removeEventListener("turbo:before-visit", this.willVisit);
943
1002
  }
944
1003
  respondsToEventTarget(target) {
945
- const element = target instanceof Element
946
- ? target
947
- : target instanceof Node
948
- ? target.parentElement
949
- : null;
1004
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
950
1005
  return element && element.closest("turbo-frame, html") == this.element;
951
1006
  }
952
1007
  }
953
1008
 
1009
+ class FormLinkInterceptor {
1010
+ constructor(delegate, element) {
1011
+ this.delegate = delegate;
1012
+ this.linkInterceptor = new LinkInterceptor(this, element);
1013
+ }
1014
+ start() {
1015
+ this.linkInterceptor.start();
1016
+ }
1017
+ stop() {
1018
+ this.linkInterceptor.stop();
1019
+ }
1020
+ shouldInterceptLinkClick(link) {
1021
+ return (this.delegate.shouldInterceptFormLinkClick(link) &&
1022
+ (link.hasAttribute("data-turbo-method") || link.hasAttribute("data-turbo-stream")));
1023
+ }
1024
+ linkClickIntercepted(link, action) {
1025
+ const form = document.createElement("form");
1026
+ form.setAttribute("data-turbo", "true");
1027
+ form.setAttribute("action", action);
1028
+ form.setAttribute("hidden", "");
1029
+ const method = link.getAttribute("data-turbo-method");
1030
+ if (method)
1031
+ form.setAttribute("method", method);
1032
+ const turboFrame = link.getAttribute("data-turbo-frame");
1033
+ if (turboFrame)
1034
+ form.setAttribute("data-turbo-frame", turboFrame);
1035
+ const turboConfirm = link.getAttribute("data-turbo-confirm");
1036
+ if (turboConfirm)
1037
+ form.setAttribute("data-turbo-confirm", turboConfirm);
1038
+ const turboStream = link.hasAttribute("data-turbo-stream");
1039
+ if (turboStream)
1040
+ form.setAttribute("data-turbo-stream", "");
1041
+ this.delegate.formLinkClickIntercepted(link, form);
1042
+ document.body.appendChild(form);
1043
+ form.requestSubmit();
1044
+ form.remove();
1045
+ }
1046
+ }
1047
+
954
1048
  class Bardo {
955
- constructor(permanentElementMap) {
1049
+ constructor(delegate, permanentElementMap) {
1050
+ this.delegate = delegate;
956
1051
  this.permanentElementMap = permanentElementMap;
957
1052
  }
958
- static preservingPermanentElements(permanentElementMap, callback) {
959
- const bardo = new this(permanentElementMap);
1053
+ static preservingPermanentElements(delegate, permanentElementMap, callback) {
1054
+ const bardo = new this(delegate, permanentElementMap);
960
1055
  bardo.enter();
961
1056
  callback();
962
1057
  bardo.leave();
963
1058
  }
964
1059
  enter() {
965
1060
  for (const id in this.permanentElementMap) {
966
- const [, newPermanentElement] = this.permanentElementMap[id];
1061
+ const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
1062
+ this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
967
1063
  this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
968
1064
  }
969
1065
  }
@@ -972,6 +1068,7 @@ class Bardo {
972
1068
  const [currentPermanentElement] = this.permanentElementMap[id];
973
1069
  this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
974
1070
  this.replacePlaceholderWithPermanentElement(currentPermanentElement);
1071
+ this.delegate.leavingBardo(currentPermanentElement);
975
1072
  }
976
1073
  }
977
1074
  replaceNewPermanentElementWithPlaceholder(permanentElement) {
@@ -987,7 +1084,7 @@ class Bardo {
987
1084
  placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
988
1085
  }
989
1086
  getPlaceholderById(id) {
990
- return this.placeholders.find(element => element.content == id);
1087
+ return this.placeholders.find((element) => element.content == id);
991
1088
  }
992
1089
  get placeholders() {
993
1090
  return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")];
@@ -1001,16 +1098,21 @@ function createPlaceholderForPermanentElement(permanentElement) {
1001
1098
  }
1002
1099
 
1003
1100
  class Renderer {
1004
- constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
1101
+ constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1102
+ this.activeElement = null;
1005
1103
  this.currentSnapshot = currentSnapshot;
1006
1104
  this.newSnapshot = newSnapshot;
1007
1105
  this.isPreview = isPreview;
1008
1106
  this.willRender = willRender;
1009
- this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
1107
+ this.renderElement = renderElement;
1108
+ this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));
1010
1109
  }
1011
1110
  get shouldRender() {
1012
1111
  return true;
1013
1112
  }
1113
+ get reloadReason() {
1114
+ return;
1115
+ }
1014
1116
  prepareToRender() {
1015
1117
  return;
1016
1118
  }
@@ -1036,7 +1138,7 @@ class Renderer {
1036
1138
  }
1037
1139
  }
1038
1140
  preservingPermanentElements(callback) {
1039
- Bardo.preservingPermanentElements(this.permanentElementMap, callback);
1141
+ Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1040
1142
  }
1041
1143
  focusFirstAutofocusableElement() {
1042
1144
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1044,6 +1146,19 @@ class Renderer {
1044
1146
  element.focus();
1045
1147
  }
1046
1148
  }
1149
+ enteringBardo(currentPermanentElement) {
1150
+ if (this.activeElement)
1151
+ return;
1152
+ if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
1153
+ this.activeElement = this.currentSnapshot.activeElement;
1154
+ }
1155
+ }
1156
+ leavingBardo(currentPermanentElement) {
1157
+ if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
1158
+ this.activeElement.focus();
1159
+ this.activeElement = null;
1160
+ }
1161
+ }
1047
1162
  get connectedSnapshot() {
1048
1163
  return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
1049
1164
  }
@@ -1057,8 +1172,7 @@ class Renderer {
1057
1172
  return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1058
1173
  }
1059
1174
  get cspNonce() {
1060
- var _a;
1061
- return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
1175
+ return getMetaContent("csp-nonce");
1062
1176
  }
1063
1177
  }
1064
1178
  function copyElementAttributes(destinationElement, sourceElement) {
@@ -1071,6 +1185,22 @@ function elementIsFocusable(element) {
1071
1185
  }
1072
1186
 
1073
1187
  class FrameRenderer extends Renderer {
1188
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1189
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1190
+ this.delegate = delegate;
1191
+ }
1192
+ static renderElement(currentElement, newElement) {
1193
+ var _a;
1194
+ const destinationRange = document.createRange();
1195
+ destinationRange.selectNodeContents(currentElement);
1196
+ destinationRange.deleteContents();
1197
+ const frameElement = newElement;
1198
+ const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1199
+ if (sourceRange) {
1200
+ sourceRange.selectNodeContents(frameElement);
1201
+ currentElement.appendChild(sourceRange.extractContents());
1202
+ }
1203
+ }
1074
1204
  get shouldRender() {
1075
1205
  return true;
1076
1206
  }
@@ -1086,23 +1216,16 @@ class FrameRenderer extends Renderer {
1086
1216
  this.activateScriptElements();
1087
1217
  }
1088
1218
  loadFrameElement() {
1089
- var _a;
1090
- const destinationRange = document.createRange();
1091
- destinationRange.selectNodeContents(this.currentElement);
1092
- destinationRange.deleteContents();
1093
- const frameElement = this.newElement;
1094
- const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1095
- if (sourceRange) {
1096
- sourceRange.selectNodeContents(frameElement);
1097
- this.currentElement.appendChild(sourceRange.extractContents());
1098
- }
1219
+ this.delegate.frameExtracted(this.newElement.cloneNode(true));
1220
+ this.renderElement(this.currentElement, this.newElement);
1099
1221
  }
1100
1222
  scrollFrameIntoView() {
1101
1223
  if (this.currentElement.autoscroll || this.newElement.autoscroll) {
1102
1224
  const element = this.currentElement.firstElementChild;
1103
1225
  const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
1226
+ const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
1104
1227
  if (element) {
1105
- element.scrollIntoView({ block });
1228
+ element.scrollIntoView({ block, behavior });
1106
1229
  return true;
1107
1230
  }
1108
1231
  }
@@ -1126,6 +1249,14 @@ function readScrollLogicalPosition(value, defaultValue) {
1126
1249
  return defaultValue;
1127
1250
  }
1128
1251
  }
1252
+ function readScrollBehavior(value, defaultValue) {
1253
+ if (value == "auto" || value == "smooth") {
1254
+ return value;
1255
+ }
1256
+ else {
1257
+ return defaultValue;
1258
+ }
1259
+ }
1129
1260
 
1130
1261
  class ProgressBar {
1131
1262
  constructor() {
@@ -1149,7 +1280,7 @@ class ProgressBar {
1149
1280
  left: 0;
1150
1281
  height: 3px;
1151
1282
  background: #0076ff;
1152
- z-index: 9999;
1283
+ z-index: 2147483647;
1153
1284
  transition:
1154
1285
  width ${ProgressBar.animationDuration}ms ease-out,
1155
1286
  opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
@@ -1208,13 +1339,16 @@ class ProgressBar {
1208
1339
  }
1209
1340
  refresh() {
1210
1341
  requestAnimationFrame(() => {
1211
- this.progressElement.style.width = `${10 + (this.value * 90)}%`;
1342
+ this.progressElement.style.width = `${10 + this.value * 90}%`;
1212
1343
  });
1213
1344
  }
1214
1345
  createStylesheetElement() {
1215
1346
  const element = document.createElement("style");
1216
1347
  element.type = "text/css";
1217
1348
  element.textContent = ProgressBar.defaultCSS;
1349
+ if (this.cspNonce) {
1350
+ element.nonce = this.cspNonce;
1351
+ }
1218
1352
  return element;
1219
1353
  }
1220
1354
  createProgressElement() {
@@ -1222,6 +1356,9 @@ class ProgressBar {
1222
1356
  element.className = "turbo-progress-bar";
1223
1357
  return element;
1224
1358
  }
1359
+ get cspNonce() {
1360
+ return getMetaContent("csp-nonce");
1361
+ }
1225
1362
  }
1226
1363
  ProgressBar.animationDuration = 300;
1227
1364
 
@@ -1238,14 +1375,14 @@ class HeadSnapshot extends Snapshot {
1238
1375
  : {
1239
1376
  type: elementType(element),
1240
1377
  tracked: elementIsTracked(element),
1241
- elements: []
1378
+ elements: [],
1242
1379
  };
1243
1380
  return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });
1244
1381
  }, {});
1245
1382
  }
1246
1383
  get trackedElementSignature() {
1247
1384
  return Object.keys(this.detailsByOuterHTML)
1248
- .filter(outerHTML => this.detailsByOuterHTML[outerHTML].tracked)
1385
+ .filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked)
1249
1386
  .join("");
1250
1387
  }
1251
1388
  getScriptElementsNotInSnapshot(snapshot) {
@@ -1256,8 +1393,8 @@ class HeadSnapshot extends Snapshot {
1256
1393
  }
1257
1394
  getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
1258
1395
  return Object.keys(this.detailsByOuterHTML)
1259
- .filter(outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))
1260
- .map(outerHTML => this.detailsByOuterHTML[outerHTML])
1396
+ .filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML))
1397
+ .map((outerHTML) => this.detailsByOuterHTML[outerHTML])
1261
1398
  .filter(({ type }) => type == matchedType)
1262
1399
  .map(({ elements: [element] }) => element);
1263
1400
  }
@@ -1277,13 +1414,11 @@ class HeadSnapshot extends Snapshot {
1277
1414
  }
1278
1415
  getMetaValue(name) {
1279
1416
  const element = this.findMetaElementByName(name);
1280
- return element
1281
- ? element.getAttribute("content")
1282
- : null;
1417
+ return element ? element.getAttribute("content") : null;
1283
1418
  }
1284
1419
  findMetaElementByName(name) {
1285
1420
  return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
1286
- const { elements: [element] } = this.detailsByOuterHTML[outerHTML];
1421
+ const { elements: [element], } = this.detailsByOuterHTML[outerHTML];
1287
1422
  return elementIsMetaElementWithName(element, name) ? element : result;
1288
1423
  }, undefined);
1289
1424
  }
@@ -1450,9 +1585,11 @@ class Visit {
1450
1585
  if (this.state == VisitState.started) {
1451
1586
  this.recordTimingMetric(TimingMetric.visitEnd);
1452
1587
  this.state = VisitState.completed;
1453
- this.adapter.visitCompleted(this);
1454
- this.delegate.visitCompleted(this);
1455
1588
  this.followRedirect();
1589
+ if (!this.followedRedirect) {
1590
+ this.adapter.visitCompleted(this);
1591
+ this.delegate.visitCompleted(this);
1592
+ }
1456
1593
  }
1457
1594
  }
1458
1595
  fail() {
@@ -1514,12 +1651,12 @@ class Visit {
1514
1651
  if (this.view.renderPromise)
1515
1652
  await this.view.renderPromise;
1516
1653
  if (isSuccessful(statusCode) && responseHTML != null) {
1517
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
1654
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
1518
1655
  this.adapter.visitRendered(this);
1519
1656
  this.complete();
1520
1657
  }
1521
1658
  else {
1522
- await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
1659
+ await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
1523
1660
  this.adapter.visitRendered(this);
1524
1661
  this.fail();
1525
1662
  }
@@ -1554,7 +1691,7 @@ class Visit {
1554
1691
  else {
1555
1692
  if (this.view.renderPromise)
1556
1693
  await this.view.renderPromise;
1557
- await this.view.renderPage(snapshot, isPreview, this.willRender);
1694
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
1558
1695
  this.adapter.visitRendered(this);
1559
1696
  if (!isPreview) {
1560
1697
  this.complete();
@@ -1567,8 +1704,9 @@ class Visit {
1567
1704
  var _a;
1568
1705
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1569
1706
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1570
- action: 'replace',
1571
- response: this.response
1707
+ action: "replace",
1708
+ willRender: false,
1709
+ response: this.response,
1572
1710
  });
1573
1711
  this.followedRedirect = true;
1574
1712
  }
@@ -1584,13 +1722,15 @@ class Visit {
1584
1722
  requestStarted() {
1585
1723
  this.startRequest();
1586
1724
  }
1587
- requestPreventedHandlingResponse(request, response) {
1588
- }
1725
+ requestPreventedHandlingResponse(_request, _response) { }
1589
1726
  async requestSucceededWithResponse(request, response) {
1590
1727
  const responseHTML = await response.responseHTML;
1591
1728
  const { redirected, statusCode } = response;
1592
1729
  if (responseHTML == undefined) {
1593
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1730
+ this.recordResponse({
1731
+ statusCode: SystemStatusCode.contentTypeMismatch,
1732
+ redirected,
1733
+ });
1594
1734
  }
1595
1735
  else {
1596
1736
  this.redirectedToLocation = response.redirected ? response.location : undefined;
@@ -1601,14 +1741,20 @@ class Visit {
1601
1741
  const responseHTML = await response.responseHTML;
1602
1742
  const { redirected, statusCode } = response;
1603
1743
  if (responseHTML == undefined) {
1604
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1744
+ this.recordResponse({
1745
+ statusCode: SystemStatusCode.contentTypeMismatch,
1746
+ redirected,
1747
+ });
1605
1748
  }
1606
1749
  else {
1607
1750
  this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
1608
1751
  }
1609
1752
  }
1610
- requestErrored(request, error) {
1611
- this.recordResponse({ statusCode: SystemStatusCode.networkFailure, redirected: false });
1753
+ requestErrored(_request, _error) {
1754
+ this.recordResponse({
1755
+ statusCode: SystemStatusCode.networkFailure,
1756
+ redirected: false,
1757
+ });
1612
1758
  }
1613
1759
  requestFinished() {
1614
1760
  this.finishRequest();
@@ -1649,9 +1795,11 @@ class Visit {
1649
1795
  }
1650
1796
  getHistoryMethodForAction(action) {
1651
1797
  switch (action) {
1652
- case "replace": return history.replaceState;
1798
+ case "replace":
1799
+ return history.replaceState;
1653
1800
  case "advance":
1654
- case "restore": return history.pushState;
1801
+ case "restore":
1802
+ return history.pushState;
1655
1803
  }
1656
1804
  }
1657
1805
  hasPreloadedResponse() {
@@ -1670,18 +1818,20 @@ class Visit {
1670
1818
  }
1671
1819
  cacheSnapshot() {
1672
1820
  if (!this.snapshotCached) {
1673
- this.view.cacheSnapshot().then(snapshot => snapshot && this.visitCachedSnapshot(snapshot));
1821
+ this.view.cacheSnapshot().then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot));
1674
1822
  this.snapshotCached = true;
1675
1823
  }
1676
1824
  }
1677
1825
  async render(callback) {
1678
1826
  this.cancelRender();
1679
- await new Promise(resolve => {
1827
+ await new Promise((resolve) => {
1680
1828
  this.frame = requestAnimationFrame(() => resolve());
1681
1829
  });
1682
1830
  await callback();
1683
1831
  delete this.frame;
1684
- this.performScroll();
1832
+ if (!this.view.forceReloaded) {
1833
+ this.performScroll();
1834
+ }
1685
1835
  }
1686
1836
  cancelRender() {
1687
1837
  if (this.frame) {
@@ -1696,7 +1846,7 @@ function isSuccessful(statusCode) {
1696
1846
 
1697
1847
  class BrowserAdapter {
1698
1848
  constructor(session) {
1699
- this.progressBar = new ProgressBar;
1849
+ this.progressBar = new ProgressBar();
1700
1850
  this.showProgressBar = () => {
1701
1851
  this.progressBar.show();
1702
1852
  };
@@ -1706,9 +1856,9 @@ class BrowserAdapter {
1706
1856
  this.navigator.startVisit(location, uuid(), options);
1707
1857
  }
1708
1858
  visitStarted(visit) {
1859
+ this.location = visit.location;
1709
1860
  visit.loadCachedSnapshot();
1710
1861
  visit.issueRequest();
1711
- visit.changeHistory();
1712
1862
  visit.goToSamePageAnchor();
1713
1863
  }
1714
1864
  visitRequestStarted(visit) {
@@ -1728,29 +1878,31 @@ class BrowserAdapter {
1728
1878
  case SystemStatusCode.networkFailure:
1729
1879
  case SystemStatusCode.timeoutFailure:
1730
1880
  case SystemStatusCode.contentTypeMismatch:
1731
- return this.reload();
1881
+ return this.reload({
1882
+ reason: "request_failed",
1883
+ context: {
1884
+ statusCode,
1885
+ },
1886
+ });
1732
1887
  default:
1733
1888
  return visit.loadResponse();
1734
1889
  }
1735
1890
  }
1736
- visitRequestFinished(visit) {
1891
+ visitRequestFinished(_visit) {
1737
1892
  this.progressBar.setValue(1);
1738
1893
  this.hideVisitProgressBar();
1739
1894
  }
1740
- visitCompleted(visit) {
1741
- }
1742
- pageInvalidated() {
1743
- this.reload();
1744
- }
1745
- visitFailed(visit) {
1895
+ visitCompleted(_visit) { }
1896
+ pageInvalidated(reason) {
1897
+ this.reload(reason);
1746
1898
  }
1747
- visitRendered(visit) {
1748
- }
1749
- formSubmissionStarted(formSubmission) {
1899
+ visitFailed(_visit) { }
1900
+ visitRendered(_visit) { }
1901
+ formSubmissionStarted(_formSubmission) {
1750
1902
  this.progressBar.setValue(0);
1751
1903
  this.showFormProgressBarAfterDelay();
1752
1904
  }
1753
- formSubmissionFinished(formSubmission) {
1905
+ formSubmissionFinished(_formSubmission) {
1754
1906
  this.progressBar.setValue(1);
1755
1907
  this.hideFormProgressBar();
1756
1908
  }
@@ -1776,8 +1928,11 @@ class BrowserAdapter {
1776
1928
  delete this.formProgressBarTimeout;
1777
1929
  }
1778
1930
  }
1779
- reload() {
1780
- window.location.reload();
1931
+ reload(reason) {
1932
+ dispatch("turbo:reload", { detail: reason });
1933
+ if (!this.location)
1934
+ return;
1935
+ window.location.href = this.location.toString();
1781
1936
  }
1782
1937
  get navigator() {
1783
1938
  return this.session.navigator;
@@ -1787,6 +1942,12 @@ class BrowserAdapter {
1787
1942
  class CacheObserver {
1788
1943
  constructor() {
1789
1944
  this.started = false;
1945
+ this.removeStaleElements = ((_event) => {
1946
+ const staleElements = [...document.querySelectorAll('[data-turbo-cache="false"]')];
1947
+ for (const element of staleElements) {
1948
+ element.remove();
1949
+ }
1950
+ });
1790
1951
  }
1791
1952
  start() {
1792
1953
  if (!this.started) {
@@ -1800,12 +1961,6 @@ class CacheObserver {
1800
1961
  removeEventListener("turbo:before-cache", this.removeStaleElements, false);
1801
1962
  }
1802
1963
  }
1803
- removeStaleElements() {
1804
- const staleElements = [...document.querySelectorAll('[data-turbo-cache="false"]')];
1805
- for (const element of staleElements) {
1806
- element.remove();
1807
- }
1808
- }
1809
1964
  }
1810
1965
 
1811
1966
  class FormSubmitObserver {
@@ -1819,12 +1974,12 @@ class FormSubmitObserver {
1819
1974
  if (!event.defaultPrevented) {
1820
1975
  const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1821
1976
  const submitter = event.submitter || undefined;
1822
- if (form) {
1823
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
1824
- if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
1825
- event.preventDefault();
1826
- this.delegate.formSubmitted(form, submitter);
1827
- }
1977
+ if (form &&
1978
+ submissionDoesNotDismissDialog(form, submitter) &&
1979
+ submissionDoesNotTargetIFrame(form, submitter) &&
1980
+ this.delegate.willSubmitForm(form, submitter)) {
1981
+ event.preventDefault();
1982
+ this.delegate.formSubmitted(form, submitter);
1828
1983
  }
1829
1984
  }
1830
1985
  });
@@ -1843,6 +1998,18 @@ class FormSubmitObserver {
1843
1998
  }
1844
1999
  }
1845
2000
  }
2001
+ function submissionDoesNotDismissDialog(form, submitter) {
2002
+ const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
2003
+ return method != "dialog";
2004
+ }
2005
+ function submissionDoesNotTargetIFrame(form, submitter) {
2006
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
2007
+ for (const element of document.getElementsByName(target)) {
2008
+ if (element instanceof HTMLIFrameElement)
2009
+ return false;
2010
+ }
2011
+ return true;
2012
+ }
1846
2013
 
1847
2014
  class FrameRedirector {
1848
2015
  constructor(element) {
@@ -1858,7 +2025,7 @@ class FrameRedirector {
1858
2025
  this.linkInterceptor.stop();
1859
2026
  this.formInterceptor.stop();
1860
2027
  }
1861
- shouldInterceptLinkClick(element, url) {
2028
+ shouldInterceptLinkClick(element, _url) {
1862
2029
  return this.shouldRedirect(element);
1863
2030
  }
1864
2031
  linkClickIntercepted(element, url) {
@@ -1873,7 +2040,6 @@ class FrameRedirector {
1873
2040
  formSubmissionIntercepted(element, submitter) {
1874
2041
  const frame = this.findFrameElement(element, submitter);
1875
2042
  if (frame) {
1876
- frame.removeAttribute("reloadable");
1877
2043
  frame.delegate.formSubmissionIntercepted(element, submitter);
1878
2044
  }
1879
2045
  }
@@ -1916,7 +2082,7 @@ class History {
1916
2082
  }
1917
2083
  }
1918
2084
  };
1919
- this.onPageLoad = async (event) => {
2085
+ this.onPageLoad = async (_event) => {
1920
2086
  await nextMicrotask();
1921
2087
  this.pageLoaded = true;
1922
2088
  };
@@ -1989,9 +2155,9 @@ class LinkClickObserver {
1989
2155
  if (this.clickEventIsSignificant(event)) {
1990
2156
  const target = (event.composedPath && event.composedPath()[0]) || event.target;
1991
2157
  const link = this.findLinkFromClickTarget(target);
1992
- if (link) {
2158
+ if (link && doesNotTargetIFrame(link)) {
1993
2159
  const location = this.getLocationForLink(link);
1994
- if (this.delegate.willFollowLinkToLocation(link, location)) {
2160
+ if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1995
2161
  event.preventDefault();
1996
2162
  this.delegate.followedLinkToLocation(link, location);
1997
2163
  }
@@ -2013,13 +2179,13 @@ class LinkClickObserver {
2013
2179
  }
2014
2180
  }
2015
2181
  clickEventIsSignificant(event) {
2016
- return !((event.target && event.target.isContentEditable)
2017
- || event.defaultPrevented
2018
- || event.which > 1
2019
- || event.altKey
2020
- || event.ctrlKey
2021
- || event.metaKey
2022
- || event.shiftKey);
2182
+ return !((event.target && event.target.isContentEditable) ||
2183
+ event.defaultPrevented ||
2184
+ event.which > 1 ||
2185
+ event.altKey ||
2186
+ event.ctrlKey ||
2187
+ event.metaKey ||
2188
+ event.shiftKey);
2023
2189
  }
2024
2190
  findLinkFromClickTarget(target) {
2025
2191
  if (target instanceof Element) {
@@ -2030,6 +2196,13 @@ class LinkClickObserver {
2030
2196
  return expandURL(link.getAttribute("href") || "");
2031
2197
  }
2032
2198
  }
2199
+ function doesNotTargetIFrame(anchor) {
2200
+ for (const element of document.getElementsByName(anchor.target)) {
2201
+ if (element instanceof HTMLIFrameElement)
2202
+ return false;
2203
+ }
2204
+ return true;
2205
+ }
2033
2206
 
2034
2207
  function isAction(action) {
2035
2208
  return action == "advance" || action == "replace" || action == "restore";
@@ -2050,6 +2223,7 @@ class Navigator {
2050
2223
  }
2051
2224
  }
2052
2225
  startVisit(locatable, restorationIdentifier, options = {}) {
2226
+ this.lastVisit = this.currentVisit;
2053
2227
  this.stop();
2054
2228
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));
2055
2229
  this.currentVisit.start();
@@ -2079,7 +2253,7 @@ class Navigator {
2079
2253
  return this.delegate.history;
2080
2254
  }
2081
2255
  formSubmissionStarted(formSubmission) {
2082
- if (typeof this.adapter.formSubmissionStarted === 'function') {
2256
+ if (typeof this.adapter.formSubmissionStarted === "function") {
2083
2257
  this.adapter.formSubmissionStarted(formSubmission);
2084
2258
  }
2085
2259
  }
@@ -2092,7 +2266,10 @@ class Navigator {
2092
2266
  }
2093
2267
  const { statusCode, redirected } = fetchResponse;
2094
2268
  const action = this.getActionForFormSubmission(formSubmission);
2095
- const visitOptions = { action, response: { statusCode, responseHTML, redirected } };
2269
+ const visitOptions = {
2270
+ action,
2271
+ response: { statusCode, responseHTML, redirected },
2272
+ };
2096
2273
  this.proposeVisit(fetchResponse.location, visitOptions);
2097
2274
  }
2098
2275
  }
@@ -2102,10 +2279,10 @@ class Navigator {
2102
2279
  if (responseHTML) {
2103
2280
  const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2104
2281
  if (fetchResponse.serverError) {
2105
- await this.view.renderError(snapshot);
2282
+ await this.view.renderError(snapshot, this.currentVisit);
2106
2283
  }
2107
2284
  else {
2108
- await this.view.renderPage(snapshot);
2285
+ await this.view.renderPage(snapshot, false, true, this.currentVisit);
2109
2286
  }
2110
2287
  this.view.scrollToTop();
2111
2288
  this.view.clearSnapshotCache();
@@ -2115,7 +2292,7 @@ class Navigator {
2115
2292
  console.error(error);
2116
2293
  }
2117
2294
  formSubmissionFinished(formSubmission) {
2118
- if (typeof this.adapter.formSubmissionFinished === 'function') {
2295
+ if (typeof this.adapter.formSubmissionFinished === "function") {
2119
2296
  this.adapter.formSubmissionFinished(formSubmission);
2120
2297
  }
2121
2298
  }
@@ -2126,12 +2303,14 @@ class Navigator {
2126
2303
  this.delegate.visitCompleted(visit);
2127
2304
  }
2128
2305
  locationWithActionIsSamePage(location, action) {
2306
+ var _a;
2129
2307
  const anchor = getAnchor(location);
2130
- const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2131
- const isRestorationToTop = action === 'restore' && typeof anchor === 'undefined';
2132
- return action !== "replace" &&
2133
- getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&
2134
- (isRestorationToTop || (anchor != null && anchor !== currentAnchor));
2308
+ const lastLocation = ((_a = this.lastVisit) === null || _a === void 0 ? void 0 : _a.location) || this.view.lastRenderedLocation;
2309
+ const currentAnchor = getAnchor(lastLocation);
2310
+ const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2311
+ return (action !== "replace" &&
2312
+ getRequestURL(location) === getRequestURL(lastLocation) &&
2313
+ (isRestorationToTop || (anchor != null && anchor !== currentAnchor)));
2135
2314
  }
2136
2315
  visitScrolledToSamePageLocation(oldURL, newURL) {
2137
2316
  this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
@@ -2237,7 +2416,7 @@ class ScrollObserver {
2237
2416
 
2238
2417
  class StreamObserver {
2239
2418
  constructor(delegate) {
2240
- this.sources = new Set;
2419
+ this.sources = new Set();
2241
2420
  this.started = false;
2242
2421
  this.inspectFetchResponse = ((event) => {
2243
2422
  const response = fetchResponseFromEvent(event);
@@ -2304,14 +2483,18 @@ function fetchResponseIsStream(response) {
2304
2483
  }
2305
2484
 
2306
2485
  class ErrorRenderer extends Renderer {
2486
+ static renderElement(currentElement, newElement) {
2487
+ const { documentElement, body } = document;
2488
+ documentElement.replaceChild(newElement, body);
2489
+ }
2307
2490
  async render() {
2308
2491
  this.replaceHeadAndBody();
2309
2492
  this.activateScriptElements();
2310
2493
  }
2311
2494
  replaceHeadAndBody() {
2312
- const { documentElement, head, body } = document;
2495
+ const { documentElement, head } = document;
2313
2496
  documentElement.replaceChild(this.newHead, head);
2314
- documentElement.replaceChild(this.newElement, body);
2497
+ this.renderElement(this.currentElement, this.newElement);
2315
2498
  }
2316
2499
  activateScriptElements() {
2317
2500
  for (const replaceableElement of this.scriptElements) {
@@ -2331,9 +2514,29 @@ class ErrorRenderer extends Renderer {
2331
2514
  }
2332
2515
 
2333
2516
  class PageRenderer extends Renderer {
2517
+ static renderElement(currentElement, newElement) {
2518
+ if (document.body && newElement instanceof HTMLBodyElement) {
2519
+ document.body.replaceWith(newElement);
2520
+ }
2521
+ else {
2522
+ document.documentElement.appendChild(newElement);
2523
+ }
2524
+ }
2334
2525
  get shouldRender() {
2335
2526
  return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
2336
2527
  }
2528
+ get reloadReason() {
2529
+ if (!this.newSnapshot.isVisitable) {
2530
+ return {
2531
+ reason: "turbo_visit_control_is_reload",
2532
+ };
2533
+ }
2534
+ if (!this.trackedElementsAreIdentical) {
2535
+ return {
2536
+ reason: "tracked_element_mismatch",
2537
+ };
2538
+ }
2539
+ }
2337
2540
  prepareToRender() {
2338
2541
  this.mergeHead();
2339
2542
  }
@@ -2403,12 +2606,7 @@ class PageRenderer extends Renderer {
2403
2606
  }
2404
2607
  }
2405
2608
  assignNewBody() {
2406
- if (document.body && this.newElement instanceof HTMLBodyElement) {
2407
- document.body.replaceWith(this.newElement);
2408
- }
2409
- else {
2410
- document.documentElement.appendChild(this.newElement);
2411
- }
2609
+ this.renderElement(this.currentElement, this.newElement);
2412
2610
  }
2413
2611
  get newHeadStylesheetElements() {
2414
2612
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -2477,13 +2675,21 @@ class PageView extends View {
2477
2675
  super(...arguments);
2478
2676
  this.snapshotCache = new SnapshotCache(10);
2479
2677
  this.lastRenderedLocation = new URL(location.href);
2678
+ this.forceReloaded = false;
2480
2679
  }
2481
- renderPage(snapshot, isPreview = false, willRender = true) {
2482
- const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
2680
+ renderPage(snapshot, isPreview = false, willRender = true, visit) {
2681
+ const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
2682
+ if (!renderer.shouldRender) {
2683
+ this.forceReloaded = true;
2684
+ }
2685
+ else {
2686
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2687
+ }
2483
2688
  return this.render(renderer);
2484
2689
  }
2485
- renderError(snapshot) {
2486
- const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2690
+ renderError(snapshot, visit) {
2691
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2692
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
2487
2693
  return this.render(renderer);
2488
2694
  }
2489
2695
  clearSnapshotCache() {
@@ -2510,10 +2716,50 @@ class PageView extends View {
2510
2716
  }
2511
2717
  }
2512
2718
 
2719
+ class Preloader {
2720
+ constructor(delegate) {
2721
+ this.selector = "a[data-turbo-preload]";
2722
+ this.delegate = delegate;
2723
+ }
2724
+ get snapshotCache() {
2725
+ return this.delegate.navigator.view.snapshotCache;
2726
+ }
2727
+ start() {
2728
+ if (document.readyState === "loading") {
2729
+ return document.addEventListener("DOMContentLoaded", () => {
2730
+ this.preloadOnLoadLinksForView(document.body);
2731
+ });
2732
+ }
2733
+ else {
2734
+ this.preloadOnLoadLinksForView(document.body);
2735
+ }
2736
+ }
2737
+ preloadOnLoadLinksForView(element) {
2738
+ for (const link of element.querySelectorAll(this.selector)) {
2739
+ this.preloadURL(link);
2740
+ }
2741
+ }
2742
+ async preloadURL(link) {
2743
+ const location = new URL(link.href);
2744
+ if (this.snapshotCache.has(location)) {
2745
+ return;
2746
+ }
2747
+ try {
2748
+ const response = await fetch(location.toString(), { headers: { "VND.PREFETCH": "true", Accept: "text/html" } });
2749
+ const responseText = await response.text();
2750
+ const snapshot = PageSnapshot.fromHTMLString(responseText);
2751
+ this.snapshotCache.put(location, snapshot);
2752
+ }
2753
+ catch (_) {
2754
+ }
2755
+ }
2756
+ }
2757
+
2513
2758
  class Session {
2514
2759
  constructor() {
2515
2760
  this.navigator = new Navigator(this);
2516
2761
  this.history = new History(this);
2762
+ this.preloader = new Preloader(this);
2517
2763
  this.view = new PageView(this, document.documentElement);
2518
2764
  this.adapter = new BrowserAdapter(this);
2519
2765
  this.pageObserver = new PageObserver(this);
@@ -2522,22 +2768,26 @@ class Session {
2522
2768
  this.formSubmitObserver = new FormSubmitObserver(this);
2523
2769
  this.scrollObserver = new ScrollObserver(this);
2524
2770
  this.streamObserver = new StreamObserver(this);
2771
+ this.formLinkInterceptor = new FormLinkInterceptor(this, document.documentElement);
2525
2772
  this.frameRedirector = new FrameRedirector(document.documentElement);
2526
2773
  this.drive = true;
2527
2774
  this.enabled = true;
2528
2775
  this.progressBarDelay = 500;
2529
2776
  this.started = false;
2777
+ this.formMode = "on";
2530
2778
  }
2531
2779
  start() {
2532
2780
  if (!this.started) {
2533
2781
  this.pageObserver.start();
2534
2782
  this.cacheObserver.start();
2783
+ this.formLinkInterceptor.start();
2535
2784
  this.linkClickObserver.start();
2536
2785
  this.formSubmitObserver.start();
2537
2786
  this.scrollObserver.start();
2538
2787
  this.streamObserver.start();
2539
2788
  this.frameRedirector.start();
2540
2789
  this.history.start();
2790
+ this.preloader.start();
2541
2791
  this.started = true;
2542
2792
  this.enabled = true;
2543
2793
  }
@@ -2549,6 +2799,7 @@ class Session {
2549
2799
  if (this.started) {
2550
2800
  this.pageObserver.stop();
2551
2801
  this.cacheObserver.stop();
2802
+ this.formLinkInterceptor.stop();
2552
2803
  this.linkClickObserver.stop();
2553
2804
  this.formSubmitObserver.stop();
2554
2805
  this.scrollObserver.stop();
@@ -2579,6 +2830,9 @@ class Session {
2579
2830
  setProgressBarDelay(delay) {
2580
2831
  this.progressBarDelay = delay;
2581
2832
  }
2833
+ setFormMode(mode) {
2834
+ this.formMode = mode;
2835
+ }
2582
2836
  get location() {
2583
2837
  return this.history.location;
2584
2838
  }
@@ -2587,48 +2841,32 @@ class Session {
2587
2841
  }
2588
2842
  historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
2589
2843
  if (this.enabled) {
2590
- this.navigator.startVisit(location, restorationIdentifier, { action: "restore", historyChanged: true });
2844
+ this.navigator.startVisit(location, restorationIdentifier, {
2845
+ action: "restore",
2846
+ historyChanged: true,
2847
+ });
2591
2848
  }
2592
2849
  else {
2593
- this.adapter.pageInvalidated();
2850
+ this.adapter.pageInvalidated({
2851
+ reason: "turbo_disabled",
2852
+ });
2594
2853
  }
2595
2854
  }
2596
2855
  scrollPositionChanged(position) {
2597
2856
  this.history.updateRestorationData({ scrollPosition: position });
2598
2857
  }
2599
- willFollowLinkToLocation(link, location) {
2600
- return this.elementDriveEnabled(link)
2601
- && locationIsVisitable(location, this.snapshot.rootLocation)
2602
- && this.applicationAllowsFollowingLinkToLocation(link, location);
2858
+ shouldInterceptFormLinkClick(_link) {
2859
+ return true;
2860
+ }
2861
+ formLinkClickIntercepted(_link, _form) { }
2862
+ willFollowLinkToLocation(link, location, event) {
2863
+ return (this.elementDriveEnabled(link) &&
2864
+ locationIsVisitable(location, this.snapshot.rootLocation) &&
2865
+ this.applicationAllowsFollowingLinkToLocation(link, location, event));
2603
2866
  }
2604
2867
  followedLinkToLocation(link, location) {
2605
2868
  const action = this.getActionForLink(link);
2606
- this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, { action });
2607
- }
2608
- convertLinkWithMethodClickToFormSubmission(link) {
2609
- const linkMethod = link.getAttribute("data-turbo-method");
2610
- if (linkMethod) {
2611
- const form = document.createElement("form");
2612
- form.method = linkMethod;
2613
- form.action = link.getAttribute("href") || "undefined";
2614
- form.hidden = true;
2615
- if (link.hasAttribute("data-turbo-confirm")) {
2616
- form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
2617
- }
2618
- const frame = this.getTargetFrameForLink(link);
2619
- if (frame) {
2620
- form.setAttribute("data-turbo-frame", frame);
2621
- form.addEventListener("turbo:submit-start", () => form.remove());
2622
- }
2623
- else {
2624
- form.addEventListener("submit", () => form.remove());
2625
- }
2626
- document.body.appendChild(form);
2627
- return dispatch("submit", { cancelable: true, target: form });
2628
- }
2629
- else {
2630
- return false;
2631
- }
2869
+ this.visit(location.href, { action });
2632
2870
  }
2633
2871
  allowsVisitingLocationWithAction(location, action) {
2634
2872
  return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
@@ -2654,9 +2892,9 @@ class Session {
2654
2892
  }
2655
2893
  willSubmitForm(form, submitter) {
2656
2894
  const action = getAction(form, submitter);
2657
- return this.elementDriveEnabled(form)
2658
- && (!submitter || this.elementDriveEnabled(submitter))
2659
- && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
2895
+ return (this.elementDriveEnabled(form) &&
2896
+ (!submitter || this.formElementDriveEnabled(submitter)) &&
2897
+ locationIsVisitable(expandURL(action), this.snapshot.rootLocation));
2660
2898
  }
2661
2899
  formSubmitted(form, submitter) {
2662
2900
  this.navigator.submitForm(form, submitter);
@@ -2680,16 +2918,23 @@ class Session {
2680
2918
  this.notifyApplicationBeforeCachingSnapshot();
2681
2919
  }
2682
2920
  }
2683
- allowsImmediateRender({ element }, resume) {
2684
- const event = this.notifyApplicationBeforeRender(element, resume);
2685
- return !event.defaultPrevented;
2921
+ allowsImmediateRender({ element }, options) {
2922
+ const event = this.notifyApplicationBeforeRender(element, options);
2923
+ const { defaultPrevented, detail: { render }, } = event;
2924
+ if (this.view.renderer && render) {
2925
+ this.view.renderer.renderElement = render;
2926
+ }
2927
+ return !defaultPrevented;
2686
2928
  }
2687
- viewRenderedSnapshot(snapshot, isPreview) {
2929
+ viewRenderedSnapshot(_snapshot, _isPreview) {
2688
2930
  this.view.lastRenderedLocation = this.history.location;
2689
2931
  this.notifyApplicationAfterRender();
2690
2932
  }
2691
- viewInvalidated() {
2692
- this.adapter.pageInvalidated();
2933
+ preloadOnLoadLinksForView(element) {
2934
+ this.preloader.preloadOnLoadLinksForView(element);
2935
+ }
2936
+ viewInvalidated(reason) {
2937
+ this.adapter.pageInvalidated(reason);
2693
2938
  }
2694
2939
  frameLoaded(frame) {
2695
2940
  this.notifyApplicationAfterFrameLoad(frame);
@@ -2697,19 +2942,26 @@ class Session {
2697
2942
  frameRendered(fetchResponse, frame) {
2698
2943
  this.notifyApplicationAfterFrameRender(fetchResponse, frame);
2699
2944
  }
2700
- applicationAllowsFollowingLinkToLocation(link, location) {
2701
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
2945
+ applicationAllowsFollowingLinkToLocation(link, location, ev) {
2946
+ const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
2702
2947
  return !event.defaultPrevented;
2703
2948
  }
2704
2949
  applicationAllowsVisitingLocation(location) {
2705
2950
  const event = this.notifyApplicationBeforeVisitingLocation(location);
2706
2951
  return !event.defaultPrevented;
2707
2952
  }
2708
- notifyApplicationAfterClickingLinkToLocation(link, location) {
2709
- return dispatch("turbo:click", { target: link, detail: { url: location.href }, cancelable: true });
2953
+ notifyApplicationAfterClickingLinkToLocation(link, location, event) {
2954
+ return dispatch("turbo:click", {
2955
+ target: link,
2956
+ detail: { url: location.href, originalEvent: event },
2957
+ cancelable: true,
2958
+ });
2710
2959
  }
2711
2960
  notifyApplicationBeforeVisitingLocation(location) {
2712
- return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
2961
+ return dispatch("turbo:before-visit", {
2962
+ detail: { url: location.href },
2963
+ cancelable: true,
2964
+ });
2713
2965
  }
2714
2966
  notifyApplicationAfterVisitingLocation(location, action) {
2715
2967
  markAsBusy(document.documentElement);
@@ -2718,24 +2970,46 @@ class Session {
2718
2970
  notifyApplicationBeforeCachingSnapshot() {
2719
2971
  return dispatch("turbo:before-cache");
2720
2972
  }
2721
- notifyApplicationBeforeRender(newBody, resume) {
2722
- return dispatch("turbo:before-render", { detail: { newBody, resume }, cancelable: true });
2973
+ notifyApplicationBeforeRender(newBody, options) {
2974
+ return dispatch("turbo:before-render", {
2975
+ detail: Object.assign({ newBody }, options),
2976
+ cancelable: true,
2977
+ });
2723
2978
  }
2724
2979
  notifyApplicationAfterRender() {
2725
2980
  return dispatch("turbo:render");
2726
2981
  }
2727
2982
  notifyApplicationAfterPageLoad(timing = {}) {
2728
2983
  clearBusyState(document.documentElement);
2729
- return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
2984
+ return dispatch("turbo:load", {
2985
+ detail: { url: this.location.href, timing },
2986
+ });
2730
2987
  }
2731
2988
  notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
2732
- dispatchEvent(new HashChangeEvent("hashchange", { oldURL: oldURL.toString(), newURL: newURL.toString() }));
2989
+ dispatchEvent(new HashChangeEvent("hashchange", {
2990
+ oldURL: oldURL.toString(),
2991
+ newURL: newURL.toString(),
2992
+ }));
2733
2993
  }
2734
2994
  notifyApplicationAfterFrameLoad(frame) {
2735
2995
  return dispatch("turbo:frame-load", { target: frame });
2736
2996
  }
2737
2997
  notifyApplicationAfterFrameRender(fetchResponse, frame) {
2738
- return dispatch("turbo:frame-render", { detail: { fetchResponse }, target: frame, cancelable: true });
2998
+ return dispatch("turbo:frame-render", {
2999
+ detail: { fetchResponse },
3000
+ target: frame,
3001
+ cancelable: true,
3002
+ });
3003
+ }
3004
+ formElementDriveEnabled(element) {
3005
+ if (this.formMode == "off") {
3006
+ return false;
3007
+ }
3008
+ if (this.formMode == "optin") {
3009
+ const form = element === null || element === void 0 ? void 0 : element.closest("form[data-turbo]");
3010
+ return (form === null || form === void 0 ? void 0 : form.getAttribute("data-turbo")) == "true";
3011
+ }
3012
+ return this.elementDriveEnabled(element);
2739
3013
  }
2740
3014
  elementDriveEnabled(element) {
2741
3015
  const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
@@ -2760,18 +3034,6 @@ class Session {
2760
3034
  const action = link.getAttribute("data-turbo-action");
2761
3035
  return isAction(action) ? action : "advance";
2762
3036
  }
2763
- getTargetFrameForLink(link) {
2764
- const frame = link.getAttribute("data-turbo-frame");
2765
- if (frame) {
2766
- return frame;
2767
- }
2768
- else {
2769
- const container = link.closest("turbo-frame");
2770
- if (container) {
2771
- return container.id;
2772
- }
2773
- }
2774
- }
2775
3037
  get snapshot() {
2776
3038
  return this.view.snapshot;
2777
3039
  }
@@ -2783,11 +3045,59 @@ const deprecatedLocationPropertyDescriptors = {
2783
3045
  absoluteURL: {
2784
3046
  get() {
2785
3047
  return this.toString();
2786
- }
3048
+ },
3049
+ },
3050
+ };
3051
+
3052
+ class Cache {
3053
+ constructor(session) {
3054
+ this.session = session;
2787
3055
  }
3056
+ clear() {
3057
+ this.session.clearCache();
3058
+ }
3059
+ resetCacheControl() {
3060
+ this.setCacheControl("");
3061
+ }
3062
+ exemptPageFromCache() {
3063
+ this.setCacheControl("no-cache");
3064
+ }
3065
+ exemptPageFromPreview() {
3066
+ this.setCacheControl("no-preview");
3067
+ }
3068
+ setCacheControl(value) {
3069
+ setMetaContent("turbo-cache-control", value);
3070
+ }
3071
+ }
3072
+
3073
+ const StreamActions = {
3074
+ after() {
3075
+ this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });
3076
+ },
3077
+ append() {
3078
+ this.removeDuplicateTargetChildren();
3079
+ this.targetElements.forEach((e) => e.append(this.templateContent));
3080
+ },
3081
+ before() {
3082
+ this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });
3083
+ },
3084
+ prepend() {
3085
+ this.removeDuplicateTargetChildren();
3086
+ this.targetElements.forEach((e) => e.prepend(this.templateContent));
3087
+ },
3088
+ remove() {
3089
+ this.targetElements.forEach((e) => e.remove());
3090
+ },
3091
+ replace() {
3092
+ this.targetElements.forEach((e) => e.replaceWith(this.templateContent));
3093
+ },
3094
+ update() {
3095
+ this.targetElements.forEach((e) => e.replaceChildren(this.templateContent));
3096
+ },
2788
3097
  };
2789
3098
 
2790
- const session = new Session;
3099
+ const session = new Session();
3100
+ const cache = new Cache(session);
2791
3101
  const { navigator: navigator$1 } = session;
2792
3102
  function start() {
2793
3103
  session.start();
@@ -2808,6 +3118,7 @@ function renderStreamMessage(message) {
2808
3118
  session.renderStreamMessage(message);
2809
3119
  }
2810
3120
  function clearCache() {
3121
+ console.warn("Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
2811
3122
  session.clearCache();
2812
3123
  }
2813
3124
  function setProgressBarDelay(delay) {
@@ -2816,13 +3127,18 @@ function setProgressBarDelay(delay) {
2816
3127
  function setConfirmMethod(confirmMethod) {
2817
3128
  FormSubmission.confirmMethod = confirmMethod;
2818
3129
  }
3130
+ function setFormMode(mode) {
3131
+ session.setFormMode(mode);
3132
+ }
2819
3133
 
2820
3134
  var Turbo = /*#__PURE__*/Object.freeze({
2821
3135
  __proto__: null,
2822
3136
  navigator: navigator$1,
2823
3137
  session: session,
3138
+ cache: cache,
2824
3139
  PageRenderer: PageRenderer,
2825
3140
  PageSnapshot: PageSnapshot,
3141
+ FrameRenderer: FrameRenderer,
2826
3142
  start: start,
2827
3143
  registerAdapter: registerAdapter,
2828
3144
  visit: visit,
@@ -2831,39 +3147,52 @@ var Turbo = /*#__PURE__*/Object.freeze({
2831
3147
  renderStreamMessage: renderStreamMessage,
2832
3148
  clearCache: clearCache,
2833
3149
  setProgressBarDelay: setProgressBarDelay,
2834
- setConfirmMethod: setConfirmMethod
3150
+ setConfirmMethod: setConfirmMethod,
3151
+ setFormMode: setFormMode,
3152
+ StreamActions: StreamActions
2835
3153
  });
2836
3154
 
2837
3155
  class FrameController {
2838
3156
  constructor(element) {
2839
- this.fetchResponseLoaded = (fetchResponse) => { };
3157
+ this.fetchResponseLoaded = (_fetchResponse) => { };
2840
3158
  this.currentFetchRequest = null;
2841
3159
  this.resolveVisitPromise = () => { };
2842
3160
  this.connected = false;
2843
3161
  this.hasBeenLoaded = false;
2844
- this.settingSourceURL = false;
3162
+ this.ignoredAttributes = new Set();
3163
+ this.visitCachedSnapshot = ({ element }) => {
3164
+ const frame = element.querySelector("#" + this.element.id);
3165
+ if (frame && this.previousFrameElement) {
3166
+ frame.replaceChildren(...this.previousFrameElement.children);
3167
+ }
3168
+ delete this.previousFrameElement;
3169
+ };
2845
3170
  this.element = element;
2846
3171
  this.view = new FrameView(this, this.element);
2847
3172
  this.appearanceObserver = new AppearanceObserver(this, this.element);
3173
+ this.formLinkInterceptor = new FormLinkInterceptor(this, this.element);
2848
3174
  this.linkInterceptor = new LinkInterceptor(this, this.element);
2849
3175
  this.formInterceptor = new FormInterceptor(this, this.element);
2850
3176
  }
2851
3177
  connect() {
2852
3178
  if (!this.connected) {
2853
3179
  this.connected = true;
2854
- this.reloadable = false;
2855
3180
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2856
3181
  this.appearanceObserver.start();
2857
3182
  }
3183
+ else {
3184
+ this.loadSourceURL();
3185
+ }
3186
+ this.formLinkInterceptor.start();
2858
3187
  this.linkInterceptor.start();
2859
3188
  this.formInterceptor.start();
2860
- this.sourceURLChanged();
2861
3189
  }
2862
3190
  }
2863
3191
  disconnect() {
2864
3192
  if (this.connected) {
2865
3193
  this.connected = false;
2866
3194
  this.appearanceObserver.stop();
3195
+ this.formLinkInterceptor.stop();
2867
3196
  this.linkInterceptor.stop();
2868
3197
  this.formInterceptor.stop();
2869
3198
  }
@@ -2874,10 +3203,20 @@ class FrameController {
2874
3203
  }
2875
3204
  }
2876
3205
  sourceURLChanged() {
3206
+ if (this.isIgnoringChangesTo("src"))
3207
+ return;
3208
+ if (this.element.isConnected) {
3209
+ this.complete = false;
3210
+ }
2877
3211
  if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
2878
3212
  this.loadSourceURL();
2879
3213
  }
2880
3214
  }
3215
+ completeChanged() {
3216
+ if (this.isIgnoringChangesTo("complete"))
3217
+ return;
3218
+ this.loadSourceURL();
3219
+ }
2881
3220
  loadingStyleChanged() {
2882
3221
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2883
3222
  this.appearanceObserver.start();
@@ -2888,21 +3227,11 @@ class FrameController {
2888
3227
  }
2889
3228
  }
2890
3229
  async loadSourceURL() {
2891
- if (!this.settingSourceURL && this.enabled && this.isActive && (this.reloadable || this.sourceURL != this.currentURL)) {
2892
- const previousURL = this.currentURL;
2893
- this.currentURL = this.sourceURL;
2894
- if (this.sourceURL) {
2895
- try {
2896
- this.element.loaded = this.visit(expandURL(this.sourceURL));
2897
- this.appearanceObserver.stop();
2898
- await this.element.loaded;
2899
- this.hasBeenLoaded = true;
2900
- }
2901
- catch (error) {
2902
- this.currentURL = previousURL;
2903
- throw error;
2904
- }
2905
- }
3230
+ if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
3231
+ this.element.loaded = this.visit(expandURL(this.sourceURL));
3232
+ this.appearanceObserver.stop();
3233
+ await this.element.loaded;
3234
+ this.hasBeenLoaded = true;
2906
3235
  }
2907
3236
  }
2908
3237
  async loadResponse(fetchResponse) {
@@ -2914,10 +3243,11 @@ class FrameController {
2914
3243
  if (html) {
2915
3244
  const { body } = parseHTMLDocument(html);
2916
3245
  const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
2917
- const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
3246
+ const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
2918
3247
  if (this.view.renderPromise)
2919
3248
  await this.view.renderPromise;
2920
3249
  await this.view.render(renderer);
3250
+ this.complete = true;
2921
3251
  session.frameRendered(fetchResponse, this.element);
2922
3252
  session.frameLoaded(this.element);
2923
3253
  this.fetchResponseLoaded(fetchResponse);
@@ -2931,19 +3261,21 @@ class FrameController {
2931
3261
  this.fetchResponseLoaded = () => { };
2932
3262
  }
2933
3263
  }
2934
- elementAppearedInViewport(element) {
3264
+ elementAppearedInViewport(_element) {
2935
3265
  this.loadSourceURL();
2936
3266
  }
2937
- shouldInterceptLinkClick(element, url) {
2938
- if (element.hasAttribute("data-turbo-method")) {
2939
- return false;
2940
- }
2941
- else {
2942
- return this.shouldInterceptNavigation(element);
2943
- }
3267
+ shouldInterceptFormLinkClick(link) {
3268
+ return this.shouldInterceptNavigation(link);
3269
+ }
3270
+ formLinkClickIntercepted(link, form) {
3271
+ const frame = this.findFrameElement(link);
3272
+ if (frame)
3273
+ form.setAttribute("data-turbo-frame", frame.id);
3274
+ }
3275
+ shouldInterceptLinkClick(element, _url) {
3276
+ return this.shouldInterceptNavigation(element);
2944
3277
  }
2945
3278
  linkClickIntercepted(element, url) {
2946
- this.reloadable = true;
2947
3279
  this.navigateFrame(element, url);
2948
3280
  }
2949
3281
  shouldInterceptFormSubmission(element, submitter) {
@@ -2953,19 +3285,18 @@ class FrameController {
2953
3285
  if (this.formSubmission) {
2954
3286
  this.formSubmission.stop();
2955
3287
  }
2956
- this.reloadable = false;
2957
3288
  this.formSubmission = new FormSubmission(this, element, submitter);
2958
3289
  const { fetchRequest } = this.formSubmission;
2959
3290
  this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
2960
3291
  this.formSubmission.start();
2961
3292
  }
2962
- prepareHeadersForRequest(headers, request) {
3293
+ prepareHeadersForRequest(headers, _request) {
2963
3294
  headers["Turbo-Frame"] = this.id;
2964
3295
  }
2965
- requestStarted(request) {
3296
+ requestStarted(_request) {
2966
3297
  markAsBusy(this.element);
2967
3298
  }
2968
- requestPreventedHandlingResponse(request, response) {
3299
+ requestPreventedHandlingResponse(_request, _response) {
2969
3300
  this.resolveVisitPromise();
2970
3301
  }
2971
3302
  async requestSucceededWithResponse(request, response) {
@@ -2980,7 +3311,7 @@ class FrameController {
2980
3311
  console.error(error);
2981
3312
  this.resolveVisitPromise();
2982
3313
  }
2983
- requestFinished(request) {
3314
+ requestFinished(_request) {
2984
3315
  clearBusyState(this.element);
2985
3316
  }
2986
3317
  formSubmissionStarted({ formElement }) {
@@ -3000,19 +3331,32 @@ class FrameController {
3000
3331
  formSubmissionFinished({ formElement }) {
3001
3332
  clearBusyState(formElement, this.findFrameElement(formElement));
3002
3333
  }
3003
- allowsImmediateRender(snapshot, resume) {
3004
- return true;
3334
+ allowsImmediateRender({ element: newFrame }, options) {
3335
+ const event = dispatch("turbo:before-frame-render", {
3336
+ target: this.element,
3337
+ detail: Object.assign({ newFrame }, options),
3338
+ cancelable: true,
3339
+ });
3340
+ const { defaultPrevented, detail: { render }, } = event;
3341
+ if (this.view.renderer && render) {
3342
+ this.view.renderer.renderElement = render;
3343
+ }
3344
+ return !defaultPrevented;
3005
3345
  }
3006
- viewRenderedSnapshot(snapshot, isPreview) {
3346
+ viewRenderedSnapshot(_snapshot, _isPreview) { }
3347
+ preloadOnLoadLinksForView(element) {
3348
+ session.preloadOnLoadLinksForView(element);
3007
3349
  }
3008
- viewInvalidated() {
3350
+ viewInvalidated() { }
3351
+ frameExtracted(element) {
3352
+ this.previousFrameElement = element;
3009
3353
  }
3010
3354
  async visit(url) {
3011
3355
  var _a;
3012
- const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
3356
+ const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);
3013
3357
  (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
3014
3358
  this.currentFetchRequest = request;
3015
- return new Promise(resolve => {
3359
+ return new Promise((resolve) => {
3016
3360
  this.resolveVisitPromise = () => {
3017
3361
  this.resolveVisitPromise = () => { };
3018
3362
  this.currentFetchRequest = null;
@@ -3024,19 +3368,23 @@ class FrameController {
3024
3368
  navigateFrame(element, url, submitter) {
3025
3369
  const frame = this.findFrameElement(element, submitter);
3026
3370
  this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3027
- frame.setAttribute("reloadable", "");
3028
3371
  frame.src = url;
3029
3372
  }
3030
3373
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3031
3374
  const action = getAttribute("data-turbo-action", submitter, element, frame);
3032
3375
  if (isAction(action)) {
3033
- const { visitCachedSnapshot } = new SnapshotSubstitution(frame);
3376
+ const { visitCachedSnapshot } = frame.delegate;
3034
3377
  frame.delegate.fetchResponseLoaded = (fetchResponse) => {
3035
3378
  if (frame.src) {
3036
3379
  const { statusCode, redirected } = fetchResponse;
3037
3380
  const responseHTML = frame.ownerDocument.documentElement.outerHTML;
3038
3381
  const response = { statusCode, redirected, responseHTML };
3039
- session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false });
3382
+ session.visit(frame.src, {
3383
+ action,
3384
+ response,
3385
+ visitCachedSnapshot,
3386
+ willRender: false,
3387
+ });
3040
3388
  }
3041
3389
  };
3042
3390
  }
@@ -3050,10 +3398,12 @@ class FrameController {
3050
3398
  let element;
3051
3399
  const id = CSS.escape(this.id);
3052
3400
  try {
3053
- if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
3401
+ element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
3402
+ if (element) {
3054
3403
  return element;
3055
3404
  }
3056
- if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
3405
+ element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);
3406
+ if (element) {
3057
3407
  await element.loaded;
3058
3408
  return await this.extractForeignFrameElement(element);
3059
3409
  }
@@ -3101,24 +3451,10 @@ class FrameController {
3101
3451
  return this.element.src;
3102
3452
  }
3103
3453
  }
3104
- get reloadable() {
3105
- const frame = this.findFrameElement(this.element);
3106
- return frame.hasAttribute("reloadable");
3107
- }
3108
- set reloadable(value) {
3109
- const frame = this.findFrameElement(this.element);
3110
- if (value) {
3111
- frame.setAttribute("reloadable", "");
3112
- }
3113
- else {
3114
- frame.removeAttribute("reloadable");
3115
- }
3116
- }
3117
3454
  set sourceURL(sourceURL) {
3118
- this.settingSourceURL = true;
3119
- this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3120
- this.currentURL = this.element.src;
3121
- this.settingSourceURL = false;
3455
+ this.ignoringChangesToAttribute("src", () => {
3456
+ this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3457
+ });
3122
3458
  }
3123
3459
  get loadingStyle() {
3124
3460
  return this.element.loading;
@@ -3126,6 +3462,19 @@ class FrameController {
3126
3462
  get isLoading() {
3127
3463
  return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
3128
3464
  }
3465
+ get complete() {
3466
+ return this.element.hasAttribute("complete");
3467
+ }
3468
+ set complete(value) {
3469
+ this.ignoringChangesToAttribute("complete", () => {
3470
+ if (value) {
3471
+ this.element.setAttribute("complete", "");
3472
+ }
3473
+ else {
3474
+ this.element.removeAttribute("complete");
3475
+ }
3476
+ });
3477
+ }
3129
3478
  get isActive() {
3130
3479
  return this.element.isActive && this.connected;
3131
3480
  }
@@ -3135,16 +3484,13 @@ class FrameController {
3135
3484
  const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3136
3485
  return expandURL(root);
3137
3486
  }
3138
- }
3139
- class SnapshotSubstitution {
3140
- constructor(element) {
3141
- this.visitCachedSnapshot = ({ element }) => {
3142
- var _a;
3143
- const { id, clone } = this;
3144
- (_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
3145
- };
3146
- this.clone = element.cloneNode(true);
3147
- this.id = element.id;
3487
+ isIgnoringChangesTo(attributeName) {
3488
+ return this.ignoredAttributes.has(attributeName);
3489
+ }
3490
+ ignoringChangesToAttribute(attributeName, callback) {
3491
+ this.ignoredAttributes.add(attributeName);
3492
+ callback();
3493
+ this.ignoredAttributes.delete(attributeName);
3148
3494
  }
3149
3495
  }
3150
3496
  function getFrameElementById(id) {
@@ -3172,35 +3518,6 @@ function activateElement(element, currentURL) {
3172
3518
  }
3173
3519
  }
3174
3520
 
3175
- const StreamActions = {
3176
- after() {
3177
- this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });
3178
- },
3179
- append() {
3180
- this.removeDuplicateTargetChildren();
3181
- this.targetElements.forEach(e => e.append(this.templateContent));
3182
- },
3183
- before() {
3184
- this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });
3185
- },
3186
- prepend() {
3187
- this.removeDuplicateTargetChildren();
3188
- this.targetElements.forEach(e => e.prepend(this.templateContent));
3189
- },
3190
- remove() {
3191
- this.targetElements.forEach(e => e.remove());
3192
- },
3193
- replace() {
3194
- this.targetElements.forEach(e => e.replaceWith(this.templateContent));
3195
- },
3196
- update() {
3197
- this.targetElements.forEach(e => {
3198
- e.innerHTML = "";
3199
- e.append(this.templateContent);
3200
- });
3201
- }
3202
- };
3203
-
3204
3521
  class StreamElement extends HTMLElement {
3205
3522
  async connectedCallback() {
3206
3523
  try {
@@ -3215,12 +3532,12 @@ class StreamElement extends HTMLElement {
3215
3532
  }
3216
3533
  async render() {
3217
3534
  var _a;
3218
- return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
3535
+ return ((_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
3219
3536
  if (this.dispatchEvent(this.beforeRenderEvent)) {
3220
3537
  await nextAnimationFrame();
3221
3538
  this.performAction();
3222
3539
  }
3223
- })());
3540
+ })()));
3224
3541
  }
3225
3542
  disconnect() {
3226
3543
  try {
@@ -3229,13 +3546,13 @@ class StreamElement extends HTMLElement {
3229
3546
  catch (_a) { }
3230
3547
  }
3231
3548
  removeDuplicateTargetChildren() {
3232
- this.duplicateChildren.forEach(c => c.remove());
3549
+ this.duplicateChildren.forEach((c) => c.remove());
3233
3550
  }
3234
3551
  get duplicateChildren() {
3235
3552
  var _a;
3236
- const existingChildren = this.targetElements.flatMap(e => [...e.children]).filter(c => !!c.id);
3237
- const newChildrenIds = [...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children].filter(c => !!c.id).map(c => c.id);
3238
- return existingChildren.filter(c => newChildrenIds.includes(c.id));
3553
+ const existingChildren = this.targetElements.flatMap((e) => [...e.children]).filter((c) => !!c.id);
3554
+ const newChildrenIds = [...(((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [])].filter((c) => !!c.id).map((c) => c.id);
3555
+ return existingChildren.filter((c) => newChildrenIds.includes(c.id));
3239
3556
  }
3240
3557
  get performAction() {
3241
3558
  if (this.action) {
@@ -3284,7 +3601,10 @@ class StreamElement extends HTMLElement {
3284
3601
  return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
3285
3602
  }
3286
3603
  get beforeRenderEvent() {
3287
- return new CustomEvent("turbo:before-stream-render", { bubbles: true, cancelable: true });
3604
+ return new CustomEvent("turbo:before-stream-render", {
3605
+ bubbles: true,
3606
+ cancelable: true,
3607
+ });
3288
3608
  }
3289
3609
  get targetElementsById() {
3290
3610
  var _a;
@@ -3308,9 +3628,35 @@ class StreamElement extends HTMLElement {
3308
3628
  }
3309
3629
  }
3310
3630
 
3631
+ class StreamSourceElement extends HTMLElement {
3632
+ constructor() {
3633
+ super(...arguments);
3634
+ this.streamSource = null;
3635
+ }
3636
+ connectedCallback() {
3637
+ this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
3638
+ connectStreamSource(this.streamSource);
3639
+ }
3640
+ disconnectedCallback() {
3641
+ if (this.streamSource) {
3642
+ disconnectStreamSource(this.streamSource);
3643
+ }
3644
+ }
3645
+ get src() {
3646
+ return this.getAttribute("src") || "";
3647
+ }
3648
+ }
3649
+
3311
3650
  FrameElement.delegateConstructor = FrameController;
3312
- customElements.define("turbo-frame", FrameElement);
3313
- customElements.define("turbo-stream", StreamElement);
3651
+ if (customElements.get("turbo-frame") === undefined) {
3652
+ customElements.define("turbo-frame", FrameElement);
3653
+ }
3654
+ if (customElements.get("turbo-stream") === undefined) {
3655
+ customElements.define("turbo-stream", StreamElement);
3656
+ }
3657
+ if (customElements.get("turbo-stream-source") === undefined) {
3658
+ customElements.define("turbo-stream-source", StreamSourceElement);
3659
+ }
3314
3660
 
3315
3661
  (() => {
3316
3662
  let element = document.currentScript;
@@ -3318,7 +3664,8 @@ customElements.define("turbo-stream", StreamElement);
3318
3664
  return;
3319
3665
  if (element.hasAttribute("data-turbo-suppress-warning"))
3320
3666
  return;
3321
- while (element = element.parentElement) {
3667
+ element = element.parentElement;
3668
+ while (element) {
3322
3669
  if (element == document.body) {
3323
3670
  return console.warn(unindent `
3324
3671
  You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
@@ -3331,10 +3678,11 @@ customElements.define("turbo-stream", StreamElement);
3331
3678
  Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
3332
3679
  `, element.outerHTML);
3333
3680
  }
3681
+ element = element.parentElement;
3334
3682
  }
3335
3683
  })();
3336
3684
 
3337
3685
  window.Turbo = Turbo;
3338
3686
  start();
3339
3687
 
3340
- export { PageRenderer, PageSnapshot, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setProgressBarDelay, start, visit };
3688
+ export { FrameRenderer, PageRenderer, PageSnapshot, StreamActions, cache, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setFormMode, setProgressBarDelay, start, visit };