@hotwired/turbo 7.1.0-rc.2 → 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 +730 -391
  2. package/dist/turbo.es2017-umd.js +735 -392
  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 +13 -4
  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 -79
  40. package/dist/types/tests/functional/frame_navigation_tests.d.ts +1 -7
  41. package/dist/types/tests/functional/frame_tests.d.ts +1 -51
  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 -37
  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 -13
  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-rc.1
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,27 +417,27 @@ 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;
407
- if (this.isIdempotent) {
408
- this.url = mergeFormDataEntries(location, [...body.entries()]);
409
- }
410
- else {
411
- this.body = body;
412
- this.url = location;
413
- }
439
+ this.body = body;
440
+ this.url = location;
414
441
  this.target = target;
415
442
  }
416
443
  get location() {
@@ -436,7 +463,7 @@ class FetchRequest {
436
463
  return await this.receive(response);
437
464
  }
438
465
  catch (error) {
439
- if (error.name !== 'AbortError') {
466
+ if (error.name !== "AbortError") {
440
467
  this.delegate.requestErrored(this, error);
441
468
  throw error;
442
469
  }
@@ -447,7 +474,11 @@ class FetchRequest {
447
474
  }
448
475
  async receive(response) {
449
476
  const fetchResponse = new FetchResponse(response);
450
- 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
+ });
451
482
  if (event.defaultPrevented) {
452
483
  this.delegate.requestPreventedHandlingResponse(this, fetchResponse);
453
484
  }
@@ -466,14 +497,14 @@ class FetchRequest {
466
497
  credentials: "same-origin",
467
498
  headers: this.headers,
468
499
  redirect: "follow",
469
- body: this.body,
500
+ body: this.isIdempotent ? null : this.body,
470
501
  signal: this.abortSignal,
471
- 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,
472
503
  };
473
504
  }
474
505
  get defaultHeaders() {
475
506
  return {
476
- "Accept": "text/html, application/xhtml+xml"
507
+ Accept: "text/html, application/xhtml+xml",
477
508
  };
478
509
  }
479
510
  get isIdempotent() {
@@ -483,40 +514,25 @@ class FetchRequest {
483
514
  return this.abortController.signal;
484
515
  }
485
516
  async allowRequestToBeIntercepted(fetchOptions) {
486
- const requestInterception = new Promise(resolve => this.resolveRequestPromise = resolve);
517
+ const requestInterception = new Promise((resolve) => (this.resolveRequestPromise = resolve));
487
518
  const event = dispatch("turbo:before-fetch-request", {
488
519
  cancelable: true,
489
520
  detail: {
490
521
  fetchOptions,
491
- url: this.url.href,
492
- resume: this.resolveRequestPromise
522
+ url: this.url,
523
+ resume: this.resolveRequestPromise,
493
524
  },
494
- target: this.target
525
+ target: this.target,
495
526
  });
496
527
  if (event.defaultPrevented)
497
528
  await requestInterception;
498
529
  }
499
530
  }
500
- function mergeFormDataEntries(url, entries) {
501
- const currentSearchParams = new URLSearchParams(url.search);
502
- for (const [name, value] of entries) {
503
- if (value instanceof File)
504
- continue;
505
- if (currentSearchParams.has(name)) {
506
- currentSearchParams.delete(name);
507
- url.searchParams.set(name, value);
508
- }
509
- else {
510
- url.searchParams.append(name, value);
511
- }
512
- }
513
- return url;
514
- }
515
531
 
516
532
  class AppearanceObserver {
517
533
  constructor(delegate, element) {
518
534
  this.started = false;
519
- this.intersect = entries => {
535
+ this.intersect = (entries) => {
520
536
  const lastEntry = entries.slice(-1)[0];
521
537
  if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
522
538
  this.delegate.elementAppearedInViewport(this.element);
@@ -593,9 +609,12 @@ var FormEnctype;
593
609
  })(FormEnctype || (FormEnctype = {}));
594
610
  function formEnctypeFromString(encoding) {
595
611
  switch (encoding.toLowerCase()) {
596
- case FormEnctype.multipart: return FormEnctype.multipart;
597
- case FormEnctype.plain: return FormEnctype.plain;
598
- 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;
599
618
  }
600
619
  }
601
620
  class FormSubmission {
@@ -605,11 +624,15 @@ class FormSubmission {
605
624
  this.formElement = formElement;
606
625
  this.submitter = submitter;
607
626
  this.formData = buildFormData(formElement, submitter);
627
+ this.location = expandURL(this.action);
628
+ if (this.method == FetchMethod.get) {
629
+ mergeFormDataEntries(this.location, [...this.body.entries()]);
630
+ }
608
631
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
609
632
  this.mustRedirect = mustRedirect;
610
633
  }
611
- static confirmMethod(message, element) {
612
- return confirm(message);
634
+ static confirmMethod(message, _element) {
635
+ return Promise.resolve(confirm(message));
613
636
  }
614
637
  get method() {
615
638
  var _a;
@@ -618,11 +641,13 @@ class FormSubmission {
618
641
  }
619
642
  get action() {
620
643
  var _a;
621
- const formElementAction = typeof this.formElement.action === 'string' ? this.formElement.action : null;
622
- return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.getAttribute("action") || formElementAction || "";
623
- }
624
- get location() {
625
- return expandURL(this.action);
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
+ }
626
651
  }
627
652
  get body() {
628
653
  if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
@@ -645,7 +670,8 @@ class FormSubmission {
645
670
  }, []);
646
671
  }
647
672
  get confirmationMessage() {
648
- 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");
649
675
  }
650
676
  get needsConfirmation() {
651
677
  return this.confirmationMessage !== null;
@@ -653,7 +679,7 @@ class FormSubmission {
653
679
  async start() {
654
680
  const { initialized, requesting } = FormSubmissionState;
655
681
  if (this.needsConfirmation) {
656
- const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
682
+ const answer = await FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
657
683
  if (!answer) {
658
684
  return;
659
685
  }
@@ -677,14 +703,19 @@ class FormSubmission {
677
703
  if (token) {
678
704
  headers["X-CSRF-Token"] = token;
679
705
  }
706
+ }
707
+ if (this.requestAcceptsTurboStreamResponse(request)) {
680
708
  headers["Accept"] = [StreamMessage.contentType, headers["Accept"]].join(", ");
681
709
  }
682
710
  }
683
- requestStarted(request) {
711
+ requestStarted(_request) {
684
712
  var _a;
685
713
  this.state = FormSubmissionState.waiting;
686
714
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
687
- dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } });
715
+ dispatch("turbo:submit-start", {
716
+ target: this.formElement,
717
+ detail: { formSubmission: this },
718
+ });
688
719
  this.delegate.formSubmissionStarted(this);
689
720
  }
690
721
  requestPreventedHandlingResponse(request, response) {
@@ -712,16 +743,22 @@ class FormSubmission {
712
743
  this.result = { success: false, error };
713
744
  this.delegate.formSubmissionErrored(this, error);
714
745
  }
715
- requestFinished(request) {
746
+ requestFinished(_request) {
716
747
  var _a;
717
748
  this.state = FormSubmissionState.stopped;
718
749
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
719
- 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
+ });
720
754
  this.delegate.formSubmissionFinished(this);
721
755
  }
722
756
  requestMustRedirect(request) {
723
757
  return !request.isIdempotent && this.mustRedirect;
724
758
  }
759
+ requestAcceptsTurboStreamResponse(request) {
760
+ return !request.isIdempotent || this.formElement.hasAttribute("data-turbo-stream");
761
+ }
725
762
  }
726
763
  function buildFormData(formElement, submitter) {
727
764
  const formData = new FormData(formElement);
@@ -742,18 +779,27 @@ function getCookieValue(cookieName) {
742
779
  }
743
780
  }
744
781
  }
745
- function getMetaContent(name) {
746
- const element = document.querySelector(`meta[name="${name}"]`);
747
- return element && element.content;
748
- }
749
782
  function responseSucceededWithoutRedirect(response) {
750
783
  return response.statusCode == 200 && !response.redirected;
751
784
  }
785
+ function mergeFormDataEntries(url, entries) {
786
+ const searchParams = new URLSearchParams();
787
+ for (const [name, value] of entries) {
788
+ if (value instanceof File)
789
+ continue;
790
+ searchParams.append(name, value);
791
+ }
792
+ url.search = searchParams.toString();
793
+ return url;
794
+ }
752
795
 
753
796
  class Snapshot {
754
797
  constructor(element) {
755
798
  this.element = element;
756
799
  }
800
+ get activeElement() {
801
+ return this.element.ownerDocument.activeElement;
802
+ }
757
803
  get children() {
758
804
  return [...this.element.children];
759
805
  }
@@ -792,7 +838,9 @@ class FormInterceptor {
792
838
  constructor(delegate, element) {
793
839
  this.submitBubbled = ((event) => {
794
840
  const form = event.target;
795
- if (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) {
796
844
  const submitter = event.submitter || undefined;
797
845
  const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
798
846
  if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
@@ -815,8 +863,8 @@ class FormInterceptor {
815
863
 
816
864
  class View {
817
865
  constructor(delegate, element) {
818
- this.resolveRenderPromise = (value) => { };
819
- this.resolveInterceptionPromise = (value) => { };
866
+ this.resolveRenderPromise = (_value) => { };
867
+ this.resolveInterceptionPromise = (_value) => { };
820
868
  this.delegate = delegate;
821
869
  this.element = element;
822
870
  }
@@ -861,15 +909,17 @@ class View {
861
909
  const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
862
910
  if (shouldRender) {
863
911
  try {
864
- this.renderPromise = new Promise(resolve => this.resolveRenderPromise = resolve);
912
+ this.renderPromise = new Promise((resolve) => (this.resolveRenderPromise = resolve));
865
913
  this.renderer = renderer;
866
914
  this.prepareToRenderSnapshot(renderer);
867
- const renderInterception = new Promise(resolve => this.resolveInterceptionPromise = resolve);
868
- 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);
869
918
  if (!immediateRender)
870
919
  await renderInterception;
871
920
  await this.renderSnapshot(renderer);
872
921
  this.delegate.viewRenderedSnapshot(snapshot, isPreview);
922
+ this.delegate.preloadOnLoadLinksForView(this.element);
873
923
  this.finishRenderingSnapshot(renderer);
874
924
  }
875
925
  finally {
@@ -879,11 +929,11 @@ class View {
879
929
  }
880
930
  }
881
931
  else {
882
- this.invalidate();
932
+ this.invalidate(renderer.reloadReason);
883
933
  }
884
934
  }
885
- invalidate() {
886
- this.delegate.viewInvalidated();
935
+ invalidate(reason) {
936
+ this.delegate.viewInvalidated(reason);
887
937
  }
888
938
  prepareToRenderSnapshot(renderer) {
889
939
  this.markAsPreview(renderer.isPreview);
@@ -934,9 +984,9 @@ class LinkInterceptor {
934
984
  }
935
985
  delete this.clickEvent;
936
986
  });
937
- this.willVisit = () => {
987
+ this.willVisit = ((_event) => {
938
988
  delete this.clickEvent;
939
- };
989
+ });
940
990
  this.delegate = delegate;
941
991
  this.element = element;
942
992
  }
@@ -951,28 +1001,65 @@ class LinkInterceptor {
951
1001
  document.removeEventListener("turbo:before-visit", this.willVisit);
952
1002
  }
953
1003
  respondsToEventTarget(target) {
954
- const element = target instanceof Element
955
- ? target
956
- : target instanceof Node
957
- ? target.parentElement
958
- : null;
1004
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
959
1005
  return element && element.closest("turbo-frame, html") == this.element;
960
1006
  }
961
1007
  }
962
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
+
963
1048
  class Bardo {
964
- constructor(permanentElementMap) {
1049
+ constructor(delegate, permanentElementMap) {
1050
+ this.delegate = delegate;
965
1051
  this.permanentElementMap = permanentElementMap;
966
1052
  }
967
- static preservingPermanentElements(permanentElementMap, callback) {
968
- const bardo = new this(permanentElementMap);
1053
+ static preservingPermanentElements(delegate, permanentElementMap, callback) {
1054
+ const bardo = new this(delegate, permanentElementMap);
969
1055
  bardo.enter();
970
1056
  callback();
971
1057
  bardo.leave();
972
1058
  }
973
1059
  enter() {
974
1060
  for (const id in this.permanentElementMap) {
975
- const [, newPermanentElement] = this.permanentElementMap[id];
1061
+ const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
1062
+ this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
976
1063
  this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
977
1064
  }
978
1065
  }
@@ -981,6 +1068,7 @@ class Bardo {
981
1068
  const [currentPermanentElement] = this.permanentElementMap[id];
982
1069
  this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
983
1070
  this.replacePlaceholderWithPermanentElement(currentPermanentElement);
1071
+ this.delegate.leavingBardo(currentPermanentElement);
984
1072
  }
985
1073
  }
986
1074
  replaceNewPermanentElementWithPlaceholder(permanentElement) {
@@ -996,7 +1084,7 @@ class Bardo {
996
1084
  placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
997
1085
  }
998
1086
  getPlaceholderById(id) {
999
- return this.placeholders.find(element => element.content == id);
1087
+ return this.placeholders.find((element) => element.content == id);
1000
1088
  }
1001
1089
  get placeholders() {
1002
1090
  return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")];
@@ -1010,16 +1098,21 @@ function createPlaceholderForPermanentElement(permanentElement) {
1010
1098
  }
1011
1099
 
1012
1100
  class Renderer {
1013
- constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
1101
+ constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1102
+ this.activeElement = null;
1014
1103
  this.currentSnapshot = currentSnapshot;
1015
1104
  this.newSnapshot = newSnapshot;
1016
1105
  this.isPreview = isPreview;
1017
1106
  this.willRender = willRender;
1018
- 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 }));
1019
1109
  }
1020
1110
  get shouldRender() {
1021
1111
  return true;
1022
1112
  }
1113
+ get reloadReason() {
1114
+ return;
1115
+ }
1023
1116
  prepareToRender() {
1024
1117
  return;
1025
1118
  }
@@ -1045,7 +1138,7 @@ class Renderer {
1045
1138
  }
1046
1139
  }
1047
1140
  preservingPermanentElements(callback) {
1048
- Bardo.preservingPermanentElements(this.permanentElementMap, callback);
1141
+ Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1049
1142
  }
1050
1143
  focusFirstAutofocusableElement() {
1051
1144
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1053,6 +1146,19 @@ class Renderer {
1053
1146
  element.focus();
1054
1147
  }
1055
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
+ }
1056
1162
  get connectedSnapshot() {
1057
1163
  return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
1058
1164
  }
@@ -1066,8 +1172,7 @@ class Renderer {
1066
1172
  return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1067
1173
  }
1068
1174
  get cspNonce() {
1069
- var _a;
1070
- return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
1175
+ return getMetaContent("csp-nonce");
1071
1176
  }
1072
1177
  }
1073
1178
  function copyElementAttributes(destinationElement, sourceElement) {
@@ -1080,6 +1185,22 @@ function elementIsFocusable(element) {
1080
1185
  }
1081
1186
 
1082
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
+ }
1083
1204
  get shouldRender() {
1084
1205
  return true;
1085
1206
  }
@@ -1095,23 +1216,16 @@ class FrameRenderer extends Renderer {
1095
1216
  this.activateScriptElements();
1096
1217
  }
1097
1218
  loadFrameElement() {
1098
- var _a;
1099
- const destinationRange = document.createRange();
1100
- destinationRange.selectNodeContents(this.currentElement);
1101
- destinationRange.deleteContents();
1102
- const frameElement = this.newElement;
1103
- const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1104
- if (sourceRange) {
1105
- sourceRange.selectNodeContents(frameElement);
1106
- this.currentElement.appendChild(sourceRange.extractContents());
1107
- }
1219
+ this.delegate.frameExtracted(this.newElement.cloneNode(true));
1220
+ this.renderElement(this.currentElement, this.newElement);
1108
1221
  }
1109
1222
  scrollFrameIntoView() {
1110
1223
  if (this.currentElement.autoscroll || this.newElement.autoscroll) {
1111
1224
  const element = this.currentElement.firstElementChild;
1112
1225
  const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
1226
+ const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
1113
1227
  if (element) {
1114
- element.scrollIntoView({ block });
1228
+ element.scrollIntoView({ block, behavior });
1115
1229
  return true;
1116
1230
  }
1117
1231
  }
@@ -1135,6 +1249,14 @@ function readScrollLogicalPosition(value, defaultValue) {
1135
1249
  return defaultValue;
1136
1250
  }
1137
1251
  }
1252
+ function readScrollBehavior(value, defaultValue) {
1253
+ if (value == "auto" || value == "smooth") {
1254
+ return value;
1255
+ }
1256
+ else {
1257
+ return defaultValue;
1258
+ }
1259
+ }
1138
1260
 
1139
1261
  class ProgressBar {
1140
1262
  constructor() {
@@ -1158,7 +1280,7 @@ class ProgressBar {
1158
1280
  left: 0;
1159
1281
  height: 3px;
1160
1282
  background: #0076ff;
1161
- z-index: 9999;
1283
+ z-index: 2147483647;
1162
1284
  transition:
1163
1285
  width ${ProgressBar.animationDuration}ms ease-out,
1164
1286
  opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
@@ -1217,13 +1339,16 @@ class ProgressBar {
1217
1339
  }
1218
1340
  refresh() {
1219
1341
  requestAnimationFrame(() => {
1220
- this.progressElement.style.width = `${10 + (this.value * 90)}%`;
1342
+ this.progressElement.style.width = `${10 + this.value * 90}%`;
1221
1343
  });
1222
1344
  }
1223
1345
  createStylesheetElement() {
1224
1346
  const element = document.createElement("style");
1225
1347
  element.type = "text/css";
1226
1348
  element.textContent = ProgressBar.defaultCSS;
1349
+ if (this.cspNonce) {
1350
+ element.nonce = this.cspNonce;
1351
+ }
1227
1352
  return element;
1228
1353
  }
1229
1354
  createProgressElement() {
@@ -1231,6 +1356,9 @@ class ProgressBar {
1231
1356
  element.className = "turbo-progress-bar";
1232
1357
  return element;
1233
1358
  }
1359
+ get cspNonce() {
1360
+ return getMetaContent("csp-nonce");
1361
+ }
1234
1362
  }
1235
1363
  ProgressBar.animationDuration = 300;
1236
1364
 
@@ -1247,14 +1375,14 @@ class HeadSnapshot extends Snapshot {
1247
1375
  : {
1248
1376
  type: elementType(element),
1249
1377
  tracked: elementIsTracked(element),
1250
- elements: []
1378
+ elements: [],
1251
1379
  };
1252
1380
  return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });
1253
1381
  }, {});
1254
1382
  }
1255
1383
  get trackedElementSignature() {
1256
1384
  return Object.keys(this.detailsByOuterHTML)
1257
- .filter(outerHTML => this.detailsByOuterHTML[outerHTML].tracked)
1385
+ .filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked)
1258
1386
  .join("");
1259
1387
  }
1260
1388
  getScriptElementsNotInSnapshot(snapshot) {
@@ -1265,8 +1393,8 @@ class HeadSnapshot extends Snapshot {
1265
1393
  }
1266
1394
  getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
1267
1395
  return Object.keys(this.detailsByOuterHTML)
1268
- .filter(outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))
1269
- .map(outerHTML => this.detailsByOuterHTML[outerHTML])
1396
+ .filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML))
1397
+ .map((outerHTML) => this.detailsByOuterHTML[outerHTML])
1270
1398
  .filter(({ type }) => type == matchedType)
1271
1399
  .map(({ elements: [element] }) => element);
1272
1400
  }
@@ -1286,13 +1414,11 @@ class HeadSnapshot extends Snapshot {
1286
1414
  }
1287
1415
  getMetaValue(name) {
1288
1416
  const element = this.findMetaElementByName(name);
1289
- return element
1290
- ? element.getAttribute("content")
1291
- : null;
1417
+ return element ? element.getAttribute("content") : null;
1292
1418
  }
1293
1419
  findMetaElementByName(name) {
1294
1420
  return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
1295
- const { elements: [element] } = this.detailsByOuterHTML[outerHTML];
1421
+ const { elements: [element], } = this.detailsByOuterHTML[outerHTML];
1296
1422
  return elementIsMetaElementWithName(element, name) ? element : result;
1297
1423
  }, undefined);
1298
1424
  }
@@ -1459,9 +1585,11 @@ class Visit {
1459
1585
  if (this.state == VisitState.started) {
1460
1586
  this.recordTimingMetric(TimingMetric.visitEnd);
1461
1587
  this.state = VisitState.completed;
1462
- this.adapter.visitCompleted(this);
1463
- this.delegate.visitCompleted(this);
1464
1588
  this.followRedirect();
1589
+ if (!this.followedRedirect) {
1590
+ this.adapter.visitCompleted(this);
1591
+ this.delegate.visitCompleted(this);
1592
+ }
1465
1593
  }
1466
1594
  }
1467
1595
  fail() {
@@ -1523,12 +1651,12 @@ class Visit {
1523
1651
  if (this.view.renderPromise)
1524
1652
  await this.view.renderPromise;
1525
1653
  if (isSuccessful(statusCode) && responseHTML != null) {
1526
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
1654
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
1527
1655
  this.adapter.visitRendered(this);
1528
1656
  this.complete();
1529
1657
  }
1530
1658
  else {
1531
- await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
1659
+ await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
1532
1660
  this.adapter.visitRendered(this);
1533
1661
  this.fail();
1534
1662
  }
@@ -1563,7 +1691,7 @@ class Visit {
1563
1691
  else {
1564
1692
  if (this.view.renderPromise)
1565
1693
  await this.view.renderPromise;
1566
- await this.view.renderPage(snapshot, isPreview, this.willRender);
1694
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
1567
1695
  this.adapter.visitRendered(this);
1568
1696
  if (!isPreview) {
1569
1697
  this.complete();
@@ -1576,8 +1704,9 @@ class Visit {
1576
1704
  var _a;
1577
1705
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1578
1706
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1579
- action: 'replace',
1580
- response: this.response
1707
+ action: "replace",
1708
+ willRender: false,
1709
+ response: this.response,
1581
1710
  });
1582
1711
  this.followedRedirect = true;
1583
1712
  }
@@ -1593,13 +1722,15 @@ class Visit {
1593
1722
  requestStarted() {
1594
1723
  this.startRequest();
1595
1724
  }
1596
- requestPreventedHandlingResponse(request, response) {
1597
- }
1725
+ requestPreventedHandlingResponse(_request, _response) { }
1598
1726
  async requestSucceededWithResponse(request, response) {
1599
1727
  const responseHTML = await response.responseHTML;
1600
1728
  const { redirected, statusCode } = response;
1601
1729
  if (responseHTML == undefined) {
1602
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1730
+ this.recordResponse({
1731
+ statusCode: SystemStatusCode.contentTypeMismatch,
1732
+ redirected,
1733
+ });
1603
1734
  }
1604
1735
  else {
1605
1736
  this.redirectedToLocation = response.redirected ? response.location : undefined;
@@ -1610,14 +1741,20 @@ class Visit {
1610
1741
  const responseHTML = await response.responseHTML;
1611
1742
  const { redirected, statusCode } = response;
1612
1743
  if (responseHTML == undefined) {
1613
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1744
+ this.recordResponse({
1745
+ statusCode: SystemStatusCode.contentTypeMismatch,
1746
+ redirected,
1747
+ });
1614
1748
  }
1615
1749
  else {
1616
1750
  this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
1617
1751
  }
1618
1752
  }
1619
- requestErrored(request, error) {
1620
- this.recordResponse({ statusCode: SystemStatusCode.networkFailure, redirected: false });
1753
+ requestErrored(_request, _error) {
1754
+ this.recordResponse({
1755
+ statusCode: SystemStatusCode.networkFailure,
1756
+ redirected: false,
1757
+ });
1621
1758
  }
1622
1759
  requestFinished() {
1623
1760
  this.finishRequest();
@@ -1658,9 +1795,11 @@ class Visit {
1658
1795
  }
1659
1796
  getHistoryMethodForAction(action) {
1660
1797
  switch (action) {
1661
- case "replace": return history.replaceState;
1798
+ case "replace":
1799
+ return history.replaceState;
1662
1800
  case "advance":
1663
- case "restore": return history.pushState;
1801
+ case "restore":
1802
+ return history.pushState;
1664
1803
  }
1665
1804
  }
1666
1805
  hasPreloadedResponse() {
@@ -1679,18 +1818,20 @@ class Visit {
1679
1818
  }
1680
1819
  cacheSnapshot() {
1681
1820
  if (!this.snapshotCached) {
1682
- this.view.cacheSnapshot().then(snapshot => snapshot && this.visitCachedSnapshot(snapshot));
1821
+ this.view.cacheSnapshot().then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot));
1683
1822
  this.snapshotCached = true;
1684
1823
  }
1685
1824
  }
1686
1825
  async render(callback) {
1687
1826
  this.cancelRender();
1688
- await new Promise(resolve => {
1827
+ await new Promise((resolve) => {
1689
1828
  this.frame = requestAnimationFrame(() => resolve());
1690
1829
  });
1691
1830
  await callback();
1692
1831
  delete this.frame;
1693
- this.performScroll();
1832
+ if (!this.view.forceReloaded) {
1833
+ this.performScroll();
1834
+ }
1694
1835
  }
1695
1836
  cancelRender() {
1696
1837
  if (this.frame) {
@@ -1705,7 +1846,7 @@ function isSuccessful(statusCode) {
1705
1846
 
1706
1847
  class BrowserAdapter {
1707
1848
  constructor(session) {
1708
- this.progressBar = new ProgressBar;
1849
+ this.progressBar = new ProgressBar();
1709
1850
  this.showProgressBar = () => {
1710
1851
  this.progressBar.show();
1711
1852
  };
@@ -1715,10 +1856,10 @@ class BrowserAdapter {
1715
1856
  this.navigator.startVisit(location, uuid(), options);
1716
1857
  }
1717
1858
  visitStarted(visit) {
1859
+ this.location = visit.location;
1860
+ visit.loadCachedSnapshot();
1718
1861
  visit.issueRequest();
1719
- visit.changeHistory();
1720
1862
  visit.goToSamePageAnchor();
1721
- visit.loadCachedSnapshot();
1722
1863
  }
1723
1864
  visitRequestStarted(visit) {
1724
1865
  this.progressBar.setValue(0);
@@ -1737,29 +1878,31 @@ class BrowserAdapter {
1737
1878
  case SystemStatusCode.networkFailure:
1738
1879
  case SystemStatusCode.timeoutFailure:
1739
1880
  case SystemStatusCode.contentTypeMismatch:
1740
- return this.reload();
1881
+ return this.reload({
1882
+ reason: "request_failed",
1883
+ context: {
1884
+ statusCode,
1885
+ },
1886
+ });
1741
1887
  default:
1742
1888
  return visit.loadResponse();
1743
1889
  }
1744
1890
  }
1745
- visitRequestFinished(visit) {
1891
+ visitRequestFinished(_visit) {
1746
1892
  this.progressBar.setValue(1);
1747
1893
  this.hideVisitProgressBar();
1748
1894
  }
1749
- visitCompleted(visit) {
1750
- }
1751
- pageInvalidated() {
1752
- this.reload();
1895
+ visitCompleted(_visit) { }
1896
+ pageInvalidated(reason) {
1897
+ this.reload(reason);
1753
1898
  }
1754
- visitFailed(visit) {
1755
- }
1756
- visitRendered(visit) {
1757
- }
1758
- formSubmissionStarted(formSubmission) {
1899
+ visitFailed(_visit) { }
1900
+ visitRendered(_visit) { }
1901
+ formSubmissionStarted(_formSubmission) {
1759
1902
  this.progressBar.setValue(0);
1760
1903
  this.showFormProgressBarAfterDelay();
1761
1904
  }
1762
- formSubmissionFinished(formSubmission) {
1905
+ formSubmissionFinished(_formSubmission) {
1763
1906
  this.progressBar.setValue(1);
1764
1907
  this.hideFormProgressBar();
1765
1908
  }
@@ -1785,8 +1928,11 @@ class BrowserAdapter {
1785
1928
  delete this.formProgressBarTimeout;
1786
1929
  }
1787
1930
  }
1788
- reload() {
1789
- 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();
1790
1936
  }
1791
1937
  get navigator() {
1792
1938
  return this.session.navigator;
@@ -1796,6 +1942,12 @@ class BrowserAdapter {
1796
1942
  class CacheObserver {
1797
1943
  constructor() {
1798
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
+ });
1799
1951
  }
1800
1952
  start() {
1801
1953
  if (!this.started) {
@@ -1809,12 +1961,6 @@ class CacheObserver {
1809
1961
  removeEventListener("turbo:before-cache", this.removeStaleElements, false);
1810
1962
  }
1811
1963
  }
1812
- removeStaleElements() {
1813
- const staleElements = [...document.querySelectorAll('[data-turbo-cache="false"]')];
1814
- for (const element of staleElements) {
1815
- element.remove();
1816
- }
1817
- }
1818
1964
  }
1819
1965
 
1820
1966
  class FormSubmitObserver {
@@ -1828,12 +1974,12 @@ class FormSubmitObserver {
1828
1974
  if (!event.defaultPrevented) {
1829
1975
  const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1830
1976
  const submitter = event.submitter || undefined;
1831
- if (form) {
1832
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
1833
- if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
1834
- event.preventDefault();
1835
- this.delegate.formSubmitted(form, submitter);
1836
- }
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);
1837
1983
  }
1838
1984
  }
1839
1985
  });
@@ -1852,6 +1998,18 @@ class FormSubmitObserver {
1852
1998
  }
1853
1999
  }
1854
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
+ }
1855
2013
 
1856
2014
  class FrameRedirector {
1857
2015
  constructor(element) {
@@ -1867,7 +2025,7 @@ class FrameRedirector {
1867
2025
  this.linkInterceptor.stop();
1868
2026
  this.formInterceptor.stop();
1869
2027
  }
1870
- shouldInterceptLinkClick(element, url) {
2028
+ shouldInterceptLinkClick(element, _url) {
1871
2029
  return this.shouldRedirect(element);
1872
2030
  }
1873
2031
  linkClickIntercepted(element, url) {
@@ -1882,7 +2040,6 @@ class FrameRedirector {
1882
2040
  formSubmissionIntercepted(element, submitter) {
1883
2041
  const frame = this.findFrameElement(element, submitter);
1884
2042
  if (frame) {
1885
- frame.removeAttribute("reloadable");
1886
2043
  frame.delegate.formSubmissionIntercepted(element, submitter);
1887
2044
  }
1888
2045
  }
@@ -1925,7 +2082,7 @@ class History {
1925
2082
  }
1926
2083
  }
1927
2084
  };
1928
- this.onPageLoad = async (event) => {
2085
+ this.onPageLoad = async (_event) => {
1929
2086
  await nextMicrotask();
1930
2087
  this.pageLoaded = true;
1931
2088
  };
@@ -1998,9 +2155,9 @@ class LinkClickObserver {
1998
2155
  if (this.clickEventIsSignificant(event)) {
1999
2156
  const target = (event.composedPath && event.composedPath()[0]) || event.target;
2000
2157
  const link = this.findLinkFromClickTarget(target);
2001
- if (link) {
2158
+ if (link && doesNotTargetIFrame(link)) {
2002
2159
  const location = this.getLocationForLink(link);
2003
- if (this.delegate.willFollowLinkToLocation(link, location)) {
2160
+ if (this.delegate.willFollowLinkToLocation(link, location, event)) {
2004
2161
  event.preventDefault();
2005
2162
  this.delegate.followedLinkToLocation(link, location);
2006
2163
  }
@@ -2022,13 +2179,13 @@ class LinkClickObserver {
2022
2179
  }
2023
2180
  }
2024
2181
  clickEventIsSignificant(event) {
2025
- return !((event.target && event.target.isContentEditable)
2026
- || event.defaultPrevented
2027
- || event.which > 1
2028
- || event.altKey
2029
- || event.ctrlKey
2030
- || event.metaKey
2031
- || 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);
2032
2189
  }
2033
2190
  findLinkFromClickTarget(target) {
2034
2191
  if (target instanceof Element) {
@@ -2039,6 +2196,13 @@ class LinkClickObserver {
2039
2196
  return expandURL(link.getAttribute("href") || "");
2040
2197
  }
2041
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
+ }
2042
2206
 
2043
2207
  function isAction(action) {
2044
2208
  return action == "advance" || action == "replace" || action == "restore";
@@ -2059,6 +2223,7 @@ class Navigator {
2059
2223
  }
2060
2224
  }
2061
2225
  startVisit(locatable, restorationIdentifier, options = {}) {
2226
+ this.lastVisit = this.currentVisit;
2062
2227
  this.stop();
2063
2228
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));
2064
2229
  this.currentVisit.start();
@@ -2088,7 +2253,7 @@ class Navigator {
2088
2253
  return this.delegate.history;
2089
2254
  }
2090
2255
  formSubmissionStarted(formSubmission) {
2091
- if (typeof this.adapter.formSubmissionStarted === 'function') {
2256
+ if (typeof this.adapter.formSubmissionStarted === "function") {
2092
2257
  this.adapter.formSubmissionStarted(formSubmission);
2093
2258
  }
2094
2259
  }
@@ -2101,7 +2266,10 @@ class Navigator {
2101
2266
  }
2102
2267
  const { statusCode, redirected } = fetchResponse;
2103
2268
  const action = this.getActionForFormSubmission(formSubmission);
2104
- const visitOptions = { action, response: { statusCode, responseHTML, redirected } };
2269
+ const visitOptions = {
2270
+ action,
2271
+ response: { statusCode, responseHTML, redirected },
2272
+ };
2105
2273
  this.proposeVisit(fetchResponse.location, visitOptions);
2106
2274
  }
2107
2275
  }
@@ -2111,10 +2279,10 @@ class Navigator {
2111
2279
  if (responseHTML) {
2112
2280
  const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2113
2281
  if (fetchResponse.serverError) {
2114
- await this.view.renderError(snapshot);
2282
+ await this.view.renderError(snapshot, this.currentVisit);
2115
2283
  }
2116
2284
  else {
2117
- await this.view.renderPage(snapshot);
2285
+ await this.view.renderPage(snapshot, false, true, this.currentVisit);
2118
2286
  }
2119
2287
  this.view.scrollToTop();
2120
2288
  this.view.clearSnapshotCache();
@@ -2124,7 +2292,7 @@ class Navigator {
2124
2292
  console.error(error);
2125
2293
  }
2126
2294
  formSubmissionFinished(formSubmission) {
2127
- if (typeof this.adapter.formSubmissionFinished === 'function') {
2295
+ if (typeof this.adapter.formSubmissionFinished === "function") {
2128
2296
  this.adapter.formSubmissionFinished(formSubmission);
2129
2297
  }
2130
2298
  }
@@ -2135,12 +2303,14 @@ class Navigator {
2135
2303
  this.delegate.visitCompleted(visit);
2136
2304
  }
2137
2305
  locationWithActionIsSamePage(location, action) {
2306
+ var _a;
2138
2307
  const anchor = getAnchor(location);
2139
- const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2140
- const isRestorationToTop = action === 'restore' && typeof anchor === 'undefined';
2141
- return action !== "replace" &&
2142
- getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&
2143
- (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)));
2144
2314
  }
2145
2315
  visitScrolledToSamePageLocation(oldURL, newURL) {
2146
2316
  this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
@@ -2246,7 +2416,7 @@ class ScrollObserver {
2246
2416
 
2247
2417
  class StreamObserver {
2248
2418
  constructor(delegate) {
2249
- this.sources = new Set;
2419
+ this.sources = new Set();
2250
2420
  this.started = false;
2251
2421
  this.inspectFetchResponse = ((event) => {
2252
2422
  const response = fetchResponseFromEvent(event);
@@ -2313,14 +2483,18 @@ function fetchResponseIsStream(response) {
2313
2483
  }
2314
2484
 
2315
2485
  class ErrorRenderer extends Renderer {
2486
+ static renderElement(currentElement, newElement) {
2487
+ const { documentElement, body } = document;
2488
+ documentElement.replaceChild(newElement, body);
2489
+ }
2316
2490
  async render() {
2317
2491
  this.replaceHeadAndBody();
2318
2492
  this.activateScriptElements();
2319
2493
  }
2320
2494
  replaceHeadAndBody() {
2321
- const { documentElement, head, body } = document;
2495
+ const { documentElement, head } = document;
2322
2496
  documentElement.replaceChild(this.newHead, head);
2323
- documentElement.replaceChild(this.newElement, body);
2497
+ this.renderElement(this.currentElement, this.newElement);
2324
2498
  }
2325
2499
  activateScriptElements() {
2326
2500
  for (const replaceableElement of this.scriptElements) {
@@ -2340,9 +2514,29 @@ class ErrorRenderer extends Renderer {
2340
2514
  }
2341
2515
 
2342
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
+ }
2343
2525
  get shouldRender() {
2344
2526
  return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
2345
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
+ }
2346
2540
  prepareToRender() {
2347
2541
  this.mergeHead();
2348
2542
  }
@@ -2412,12 +2606,7 @@ class PageRenderer extends Renderer {
2412
2606
  }
2413
2607
  }
2414
2608
  assignNewBody() {
2415
- if (document.body && this.newElement instanceof HTMLBodyElement) {
2416
- document.body.replaceWith(this.newElement);
2417
- }
2418
- else {
2419
- document.documentElement.appendChild(this.newElement);
2420
- }
2609
+ this.renderElement(this.currentElement, this.newElement);
2421
2610
  }
2422
2611
  get newHeadStylesheetElements() {
2423
2612
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -2486,13 +2675,21 @@ class PageView extends View {
2486
2675
  super(...arguments);
2487
2676
  this.snapshotCache = new SnapshotCache(10);
2488
2677
  this.lastRenderedLocation = new URL(location.href);
2678
+ this.forceReloaded = false;
2489
2679
  }
2490
- renderPage(snapshot, isPreview = false, willRender = true) {
2491
- 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
+ }
2492
2688
  return this.render(renderer);
2493
2689
  }
2494
- renderError(snapshot) {
2495
- 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);
2496
2693
  return this.render(renderer);
2497
2694
  }
2498
2695
  clearSnapshotCache() {
@@ -2519,10 +2716,50 @@ class PageView extends View {
2519
2716
  }
2520
2717
  }
2521
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
+
2522
2758
  class Session {
2523
2759
  constructor() {
2524
2760
  this.navigator = new Navigator(this);
2525
2761
  this.history = new History(this);
2762
+ this.preloader = new Preloader(this);
2526
2763
  this.view = new PageView(this, document.documentElement);
2527
2764
  this.adapter = new BrowserAdapter(this);
2528
2765
  this.pageObserver = new PageObserver(this);
@@ -2531,22 +2768,26 @@ class Session {
2531
2768
  this.formSubmitObserver = new FormSubmitObserver(this);
2532
2769
  this.scrollObserver = new ScrollObserver(this);
2533
2770
  this.streamObserver = new StreamObserver(this);
2771
+ this.formLinkInterceptor = new FormLinkInterceptor(this, document.documentElement);
2534
2772
  this.frameRedirector = new FrameRedirector(document.documentElement);
2535
2773
  this.drive = true;
2536
2774
  this.enabled = true;
2537
2775
  this.progressBarDelay = 500;
2538
2776
  this.started = false;
2777
+ this.formMode = "on";
2539
2778
  }
2540
2779
  start() {
2541
2780
  if (!this.started) {
2542
2781
  this.pageObserver.start();
2543
2782
  this.cacheObserver.start();
2783
+ this.formLinkInterceptor.start();
2544
2784
  this.linkClickObserver.start();
2545
2785
  this.formSubmitObserver.start();
2546
2786
  this.scrollObserver.start();
2547
2787
  this.streamObserver.start();
2548
2788
  this.frameRedirector.start();
2549
2789
  this.history.start();
2790
+ this.preloader.start();
2550
2791
  this.started = true;
2551
2792
  this.enabled = true;
2552
2793
  }
@@ -2558,6 +2799,7 @@ class Session {
2558
2799
  if (this.started) {
2559
2800
  this.pageObserver.stop();
2560
2801
  this.cacheObserver.stop();
2802
+ this.formLinkInterceptor.stop();
2561
2803
  this.linkClickObserver.stop();
2562
2804
  this.formSubmitObserver.stop();
2563
2805
  this.scrollObserver.stop();
@@ -2588,6 +2830,9 @@ class Session {
2588
2830
  setProgressBarDelay(delay) {
2589
2831
  this.progressBarDelay = delay;
2590
2832
  }
2833
+ setFormMode(mode) {
2834
+ this.formMode = mode;
2835
+ }
2591
2836
  get location() {
2592
2837
  return this.history.location;
2593
2838
  }
@@ -2596,48 +2841,32 @@ class Session {
2596
2841
  }
2597
2842
  historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
2598
2843
  if (this.enabled) {
2599
- this.navigator.startVisit(location, restorationIdentifier, { action: "restore", historyChanged: true });
2844
+ this.navigator.startVisit(location, restorationIdentifier, {
2845
+ action: "restore",
2846
+ historyChanged: true,
2847
+ });
2600
2848
  }
2601
2849
  else {
2602
- this.adapter.pageInvalidated();
2850
+ this.adapter.pageInvalidated({
2851
+ reason: "turbo_disabled",
2852
+ });
2603
2853
  }
2604
2854
  }
2605
2855
  scrollPositionChanged(position) {
2606
2856
  this.history.updateRestorationData({ scrollPosition: position });
2607
2857
  }
2608
- willFollowLinkToLocation(link, location) {
2609
- return this.elementDriveEnabled(link)
2610
- && locationIsVisitable(location, this.snapshot.rootLocation)
2611
- && 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));
2612
2866
  }
2613
2867
  followedLinkToLocation(link, location) {
2614
2868
  const action = this.getActionForLink(link);
2615
- this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, { action });
2616
- }
2617
- convertLinkWithMethodClickToFormSubmission(link) {
2618
- const linkMethod = link.getAttribute("data-turbo-method");
2619
- if (linkMethod) {
2620
- const form = document.createElement("form");
2621
- form.method = linkMethod;
2622
- form.action = link.getAttribute("href") || "undefined";
2623
- form.hidden = true;
2624
- if (link.hasAttribute("data-turbo-confirm")) {
2625
- form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
2626
- }
2627
- const frame = this.getTargetFrameForLink(link);
2628
- if (frame) {
2629
- form.setAttribute("data-turbo-frame", frame);
2630
- form.addEventListener("turbo:submit-start", () => form.remove());
2631
- }
2632
- else {
2633
- form.addEventListener("submit", () => form.remove());
2634
- }
2635
- document.body.appendChild(form);
2636
- return dispatch("submit", { cancelable: true, target: form });
2637
- }
2638
- else {
2639
- return false;
2640
- }
2869
+ this.visit(location.href, { action });
2641
2870
  }
2642
2871
  allowsVisitingLocationWithAction(location, action) {
2643
2872
  return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
@@ -2663,9 +2892,9 @@ class Session {
2663
2892
  }
2664
2893
  willSubmitForm(form, submitter) {
2665
2894
  const action = getAction(form, submitter);
2666
- return this.elementDriveEnabled(form)
2667
- && (!submitter || this.elementDriveEnabled(submitter))
2668
- && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
2895
+ return (this.elementDriveEnabled(form) &&
2896
+ (!submitter || this.formElementDriveEnabled(submitter)) &&
2897
+ locationIsVisitable(expandURL(action), this.snapshot.rootLocation));
2669
2898
  }
2670
2899
  formSubmitted(form, submitter) {
2671
2900
  this.navigator.submitForm(form, submitter);
@@ -2689,16 +2918,23 @@ class Session {
2689
2918
  this.notifyApplicationBeforeCachingSnapshot();
2690
2919
  }
2691
2920
  }
2692
- allowsImmediateRender({ element }, resume) {
2693
- const event = this.notifyApplicationBeforeRender(element, resume);
2694
- 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;
2695
2928
  }
2696
- viewRenderedSnapshot(snapshot, isPreview) {
2929
+ viewRenderedSnapshot(_snapshot, _isPreview) {
2697
2930
  this.view.lastRenderedLocation = this.history.location;
2698
2931
  this.notifyApplicationAfterRender();
2699
2932
  }
2700
- viewInvalidated() {
2701
- this.adapter.pageInvalidated();
2933
+ preloadOnLoadLinksForView(element) {
2934
+ this.preloader.preloadOnLoadLinksForView(element);
2935
+ }
2936
+ viewInvalidated(reason) {
2937
+ this.adapter.pageInvalidated(reason);
2702
2938
  }
2703
2939
  frameLoaded(frame) {
2704
2940
  this.notifyApplicationAfterFrameLoad(frame);
@@ -2706,19 +2942,26 @@ class Session {
2706
2942
  frameRendered(fetchResponse, frame) {
2707
2943
  this.notifyApplicationAfterFrameRender(fetchResponse, frame);
2708
2944
  }
2709
- applicationAllowsFollowingLinkToLocation(link, location) {
2710
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
2945
+ applicationAllowsFollowingLinkToLocation(link, location, ev) {
2946
+ const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
2711
2947
  return !event.defaultPrevented;
2712
2948
  }
2713
2949
  applicationAllowsVisitingLocation(location) {
2714
2950
  const event = this.notifyApplicationBeforeVisitingLocation(location);
2715
2951
  return !event.defaultPrevented;
2716
2952
  }
2717
- notifyApplicationAfterClickingLinkToLocation(link, location) {
2718
- 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
+ });
2719
2959
  }
2720
2960
  notifyApplicationBeforeVisitingLocation(location) {
2721
- 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
+ });
2722
2965
  }
2723
2966
  notifyApplicationAfterVisitingLocation(location, action) {
2724
2967
  markAsBusy(document.documentElement);
@@ -2727,24 +2970,46 @@ class Session {
2727
2970
  notifyApplicationBeforeCachingSnapshot() {
2728
2971
  return dispatch("turbo:before-cache");
2729
2972
  }
2730
- notifyApplicationBeforeRender(newBody, resume) {
2731
- 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
+ });
2732
2978
  }
2733
2979
  notifyApplicationAfterRender() {
2734
2980
  return dispatch("turbo:render");
2735
2981
  }
2736
2982
  notifyApplicationAfterPageLoad(timing = {}) {
2737
2983
  clearBusyState(document.documentElement);
2738
- return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
2984
+ return dispatch("turbo:load", {
2985
+ detail: { url: this.location.href, timing },
2986
+ });
2739
2987
  }
2740
2988
  notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
2741
- dispatchEvent(new HashChangeEvent("hashchange", { oldURL: oldURL.toString(), newURL: newURL.toString() }));
2989
+ dispatchEvent(new HashChangeEvent("hashchange", {
2990
+ oldURL: oldURL.toString(),
2991
+ newURL: newURL.toString(),
2992
+ }));
2742
2993
  }
2743
2994
  notifyApplicationAfterFrameLoad(frame) {
2744
2995
  return dispatch("turbo:frame-load", { target: frame });
2745
2996
  }
2746
2997
  notifyApplicationAfterFrameRender(fetchResponse, frame) {
2747
- 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);
2748
3013
  }
2749
3014
  elementDriveEnabled(element) {
2750
3015
  const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
@@ -2769,18 +3034,6 @@ class Session {
2769
3034
  const action = link.getAttribute("data-turbo-action");
2770
3035
  return isAction(action) ? action : "advance";
2771
3036
  }
2772
- getTargetFrameForLink(link) {
2773
- const frame = link.getAttribute("data-turbo-frame");
2774
- if (frame) {
2775
- return frame;
2776
- }
2777
- else {
2778
- const container = link.closest("turbo-frame");
2779
- if (container) {
2780
- return container.id;
2781
- }
2782
- }
2783
- }
2784
3037
  get snapshot() {
2785
3038
  return this.view.snapshot;
2786
3039
  }
@@ -2792,11 +3045,59 @@ const deprecatedLocationPropertyDescriptors = {
2792
3045
  absoluteURL: {
2793
3046
  get() {
2794
3047
  return this.toString();
2795
- }
3048
+ },
3049
+ },
3050
+ };
3051
+
3052
+ class Cache {
3053
+ constructor(session) {
3054
+ this.session = session;
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);
2796
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
+ },
2797
3097
  };
2798
3098
 
2799
- const session = new Session;
3099
+ const session = new Session();
3100
+ const cache = new Cache(session);
2800
3101
  const { navigator: navigator$1 } = session;
2801
3102
  function start() {
2802
3103
  session.start();
@@ -2817,6 +3118,7 @@ function renderStreamMessage(message) {
2817
3118
  session.renderStreamMessage(message);
2818
3119
  }
2819
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.`");
2820
3122
  session.clearCache();
2821
3123
  }
2822
3124
  function setProgressBarDelay(delay) {
@@ -2825,13 +3127,18 @@ function setProgressBarDelay(delay) {
2825
3127
  function setConfirmMethod(confirmMethod) {
2826
3128
  FormSubmission.confirmMethod = confirmMethod;
2827
3129
  }
3130
+ function setFormMode(mode) {
3131
+ session.setFormMode(mode);
3132
+ }
2828
3133
 
2829
3134
  var Turbo = /*#__PURE__*/Object.freeze({
2830
3135
  __proto__: null,
2831
3136
  navigator: navigator$1,
2832
3137
  session: session,
3138
+ cache: cache,
2833
3139
  PageRenderer: PageRenderer,
2834
3140
  PageSnapshot: PageSnapshot,
3141
+ FrameRenderer: FrameRenderer,
2835
3142
  start: start,
2836
3143
  registerAdapter: registerAdapter,
2837
3144
  visit: visit,
@@ -2840,39 +3147,52 @@ var Turbo = /*#__PURE__*/Object.freeze({
2840
3147
  renderStreamMessage: renderStreamMessage,
2841
3148
  clearCache: clearCache,
2842
3149
  setProgressBarDelay: setProgressBarDelay,
2843
- setConfirmMethod: setConfirmMethod
3150
+ setConfirmMethod: setConfirmMethod,
3151
+ setFormMode: setFormMode,
3152
+ StreamActions: StreamActions
2844
3153
  });
2845
3154
 
2846
3155
  class FrameController {
2847
3156
  constructor(element) {
2848
- this.fetchResponseLoaded = (fetchResponse) => { };
3157
+ this.fetchResponseLoaded = (_fetchResponse) => { };
2849
3158
  this.currentFetchRequest = null;
2850
3159
  this.resolveVisitPromise = () => { };
2851
3160
  this.connected = false;
2852
3161
  this.hasBeenLoaded = false;
2853
- 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
+ };
2854
3170
  this.element = element;
2855
3171
  this.view = new FrameView(this, this.element);
2856
3172
  this.appearanceObserver = new AppearanceObserver(this, this.element);
3173
+ this.formLinkInterceptor = new FormLinkInterceptor(this, this.element);
2857
3174
  this.linkInterceptor = new LinkInterceptor(this, this.element);
2858
3175
  this.formInterceptor = new FormInterceptor(this, this.element);
2859
3176
  }
2860
3177
  connect() {
2861
3178
  if (!this.connected) {
2862
3179
  this.connected = true;
2863
- this.reloadable = false;
2864
3180
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2865
3181
  this.appearanceObserver.start();
2866
3182
  }
3183
+ else {
3184
+ this.loadSourceURL();
3185
+ }
3186
+ this.formLinkInterceptor.start();
2867
3187
  this.linkInterceptor.start();
2868
3188
  this.formInterceptor.start();
2869
- this.sourceURLChanged();
2870
3189
  }
2871
3190
  }
2872
3191
  disconnect() {
2873
3192
  if (this.connected) {
2874
3193
  this.connected = false;
2875
3194
  this.appearanceObserver.stop();
3195
+ this.formLinkInterceptor.stop();
2876
3196
  this.linkInterceptor.stop();
2877
3197
  this.formInterceptor.stop();
2878
3198
  }
@@ -2883,10 +3203,20 @@ class FrameController {
2883
3203
  }
2884
3204
  }
2885
3205
  sourceURLChanged() {
3206
+ if (this.isIgnoringChangesTo("src"))
3207
+ return;
3208
+ if (this.element.isConnected) {
3209
+ this.complete = false;
3210
+ }
2886
3211
  if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
2887
3212
  this.loadSourceURL();
2888
3213
  }
2889
3214
  }
3215
+ completeChanged() {
3216
+ if (this.isIgnoringChangesTo("complete"))
3217
+ return;
3218
+ this.loadSourceURL();
3219
+ }
2890
3220
  loadingStyleChanged() {
2891
3221
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2892
3222
  this.appearanceObserver.start();
@@ -2897,21 +3227,11 @@ class FrameController {
2897
3227
  }
2898
3228
  }
2899
3229
  async loadSourceURL() {
2900
- if (!this.settingSourceURL && this.enabled && this.isActive && (this.reloadable || this.sourceURL != this.currentURL)) {
2901
- const previousURL = this.currentURL;
2902
- this.currentURL = this.sourceURL;
2903
- if (this.sourceURL) {
2904
- try {
2905
- this.element.loaded = this.visit(this.sourceURL);
2906
- this.appearanceObserver.stop();
2907
- await this.element.loaded;
2908
- this.hasBeenLoaded = true;
2909
- }
2910
- catch (error) {
2911
- this.currentURL = previousURL;
2912
- throw error;
2913
- }
2914
- }
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;
2915
3235
  }
2916
3236
  }
2917
3237
  async loadResponse(fetchResponse) {
@@ -2923,10 +3243,11 @@ class FrameController {
2923
3243
  if (html) {
2924
3244
  const { body } = parseHTMLDocument(html);
2925
3245
  const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
2926
- const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
3246
+ const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
2927
3247
  if (this.view.renderPromise)
2928
3248
  await this.view.renderPromise;
2929
3249
  await this.view.render(renderer);
3250
+ this.complete = true;
2930
3251
  session.frameRendered(fetchResponse, this.element);
2931
3252
  session.frameLoaded(this.element);
2932
3253
  this.fetchResponseLoaded(fetchResponse);
@@ -2940,19 +3261,21 @@ class FrameController {
2940
3261
  this.fetchResponseLoaded = () => { };
2941
3262
  }
2942
3263
  }
2943
- elementAppearedInViewport(element) {
3264
+ elementAppearedInViewport(_element) {
2944
3265
  this.loadSourceURL();
2945
3266
  }
2946
- shouldInterceptLinkClick(element, url) {
2947
- if (element.hasAttribute("data-turbo-method")) {
2948
- return false;
2949
- }
2950
- else {
2951
- return this.shouldInterceptNavigation(element);
2952
- }
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);
2953
3277
  }
2954
3278
  linkClickIntercepted(element, url) {
2955
- this.reloadable = true;
2956
3279
  this.navigateFrame(element, url);
2957
3280
  }
2958
3281
  shouldInterceptFormSubmission(element, submitter) {
@@ -2962,19 +3285,18 @@ class FrameController {
2962
3285
  if (this.formSubmission) {
2963
3286
  this.formSubmission.stop();
2964
3287
  }
2965
- this.reloadable = false;
2966
3288
  this.formSubmission = new FormSubmission(this, element, submitter);
2967
3289
  const { fetchRequest } = this.formSubmission;
2968
3290
  this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
2969
3291
  this.formSubmission.start();
2970
3292
  }
2971
- prepareHeadersForRequest(headers, request) {
3293
+ prepareHeadersForRequest(headers, _request) {
2972
3294
  headers["Turbo-Frame"] = this.id;
2973
3295
  }
2974
- requestStarted(request) {
3296
+ requestStarted(_request) {
2975
3297
  markAsBusy(this.element);
2976
3298
  }
2977
- requestPreventedHandlingResponse(request, response) {
3299
+ requestPreventedHandlingResponse(_request, _response) {
2978
3300
  this.resolveVisitPromise();
2979
3301
  }
2980
3302
  async requestSucceededWithResponse(request, response) {
@@ -2989,7 +3311,7 @@ class FrameController {
2989
3311
  console.error(error);
2990
3312
  this.resolveVisitPromise();
2991
3313
  }
2992
- requestFinished(request) {
3314
+ requestFinished(_request) {
2993
3315
  clearBusyState(this.element);
2994
3316
  }
2995
3317
  formSubmissionStarted({ formElement }) {
@@ -3009,19 +3331,32 @@ class FrameController {
3009
3331
  formSubmissionFinished({ formElement }) {
3010
3332
  clearBusyState(formElement, this.findFrameElement(formElement));
3011
3333
  }
3012
- allowsImmediateRender(snapshot, resume) {
3013
- 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;
3014
3345
  }
3015
- viewRenderedSnapshot(snapshot, isPreview) {
3346
+ viewRenderedSnapshot(_snapshot, _isPreview) { }
3347
+ preloadOnLoadLinksForView(element) {
3348
+ session.preloadOnLoadLinksForView(element);
3016
3349
  }
3017
- viewInvalidated() {
3350
+ viewInvalidated() { }
3351
+ frameExtracted(element) {
3352
+ this.previousFrameElement = element;
3018
3353
  }
3019
3354
  async visit(url) {
3020
3355
  var _a;
3021
- const request = new FetchRequest(this, FetchMethod.get, expandURL(url), new URLSearchParams, this.element);
3356
+ const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);
3022
3357
  (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
3023
3358
  this.currentFetchRequest = request;
3024
- return new Promise(resolve => {
3359
+ return new Promise((resolve) => {
3025
3360
  this.resolveVisitPromise = () => {
3026
3361
  this.resolveVisitPromise = () => { };
3027
3362
  this.currentFetchRequest = null;
@@ -3033,19 +3368,23 @@ class FrameController {
3033
3368
  navigateFrame(element, url, submitter) {
3034
3369
  const frame = this.findFrameElement(element, submitter);
3035
3370
  this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3036
- frame.setAttribute("reloadable", "");
3037
3371
  frame.src = url;
3038
3372
  }
3039
3373
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3040
3374
  const action = getAttribute("data-turbo-action", submitter, element, frame);
3041
3375
  if (isAction(action)) {
3042
- const { visitCachedSnapshot } = new SnapshotSubstitution(frame);
3376
+ const { visitCachedSnapshot } = frame.delegate;
3043
3377
  frame.delegate.fetchResponseLoaded = (fetchResponse) => {
3044
3378
  if (frame.src) {
3045
3379
  const { statusCode, redirected } = fetchResponse;
3046
3380
  const responseHTML = frame.ownerDocument.documentElement.outerHTML;
3047
3381
  const response = { statusCode, redirected, responseHTML };
3048
- session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false });
3382
+ session.visit(frame.src, {
3383
+ action,
3384
+ response,
3385
+ visitCachedSnapshot,
3386
+ willRender: false,
3387
+ });
3049
3388
  }
3050
3389
  };
3051
3390
  }
@@ -3059,10 +3398,12 @@ class FrameController {
3059
3398
  let element;
3060
3399
  const id = CSS.escape(this.id);
3061
3400
  try {
3062
- if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
3401
+ element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
3402
+ if (element) {
3063
3403
  return element;
3064
3404
  }
3065
- 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) {
3066
3407
  await element.loaded;
3067
3408
  return await this.extractForeignFrameElement(element);
3068
3409
  }
@@ -3110,24 +3451,10 @@ class FrameController {
3110
3451
  return this.element.src;
3111
3452
  }
3112
3453
  }
3113
- get reloadable() {
3114
- const frame = this.findFrameElement(this.element);
3115
- return frame.hasAttribute("reloadable");
3116
- }
3117
- set reloadable(value) {
3118
- const frame = this.findFrameElement(this.element);
3119
- if (value) {
3120
- frame.setAttribute("reloadable", "");
3121
- }
3122
- else {
3123
- frame.removeAttribute("reloadable");
3124
- }
3125
- }
3126
3454
  set sourceURL(sourceURL) {
3127
- this.settingSourceURL = true;
3128
- this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3129
- this.currentURL = this.element.src;
3130
- this.settingSourceURL = false;
3455
+ this.ignoringChangesToAttribute("src", () => {
3456
+ this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3457
+ });
3131
3458
  }
3132
3459
  get loadingStyle() {
3133
3460
  return this.element.loading;
@@ -3135,6 +3462,19 @@ class FrameController {
3135
3462
  get isLoading() {
3136
3463
  return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
3137
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
+ }
3138
3478
  get isActive() {
3139
3479
  return this.element.isActive && this.connected;
3140
3480
  }
@@ -3144,16 +3484,13 @@ class FrameController {
3144
3484
  const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3145
3485
  return expandURL(root);
3146
3486
  }
3147
- }
3148
- class SnapshotSubstitution {
3149
- constructor(element) {
3150
- this.visitCachedSnapshot = ({ element }) => {
3151
- var _a;
3152
- const { id, clone } = this;
3153
- (_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
3154
- };
3155
- this.clone = element.cloneNode(true);
3156
- 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);
3157
3494
  }
3158
3495
  }
3159
3496
  function getFrameElementById(id) {
@@ -3181,35 +3518,6 @@ function activateElement(element, currentURL) {
3181
3518
  }
3182
3519
  }
3183
3520
 
3184
- const StreamActions = {
3185
- after() {
3186
- this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });
3187
- },
3188
- append() {
3189
- this.removeDuplicateTargetChildren();
3190
- this.targetElements.forEach(e => e.append(this.templateContent));
3191
- },
3192
- before() {
3193
- this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });
3194
- },
3195
- prepend() {
3196
- this.removeDuplicateTargetChildren();
3197
- this.targetElements.forEach(e => e.prepend(this.templateContent));
3198
- },
3199
- remove() {
3200
- this.targetElements.forEach(e => e.remove());
3201
- },
3202
- replace() {
3203
- this.targetElements.forEach(e => e.replaceWith(this.templateContent));
3204
- },
3205
- update() {
3206
- this.targetElements.forEach(e => {
3207
- e.innerHTML = "";
3208
- e.append(this.templateContent);
3209
- });
3210
- }
3211
- };
3212
-
3213
3521
  class StreamElement extends HTMLElement {
3214
3522
  async connectedCallback() {
3215
3523
  try {
@@ -3224,12 +3532,12 @@ class StreamElement extends HTMLElement {
3224
3532
  }
3225
3533
  async render() {
3226
3534
  var _a;
3227
- 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 () => {
3228
3536
  if (this.dispatchEvent(this.beforeRenderEvent)) {
3229
3537
  await nextAnimationFrame();
3230
3538
  this.performAction();
3231
3539
  }
3232
- })());
3540
+ })()));
3233
3541
  }
3234
3542
  disconnect() {
3235
3543
  try {
@@ -3238,13 +3546,13 @@ class StreamElement extends HTMLElement {
3238
3546
  catch (_a) { }
3239
3547
  }
3240
3548
  removeDuplicateTargetChildren() {
3241
- this.duplicateChildren.forEach(c => c.remove());
3549
+ this.duplicateChildren.forEach((c) => c.remove());
3242
3550
  }
3243
3551
  get duplicateChildren() {
3244
3552
  var _a;
3245
- const existingChildren = this.targetElements.flatMap(e => [...e.children]).filter(c => !!c.id);
3246
- const newChildrenIds = [...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children].filter(c => !!c.id).map(c => c.id);
3247
- 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));
3248
3556
  }
3249
3557
  get performAction() {
3250
3558
  if (this.action) {
@@ -3293,7 +3601,10 @@ class StreamElement extends HTMLElement {
3293
3601
  return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
3294
3602
  }
3295
3603
  get beforeRenderEvent() {
3296
- 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
+ });
3297
3608
  }
3298
3609
  get targetElementsById() {
3299
3610
  var _a;
@@ -3317,9 +3628,35 @@ class StreamElement extends HTMLElement {
3317
3628
  }
3318
3629
  }
3319
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
+
3320
3650
  FrameElement.delegateConstructor = FrameController;
3321
- customElements.define("turbo-frame", FrameElement);
3322
- 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
+ }
3323
3660
 
3324
3661
  (() => {
3325
3662
  let element = document.currentScript;
@@ -3327,7 +3664,8 @@ customElements.define("turbo-stream", StreamElement);
3327
3664
  return;
3328
3665
  if (element.hasAttribute("data-turbo-suppress-warning"))
3329
3666
  return;
3330
- while (element = element.parentElement) {
3667
+ element = element.parentElement;
3668
+ while (element) {
3331
3669
  if (element == document.body) {
3332
3670
  return console.warn(unindent `
3333
3671
  You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
@@ -3340,10 +3678,11 @@ customElements.define("turbo-stream", StreamElement);
3340
3678
  Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
3341
3679
  `, element.outerHTML);
3342
3680
  }
3681
+ element = element.parentElement;
3343
3682
  }
3344
3683
  })();
3345
3684
 
3346
3685
  window.Turbo = Turbo;
3347
3686
  start();
3348
3687
 
3349
- 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 };