@hotwired/turbo 7.1.0 → 7.2.0

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 (74) hide show
  1. package/README.md +5 -1
  2. package/dist/turbo.es2017-esm.js +1108 -617
  3. package/dist/turbo.es2017-umd.js +1122 -624
  4. package/dist/types/core/bardo.d.ts +7 -2
  5. package/dist/types/core/cache.d.ts +10 -0
  6. package/dist/types/core/drive/error_renderer.d.ts +2 -1
  7. package/dist/types/core/drive/form_submission.d.ts +12 -5
  8. package/dist/types/core/drive/head_snapshot.d.ts +3 -3
  9. package/dist/types/core/drive/history.d.ts +1 -1
  10. package/dist/types/core/drive/navigator.d.ts +1 -0
  11. package/dist/types/core/drive/page_renderer.d.ts +8 -5
  12. package/dist/types/core/drive/page_view.d.ts +8 -5
  13. package/dist/types/core/drive/preloader.d.ts +14 -0
  14. package/dist/types/core/drive/progress_bar.d.ts +1 -0
  15. package/dist/types/core/drive/visit.d.ts +13 -4
  16. package/dist/types/core/frames/frame_controller.d.ts +51 -24
  17. package/dist/types/core/frames/frame_redirector.d.ts +13 -10
  18. package/dist/types/core/frames/frame_renderer.d.ts +8 -1
  19. package/dist/types/core/frames/frame_view.d.ts +2 -1
  20. package/dist/types/core/index.d.ts +11 -3
  21. package/dist/types/core/native/adapter.d.ts +2 -1
  22. package/dist/types/core/native/browser_adapter.d.ts +17 -8
  23. package/dist/types/core/renderer.d.ts +11 -5
  24. package/dist/types/core/session.d.ts +72 -17
  25. package/dist/types/core/snapshot.d.ts +5 -2
  26. package/dist/types/core/streams/stream_actions.d.ts +4 -2
  27. package/dist/types/core/streams/stream_message.d.ts +2 -6
  28. package/dist/types/core/streams/stream_message_renderer.d.ts +7 -0
  29. package/dist/types/core/view.d.ts +13 -7
  30. package/dist/types/elements/frame_element.d.ts +10 -6
  31. package/dist/types/elements/index.d.ts +1 -0
  32. package/dist/types/elements/stream_element.d.ts +8 -1
  33. package/dist/types/elements/stream_source_element.d.ts +7 -0
  34. package/dist/types/http/fetch_request.d.ts +14 -0
  35. package/dist/types/http/index.d.ts +1 -0
  36. package/dist/types/index.d.ts +2 -0
  37. package/dist/types/observers/cache_observer.d.ts +1 -1
  38. package/dist/types/observers/form_link_click_observer.d.ts +14 -0
  39. package/dist/types/observers/form_submit_observer.d.ts +2 -1
  40. package/dist/types/observers/link_click_observer.d.ts +5 -4
  41. package/dist/types/polyfills/submit-event.d.ts +1 -7
  42. package/dist/types/tests/functional/async_script_tests.d.ts +1 -6
  43. package/dist/types/tests/functional/autofocus_tests.d.ts +1 -9
  44. package/dist/types/tests/functional/cache_observer_tests.d.ts +1 -5
  45. package/dist/types/tests/functional/drive_custom_body_tests.d.ts +1 -0
  46. package/dist/types/tests/functional/drive_disabled_tests.d.ts +1 -9
  47. package/dist/types/tests/functional/drive_tests.d.ts +1 -8
  48. package/dist/types/tests/functional/form_mode_tests.d.ts +1 -0
  49. package/dist/types/tests/functional/form_submission_tests.d.ts +1 -84
  50. package/dist/types/tests/functional/frame_navigation_tests.d.ts +1 -7
  51. package/dist/types/tests/functional/frame_tests.d.ts +1 -52
  52. package/dist/types/tests/functional/import_tests.d.ts +1 -4
  53. package/dist/types/tests/functional/loading_tests.d.ts +1 -13
  54. package/dist/types/tests/functional/navigation_tests.d.ts +1 -38
  55. package/dist/types/tests/functional/pausable_rendering_tests.d.ts +1 -6
  56. package/dist/types/tests/functional/pausable_requests_tests.d.ts +1 -6
  57. package/dist/types/tests/functional/preloader_tests.d.ts +1 -0
  58. package/dist/types/tests/functional/rendering_tests.d.ts +1 -35
  59. package/dist/types/tests/functional/scroll_restoration_tests.d.ts +1 -6
  60. package/dist/types/tests/functional/stream_tests.d.ts +1 -6
  61. package/dist/types/tests/functional/visit_tests.d.ts +1 -15
  62. package/dist/types/tests/helpers/page.d.ts +52 -0
  63. package/dist/types/tests/unit/deprecated_adapter_support_test.d.ts +10 -10
  64. package/dist/types/tests/unit/export_tests.d.ts +5 -0
  65. package/dist/types/tests/unit/index.d.ts +1 -0
  66. package/dist/types/util.d.ts +13 -3
  67. package/package.json +24 -10
  68. package/CHANGELOG.md +0 -3
  69. package/dist/types/core/frames/form_interceptor.d.ts +0 -12
  70. package/dist/types/core/frames/link_interceptor.d.ts +0 -16
  71. package/dist/types/tests/functional/index.d.ts +0 -17
  72. package/dist/types/tests/helpers/functional_test_case.d.ts +0 -44
  73. package/dist/types/tests/helpers/remote_channel.d.ts +0 -10
  74. package/dist/types/tests/helpers/turbo_drive_test_case.d.ts +0 -21
@@ -1,20 +1,20 @@
1
1
  /*
2
- Turbo 7.1.0
3
- Copyright © 2021 Basecamp, LLC
2
+ Turbo 7.2.0
3
+ Copyright © 2022 37signals 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,13 +129,18 @@ 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;
135
+ return this.loaded;
134
136
  }
135
137
  attributeChangedCallback(name) {
136
138
  if (name == "loading") {
137
139
  this.delegate.loadingStyleChanged();
138
140
  }
141
+ else if (name == "complete") {
142
+ this.delegate.completeChanged();
143
+ }
139
144
  else if (name == "src") {
140
145
  this.delegate.sourceURLChanged();
141
146
  }
@@ -200,8 +205,10 @@ class FrameElement extends HTMLElement {
200
205
  }
201
206
  function frameLoadingStyleFromString(style) {
202
207
  switch (style.toLowerCase()) {
203
- case "lazy": return FrameLoadingStyle.lazy;
204
- default: return FrameLoadingStyle.eager;
208
+ case "lazy":
209
+ return FrameLoadingStyle.lazy;
210
+ default:
211
+ return FrameLoadingStyle.eager;
205
212
  }
206
213
  }
207
214
 
@@ -213,7 +220,7 @@ function getAnchor(url) {
213
220
  if (url.hash) {
214
221
  return url.hash.slice(1);
215
222
  }
216
- else if (anchorMatch = url.href.match(/#(.*)$/)) {
223
+ else if ((anchorMatch = url.href.match(/#(.*)$/))) {
217
224
  return anchorMatch[1];
218
225
  }
219
226
  }
@@ -225,7 +232,7 @@ function getExtension(url) {
225
232
  return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
226
233
  }
227
234
  function isHTML(url) {
228
- return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
235
+ return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
229
236
  }
230
237
  function isPrefixedBy(baseURL, url) {
231
238
  const prefix = getPrefix(url);
@@ -236,9 +243,7 @@ function locationIsVisitable(location, rootLocation) {
236
243
  }
237
244
  function getRequestURL(url) {
238
245
  const anchor = getAnchor(url);
239
- return anchor != null
240
- ? url.href.slice(0, -(anchor.length + 1))
241
- : url.href;
246
+ return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
242
247
  }
243
248
  function toCacheKey(url) {
244
249
  return getRequestURL(url);
@@ -306,8 +311,42 @@ class FetchResponse {
306
311
  }
307
312
  }
308
313
 
314
+ function isAction(action) {
315
+ return action == "advance" || action == "replace" || action == "restore";
316
+ }
317
+
318
+ function activateScriptElement(element) {
319
+ if (element.getAttribute("data-turbo-eval") == "false") {
320
+ return element;
321
+ }
322
+ else {
323
+ const createdScriptElement = document.createElement("script");
324
+ const cspNonce = getMetaContent("csp-nonce");
325
+ if (cspNonce) {
326
+ createdScriptElement.nonce = cspNonce;
327
+ }
328
+ createdScriptElement.textContent = element.textContent;
329
+ createdScriptElement.async = false;
330
+ copyElementAttributes(createdScriptElement, element);
331
+ return createdScriptElement;
332
+ }
333
+ }
334
+ function copyElementAttributes(destinationElement, sourceElement) {
335
+ for (const { name, value } of sourceElement.attributes) {
336
+ destinationElement.setAttribute(name, value);
337
+ }
338
+ }
339
+ function createDocumentFragment(html) {
340
+ const template = document.createElement("template");
341
+ template.innerHTML = html;
342
+ return template.content;
343
+ }
309
344
  function dispatch(eventName, { target, cancelable, detail } = {}) {
310
- const event = new CustomEvent(eventName, { cancelable, bubbles: true, detail });
345
+ const event = new CustomEvent(eventName, {
346
+ cancelable,
347
+ bubbles: true,
348
+ detail,
349
+ });
311
350
  if (target && target.isConnected) {
312
351
  target.dispatchEvent(event);
313
352
  }
@@ -317,10 +356,10 @@ function dispatch(eventName, { target, cancelable, detail } = {}) {
317
356
  return event;
318
357
  }
319
358
  function nextAnimationFrame() {
320
- return new Promise(resolve => requestAnimationFrame(() => resolve()));
359
+ return new Promise((resolve) => requestAnimationFrame(() => resolve()));
321
360
  }
322
361
  function nextEventLoopTick() {
323
- return new Promise(resolve => setTimeout(() => resolve(), 0));
362
+ return new Promise((resolve) => setTimeout(() => resolve(), 0));
324
363
  }
325
364
  function nextMicrotask() {
326
365
  return Promise.resolve();
@@ -332,7 +371,7 @@ function unindent(strings, ...values) {
332
371
  const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
333
372
  const match = lines[0].match(/^\s+/);
334
373
  const indent = match ? match[0].length : 0;
335
- return lines.map(line => line.slice(indent)).join("\n");
374
+ return lines.map((line) => line.slice(indent)).join("\n");
336
375
  }
337
376
  function interpolate(strings, values) {
338
377
  return strings.reduce((result, string, i) => {
@@ -341,7 +380,8 @@ function interpolate(strings, values) {
341
380
  }, "");
342
381
  }
343
382
  function uuid() {
344
- return Array.apply(null, { length: 36 }).map((_, i) => {
383
+ return Array.from({ length: 36 })
384
+ .map((_, i) => {
345
385
  if (i == 8 || i == 13 || i == 18 || i == 23) {
346
386
  return "-";
347
387
  }
@@ -354,15 +394,19 @@ function uuid() {
354
394
  else {
355
395
  return Math.floor(Math.random() * 15).toString(16);
356
396
  }
357
- }).join("");
397
+ })
398
+ .join("");
358
399
  }
359
400
  function getAttribute(attributeName, ...elements) {
360
- for (const value of elements.map(element => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {
401
+ for (const value of elements.map((element) => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {
361
402
  if (typeof value == "string")
362
403
  return value;
363
404
  }
364
405
  return null;
365
406
  }
407
+ function hasAttribute(attributeName, ...elements) {
408
+ return elements.some((element) => element && element.hasAttribute(attributeName));
409
+ }
366
410
  function markAsBusy(...elements) {
367
411
  for (const element of elements) {
368
412
  if (element.localName == "turbo-frame") {
@@ -379,6 +423,48 @@ function clearBusyState(...elements) {
379
423
  element.removeAttribute("aria-busy");
380
424
  }
381
425
  }
426
+ function waitForLoad(element, timeoutInMilliseconds = 2000) {
427
+ return new Promise((resolve) => {
428
+ const onComplete = () => {
429
+ element.removeEventListener("error", onComplete);
430
+ element.removeEventListener("load", onComplete);
431
+ resolve();
432
+ };
433
+ element.addEventListener("load", onComplete, { once: true });
434
+ element.addEventListener("error", onComplete, { once: true });
435
+ setTimeout(resolve, timeoutInMilliseconds);
436
+ });
437
+ }
438
+ function getHistoryMethodForAction(action) {
439
+ switch (action) {
440
+ case "replace":
441
+ return history.replaceState;
442
+ case "advance":
443
+ case "restore":
444
+ return history.pushState;
445
+ }
446
+ }
447
+ function getVisitAction(...elements) {
448
+ const action = getAttribute("data-turbo-action", ...elements);
449
+ return isAction(action) ? action : null;
450
+ }
451
+ function getMetaElement(name) {
452
+ return document.querySelector(`meta[name="${name}"]`);
453
+ }
454
+ function getMetaContent(name) {
455
+ const element = getMetaElement(name);
456
+ return element && element.content;
457
+ }
458
+ function setMetaContent(name, content) {
459
+ let element = getMetaElement(name);
460
+ if (!element) {
461
+ element = document.createElement("meta");
462
+ element.setAttribute("name", name);
463
+ document.head.appendChild(element);
464
+ }
465
+ element.setAttribute("content", content);
466
+ return element;
467
+ }
382
468
 
383
469
  var FetchMethod;
384
470
  (function (FetchMethod) {
@@ -390,17 +476,22 @@ var FetchMethod;
390
476
  })(FetchMethod || (FetchMethod = {}));
391
477
  function fetchMethodFromString(method) {
392
478
  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;
479
+ case "get":
480
+ return FetchMethod.get;
481
+ case "post":
482
+ return FetchMethod.post;
483
+ case "put":
484
+ return FetchMethod.put;
485
+ case "patch":
486
+ return FetchMethod.patch;
487
+ case "delete":
488
+ return FetchMethod.delete;
398
489
  }
399
490
  }
400
491
  class FetchRequest {
401
- constructor(delegate, method, location, body = new URLSearchParams, target = null) {
402
- this.abortController = new AbortController;
403
- this.resolveRequestPromise = (value) => { };
492
+ constructor(delegate, method, location, body = new URLSearchParams(), target = null) {
493
+ this.abortController = new AbortController();
494
+ this.resolveRequestPromise = (_value) => { };
404
495
  this.delegate = delegate;
405
496
  this.method = method;
406
497
  this.headers = this.defaultHeaders;
@@ -431,8 +522,10 @@ class FetchRequest {
431
522
  return await this.receive(response);
432
523
  }
433
524
  catch (error) {
434
- if (error.name !== 'AbortError') {
435
- this.delegate.requestErrored(this, error);
525
+ if (error.name !== "AbortError") {
526
+ if (this.willDelegateErrorHandling(error)) {
527
+ this.delegate.requestErrored(this, error);
528
+ }
436
529
  throw error;
437
530
  }
438
531
  }
@@ -442,7 +535,11 @@ class FetchRequest {
442
535
  }
443
536
  async receive(response) {
444
537
  const fetchResponse = new FetchResponse(response);
445
- const event = dispatch("turbo:before-fetch-response", { cancelable: true, detail: { fetchResponse }, target: this.target });
538
+ const event = dispatch("turbo:before-fetch-response", {
539
+ cancelable: true,
540
+ detail: { fetchResponse },
541
+ target: this.target,
542
+ });
446
543
  if (event.defaultPrevented) {
447
544
  this.delegate.requestPreventedHandlingResponse(this, fetchResponse);
448
545
  }
@@ -463,12 +560,12 @@ class FetchRequest {
463
560
  redirect: "follow",
464
561
  body: this.isIdempotent ? null : this.body,
465
562
  signal: this.abortSignal,
466
- referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
563
+ referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href,
467
564
  };
468
565
  }
469
566
  get defaultHeaders() {
470
567
  return {
471
- "Accept": "text/html, application/xhtml+xml"
568
+ Accept: "text/html, application/xhtml+xml",
472
569
  };
473
570
  }
474
571
  get isIdempotent() {
@@ -477,26 +574,37 @@ class FetchRequest {
477
574
  get abortSignal() {
478
575
  return this.abortController.signal;
479
576
  }
577
+ acceptResponseType(mimeType) {
578
+ this.headers["Accept"] = [mimeType, this.headers["Accept"]].join(", ");
579
+ }
480
580
  async allowRequestToBeIntercepted(fetchOptions) {
481
- const requestInterception = new Promise(resolve => this.resolveRequestPromise = resolve);
581
+ const requestInterception = new Promise((resolve) => (this.resolveRequestPromise = resolve));
482
582
  const event = dispatch("turbo:before-fetch-request", {
483
583
  cancelable: true,
484
584
  detail: {
485
585
  fetchOptions,
486
586
  url: this.url,
487
- resume: this.resolveRequestPromise
587
+ resume: this.resolveRequestPromise,
488
588
  },
489
- target: this.target
589
+ target: this.target,
490
590
  });
491
591
  if (event.defaultPrevented)
492
592
  await requestInterception;
493
593
  }
594
+ willDelegateErrorHandling(error) {
595
+ const event = dispatch("turbo:fetch-request-error", {
596
+ target: this.target,
597
+ cancelable: true,
598
+ detail: { request: this, error: error },
599
+ });
600
+ return !event.defaultPrevented;
601
+ }
494
602
  }
495
603
 
496
604
  class AppearanceObserver {
497
605
  constructor(delegate, element) {
498
606
  this.started = false;
499
- this.intersect = entries => {
607
+ this.intersect = (entries) => {
500
608
  const lastEntry = entries.slice(-1)[0];
501
609
  if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
502
610
  this.delegate.elementAppearedInViewport(this.element);
@@ -521,40 +629,29 @@ class AppearanceObserver {
521
629
  }
522
630
 
523
631
  class StreamMessage {
524
- constructor(html) {
525
- this.templateElement = document.createElement("template");
526
- this.templateElement.innerHTML = html;
632
+ constructor(fragment) {
633
+ this.fragment = importStreamElements(fragment);
527
634
  }
528
635
  static wrap(message) {
529
636
  if (typeof message == "string") {
530
- return new this(message);
637
+ return new this(createDocumentFragment(message));
531
638
  }
532
639
  else {
533
640
  return message;
534
641
  }
535
642
  }
536
- get fragment() {
537
- const fragment = document.createDocumentFragment();
538
- for (const element of this.foreignElements) {
539
- fragment.appendChild(document.importNode(element, true));
643
+ }
644
+ StreamMessage.contentType = "text/vnd.turbo-stream.html";
645
+ function importStreamElements(fragment) {
646
+ for (const element of fragment.querySelectorAll("turbo-stream")) {
647
+ const streamElement = document.importNode(element, true);
648
+ for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) {
649
+ inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));
540
650
  }
541
- return fragment;
542
- }
543
- get foreignElements() {
544
- return this.templateChildren.reduce((streamElements, child) => {
545
- if (child.tagName.toLowerCase() == "turbo-stream") {
546
- return [...streamElements, child];
547
- }
548
- else {
549
- return streamElements;
550
- }
551
- }, []);
552
- }
553
- get templateChildren() {
554
- return Array.from(this.templateElement.content.children);
651
+ element.replaceWith(streamElement);
555
652
  }
653
+ return fragment;
556
654
  }
557
- StreamMessage.contentType = "text/vnd.turbo-stream.html";
558
655
 
559
656
  var FormSubmissionState;
560
657
  (function (FormSubmissionState) {
@@ -573,9 +670,12 @@ var FormEnctype;
573
670
  })(FormEnctype || (FormEnctype = {}));
574
671
  function formEnctypeFromString(encoding) {
575
672
  switch (encoding.toLowerCase()) {
576
- case FormEnctype.multipart: return FormEnctype.multipart;
577
- case FormEnctype.plain: return FormEnctype.plain;
578
- default: return FormEnctype.urlEncoded;
673
+ case FormEnctype.multipart:
674
+ return FormEnctype.multipart;
675
+ case FormEnctype.plain:
676
+ return FormEnctype.plain;
677
+ default:
678
+ return FormEnctype.urlEncoded;
579
679
  }
580
680
  }
581
681
  class FormSubmission {
@@ -592,8 +692,8 @@ class FormSubmission {
592
692
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
593
693
  this.mustRedirect = mustRedirect;
594
694
  }
595
- static confirmMethod(message, element) {
596
- return confirm(message);
695
+ static confirmMethod(message, _element, _submitter) {
696
+ return Promise.resolve(confirm(message));
597
697
  }
598
698
  get method() {
599
699
  var _a;
@@ -602,8 +702,13 @@ class FormSubmission {
602
702
  }
603
703
  get action() {
604
704
  var _a;
605
- const formElementAction = typeof this.formElement.action === 'string' ? this.formElement.action : null;
606
- return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.getAttribute("action") || formElementAction || "";
705
+ const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
706
+ if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
707
+ return this.submitter.getAttribute("formaction") || "";
708
+ }
709
+ else {
710
+ return this.formElement.getAttribute("action") || formElementAction || "";
711
+ }
607
712
  }
608
713
  get body() {
609
714
  if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
@@ -625,16 +730,11 @@ class FormSubmission {
625
730
  return entries.concat(typeof value == "string" ? [[name, value]] : []);
626
731
  }, []);
627
732
  }
628
- get confirmationMessage() {
629
- return this.formElement.getAttribute("data-turbo-confirm");
630
- }
631
- get needsConfirmation() {
632
- return this.confirmationMessage !== null;
633
- }
634
733
  async start() {
635
734
  const { initialized, requesting } = FormSubmissionState;
636
- if (this.needsConfirmation) {
637
- const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
735
+ const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
736
+ if (typeof confirmationMessage === "string") {
737
+ const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);
638
738
  if (!answer) {
639
739
  return;
640
740
  }
@@ -658,14 +758,19 @@ class FormSubmission {
658
758
  if (token) {
659
759
  headers["X-CSRF-Token"] = token;
660
760
  }
661
- headers["Accept"] = [StreamMessage.contentType, headers["Accept"]].join(", ");
761
+ }
762
+ if (this.requestAcceptsTurboStreamResponse(request)) {
763
+ request.acceptResponseType(StreamMessage.contentType);
662
764
  }
663
765
  }
664
- requestStarted(request) {
766
+ requestStarted(_request) {
665
767
  var _a;
666
768
  this.state = FormSubmissionState.waiting;
667
769
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
668
- dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } });
770
+ dispatch("turbo:submit-start", {
771
+ target: this.formElement,
772
+ detail: { formSubmission: this },
773
+ });
669
774
  this.delegate.formSubmissionStarted(this);
670
775
  }
671
776
  requestPreventedHandlingResponse(request, response) {
@@ -693,23 +798,29 @@ class FormSubmission {
693
798
  this.result = { success: false, error };
694
799
  this.delegate.formSubmissionErrored(this, error);
695
800
  }
696
- requestFinished(request) {
801
+ requestFinished(_request) {
697
802
  var _a;
698
803
  this.state = FormSubmissionState.stopped;
699
804
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
700
- dispatch("turbo:submit-end", { target: this.formElement, detail: Object.assign({ formSubmission: this }, this.result) });
805
+ dispatch("turbo:submit-end", {
806
+ target: this.formElement,
807
+ detail: Object.assign({ formSubmission: this }, this.result),
808
+ });
701
809
  this.delegate.formSubmissionFinished(this);
702
810
  }
703
811
  requestMustRedirect(request) {
704
812
  return !request.isIdempotent && this.mustRedirect;
705
813
  }
814
+ requestAcceptsTurboStreamResponse(request) {
815
+ return !request.isIdempotent || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
816
+ }
706
817
  }
707
818
  function buildFormData(formElement, submitter) {
708
819
  const formData = new FormData(formElement);
709
820
  const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
710
821
  const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
711
- if (name && value != null && formData.get(name) != value) {
712
- formData.append(name, value);
822
+ if (name) {
823
+ formData.append(name, value || "");
713
824
  }
714
825
  return formData;
715
826
  }
@@ -723,15 +834,11 @@ function getCookieValue(cookieName) {
723
834
  }
724
835
  }
725
836
  }
726
- function getMetaContent(name) {
727
- const element = document.querySelector(`meta[name="${name}"]`);
728
- return element && element.content;
729
- }
730
837
  function responseSucceededWithoutRedirect(response) {
731
838
  return response.statusCode == 200 && !response.redirected;
732
839
  }
733
840
  function mergeFormDataEntries(url, entries) {
734
- const searchParams = new URLSearchParams;
841
+ const searchParams = new URLSearchParams();
735
842
  for (const [name, value] of entries) {
736
843
  if (value instanceof File)
737
844
  continue;
@@ -745,6 +852,9 @@ class Snapshot {
745
852
  constructor(element) {
746
853
  this.element = element;
747
854
  }
855
+ get activeElement() {
856
+ return this.element.ownerDocument.activeElement;
857
+ }
748
858
  get children() {
749
859
  return [...this.element.children];
750
860
  }
@@ -758,13 +868,20 @@ class Snapshot {
758
868
  return this.element.isConnected;
759
869
  }
760
870
  get firstAutofocusableElement() {
761
- return this.element.querySelector("[autofocus]");
871
+ const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
872
+ for (const element of this.element.querySelectorAll("[autofocus]")) {
873
+ if (element.closest(inertDisabledOrHidden) == null)
874
+ return element;
875
+ else
876
+ continue;
877
+ }
878
+ return null;
762
879
  }
763
880
  get permanentElements() {
764
- return [...this.element.querySelectorAll("[id][data-turbo-permanent]")];
881
+ return queryPermanentElementsAll(this.element);
765
882
  }
766
883
  getPermanentElementById(id) {
767
- return this.element.querySelector(`#${id}[data-turbo-permanent]`);
884
+ return getPermanentElementById(this.element, id);
768
885
  }
769
886
  getPermanentElementMapForSnapshot(snapshot) {
770
887
  const permanentElementMap = {};
@@ -778,36 +895,66 @@ class Snapshot {
778
895
  return permanentElementMap;
779
896
  }
780
897
  }
898
+ function getPermanentElementById(node, id) {
899
+ return node.querySelector(`#${id}[data-turbo-permanent]`);
900
+ }
901
+ function queryPermanentElementsAll(node) {
902
+ return node.querySelectorAll("[id][data-turbo-permanent]");
903
+ }
781
904
 
782
- class FormInterceptor {
783
- constructor(delegate, element) {
905
+ class FormSubmitObserver {
906
+ constructor(delegate, eventTarget) {
907
+ this.started = false;
908
+ this.submitCaptured = () => {
909
+ this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
910
+ this.eventTarget.addEventListener("submit", this.submitBubbled, false);
911
+ };
784
912
  this.submitBubbled = ((event) => {
785
- const form = event.target;
786
- if (!event.defaultPrevented && form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
913
+ if (!event.defaultPrevented) {
914
+ const form = event.target instanceof HTMLFormElement ? event.target : undefined;
787
915
  const submitter = event.submitter || undefined;
788
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
789
- if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
916
+ if (form &&
917
+ submissionDoesNotDismissDialog(form, submitter) &&
918
+ submissionDoesNotTargetIFrame(form, submitter) &&
919
+ this.delegate.willSubmitForm(form, submitter)) {
790
920
  event.preventDefault();
791
- event.stopImmediatePropagation();
792
- this.delegate.formSubmissionIntercepted(form, submitter);
921
+ this.delegate.formSubmitted(form, submitter);
793
922
  }
794
923
  }
795
924
  });
796
925
  this.delegate = delegate;
797
- this.element = element;
926
+ this.eventTarget = eventTarget;
798
927
  }
799
928
  start() {
800
- this.element.addEventListener("submit", this.submitBubbled);
929
+ if (!this.started) {
930
+ this.eventTarget.addEventListener("submit", this.submitCaptured, true);
931
+ this.started = true;
932
+ }
801
933
  }
802
934
  stop() {
803
- this.element.removeEventListener("submit", this.submitBubbled);
935
+ if (this.started) {
936
+ this.eventTarget.removeEventListener("submit", this.submitCaptured, true);
937
+ this.started = false;
938
+ }
939
+ }
940
+ }
941
+ function submissionDoesNotDismissDialog(form, submitter) {
942
+ const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
943
+ return method != "dialog";
944
+ }
945
+ function submissionDoesNotTargetIFrame(form, submitter) {
946
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
947
+ for (const element of document.getElementsByName(target)) {
948
+ if (element instanceof HTMLIFrameElement)
949
+ return false;
804
950
  }
951
+ return true;
805
952
  }
806
953
 
807
954
  class View {
808
955
  constructor(delegate, element) {
809
- this.resolveRenderPromise = (value) => { };
810
- this.resolveInterceptionPromise = (value) => { };
956
+ this.resolveRenderPromise = (_value) => { };
957
+ this.resolveInterceptionPromise = (_value) => { };
811
958
  this.delegate = delegate;
812
959
  this.element = element;
813
960
  }
@@ -852,15 +999,17 @@ class View {
852
999
  const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
853
1000
  if (shouldRender) {
854
1001
  try {
855
- this.renderPromise = new Promise(resolve => this.resolveRenderPromise = resolve);
1002
+ this.renderPromise = new Promise((resolve) => (this.resolveRenderPromise = resolve));
856
1003
  this.renderer = renderer;
857
- this.prepareToRenderSnapshot(renderer);
858
- const renderInterception = new Promise(resolve => this.resolveInterceptionPromise = resolve);
859
- const immediateRender = this.delegate.allowsImmediateRender(snapshot, this.resolveInterceptionPromise);
1004
+ await this.prepareToRenderSnapshot(renderer);
1005
+ const renderInterception = new Promise((resolve) => (this.resolveInterceptionPromise = resolve));
1006
+ const options = { resume: this.resolveInterceptionPromise, render: this.renderer.renderElement };
1007
+ const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
860
1008
  if (!immediateRender)
861
1009
  await renderInterception;
862
1010
  await this.renderSnapshot(renderer);
863
1011
  this.delegate.viewRenderedSnapshot(snapshot, isPreview);
1012
+ this.delegate.preloadOnLoadLinksForView(this.element);
864
1013
  this.finishRenderingSnapshot(renderer);
865
1014
  }
866
1015
  finally {
@@ -870,15 +1019,15 @@ class View {
870
1019
  }
871
1020
  }
872
1021
  else {
873
- this.invalidate();
1022
+ this.invalidate(renderer.reloadReason);
874
1023
  }
875
1024
  }
876
- invalidate() {
877
- this.delegate.viewInvalidated();
1025
+ invalidate(reason) {
1026
+ this.delegate.viewInvalidated(reason);
878
1027
  }
879
- prepareToRenderSnapshot(renderer) {
1028
+ async prepareToRenderSnapshot(renderer) {
880
1029
  this.markAsPreview(renderer.isPreview);
881
- renderer.prepareToRender();
1030
+ await renderer.prepareToRender();
882
1031
  }
883
1032
  markAsPreview(isPreview) {
884
1033
  if (isPreview) {
@@ -905,65 +1054,125 @@ class FrameView extends View {
905
1054
  }
906
1055
  }
907
1056
 
908
- class LinkInterceptor {
909
- constructor(delegate, element) {
910
- this.clickBubbled = (event) => {
911
- if (this.respondsToEventTarget(event.target)) {
912
- this.clickEvent = event;
913
- }
914
- else {
915
- delete this.clickEvent;
916
- }
1057
+ class LinkClickObserver {
1058
+ constructor(delegate, eventTarget) {
1059
+ this.started = false;
1060
+ this.clickCaptured = () => {
1061
+ this.eventTarget.removeEventListener("click", this.clickBubbled, false);
1062
+ this.eventTarget.addEventListener("click", this.clickBubbled, false);
917
1063
  };
918
- this.linkClicked = ((event) => {
919
- if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
920
- if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) {
921
- this.clickEvent.preventDefault();
922
- event.preventDefault();
923
- this.delegate.linkClickIntercepted(event.target, event.detail.url);
1064
+ this.clickBubbled = (event) => {
1065
+ if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
1066
+ const target = (event.composedPath && event.composedPath()[0]) || event.target;
1067
+ const link = this.findLinkFromClickTarget(target);
1068
+ if (link && doesNotTargetIFrame(link)) {
1069
+ const location = this.getLocationForLink(link);
1070
+ if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1071
+ event.preventDefault();
1072
+ this.delegate.followedLinkToLocation(link, location);
1073
+ }
924
1074
  }
925
1075
  }
926
- delete this.clickEvent;
927
- });
928
- this.willVisit = () => {
929
- delete this.clickEvent;
930
1076
  };
931
1077
  this.delegate = delegate;
932
- this.element = element;
1078
+ this.eventTarget = eventTarget;
1079
+ }
1080
+ start() {
1081
+ if (!this.started) {
1082
+ this.eventTarget.addEventListener("click", this.clickCaptured, true);
1083
+ this.started = true;
1084
+ }
1085
+ }
1086
+ stop() {
1087
+ if (this.started) {
1088
+ this.eventTarget.removeEventListener("click", this.clickCaptured, true);
1089
+ this.started = false;
1090
+ }
1091
+ }
1092
+ clickEventIsSignificant(event) {
1093
+ return !((event.target && event.target.isContentEditable) ||
1094
+ event.defaultPrevented ||
1095
+ event.which > 1 ||
1096
+ event.altKey ||
1097
+ event.ctrlKey ||
1098
+ event.metaKey ||
1099
+ event.shiftKey);
1100
+ }
1101
+ findLinkFromClickTarget(target) {
1102
+ if (target instanceof Element) {
1103
+ return target.closest("a[href]:not([target^=_]):not([download])");
1104
+ }
1105
+ }
1106
+ getLocationForLink(link) {
1107
+ return expandURL(link.getAttribute("href") || "");
1108
+ }
1109
+ }
1110
+ function doesNotTargetIFrame(anchor) {
1111
+ for (const element of document.getElementsByName(anchor.target)) {
1112
+ if (element instanceof HTMLIFrameElement)
1113
+ return false;
1114
+ }
1115
+ return true;
1116
+ }
1117
+
1118
+ class FormLinkClickObserver {
1119
+ constructor(delegate, element) {
1120
+ this.delegate = delegate;
1121
+ this.linkClickObserver = new LinkClickObserver(this, element);
933
1122
  }
934
1123
  start() {
935
- this.element.addEventListener("click", this.clickBubbled);
936
- document.addEventListener("turbo:click", this.linkClicked);
937
- document.addEventListener("turbo:before-visit", this.willVisit);
1124
+ this.linkClickObserver.start();
938
1125
  }
939
1126
  stop() {
940
- this.element.removeEventListener("click", this.clickBubbled);
941
- document.removeEventListener("turbo:click", this.linkClicked);
942
- document.removeEventListener("turbo:before-visit", this.willVisit);
1127
+ this.linkClickObserver.stop();
943
1128
  }
944
- respondsToEventTarget(target) {
945
- const element = target instanceof Element
946
- ? target
947
- : target instanceof Node
948
- ? target.parentElement
949
- : null;
950
- return element && element.closest("turbo-frame, html") == this.element;
1129
+ willFollowLinkToLocation(link, location, originalEvent) {
1130
+ return (this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) &&
1131
+ link.hasAttribute("data-turbo-method"));
1132
+ }
1133
+ followedLinkToLocation(link, location) {
1134
+ const action = location.href;
1135
+ const form = document.createElement("form");
1136
+ form.setAttribute("data-turbo", "true");
1137
+ form.setAttribute("action", action);
1138
+ form.setAttribute("hidden", "");
1139
+ const method = link.getAttribute("data-turbo-method");
1140
+ if (method)
1141
+ form.setAttribute("method", method);
1142
+ const turboFrame = link.getAttribute("data-turbo-frame");
1143
+ if (turboFrame)
1144
+ form.setAttribute("data-turbo-frame", turboFrame);
1145
+ const turboAction = link.getAttribute("data-turbo-action");
1146
+ if (turboAction)
1147
+ form.setAttribute("data-turbo-action", turboAction);
1148
+ const turboConfirm = link.getAttribute("data-turbo-confirm");
1149
+ if (turboConfirm)
1150
+ form.setAttribute("data-turbo-confirm", turboConfirm);
1151
+ const turboStream = link.hasAttribute("data-turbo-stream");
1152
+ if (turboStream)
1153
+ form.setAttribute("data-turbo-stream", "");
1154
+ this.delegate.submittedFormLinkToLocation(link, location, form);
1155
+ document.body.appendChild(form);
1156
+ form.addEventListener("turbo:submit-end", () => form.remove(), { once: true });
1157
+ requestAnimationFrame(() => form.requestSubmit());
951
1158
  }
952
1159
  }
953
1160
 
954
1161
  class Bardo {
955
- constructor(permanentElementMap) {
1162
+ constructor(delegate, permanentElementMap) {
1163
+ this.delegate = delegate;
956
1164
  this.permanentElementMap = permanentElementMap;
957
1165
  }
958
- static preservingPermanentElements(permanentElementMap, callback) {
959
- const bardo = new this(permanentElementMap);
1166
+ static preservingPermanentElements(delegate, permanentElementMap, callback) {
1167
+ const bardo = new this(delegate, permanentElementMap);
960
1168
  bardo.enter();
961
1169
  callback();
962
1170
  bardo.leave();
963
1171
  }
964
1172
  enter() {
965
1173
  for (const id in this.permanentElementMap) {
966
- const [, newPermanentElement] = this.permanentElementMap[id];
1174
+ const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
1175
+ this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
967
1176
  this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
968
1177
  }
969
1178
  }
@@ -972,6 +1181,7 @@ class Bardo {
972
1181
  const [currentPermanentElement] = this.permanentElementMap[id];
973
1182
  this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
974
1183
  this.replacePlaceholderWithPermanentElement(currentPermanentElement);
1184
+ this.delegate.leavingBardo(currentPermanentElement);
975
1185
  }
976
1186
  }
977
1187
  replaceNewPermanentElementWithPlaceholder(permanentElement) {
@@ -987,7 +1197,7 @@ class Bardo {
987
1197
  placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
988
1198
  }
989
1199
  getPlaceholderById(id) {
990
- return this.placeholders.find(element => element.content == id);
1200
+ return this.placeholders.find((element) => element.content == id);
991
1201
  }
992
1202
  get placeholders() {
993
1203
  return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")];
@@ -1001,16 +1211,21 @@ function createPlaceholderForPermanentElement(permanentElement) {
1001
1211
  }
1002
1212
 
1003
1213
  class Renderer {
1004
- constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
1214
+ constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1215
+ this.activeElement = null;
1005
1216
  this.currentSnapshot = currentSnapshot;
1006
1217
  this.newSnapshot = newSnapshot;
1007
1218
  this.isPreview = isPreview;
1008
1219
  this.willRender = willRender;
1009
- this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
1220
+ this.renderElement = renderElement;
1221
+ this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));
1010
1222
  }
1011
1223
  get shouldRender() {
1012
1224
  return true;
1013
1225
  }
1226
+ get reloadReason() {
1227
+ return;
1228
+ }
1014
1229
  prepareToRender() {
1015
1230
  return;
1016
1231
  }
@@ -1020,23 +1235,8 @@ class Renderer {
1020
1235
  delete this.resolvingFunctions;
1021
1236
  }
1022
1237
  }
1023
- createScriptElement(element) {
1024
- if (element.getAttribute("data-turbo-eval") == "false") {
1025
- return element;
1026
- }
1027
- else {
1028
- const createdScriptElement = document.createElement("script");
1029
- if (this.cspNonce) {
1030
- createdScriptElement.nonce = this.cspNonce;
1031
- }
1032
- createdScriptElement.textContent = element.textContent;
1033
- createdScriptElement.async = false;
1034
- copyElementAttributes(createdScriptElement, element);
1035
- return createdScriptElement;
1036
- }
1037
- }
1038
1238
  preservingPermanentElements(callback) {
1039
- Bardo.preservingPermanentElements(this.permanentElementMap, callback);
1239
+ Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1040
1240
  }
1041
1241
  focusFirstAutofocusableElement() {
1042
1242
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1044,6 +1244,19 @@ class Renderer {
1044
1244
  element.focus();
1045
1245
  }
1046
1246
  }
1247
+ enteringBardo(currentPermanentElement) {
1248
+ if (this.activeElement)
1249
+ return;
1250
+ if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
1251
+ this.activeElement = this.currentSnapshot.activeElement;
1252
+ }
1253
+ }
1254
+ leavingBardo(currentPermanentElement) {
1255
+ if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
1256
+ this.activeElement.focus();
1257
+ this.activeElement = null;
1258
+ }
1259
+ }
1047
1260
  get connectedSnapshot() {
1048
1261
  return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
1049
1262
  }
@@ -1056,21 +1269,28 @@ class Renderer {
1056
1269
  get permanentElementMap() {
1057
1270
  return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1058
1271
  }
1059
- get cspNonce() {
1060
- var _a;
1061
- return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
1062
- }
1063
- }
1064
- function copyElementAttributes(destinationElement, sourceElement) {
1065
- for (const { name, value } of [...sourceElement.attributes]) {
1066
- destinationElement.setAttribute(name, value);
1067
- }
1068
1272
  }
1069
1273
  function elementIsFocusable(element) {
1070
1274
  return element && typeof element.focus == "function";
1071
1275
  }
1072
1276
 
1073
1277
  class FrameRenderer extends Renderer {
1278
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1279
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1280
+ this.delegate = delegate;
1281
+ }
1282
+ static renderElement(currentElement, newElement) {
1283
+ var _a;
1284
+ const destinationRange = document.createRange();
1285
+ destinationRange.selectNodeContents(currentElement);
1286
+ destinationRange.deleteContents();
1287
+ const frameElement = newElement;
1288
+ const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1289
+ if (sourceRange) {
1290
+ sourceRange.selectNodeContents(frameElement);
1291
+ currentElement.appendChild(sourceRange.extractContents());
1292
+ }
1293
+ }
1074
1294
  get shouldRender() {
1075
1295
  return true;
1076
1296
  }
@@ -1086,23 +1306,16 @@ class FrameRenderer extends Renderer {
1086
1306
  this.activateScriptElements();
1087
1307
  }
1088
1308
  loadFrameElement() {
1089
- var _a;
1090
- const destinationRange = document.createRange();
1091
- destinationRange.selectNodeContents(this.currentElement);
1092
- destinationRange.deleteContents();
1093
- const frameElement = this.newElement;
1094
- const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1095
- if (sourceRange) {
1096
- sourceRange.selectNodeContents(frameElement);
1097
- this.currentElement.appendChild(sourceRange.extractContents());
1098
- }
1309
+ this.delegate.willRenderFrame(this.currentElement, this.newElement);
1310
+ this.renderElement(this.currentElement, this.newElement);
1099
1311
  }
1100
1312
  scrollFrameIntoView() {
1101
1313
  if (this.currentElement.autoscroll || this.newElement.autoscroll) {
1102
1314
  const element = this.currentElement.firstElementChild;
1103
1315
  const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
1316
+ const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
1104
1317
  if (element) {
1105
- element.scrollIntoView({ block });
1318
+ element.scrollIntoView({ block, behavior });
1106
1319
  return true;
1107
1320
  }
1108
1321
  }
@@ -1110,7 +1323,7 @@ class FrameRenderer extends Renderer {
1110
1323
  }
1111
1324
  activateScriptElements() {
1112
1325
  for (const inertScriptElement of this.newScriptElements) {
1113
- const activatedScriptElement = this.createScriptElement(inertScriptElement);
1326
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
1114
1327
  inertScriptElement.replaceWith(activatedScriptElement);
1115
1328
  }
1116
1329
  }
@@ -1126,6 +1339,14 @@ function readScrollLogicalPosition(value, defaultValue) {
1126
1339
  return defaultValue;
1127
1340
  }
1128
1341
  }
1342
+ function readScrollBehavior(value, defaultValue) {
1343
+ if (value == "auto" || value == "smooth") {
1344
+ return value;
1345
+ }
1346
+ else {
1347
+ return defaultValue;
1348
+ }
1349
+ }
1129
1350
 
1130
1351
  class ProgressBar {
1131
1352
  constructor() {
@@ -1149,7 +1370,7 @@ class ProgressBar {
1149
1370
  left: 0;
1150
1371
  height: 3px;
1151
1372
  background: #0076ff;
1152
- z-index: 9999;
1373
+ z-index: 2147483647;
1153
1374
  transition:
1154
1375
  width ${ProgressBar.animationDuration}ms ease-out,
1155
1376
  opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
@@ -1208,13 +1429,16 @@ class ProgressBar {
1208
1429
  }
1209
1430
  refresh() {
1210
1431
  requestAnimationFrame(() => {
1211
- this.progressElement.style.width = `${10 + (this.value * 90)}%`;
1432
+ this.progressElement.style.width = `${10 + this.value * 90}%`;
1212
1433
  });
1213
1434
  }
1214
1435
  createStylesheetElement() {
1215
1436
  const element = document.createElement("style");
1216
1437
  element.type = "text/css";
1217
1438
  element.textContent = ProgressBar.defaultCSS;
1439
+ if (this.cspNonce) {
1440
+ element.nonce = this.cspNonce;
1441
+ }
1218
1442
  return element;
1219
1443
  }
1220
1444
  createProgressElement() {
@@ -1222,6 +1446,9 @@ class ProgressBar {
1222
1446
  element.className = "turbo-progress-bar";
1223
1447
  return element;
1224
1448
  }
1449
+ get cspNonce() {
1450
+ return getMetaContent("csp-nonce");
1451
+ }
1225
1452
  }
1226
1453
  ProgressBar.animationDuration = 300;
1227
1454
 
@@ -1238,14 +1465,14 @@ class HeadSnapshot extends Snapshot {
1238
1465
  : {
1239
1466
  type: elementType(element),
1240
1467
  tracked: elementIsTracked(element),
1241
- elements: []
1468
+ elements: [],
1242
1469
  };
1243
1470
  return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });
1244
1471
  }, {});
1245
1472
  }
1246
1473
  get trackedElementSignature() {
1247
1474
  return Object.keys(this.detailsByOuterHTML)
1248
- .filter(outerHTML => this.detailsByOuterHTML[outerHTML].tracked)
1475
+ .filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked)
1249
1476
  .join("");
1250
1477
  }
1251
1478
  getScriptElementsNotInSnapshot(snapshot) {
@@ -1256,8 +1483,8 @@ class HeadSnapshot extends Snapshot {
1256
1483
  }
1257
1484
  getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
1258
1485
  return Object.keys(this.detailsByOuterHTML)
1259
- .filter(outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))
1260
- .map(outerHTML => this.detailsByOuterHTML[outerHTML])
1486
+ .filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML))
1487
+ .map((outerHTML) => this.detailsByOuterHTML[outerHTML])
1261
1488
  .filter(({ type }) => type == matchedType)
1262
1489
  .map(({ elements: [element] }) => element);
1263
1490
  }
@@ -1277,13 +1504,11 @@ class HeadSnapshot extends Snapshot {
1277
1504
  }
1278
1505
  getMetaValue(name) {
1279
1506
  const element = this.findMetaElementByName(name);
1280
- return element
1281
- ? element.getAttribute("content")
1282
- : null;
1507
+ return element ? element.getAttribute("content") : null;
1283
1508
  }
1284
1509
  findMetaElementByName(name) {
1285
1510
  return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
1286
- const { elements: [element] } = this.detailsByOuterHTML[outerHTML];
1511
+ const { elements: [element], } = this.detailsByOuterHTML[outerHTML];
1287
1512
  return elementIsMetaElementWithName(element, name) ? element : result;
1288
1513
  }, undefined);
1289
1514
  }
@@ -1300,19 +1525,19 @@ function elementIsTracked(element) {
1300
1525
  return element.getAttribute("data-turbo-track") == "reload";
1301
1526
  }
1302
1527
  function elementIsScript(element) {
1303
- const tagName = element.tagName.toLowerCase();
1528
+ const tagName = element.localName;
1304
1529
  return tagName == "script";
1305
1530
  }
1306
1531
  function elementIsNoscript(element) {
1307
- const tagName = element.tagName.toLowerCase();
1532
+ const tagName = element.localName;
1308
1533
  return tagName == "noscript";
1309
1534
  }
1310
1535
  function elementIsStylesheet(element) {
1311
- const tagName = element.tagName.toLowerCase();
1536
+ const tagName = element.localName;
1312
1537
  return tagName == "style" || (tagName == "link" && element.getAttribute("rel") == "stylesheet");
1313
1538
  }
1314
1539
  function elementIsMetaElementWithName(element, name) {
1315
- const tagName = element.tagName.toLowerCase();
1540
+ const tagName = element.localName;
1316
1541
  return tagName == "meta" && element.getAttribute("name") == name;
1317
1542
  }
1318
1543
  function elementWithoutNonce(element) {
@@ -1337,7 +1562,20 @@ class PageSnapshot extends Snapshot {
1337
1562
  return new this(body, new HeadSnapshot(head));
1338
1563
  }
1339
1564
  clone() {
1340
- return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
1565
+ const clonedElement = this.element.cloneNode(true);
1566
+ const selectElements = this.element.querySelectorAll("select");
1567
+ const clonedSelectElements = clonedElement.querySelectorAll("select");
1568
+ for (const [index, source] of selectElements.entries()) {
1569
+ const clone = clonedSelectElements[index];
1570
+ for (const option of clone.selectedOptions)
1571
+ option.selected = false;
1572
+ for (const option of source.selectedOptions)
1573
+ clone.options[option.index].selected = true;
1574
+ }
1575
+ for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
1576
+ clonedPasswordInput.value = "";
1577
+ }
1578
+ return new PageSnapshot(clonedElement, this.headSnapshot);
1341
1579
  }
1342
1580
  get headElement() {
1343
1581
  return this.headSnapshot.element;
@@ -1384,6 +1622,9 @@ const defaultOptions = {
1384
1622
  historyChanged: false,
1385
1623
  visitCachedSnapshot: () => { },
1386
1624
  willRender: true,
1625
+ updateHistory: true,
1626
+ shouldCacheSnapshot: true,
1627
+ acceptsStreamResponse: false,
1387
1628
  };
1388
1629
  var SystemStatusCode;
1389
1630
  (function (SystemStatusCode) {
@@ -1398,12 +1639,14 @@ class Visit {
1398
1639
  this.followedRedirect = false;
1399
1640
  this.historyChanged = false;
1400
1641
  this.scrolled = false;
1642
+ this.shouldCacheSnapshot = true;
1643
+ this.acceptsStreamResponse = false;
1401
1644
  this.snapshotCached = false;
1402
1645
  this.state = VisitState.initialized;
1403
1646
  this.delegate = delegate;
1404
1647
  this.location = location;
1405
1648
  this.restorationIdentifier = restorationIdentifier || uuid();
1406
- const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender } = Object.assign(Object.assign({}, defaultOptions), options);
1649
+ const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, } = Object.assign(Object.assign({}, defaultOptions), options);
1407
1650
  this.action = action;
1408
1651
  this.historyChanged = historyChanged;
1409
1652
  this.referrer = referrer;
@@ -1412,7 +1655,10 @@ class Visit {
1412
1655
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
1413
1656
  this.visitCachedSnapshot = visitCachedSnapshot;
1414
1657
  this.willRender = willRender;
1658
+ this.updateHistory = updateHistory;
1415
1659
  this.scrolled = !willRender;
1660
+ this.shouldCacheSnapshot = shouldCacheSnapshot;
1661
+ this.acceptsStreamResponse = acceptsStreamResponse;
1416
1662
  }
1417
1663
  get adapter() {
1418
1664
  return this.delegate.adapter;
@@ -1450,9 +1696,11 @@ class Visit {
1450
1696
  if (this.state == VisitState.started) {
1451
1697
  this.recordTimingMetric(TimingMetric.visitEnd);
1452
1698
  this.state = VisitState.completed;
1453
- this.adapter.visitCompleted(this);
1454
- this.delegate.visitCompleted(this);
1455
1699
  this.followRedirect();
1700
+ if (!this.followedRedirect) {
1701
+ this.adapter.visitCompleted(this);
1702
+ this.delegate.visitCompleted(this);
1703
+ }
1456
1704
  }
1457
1705
  }
1458
1706
  fail() {
@@ -1463,9 +1711,9 @@ class Visit {
1463
1711
  }
1464
1712
  changeHistory() {
1465
1713
  var _a;
1466
- if (!this.historyChanged) {
1714
+ if (!this.historyChanged && this.updateHistory) {
1467
1715
  const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
1468
- const method = this.getHistoryMethodForAction(actionForHistory);
1716
+ const method = getHistoryMethodForAction(actionForHistory);
1469
1717
  this.history.update(method, this.location, this.restorationIdentifier);
1470
1718
  this.historyChanged = true;
1471
1719
  }
@@ -1510,16 +1758,18 @@ class Visit {
1510
1758
  if (this.response) {
1511
1759
  const { statusCode, responseHTML } = this.response;
1512
1760
  this.render(async () => {
1513
- this.cacheSnapshot();
1761
+ if (this.shouldCacheSnapshot)
1762
+ this.cacheSnapshot();
1514
1763
  if (this.view.renderPromise)
1515
1764
  await this.view.renderPromise;
1516
1765
  if (isSuccessful(statusCode) && responseHTML != null) {
1517
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
1766
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
1767
+ this.performScroll();
1518
1768
  this.adapter.visitRendered(this);
1519
1769
  this.complete();
1520
1770
  }
1521
1771
  else {
1522
- await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
1772
+ await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
1523
1773
  this.adapter.visitRendered(this);
1524
1774
  this.fail();
1525
1775
  }
@@ -1554,7 +1804,8 @@ class Visit {
1554
1804
  else {
1555
1805
  if (this.view.renderPromise)
1556
1806
  await this.view.renderPromise;
1557
- await this.view.renderPage(snapshot, isPreview, this.willRender);
1807
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
1808
+ this.performScroll();
1558
1809
  this.adapter.visitRendered(this);
1559
1810
  if (!isPreview) {
1560
1811
  this.complete();
@@ -1567,8 +1818,8 @@ class Visit {
1567
1818
  var _a;
1568
1819
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1569
1820
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1570
- action: 'replace',
1571
- response: this.response
1821
+ action: "replace",
1822
+ response: this.response,
1572
1823
  });
1573
1824
  this.followedRedirect = true;
1574
1825
  }
@@ -1577,20 +1828,28 @@ class Visit {
1577
1828
  if (this.isSamePage) {
1578
1829
  this.render(async () => {
1579
1830
  this.cacheSnapshot();
1831
+ this.performScroll();
1580
1832
  this.adapter.visitRendered(this);
1581
1833
  });
1582
1834
  }
1583
1835
  }
1836
+ prepareHeadersForRequest(headers, request) {
1837
+ if (this.acceptsStreamResponse) {
1838
+ request.acceptResponseType(StreamMessage.contentType);
1839
+ }
1840
+ }
1584
1841
  requestStarted() {
1585
1842
  this.startRequest();
1586
1843
  }
1587
- requestPreventedHandlingResponse(request, response) {
1588
- }
1844
+ requestPreventedHandlingResponse(_request, _response) { }
1589
1845
  async requestSucceededWithResponse(request, response) {
1590
1846
  const responseHTML = await response.responseHTML;
1591
1847
  const { redirected, statusCode } = response;
1592
1848
  if (responseHTML == undefined) {
1593
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1849
+ this.recordResponse({
1850
+ statusCode: SystemStatusCode.contentTypeMismatch,
1851
+ redirected,
1852
+ });
1594
1853
  }
1595
1854
  else {
1596
1855
  this.redirectedToLocation = response.redirected ? response.location : undefined;
@@ -1601,20 +1860,26 @@ class Visit {
1601
1860
  const responseHTML = await response.responseHTML;
1602
1861
  const { redirected, statusCode } = response;
1603
1862
  if (responseHTML == undefined) {
1604
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1863
+ this.recordResponse({
1864
+ statusCode: SystemStatusCode.contentTypeMismatch,
1865
+ redirected,
1866
+ });
1605
1867
  }
1606
1868
  else {
1607
1869
  this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
1608
1870
  }
1609
1871
  }
1610
- requestErrored(request, error) {
1611
- this.recordResponse({ statusCode: SystemStatusCode.networkFailure, redirected: false });
1872
+ requestErrored(_request, _error) {
1873
+ this.recordResponse({
1874
+ statusCode: SystemStatusCode.networkFailure,
1875
+ redirected: false,
1876
+ });
1612
1877
  }
1613
1878
  requestFinished() {
1614
1879
  this.finishRequest();
1615
1880
  }
1616
1881
  performScroll() {
1617
- if (!this.scrolled) {
1882
+ if (!this.scrolled && !this.view.forceReloaded) {
1618
1883
  if (this.action == "restore") {
1619
1884
  this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
1620
1885
  }
@@ -1649,9 +1914,11 @@ class Visit {
1649
1914
  }
1650
1915
  getHistoryMethodForAction(action) {
1651
1916
  switch (action) {
1652
- case "replace": return history.replaceState;
1917
+ case "replace":
1918
+ return history.replaceState;
1653
1919
  case "advance":
1654
- case "restore": return history.pushState;
1920
+ case "restore":
1921
+ return history.pushState;
1655
1922
  }
1656
1923
  }
1657
1924
  hasPreloadedResponse() {
@@ -1670,18 +1937,17 @@ class Visit {
1670
1937
  }
1671
1938
  cacheSnapshot() {
1672
1939
  if (!this.snapshotCached) {
1673
- this.view.cacheSnapshot().then(snapshot => snapshot && this.visitCachedSnapshot(snapshot));
1940
+ this.view.cacheSnapshot().then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot));
1674
1941
  this.snapshotCached = true;
1675
1942
  }
1676
1943
  }
1677
1944
  async render(callback) {
1678
1945
  this.cancelRender();
1679
- await new Promise(resolve => {
1946
+ await new Promise((resolve) => {
1680
1947
  this.frame = requestAnimationFrame(() => resolve());
1681
1948
  });
1682
1949
  await callback();
1683
1950
  delete this.frame;
1684
- this.performScroll();
1685
1951
  }
1686
1952
  cancelRender() {
1687
1953
  if (this.frame) {
@@ -1696,19 +1962,19 @@ function isSuccessful(statusCode) {
1696
1962
 
1697
1963
  class BrowserAdapter {
1698
1964
  constructor(session) {
1699
- this.progressBar = new ProgressBar;
1965
+ this.progressBar = new ProgressBar();
1700
1966
  this.showProgressBar = () => {
1701
1967
  this.progressBar.show();
1702
1968
  };
1703
1969
  this.session = session;
1704
1970
  }
1705
1971
  visitProposedToLocation(location, options) {
1706
- this.navigator.startVisit(location, uuid(), options);
1972
+ this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
1707
1973
  }
1708
1974
  visitStarted(visit) {
1975
+ this.location = visit.location;
1709
1976
  visit.loadCachedSnapshot();
1710
1977
  visit.issueRequest();
1711
- visit.changeHistory();
1712
1978
  visit.goToSamePageAnchor();
1713
1979
  }
1714
1980
  visitRequestStarted(visit) {
@@ -1728,29 +1994,31 @@ class BrowserAdapter {
1728
1994
  case SystemStatusCode.networkFailure:
1729
1995
  case SystemStatusCode.timeoutFailure:
1730
1996
  case SystemStatusCode.contentTypeMismatch:
1731
- return this.reload();
1997
+ return this.reload({
1998
+ reason: "request_failed",
1999
+ context: {
2000
+ statusCode,
2001
+ },
2002
+ });
1732
2003
  default:
1733
2004
  return visit.loadResponse();
1734
2005
  }
1735
2006
  }
1736
- visitRequestFinished(visit) {
2007
+ visitRequestFinished(_visit) {
1737
2008
  this.progressBar.setValue(1);
1738
2009
  this.hideVisitProgressBar();
1739
2010
  }
1740
- visitCompleted(visit) {
1741
- }
1742
- pageInvalidated() {
1743
- this.reload();
1744
- }
1745
- visitFailed(visit) {
2011
+ visitCompleted(_visit) { }
2012
+ pageInvalidated(reason) {
2013
+ this.reload(reason);
1746
2014
  }
1747
- visitRendered(visit) {
1748
- }
1749
- formSubmissionStarted(formSubmission) {
2015
+ visitFailed(_visit) { }
2016
+ visitRendered(_visit) { }
2017
+ formSubmissionStarted(_formSubmission) {
1750
2018
  this.progressBar.setValue(0);
1751
2019
  this.showFormProgressBarAfterDelay();
1752
2020
  }
1753
- formSubmissionFinished(formSubmission) {
2021
+ formSubmissionFinished(_formSubmission) {
1754
2022
  this.progressBar.setValue(1);
1755
2023
  this.hideFormProgressBar();
1756
2024
  }
@@ -1776,8 +2044,11 @@ class BrowserAdapter {
1776
2044
  delete this.formProgressBarTimeout;
1777
2045
  }
1778
2046
  }
1779
- reload() {
1780
- window.location.reload();
2047
+ reload(reason) {
2048
+ dispatch("turbo:reload", { detail: reason });
2049
+ if (!this.location)
2050
+ return;
2051
+ window.location.href = this.location.toString();
1781
2052
  }
1782
2053
  get navigator() {
1783
2054
  return this.session.navigator;
@@ -1787,96 +2058,70 @@ class BrowserAdapter {
1787
2058
  class CacheObserver {
1788
2059
  constructor() {
1789
2060
  this.started = false;
1790
- }
1791
- start() {
1792
- if (!this.started) {
1793
- this.started = true;
1794
- addEventListener("turbo:before-cache", this.removeStaleElements, false);
1795
- }
1796
- }
1797
- stop() {
1798
- if (this.started) {
1799
- this.started = false;
1800
- removeEventListener("turbo:before-cache", this.removeStaleElements, false);
1801
- }
1802
- }
1803
- removeStaleElements() {
1804
- const staleElements = [...document.querySelectorAll('[data-turbo-cache="false"]')];
1805
- for (const element of staleElements) {
1806
- element.remove();
1807
- }
1808
- }
1809
- }
1810
-
1811
- class FormSubmitObserver {
1812
- constructor(delegate) {
1813
- this.started = false;
1814
- this.submitCaptured = () => {
1815
- removeEventListener("submit", this.submitBubbled, false);
1816
- addEventListener("submit", this.submitBubbled, false);
1817
- };
1818
- this.submitBubbled = ((event) => {
1819
- if (!event.defaultPrevented) {
1820
- const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1821
- const submitter = event.submitter || undefined;
1822
- if (form) {
1823
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
1824
- if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
1825
- event.preventDefault();
1826
- this.delegate.formSubmitted(form, submitter);
1827
- }
1828
- }
2061
+ this.removeStaleElements = ((_event) => {
2062
+ const staleElements = [...document.querySelectorAll('[data-turbo-cache="false"]')];
2063
+ for (const element of staleElements) {
2064
+ element.remove();
1829
2065
  }
1830
2066
  });
1831
- this.delegate = delegate;
1832
2067
  }
1833
2068
  start() {
1834
2069
  if (!this.started) {
1835
- addEventListener("submit", this.submitCaptured, true);
1836
2070
  this.started = true;
2071
+ addEventListener("turbo:before-cache", this.removeStaleElements, false);
1837
2072
  }
1838
2073
  }
1839
2074
  stop() {
1840
2075
  if (this.started) {
1841
- removeEventListener("submit", this.submitCaptured, true);
1842
2076
  this.started = false;
2077
+ removeEventListener("turbo:before-cache", this.removeStaleElements, false);
1843
2078
  }
1844
2079
  }
1845
2080
  }
1846
2081
 
1847
2082
  class FrameRedirector {
1848
- constructor(element) {
2083
+ constructor(session, element) {
2084
+ this.session = session;
1849
2085
  this.element = element;
1850
- this.linkInterceptor = new LinkInterceptor(this, element);
1851
- this.formInterceptor = new FormInterceptor(this, element);
2086
+ this.linkClickObserver = new LinkClickObserver(this, element);
2087
+ this.formSubmitObserver = new FormSubmitObserver(this, element);
1852
2088
  }
1853
2089
  start() {
1854
- this.linkInterceptor.start();
1855
- this.formInterceptor.start();
2090
+ this.linkClickObserver.start();
2091
+ this.formSubmitObserver.start();
1856
2092
  }
1857
2093
  stop() {
1858
- this.linkInterceptor.stop();
1859
- this.formInterceptor.stop();
2094
+ this.linkClickObserver.stop();
2095
+ this.formSubmitObserver.stop();
1860
2096
  }
1861
- shouldInterceptLinkClick(element, url) {
1862
- return this.shouldRedirect(element);
2097
+ willFollowLinkToLocation(element, location, event) {
2098
+ return this.shouldRedirect(element) && this.frameAllowsVisitingLocation(element, location, event);
1863
2099
  }
1864
- linkClickIntercepted(element, url) {
2100
+ followedLinkToLocation(element, url) {
1865
2101
  const frame = this.findFrameElement(element);
1866
2102
  if (frame) {
1867
- frame.delegate.linkClickIntercepted(element, url);
2103
+ frame.delegate.followedLinkToLocation(element, url);
1868
2104
  }
1869
2105
  }
1870
- shouldInterceptFormSubmission(element, submitter) {
1871
- return this.shouldSubmit(element, submitter);
2106
+ willSubmitForm(element, submitter) {
2107
+ return (element.closest("turbo-frame") == null &&
2108
+ this.shouldSubmit(element, submitter) &&
2109
+ this.shouldRedirect(element, submitter));
1872
2110
  }
1873
- formSubmissionIntercepted(element, submitter) {
2111
+ formSubmitted(element, submitter) {
1874
2112
  const frame = this.findFrameElement(element, submitter);
1875
2113
  if (frame) {
1876
- frame.removeAttribute("reloadable");
1877
- frame.delegate.formSubmissionIntercepted(element, submitter);
2114
+ frame.delegate.formSubmitted(element, submitter);
1878
2115
  }
1879
2116
  }
2117
+ frameAllowsVisitingLocation(target, { href: url }, originalEvent) {
2118
+ const event = dispatch("turbo:click", {
2119
+ target,
2120
+ detail: { url, originalEvent },
2121
+ cancelable: true,
2122
+ });
2123
+ return !event.defaultPrevented;
2124
+ }
1880
2125
  shouldSubmit(form, submitter) {
1881
2126
  var _a;
1882
2127
  const action = getAction(form, submitter);
@@ -1885,8 +2130,16 @@ class FrameRedirector {
1885
2130
  return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
1886
2131
  }
1887
2132
  shouldRedirect(element, submitter) {
1888
- const frame = this.findFrameElement(element, submitter);
1889
- return frame ? frame != element.closest("turbo-frame") : false;
2133
+ const isNavigatable = element instanceof HTMLFormElement
2134
+ ? this.session.submissionIsNavigatable(element, submitter)
2135
+ : this.session.elementIsNavigatable(element);
2136
+ if (isNavigatable) {
2137
+ const frame = this.findFrameElement(element, submitter);
2138
+ return frame ? frame != element.closest("turbo-frame") : false;
2139
+ }
2140
+ else {
2141
+ return false;
2142
+ }
1890
2143
  }
1891
2144
  findFrameElement(element, submitter) {
1892
2145
  const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
@@ -1916,7 +2169,7 @@ class History {
1916
2169
  }
1917
2170
  }
1918
2171
  };
1919
- this.onPageLoad = async (event) => {
2172
+ this.onPageLoad = async (_event) => {
1920
2173
  await nextMicrotask();
1921
2174
  this.pageLoaded = true;
1922
2175
  };
@@ -1978,64 +2231,7 @@ class History {
1978
2231
  }
1979
2232
  }
1980
2233
 
1981
- class LinkClickObserver {
1982
- constructor(delegate) {
1983
- this.started = false;
1984
- this.clickCaptured = () => {
1985
- removeEventListener("click", this.clickBubbled, false);
1986
- addEventListener("click", this.clickBubbled, false);
1987
- };
1988
- this.clickBubbled = (event) => {
1989
- if (this.clickEventIsSignificant(event)) {
1990
- const target = (event.composedPath && event.composedPath()[0]) || event.target;
1991
- const link = this.findLinkFromClickTarget(target);
1992
- if (link) {
1993
- const location = this.getLocationForLink(link);
1994
- if (this.delegate.willFollowLinkToLocation(link, location)) {
1995
- event.preventDefault();
1996
- this.delegate.followedLinkToLocation(link, location);
1997
- }
1998
- }
1999
- }
2000
- };
2001
- this.delegate = delegate;
2002
- }
2003
- start() {
2004
- if (!this.started) {
2005
- addEventListener("click", this.clickCaptured, true);
2006
- this.started = true;
2007
- }
2008
- }
2009
- stop() {
2010
- if (this.started) {
2011
- removeEventListener("click", this.clickCaptured, true);
2012
- this.started = false;
2013
- }
2014
- }
2015
- clickEventIsSignificant(event) {
2016
- return !((event.target && event.target.isContentEditable)
2017
- || event.defaultPrevented
2018
- || event.which > 1
2019
- || event.altKey
2020
- || event.ctrlKey
2021
- || event.metaKey
2022
- || event.shiftKey);
2023
- }
2024
- findLinkFromClickTarget(target) {
2025
- if (target instanceof Element) {
2026
- return target.closest("a[href]:not([target^=_]):not([download])");
2027
- }
2028
- }
2029
- getLocationForLink(link) {
2030
- return expandURL(link.getAttribute("href") || "");
2031
- }
2032
- }
2033
-
2034
- function isAction(action) {
2035
- return action == "advance" || action == "replace" || action == "restore";
2036
- }
2037
-
2038
- class Navigator {
2234
+ class Navigator {
2039
2235
  constructor(delegate) {
2040
2236
  this.delegate = delegate;
2041
2237
  }
@@ -2050,6 +2246,7 @@ class Navigator {
2050
2246
  }
2051
2247
  }
2052
2248
  startVisit(locatable, restorationIdentifier, options = {}) {
2249
+ this.lastVisit = this.currentVisit;
2053
2250
  this.stop();
2054
2251
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));
2055
2252
  this.currentVisit.start();
@@ -2079,7 +2276,7 @@ class Navigator {
2079
2276
  return this.delegate.history;
2080
2277
  }
2081
2278
  formSubmissionStarted(formSubmission) {
2082
- if (typeof this.adapter.formSubmissionStarted === 'function') {
2279
+ if (typeof this.adapter.formSubmissionStarted === "function") {
2083
2280
  this.adapter.formSubmissionStarted(formSubmission);
2084
2281
  }
2085
2282
  }
@@ -2087,12 +2284,17 @@ class Navigator {
2087
2284
  if (formSubmission == this.formSubmission) {
2088
2285
  const responseHTML = await fetchResponse.responseHTML;
2089
2286
  if (responseHTML) {
2090
- if (formSubmission.method != FetchMethod.get) {
2287
+ const shouldCacheSnapshot = formSubmission.method == FetchMethod.get;
2288
+ if (!shouldCacheSnapshot) {
2091
2289
  this.view.clearSnapshotCache();
2092
2290
  }
2093
2291
  const { statusCode, redirected } = fetchResponse;
2094
2292
  const action = this.getActionForFormSubmission(formSubmission);
2095
- const visitOptions = { action, response: { statusCode, responseHTML, redirected } };
2293
+ const visitOptions = {
2294
+ action,
2295
+ shouldCacheSnapshot,
2296
+ response: { statusCode, responseHTML, redirected },
2297
+ };
2096
2298
  this.proposeVisit(fetchResponse.location, visitOptions);
2097
2299
  }
2098
2300
  }
@@ -2102,10 +2304,10 @@ class Navigator {
2102
2304
  if (responseHTML) {
2103
2305
  const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2104
2306
  if (fetchResponse.serverError) {
2105
- await this.view.renderError(snapshot);
2307
+ await this.view.renderError(snapshot, this.currentVisit);
2106
2308
  }
2107
2309
  else {
2108
- await this.view.renderPage(snapshot);
2310
+ await this.view.renderPage(snapshot, false, true, this.currentVisit);
2109
2311
  }
2110
2312
  this.view.scrollToTop();
2111
2313
  this.view.clearSnapshotCache();
@@ -2115,7 +2317,7 @@ class Navigator {
2115
2317
  console.error(error);
2116
2318
  }
2117
2319
  formSubmissionFinished(formSubmission) {
2118
- if (typeof this.adapter.formSubmissionFinished === 'function') {
2320
+ if (typeof this.adapter.formSubmissionFinished === "function") {
2119
2321
  this.adapter.formSubmissionFinished(formSubmission);
2120
2322
  }
2121
2323
  }
@@ -2126,12 +2328,14 @@ class Navigator {
2126
2328
  this.delegate.visitCompleted(visit);
2127
2329
  }
2128
2330
  locationWithActionIsSamePage(location, action) {
2331
+ var _a;
2129
2332
  const anchor = getAnchor(location);
2130
- const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2131
- const isRestorationToTop = action === 'restore' && typeof anchor === 'undefined';
2132
- return action !== "replace" &&
2133
- getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&
2134
- (isRestorationToTop || (anchor != null && anchor !== currentAnchor));
2333
+ const lastLocation = ((_a = this.lastVisit) === null || _a === void 0 ? void 0 : _a.location) || this.view.lastRenderedLocation;
2334
+ const currentAnchor = getAnchor(lastLocation);
2335
+ const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2336
+ return (action !== "replace" &&
2337
+ getRequestURL(location) === getRequestURL(lastLocation) &&
2338
+ (isRestorationToTop || (anchor != null && anchor !== currentAnchor)));
2135
2339
  }
2136
2340
  visitScrolledToSamePageLocation(oldURL, newURL) {
2137
2341
  this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
@@ -2235,9 +2439,33 @@ class ScrollObserver {
2235
2439
  }
2236
2440
  }
2237
2441
 
2442
+ class StreamMessageRenderer {
2443
+ render({ fragment }) {
2444
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => document.documentElement.appendChild(fragment));
2445
+ }
2446
+ enteringBardo(currentPermanentElement, newPermanentElement) {
2447
+ newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
2448
+ }
2449
+ leavingBardo() { }
2450
+ }
2451
+ function getPermanentElementMapForFragment(fragment) {
2452
+ const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
2453
+ const permanentElementMap = {};
2454
+ for (const permanentElementInDocument of permanentElementsInDocument) {
2455
+ const { id } = permanentElementInDocument;
2456
+ for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
2457
+ const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
2458
+ if (elementInStream) {
2459
+ permanentElementMap[id] = [permanentElementInDocument, elementInStream];
2460
+ }
2461
+ }
2462
+ }
2463
+ return permanentElementMap;
2464
+ }
2465
+
2238
2466
  class StreamObserver {
2239
2467
  constructor(delegate) {
2240
- this.sources = new Set;
2468
+ this.sources = new Set();
2241
2469
  this.started = false;
2242
2470
  this.inspectFetchResponse = ((event) => {
2243
2471
  const response = fetchResponseFromEvent(event);
@@ -2287,7 +2515,7 @@ class StreamObserver {
2287
2515
  }
2288
2516
  }
2289
2517
  receiveMessageHTML(html) {
2290
- this.delegate.receivedMessageFromStream(new StreamMessage(html));
2518
+ this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
2291
2519
  }
2292
2520
  }
2293
2521
  function fetchResponseFromEvent(event) {
@@ -2304,20 +2532,24 @@ function fetchResponseIsStream(response) {
2304
2532
  }
2305
2533
 
2306
2534
  class ErrorRenderer extends Renderer {
2535
+ static renderElement(currentElement, newElement) {
2536
+ const { documentElement, body } = document;
2537
+ documentElement.replaceChild(newElement, body);
2538
+ }
2307
2539
  async render() {
2308
2540
  this.replaceHeadAndBody();
2309
2541
  this.activateScriptElements();
2310
2542
  }
2311
2543
  replaceHeadAndBody() {
2312
- const { documentElement, head, body } = document;
2544
+ const { documentElement, head } = document;
2313
2545
  documentElement.replaceChild(this.newHead, head);
2314
- documentElement.replaceChild(this.newElement, body);
2546
+ this.renderElement(this.currentElement, this.newElement);
2315
2547
  }
2316
2548
  activateScriptElements() {
2317
2549
  for (const replaceableElement of this.scriptElements) {
2318
2550
  const parentNode = replaceableElement.parentNode;
2319
2551
  if (parentNode) {
2320
- const element = this.createScriptElement(replaceableElement);
2552
+ const element = activateScriptElement(replaceableElement);
2321
2553
  parentNode.replaceChild(element, replaceableElement);
2322
2554
  }
2323
2555
  }
@@ -2326,16 +2558,36 @@ class ErrorRenderer extends Renderer {
2326
2558
  return this.newSnapshot.headSnapshot.element;
2327
2559
  }
2328
2560
  get scriptElements() {
2329
- return [...document.documentElement.querySelectorAll("script")];
2561
+ return document.documentElement.querySelectorAll("script");
2330
2562
  }
2331
2563
  }
2332
2564
 
2333
2565
  class PageRenderer extends Renderer {
2566
+ static renderElement(currentElement, newElement) {
2567
+ if (document.body && newElement instanceof HTMLBodyElement) {
2568
+ document.body.replaceWith(newElement);
2569
+ }
2570
+ else {
2571
+ document.documentElement.appendChild(newElement);
2572
+ }
2573
+ }
2334
2574
  get shouldRender() {
2335
2575
  return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
2336
2576
  }
2337
- prepareToRender() {
2338
- this.mergeHead();
2577
+ get reloadReason() {
2578
+ if (!this.newSnapshot.isVisitable) {
2579
+ return {
2580
+ reason: "turbo_visit_control_is_reload",
2581
+ };
2582
+ }
2583
+ if (!this.trackedElementsAreIdentical) {
2584
+ return {
2585
+ reason: "tracked_element_mismatch",
2586
+ };
2587
+ }
2588
+ }
2589
+ async prepareToRender() {
2590
+ await this.mergeHead();
2339
2591
  }
2340
2592
  async render() {
2341
2593
  if (this.willRender) {
@@ -2357,11 +2609,12 @@ class PageRenderer extends Renderer {
2357
2609
  get newElement() {
2358
2610
  return this.newSnapshot.element;
2359
2611
  }
2360
- mergeHead() {
2361
- this.copyNewHeadStylesheetElements();
2612
+ async mergeHead() {
2613
+ const newStylesheetElements = this.copyNewHeadStylesheetElements();
2362
2614
  this.copyNewHeadScriptElements();
2363
2615
  this.removeCurrentHeadProvisionalElements();
2364
2616
  this.copyNewHeadProvisionalElements();
2617
+ await newStylesheetElements;
2365
2618
  }
2366
2619
  replaceBody() {
2367
2620
  this.preservingPermanentElements(() => {
@@ -2372,14 +2625,17 @@ class PageRenderer extends Renderer {
2372
2625
  get trackedElementsAreIdentical() {
2373
2626
  return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
2374
2627
  }
2375
- copyNewHeadStylesheetElements() {
2628
+ async copyNewHeadStylesheetElements() {
2629
+ const loadingElements = [];
2376
2630
  for (const element of this.newHeadStylesheetElements) {
2631
+ loadingElements.push(waitForLoad(element));
2377
2632
  document.head.appendChild(element);
2378
2633
  }
2634
+ await Promise.all(loadingElements);
2379
2635
  }
2380
2636
  copyNewHeadScriptElements() {
2381
2637
  for (const element of this.newHeadScriptElements) {
2382
- document.head.appendChild(this.createScriptElement(element));
2638
+ document.head.appendChild(activateScriptElement(element));
2383
2639
  }
2384
2640
  }
2385
2641
  removeCurrentHeadProvisionalElements() {
@@ -2398,17 +2654,12 @@ class PageRenderer extends Renderer {
2398
2654
  }
2399
2655
  activateNewBodyScriptElements() {
2400
2656
  for (const inertScriptElement of this.newBodyScriptElements) {
2401
- const activatedScriptElement = this.createScriptElement(inertScriptElement);
2657
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
2402
2658
  inertScriptElement.replaceWith(activatedScriptElement);
2403
2659
  }
2404
2660
  }
2405
2661
  assignNewBody() {
2406
- if (document.body && this.newElement instanceof HTMLBodyElement) {
2407
- document.body.replaceWith(this.newElement);
2408
- }
2409
- else {
2410
- document.documentElement.appendChild(this.newElement);
2411
- }
2662
+ this.renderElement(this.currentElement, this.newElement);
2412
2663
  }
2413
2664
  get newHeadStylesheetElements() {
2414
2665
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -2477,13 +2728,21 @@ class PageView extends View {
2477
2728
  super(...arguments);
2478
2729
  this.snapshotCache = new SnapshotCache(10);
2479
2730
  this.lastRenderedLocation = new URL(location.href);
2731
+ this.forceReloaded = false;
2480
2732
  }
2481
- renderPage(snapshot, isPreview = false, willRender = true) {
2482
- const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
2733
+ renderPage(snapshot, isPreview = false, willRender = true, visit) {
2734
+ const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
2735
+ if (!renderer.shouldRender) {
2736
+ this.forceReloaded = true;
2737
+ }
2738
+ else {
2739
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2740
+ }
2483
2741
  return this.render(renderer);
2484
2742
  }
2485
- renderError(snapshot) {
2486
- const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2743
+ renderError(snapshot, visit) {
2744
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2745
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
2487
2746
  return this.render(renderer);
2488
2747
  }
2489
2748
  clearSnapshotCache() {
@@ -2510,34 +2769,79 @@ class PageView extends View {
2510
2769
  }
2511
2770
  }
2512
2771
 
2772
+ class Preloader {
2773
+ constructor(delegate) {
2774
+ this.selector = "a[data-turbo-preload]";
2775
+ this.delegate = delegate;
2776
+ }
2777
+ get snapshotCache() {
2778
+ return this.delegate.navigator.view.snapshotCache;
2779
+ }
2780
+ start() {
2781
+ if (document.readyState === "loading") {
2782
+ return document.addEventListener("DOMContentLoaded", () => {
2783
+ this.preloadOnLoadLinksForView(document.body);
2784
+ });
2785
+ }
2786
+ else {
2787
+ this.preloadOnLoadLinksForView(document.body);
2788
+ }
2789
+ }
2790
+ preloadOnLoadLinksForView(element) {
2791
+ for (const link of element.querySelectorAll(this.selector)) {
2792
+ this.preloadURL(link);
2793
+ }
2794
+ }
2795
+ async preloadURL(link) {
2796
+ const location = new URL(link.href);
2797
+ if (this.snapshotCache.has(location)) {
2798
+ return;
2799
+ }
2800
+ try {
2801
+ const response = await fetch(location.toString(), { headers: { "VND.PREFETCH": "true", Accept: "text/html" } });
2802
+ const responseText = await response.text();
2803
+ const snapshot = PageSnapshot.fromHTMLString(responseText);
2804
+ this.snapshotCache.put(location, snapshot);
2805
+ }
2806
+ catch (_) {
2807
+ }
2808
+ }
2809
+ }
2810
+
2513
2811
  class Session {
2514
2812
  constructor() {
2515
2813
  this.navigator = new Navigator(this);
2516
2814
  this.history = new History(this);
2815
+ this.preloader = new Preloader(this);
2517
2816
  this.view = new PageView(this, document.documentElement);
2518
2817
  this.adapter = new BrowserAdapter(this);
2519
2818
  this.pageObserver = new PageObserver(this);
2520
2819
  this.cacheObserver = new CacheObserver();
2521
- this.linkClickObserver = new LinkClickObserver(this);
2522
- this.formSubmitObserver = new FormSubmitObserver(this);
2820
+ this.linkClickObserver = new LinkClickObserver(this, window);
2821
+ this.formSubmitObserver = new FormSubmitObserver(this, document);
2523
2822
  this.scrollObserver = new ScrollObserver(this);
2524
2823
  this.streamObserver = new StreamObserver(this);
2525
- this.frameRedirector = new FrameRedirector(document.documentElement);
2824
+ this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
2825
+ this.frameRedirector = new FrameRedirector(this, document.documentElement);
2826
+ this.streamMessageRenderer = new StreamMessageRenderer();
2526
2827
  this.drive = true;
2527
2828
  this.enabled = true;
2528
2829
  this.progressBarDelay = 500;
2529
2830
  this.started = false;
2831
+ this.formMode = "on";
2530
2832
  }
2531
2833
  start() {
2532
2834
  if (!this.started) {
2533
2835
  this.pageObserver.start();
2534
2836
  this.cacheObserver.start();
2837
+ this.formLinkClickObserver.start();
2535
2838
  this.linkClickObserver.start();
2536
2839
  this.formSubmitObserver.start();
2537
2840
  this.scrollObserver.start();
2538
2841
  this.streamObserver.start();
2539
2842
  this.frameRedirector.start();
2540
2843
  this.history.start();
2844
+ this.preloader.start();
2541
2845
  this.started = true;
2542
2846
  this.enabled = true;
2543
2847
  }
@@ -2549,6 +2853,7 @@ class Session {
2549
2853
  if (this.started) {
2550
2854
  this.pageObserver.stop();
2551
2855
  this.cacheObserver.stop();
2856
+ this.formLinkClickObserver.stop();
2552
2857
  this.linkClickObserver.stop();
2553
2858
  this.formSubmitObserver.stop();
2554
2859
  this.scrollObserver.stop();
@@ -2562,7 +2867,14 @@ class Session {
2562
2867
  this.adapter = adapter;
2563
2868
  }
2564
2869
  visit(location, options = {}) {
2565
- this.navigator.proposeVisit(expandURL(location), options);
2870
+ const frameElement = options.frame ? document.getElementById(options.frame) : null;
2871
+ if (frameElement instanceof FrameElement) {
2872
+ frameElement.src = location.toString();
2873
+ frameElement.loaded;
2874
+ }
2875
+ else {
2876
+ this.navigator.proposeVisit(expandURL(location), options);
2877
+ }
2566
2878
  }
2567
2879
  connectStreamSource(source) {
2568
2880
  this.streamObserver.connectStreamSource(source);
@@ -2571,7 +2883,7 @@ class Session {
2571
2883
  this.streamObserver.disconnectStreamSource(source);
2572
2884
  }
2573
2885
  renderStreamMessage(message) {
2574
- document.documentElement.appendChild(StreamMessage.wrap(message).fragment);
2886
+ this.streamMessageRenderer.render(StreamMessage.wrap(message));
2575
2887
  }
2576
2888
  clearCache() {
2577
2889
  this.view.clearSnapshotCache();
@@ -2579,6 +2891,9 @@ class Session {
2579
2891
  setProgressBarDelay(delay) {
2580
2892
  this.progressBarDelay = delay;
2581
2893
  }
2894
+ setFormMode(mode) {
2895
+ this.formMode = mode;
2896
+ }
2582
2897
  get location() {
2583
2898
  return this.history.location;
2584
2899
  }
@@ -2587,48 +2902,33 @@ class Session {
2587
2902
  }
2588
2903
  historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
2589
2904
  if (this.enabled) {
2590
- this.navigator.startVisit(location, restorationIdentifier, { action: "restore", historyChanged: true });
2905
+ this.navigator.startVisit(location, restorationIdentifier, {
2906
+ action: "restore",
2907
+ historyChanged: true,
2908
+ });
2591
2909
  }
2592
2910
  else {
2593
- this.adapter.pageInvalidated();
2911
+ this.adapter.pageInvalidated({
2912
+ reason: "turbo_disabled",
2913
+ });
2594
2914
  }
2595
2915
  }
2596
2916
  scrollPositionChanged(position) {
2597
2917
  this.history.updateRestorationData({ scrollPosition: position });
2598
2918
  }
2599
- willFollowLinkToLocation(link, location) {
2600
- return this.elementDriveEnabled(link)
2601
- && locationIsVisitable(location, this.snapshot.rootLocation)
2602
- && this.applicationAllowsFollowingLinkToLocation(link, location);
2919
+ willSubmitFormLinkToLocation(link, location) {
2920
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
2921
+ }
2922
+ submittedFormLinkToLocation() { }
2923
+ willFollowLinkToLocation(link, location, event) {
2924
+ return (this.elementIsNavigatable(link) &&
2925
+ locationIsVisitable(location, this.snapshot.rootLocation) &&
2926
+ this.applicationAllowsFollowingLinkToLocation(link, location, event));
2603
2927
  }
2604
2928
  followedLinkToLocation(link, location) {
2605
2929
  const action = this.getActionForLink(link);
2606
- this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, { action });
2607
- }
2608
- convertLinkWithMethodClickToFormSubmission(link) {
2609
- const linkMethod = link.getAttribute("data-turbo-method");
2610
- if (linkMethod) {
2611
- const form = document.createElement("form");
2612
- form.method = linkMethod;
2613
- form.action = link.getAttribute("href") || "undefined";
2614
- form.hidden = true;
2615
- if (link.hasAttribute("data-turbo-confirm")) {
2616
- form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
2617
- }
2618
- const frame = this.getTargetFrameForLink(link);
2619
- if (frame) {
2620
- form.setAttribute("data-turbo-frame", frame);
2621
- form.addEventListener("turbo:submit-start", () => form.remove());
2622
- }
2623
- else {
2624
- form.addEventListener("submit", () => form.remove());
2625
- }
2626
- document.body.appendChild(form);
2627
- return dispatch("submit", { cancelable: true, target: form });
2628
- }
2629
- else {
2630
- return false;
2631
- }
2930
+ const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
2931
+ this.visit(location.href, { action, acceptsStreamResponse });
2632
2932
  }
2633
2933
  allowsVisitingLocationWithAction(location, action) {
2634
2934
  return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
@@ -2638,12 +2938,16 @@ class Session {
2638
2938
  this.adapter.visitProposedToLocation(location, options);
2639
2939
  }
2640
2940
  visitStarted(visit) {
2941
+ if (!visit.acceptsStreamResponse) {
2942
+ markAsBusy(document.documentElement);
2943
+ }
2641
2944
  extendURLWithDeprecatedProperties(visit.location);
2642
2945
  if (!visit.silent) {
2643
2946
  this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
2644
2947
  }
2645
2948
  }
2646
2949
  visitCompleted(visit) {
2950
+ clearBusyState(document.documentElement);
2647
2951
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2648
2952
  }
2649
2953
  locationWithActionIsSamePage(location, action) {
@@ -2654,9 +2958,8 @@ class Session {
2654
2958
  }
2655
2959
  willSubmitForm(form, submitter) {
2656
2960
  const action = getAction(form, submitter);
2657
- return this.elementDriveEnabled(form)
2658
- && (!submitter || this.elementDriveEnabled(submitter))
2659
- && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
2961
+ return (this.submissionIsNavigatable(form, submitter) &&
2962
+ locationIsVisitable(expandURL(action), this.snapshot.rootLocation));
2660
2963
  }
2661
2964
  formSubmitted(form, submitter) {
2662
2965
  this.navigator.submitForm(form, submitter);
@@ -2680,16 +2983,23 @@ class Session {
2680
2983
  this.notifyApplicationBeforeCachingSnapshot();
2681
2984
  }
2682
2985
  }
2683
- allowsImmediateRender({ element }, resume) {
2684
- const event = this.notifyApplicationBeforeRender(element, resume);
2685
- return !event.defaultPrevented;
2986
+ allowsImmediateRender({ element }, options) {
2987
+ const event = this.notifyApplicationBeforeRender(element, options);
2988
+ const { defaultPrevented, detail: { render }, } = event;
2989
+ if (this.view.renderer && render) {
2990
+ this.view.renderer.renderElement = render;
2991
+ }
2992
+ return !defaultPrevented;
2686
2993
  }
2687
- viewRenderedSnapshot(snapshot, isPreview) {
2994
+ viewRenderedSnapshot(_snapshot, _isPreview) {
2688
2995
  this.view.lastRenderedLocation = this.history.location;
2689
2996
  this.notifyApplicationAfterRender();
2690
2997
  }
2691
- viewInvalidated() {
2692
- this.adapter.pageInvalidated();
2998
+ preloadOnLoadLinksForView(element) {
2999
+ this.preloader.preloadOnLoadLinksForView(element);
3000
+ }
3001
+ viewInvalidated(reason) {
3002
+ this.adapter.pageInvalidated(reason);
2693
3003
  }
2694
3004
  frameLoaded(frame) {
2695
3005
  this.notifyApplicationAfterFrameLoad(frame);
@@ -2697,49 +3007,81 @@ class Session {
2697
3007
  frameRendered(fetchResponse, frame) {
2698
3008
  this.notifyApplicationAfterFrameRender(fetchResponse, frame);
2699
3009
  }
2700
- applicationAllowsFollowingLinkToLocation(link, location) {
2701
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
3010
+ applicationAllowsFollowingLinkToLocation(link, location, ev) {
3011
+ const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
2702
3012
  return !event.defaultPrevented;
2703
3013
  }
2704
3014
  applicationAllowsVisitingLocation(location) {
2705
3015
  const event = this.notifyApplicationBeforeVisitingLocation(location);
2706
3016
  return !event.defaultPrevented;
2707
3017
  }
2708
- notifyApplicationAfterClickingLinkToLocation(link, location) {
2709
- return dispatch("turbo:click", { target: link, detail: { url: location.href }, cancelable: true });
3018
+ notifyApplicationAfterClickingLinkToLocation(link, location, event) {
3019
+ return dispatch("turbo:click", {
3020
+ target: link,
3021
+ detail: { url: location.href, originalEvent: event },
3022
+ cancelable: true,
3023
+ });
2710
3024
  }
2711
3025
  notifyApplicationBeforeVisitingLocation(location) {
2712
- return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
3026
+ return dispatch("turbo:before-visit", {
3027
+ detail: { url: location.href },
3028
+ cancelable: true,
3029
+ });
2713
3030
  }
2714
3031
  notifyApplicationAfterVisitingLocation(location, action) {
2715
- markAsBusy(document.documentElement);
2716
3032
  return dispatch("turbo:visit", { detail: { url: location.href, action } });
2717
3033
  }
2718
3034
  notifyApplicationBeforeCachingSnapshot() {
2719
3035
  return dispatch("turbo:before-cache");
2720
3036
  }
2721
- notifyApplicationBeforeRender(newBody, resume) {
2722
- return dispatch("turbo:before-render", { detail: { newBody, resume }, cancelable: true });
3037
+ notifyApplicationBeforeRender(newBody, options) {
3038
+ return dispatch("turbo:before-render", {
3039
+ detail: Object.assign({ newBody }, options),
3040
+ cancelable: true,
3041
+ });
2723
3042
  }
2724
3043
  notifyApplicationAfterRender() {
2725
3044
  return dispatch("turbo:render");
2726
3045
  }
2727
3046
  notifyApplicationAfterPageLoad(timing = {}) {
2728
- clearBusyState(document.documentElement);
2729
- return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
3047
+ return dispatch("turbo:load", {
3048
+ detail: { url: this.location.href, timing },
3049
+ });
2730
3050
  }
2731
3051
  notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
2732
- dispatchEvent(new HashChangeEvent("hashchange", { oldURL: oldURL.toString(), newURL: newURL.toString() }));
3052
+ dispatchEvent(new HashChangeEvent("hashchange", {
3053
+ oldURL: oldURL.toString(),
3054
+ newURL: newURL.toString(),
3055
+ }));
2733
3056
  }
2734
3057
  notifyApplicationAfterFrameLoad(frame) {
2735
3058
  return dispatch("turbo:frame-load", { target: frame });
2736
3059
  }
2737
3060
  notifyApplicationAfterFrameRender(fetchResponse, frame) {
2738
- return dispatch("turbo:frame-render", { detail: { fetchResponse }, target: frame, cancelable: true });
3061
+ return dispatch("turbo:frame-render", {
3062
+ detail: { fetchResponse },
3063
+ target: frame,
3064
+ cancelable: true,
3065
+ });
2739
3066
  }
2740
- elementDriveEnabled(element) {
2741
- const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
2742
- if (this.drive) {
3067
+ submissionIsNavigatable(form, submitter) {
3068
+ if (this.formMode == "off") {
3069
+ return false;
3070
+ }
3071
+ else {
3072
+ const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
3073
+ if (this.formMode == "optin") {
3074
+ return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null;
3075
+ }
3076
+ else {
3077
+ return submitterIsNavigatable && this.elementIsNavigatable(form);
3078
+ }
3079
+ }
3080
+ }
3081
+ elementIsNavigatable(element) {
3082
+ const container = element.closest("[data-turbo]");
3083
+ const withinFrame = element.closest("turbo-frame");
3084
+ if (this.drive || withinFrame) {
2743
3085
  if (container) {
2744
3086
  return container.getAttribute("data-turbo") != "false";
2745
3087
  }
@@ -2760,18 +3102,6 @@ class Session {
2760
3102
  const action = link.getAttribute("data-turbo-action");
2761
3103
  return isAction(action) ? action : "advance";
2762
3104
  }
2763
- getTargetFrameForLink(link) {
2764
- const frame = link.getAttribute("data-turbo-frame");
2765
- if (frame) {
2766
- return frame;
2767
- }
2768
- else {
2769
- const container = link.closest("turbo-frame");
2770
- if (container) {
2771
- return container.id;
2772
- }
2773
- }
2774
- }
2775
3105
  get snapshot() {
2776
3106
  return this.view.snapshot;
2777
3107
  }
@@ -2783,11 +3113,59 @@ const deprecatedLocationPropertyDescriptors = {
2783
3113
  absoluteURL: {
2784
3114
  get() {
2785
3115
  return this.toString();
2786
- }
3116
+ },
3117
+ },
3118
+ };
3119
+
3120
+ class Cache {
3121
+ constructor(session) {
3122
+ this.session = session;
3123
+ }
3124
+ clear() {
3125
+ this.session.clearCache();
2787
3126
  }
3127
+ resetCacheControl() {
3128
+ this.setCacheControl("");
3129
+ }
3130
+ exemptPageFromCache() {
3131
+ this.setCacheControl("no-cache");
3132
+ }
3133
+ exemptPageFromPreview() {
3134
+ this.setCacheControl("no-preview");
3135
+ }
3136
+ setCacheControl(value) {
3137
+ setMetaContent("turbo-cache-control", value);
3138
+ }
3139
+ }
3140
+
3141
+ const StreamActions = {
3142
+ after() {
3143
+ this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });
3144
+ },
3145
+ append() {
3146
+ this.removeDuplicateTargetChildren();
3147
+ this.targetElements.forEach((e) => e.append(this.templateContent));
3148
+ },
3149
+ before() {
3150
+ this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });
3151
+ },
3152
+ prepend() {
3153
+ this.removeDuplicateTargetChildren();
3154
+ this.targetElements.forEach((e) => e.prepend(this.templateContent));
3155
+ },
3156
+ remove() {
3157
+ this.targetElements.forEach((e) => e.remove());
3158
+ },
3159
+ replace() {
3160
+ this.targetElements.forEach((e) => e.replaceWith(this.templateContent));
3161
+ },
3162
+ update() {
3163
+ this.targetElements.forEach((e) => e.replaceChildren(this.templateContent));
3164
+ },
2788
3165
  };
2789
3166
 
2790
- const session = new Session;
3167
+ const session = new Session();
3168
+ const cache = new Cache(session);
2791
3169
  const { navigator: navigator$1 } = session;
2792
3170
  function start() {
2793
3171
  session.start();
@@ -2808,6 +3186,7 @@ function renderStreamMessage(message) {
2808
3186
  session.renderStreamMessage(message);
2809
3187
  }
2810
3188
  function clearCache() {
3189
+ console.warn("Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
2811
3190
  session.clearCache();
2812
3191
  }
2813
3192
  function setProgressBarDelay(delay) {
@@ -2816,13 +3195,18 @@ function setProgressBarDelay(delay) {
2816
3195
  function setConfirmMethod(confirmMethod) {
2817
3196
  FormSubmission.confirmMethod = confirmMethod;
2818
3197
  }
3198
+ function setFormMode(mode) {
3199
+ session.setFormMode(mode);
3200
+ }
2819
3201
 
2820
3202
  var Turbo = /*#__PURE__*/Object.freeze({
2821
3203
  __proto__: null,
2822
3204
  navigator: navigator$1,
2823
3205
  session: session,
3206
+ cache: cache,
2824
3207
  PageRenderer: PageRenderer,
2825
3208
  PageSnapshot: PageSnapshot,
3209
+ FrameRenderer: FrameRenderer,
2826
3210
  start: start,
2827
3211
  registerAdapter: registerAdapter,
2828
3212
  visit: visit,
@@ -2831,41 +3215,56 @@ var Turbo = /*#__PURE__*/Object.freeze({
2831
3215
  renderStreamMessage: renderStreamMessage,
2832
3216
  clearCache: clearCache,
2833
3217
  setProgressBarDelay: setProgressBarDelay,
2834
- setConfirmMethod: setConfirmMethod
3218
+ setConfirmMethod: setConfirmMethod,
3219
+ setFormMode: setFormMode,
3220
+ StreamActions: StreamActions
2835
3221
  });
2836
3222
 
2837
3223
  class FrameController {
2838
3224
  constructor(element) {
2839
- this.fetchResponseLoaded = (fetchResponse) => { };
3225
+ this.fetchResponseLoaded = (_fetchResponse) => { };
2840
3226
  this.currentFetchRequest = null;
2841
3227
  this.resolveVisitPromise = () => { };
2842
3228
  this.connected = false;
2843
3229
  this.hasBeenLoaded = false;
2844
- this.settingSourceURL = false;
3230
+ this.ignoredAttributes = new Set();
3231
+ this.action = null;
3232
+ this.visitCachedSnapshot = ({ element }) => {
3233
+ const frame = element.querySelector("#" + this.element.id);
3234
+ if (frame && this.previousFrameElement) {
3235
+ frame.replaceChildren(...this.previousFrameElement.children);
3236
+ }
3237
+ delete this.previousFrameElement;
3238
+ };
2845
3239
  this.element = element;
2846
3240
  this.view = new FrameView(this, this.element);
2847
3241
  this.appearanceObserver = new AppearanceObserver(this, this.element);
2848
- this.linkInterceptor = new LinkInterceptor(this, this.element);
2849
- this.formInterceptor = new FormInterceptor(this, this.element);
3242
+ this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);
3243
+ this.linkClickObserver = new LinkClickObserver(this, this.element);
3244
+ this.restorationIdentifier = uuid();
3245
+ this.formSubmitObserver = new FormSubmitObserver(this, this.element);
2850
3246
  }
2851
3247
  connect() {
2852
3248
  if (!this.connected) {
2853
3249
  this.connected = true;
2854
- this.reloadable = false;
2855
3250
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2856
3251
  this.appearanceObserver.start();
2857
3252
  }
2858
- this.linkInterceptor.start();
2859
- this.formInterceptor.start();
2860
- this.sourceURLChanged();
3253
+ else {
3254
+ this.loadSourceURL();
3255
+ }
3256
+ this.formLinkClickObserver.start();
3257
+ this.linkClickObserver.start();
3258
+ this.formSubmitObserver.start();
2861
3259
  }
2862
3260
  }
2863
3261
  disconnect() {
2864
3262
  if (this.connected) {
2865
3263
  this.connected = false;
2866
3264
  this.appearanceObserver.stop();
2867
- this.linkInterceptor.stop();
2868
- this.formInterceptor.stop();
3265
+ this.formLinkClickObserver.stop();
3266
+ this.linkClickObserver.stop();
3267
+ this.formSubmitObserver.stop();
2869
3268
  }
2870
3269
  }
2871
3270
  disabledChanged() {
@@ -2874,10 +3273,20 @@ class FrameController {
2874
3273
  }
2875
3274
  }
2876
3275
  sourceURLChanged() {
3276
+ if (this.isIgnoringChangesTo("src"))
3277
+ return;
3278
+ if (this.element.isConnected) {
3279
+ this.complete = false;
3280
+ }
2877
3281
  if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
2878
3282
  this.loadSourceURL();
2879
3283
  }
2880
3284
  }
3285
+ completeChanged() {
3286
+ if (this.isIgnoringChangesTo("complete"))
3287
+ return;
3288
+ this.loadSourceURL();
3289
+ }
2881
3290
  loadingStyleChanged() {
2882
3291
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2883
3292
  this.appearanceObserver.start();
@@ -2888,21 +3297,11 @@ class FrameController {
2888
3297
  }
2889
3298
  }
2890
3299
  async loadSourceURL() {
2891
- if (!this.settingSourceURL && this.enabled && this.isActive && (this.reloadable || this.sourceURL != this.currentURL)) {
2892
- const previousURL = this.currentURL;
2893
- this.currentURL = this.sourceURL;
2894
- if (this.sourceURL) {
2895
- try {
2896
- this.element.loaded = this.visit(expandURL(this.sourceURL));
2897
- this.appearanceObserver.stop();
2898
- await this.element.loaded;
2899
- this.hasBeenLoaded = true;
2900
- }
2901
- catch (error) {
2902
- this.currentURL = previousURL;
2903
- throw error;
2904
- }
2905
- }
3300
+ if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
3301
+ this.element.loaded = this.visit(expandURL(this.sourceURL));
3302
+ this.appearanceObserver.stop();
3303
+ await this.element.loaded;
3304
+ this.hasBeenLoaded = true;
2906
3305
  }
2907
3306
  }
2908
3307
  async loadResponse(fetchResponse) {
@@ -2913,14 +3312,23 @@ class FrameController {
2913
3312
  const html = await fetchResponse.responseHTML;
2914
3313
  if (html) {
2915
3314
  const { body } = parseHTMLDocument(html);
2916
- const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
2917
- const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
2918
- if (this.view.renderPromise)
2919
- await this.view.renderPromise;
2920
- await this.view.render(renderer);
2921
- session.frameRendered(fetchResponse, this.element);
2922
- session.frameLoaded(this.element);
2923
- this.fetchResponseLoaded(fetchResponse);
3315
+ const newFrameElement = await this.extractForeignFrameElement(body);
3316
+ if (newFrameElement) {
3317
+ const snapshot = new Snapshot(newFrameElement);
3318
+ const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
3319
+ if (this.view.renderPromise)
3320
+ await this.view.renderPromise;
3321
+ this.changeHistory();
3322
+ await this.view.render(renderer);
3323
+ this.complete = true;
3324
+ session.frameRendered(fetchResponse, this.element);
3325
+ session.frameLoaded(this.element);
3326
+ this.fetchResponseLoaded(fetchResponse);
3327
+ }
3328
+ else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3329
+ console.warn(`A matching frame for #${this.element.id} was missing from the response, transforming into full-page Visit.`);
3330
+ this.visitResponse(fetchResponse.response);
3331
+ }
2924
3332
  }
2925
3333
  }
2926
3334
  catch (error) {
@@ -2931,56 +3339,62 @@ class FrameController {
2931
3339
  this.fetchResponseLoaded = () => { };
2932
3340
  }
2933
3341
  }
2934
- elementAppearedInViewport(element) {
3342
+ elementAppearedInViewport(_element) {
2935
3343
  this.loadSourceURL();
2936
3344
  }
2937
- shouldInterceptLinkClick(element, url) {
2938
- if (element.hasAttribute("data-turbo-method")) {
2939
- return false;
2940
- }
2941
- else {
2942
- return this.shouldInterceptNavigation(element);
2943
- }
3345
+ willSubmitFormLinkToLocation(link) {
3346
+ return link.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(link);
2944
3347
  }
2945
- linkClickIntercepted(element, url) {
2946
- this.reloadable = true;
2947
- this.navigateFrame(element, url);
3348
+ submittedFormLinkToLocation(link, _location, form) {
3349
+ const frame = this.findFrameElement(link);
3350
+ if (frame)
3351
+ form.setAttribute("data-turbo-frame", frame.id);
2948
3352
  }
2949
- shouldInterceptFormSubmission(element, submitter) {
2950
- return this.shouldInterceptNavigation(element, submitter);
3353
+ willFollowLinkToLocation(element, location, event) {
3354
+ return this.shouldInterceptNavigation(element) && this.frameAllowsVisitingLocation(element, location, event);
2951
3355
  }
2952
- formSubmissionIntercepted(element, submitter) {
3356
+ followedLinkToLocation(element, location) {
3357
+ this.navigateFrame(element, location.href);
3358
+ }
3359
+ willSubmitForm(element, submitter) {
3360
+ return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
3361
+ }
3362
+ formSubmitted(element, submitter) {
2953
3363
  if (this.formSubmission) {
2954
3364
  this.formSubmission.stop();
2955
3365
  }
2956
- this.reloadable = false;
2957
3366
  this.formSubmission = new FormSubmission(this, element, submitter);
2958
3367
  const { fetchRequest } = this.formSubmission;
2959
3368
  this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
2960
3369
  this.formSubmission.start();
2961
3370
  }
2962
3371
  prepareHeadersForRequest(headers, request) {
3372
+ var _a;
2963
3373
  headers["Turbo-Frame"] = this.id;
3374
+ if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3375
+ request.acceptResponseType(StreamMessage.contentType);
3376
+ }
2964
3377
  }
2965
- requestStarted(request) {
3378
+ requestStarted(_request) {
2966
3379
  markAsBusy(this.element);
2967
3380
  }
2968
- requestPreventedHandlingResponse(request, response) {
3381
+ requestPreventedHandlingResponse(_request, _response) {
2969
3382
  this.resolveVisitPromise();
2970
3383
  }
2971
3384
  async requestSucceededWithResponse(request, response) {
2972
3385
  await this.loadResponse(response);
2973
3386
  this.resolveVisitPromise();
2974
3387
  }
2975
- requestFailedWithResponse(request, response) {
3388
+ async requestFailedWithResponse(request, response) {
2976
3389
  console.error(response);
3390
+ await this.loadResponse(response);
2977
3391
  this.resolveVisitPromise();
2978
3392
  }
2979
3393
  requestErrored(request, error) {
2980
3394
  console.error(error);
2981
3395
  this.resolveVisitPromise();
2982
3396
  }
2983
- requestFinished(request) {
3397
+ requestFinished(_request) {
2984
3398
  clearBusyState(this.element);
2985
3399
  }
2986
3400
  formSubmissionStarted({ formElement }) {
@@ -3000,19 +3414,32 @@ class FrameController {
3000
3414
  formSubmissionFinished({ formElement }) {
3001
3415
  clearBusyState(formElement, this.findFrameElement(formElement));
3002
3416
  }
3003
- allowsImmediateRender(snapshot, resume) {
3004
- return true;
3417
+ allowsImmediateRender({ element: newFrame }, options) {
3418
+ const event = dispatch("turbo:before-frame-render", {
3419
+ target: this.element,
3420
+ detail: Object.assign({ newFrame }, options),
3421
+ cancelable: true,
3422
+ });
3423
+ const { defaultPrevented, detail: { render }, } = event;
3424
+ if (this.view.renderer && render) {
3425
+ this.view.renderer.renderElement = render;
3426
+ }
3427
+ return !defaultPrevented;
3005
3428
  }
3006
- viewRenderedSnapshot(snapshot, isPreview) {
3429
+ viewRenderedSnapshot(_snapshot, _isPreview) { }
3430
+ preloadOnLoadLinksForView(element) {
3431
+ session.preloadOnLoadLinksForView(element);
3007
3432
  }
3008
- viewInvalidated() {
3433
+ viewInvalidated() { }
3434
+ willRenderFrame(currentElement, _newElement) {
3435
+ this.previousFrameElement = currentElement.cloneNode(true);
3009
3436
  }
3010
3437
  async visit(url) {
3011
3438
  var _a;
3012
- const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
3439
+ const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);
3013
3440
  (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
3014
3441
  this.currentFetchRequest = request;
3015
- return new Promise(resolve => {
3442
+ return new Promise((resolve) => {
3016
3443
  this.resolveVisitPromise = () => {
3017
3444
  this.resolveVisitPromise = () => { };
3018
3445
  this.currentFetchRequest = null;
@@ -3024,23 +3451,64 @@ class FrameController {
3024
3451
  navigateFrame(element, url, submitter) {
3025
3452
  const frame = this.findFrameElement(element, submitter);
3026
3453
  this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3027
- frame.setAttribute("reloadable", "");
3028
- frame.src = url;
3454
+ this.withCurrentNavigationElement(element, () => {
3455
+ frame.src = url;
3456
+ });
3029
3457
  }
3030
3458
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3031
- const action = getAttribute("data-turbo-action", submitter, element, frame);
3032
- if (isAction(action)) {
3033
- const { visitCachedSnapshot } = new SnapshotSubstitution(frame);
3459
+ this.action = getVisitAction(submitter, element, frame);
3460
+ this.frame = frame;
3461
+ if (isAction(this.action)) {
3462
+ const { visitCachedSnapshot } = frame.delegate;
3034
3463
  frame.delegate.fetchResponseLoaded = (fetchResponse) => {
3035
3464
  if (frame.src) {
3036
3465
  const { statusCode, redirected } = fetchResponse;
3037
3466
  const responseHTML = frame.ownerDocument.documentElement.outerHTML;
3038
3467
  const response = { statusCode, redirected, responseHTML };
3039
- session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false });
3468
+ const options = {
3469
+ response,
3470
+ visitCachedSnapshot,
3471
+ willRender: false,
3472
+ updateHistory: false,
3473
+ restorationIdentifier: this.restorationIdentifier,
3474
+ };
3475
+ if (this.action)
3476
+ options.action = this.action;
3477
+ session.visit(frame.src, options);
3040
3478
  }
3041
3479
  };
3042
3480
  }
3043
3481
  }
3482
+ changeHistory() {
3483
+ if (this.action && this.frame) {
3484
+ const method = getHistoryMethodForAction(this.action);
3485
+ session.history.update(method, expandURL(this.frame.src || ""), this.restorationIdentifier);
3486
+ }
3487
+ }
3488
+ willHandleFrameMissingFromResponse(fetchResponse) {
3489
+ this.element.setAttribute("complete", "");
3490
+ const response = fetchResponse.response;
3491
+ const visit = async (url, options = {}) => {
3492
+ if (url instanceof Response) {
3493
+ this.visitResponse(url);
3494
+ }
3495
+ else {
3496
+ session.visit(url, options);
3497
+ }
3498
+ };
3499
+ const event = dispatch("turbo:frame-missing", {
3500
+ target: this.element,
3501
+ detail: { response, visit },
3502
+ cancelable: true,
3503
+ });
3504
+ return !event.defaultPrevented;
3505
+ }
3506
+ async visitResponse(response) {
3507
+ const wrapped = new FetchResponse(response);
3508
+ const responseHTML = await wrapped.responseHTML;
3509
+ const { location, redirected, statusCode } = wrapped;
3510
+ return session.visit(location, { response: { redirected, statusCode, responseHTML } });
3511
+ }
3044
3512
  findFrameElement(element, submitter) {
3045
3513
  var _a;
3046
3514
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
@@ -3050,19 +3518,21 @@ class FrameController {
3050
3518
  let element;
3051
3519
  const id = CSS.escape(this.id);
3052
3520
  try {
3053
- if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
3521
+ element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
3522
+ if (element) {
3054
3523
  return element;
3055
3524
  }
3056
- if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
3525
+ element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);
3526
+ if (element) {
3057
3527
  await element.loaded;
3058
3528
  return await this.extractForeignFrameElement(element);
3059
3529
  }
3060
- console.error(`Response has no matching <turbo-frame id="${id}"> element`);
3061
3530
  }
3062
3531
  catch (error) {
3063
3532
  console.error(error);
3533
+ return new FrameElement();
3064
3534
  }
3065
- return new FrameElement();
3535
+ return null;
3066
3536
  }
3067
3537
  formActionIsVisitable(form, submitter) {
3068
3538
  const action = getAction(form, submitter);
@@ -3082,10 +3552,10 @@ class FrameController {
3082
3552
  return !frameElement.disabled;
3083
3553
  }
3084
3554
  }
3085
- if (!session.elementDriveEnabled(element)) {
3555
+ if (!session.elementIsNavigatable(element)) {
3086
3556
  return false;
3087
3557
  }
3088
- if (submitter && !session.elementDriveEnabled(submitter)) {
3558
+ if (submitter && !session.elementIsNavigatable(submitter)) {
3089
3559
  return false;
3090
3560
  }
3091
3561
  return true;
@@ -3101,24 +3571,10 @@ class FrameController {
3101
3571
  return this.element.src;
3102
3572
  }
3103
3573
  }
3104
- get reloadable() {
3105
- const frame = this.findFrameElement(this.element);
3106
- return frame.hasAttribute("reloadable");
3107
- }
3108
- set reloadable(value) {
3109
- const frame = this.findFrameElement(this.element);
3110
- if (value) {
3111
- frame.setAttribute("reloadable", "");
3112
- }
3113
- else {
3114
- frame.removeAttribute("reloadable");
3115
- }
3116
- }
3117
3574
  set sourceURL(sourceURL) {
3118
- this.settingSourceURL = true;
3119
- this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3120
- this.currentURL = this.element.src;
3121
- this.settingSourceURL = false;
3575
+ this.ignoringChangesToAttribute("src", () => {
3576
+ this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3577
+ });
3122
3578
  }
3123
3579
  get loadingStyle() {
3124
3580
  return this.element.loading;
@@ -3126,6 +3582,19 @@ class FrameController {
3126
3582
  get isLoading() {
3127
3583
  return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
3128
3584
  }
3585
+ get complete() {
3586
+ return this.element.hasAttribute("complete");
3587
+ }
3588
+ set complete(value) {
3589
+ this.ignoringChangesToAttribute("complete", () => {
3590
+ if (value) {
3591
+ this.element.setAttribute("complete", "");
3592
+ }
3593
+ else {
3594
+ this.element.removeAttribute("complete");
3595
+ }
3596
+ });
3597
+ }
3129
3598
  get isActive() {
3130
3599
  return this.element.isActive && this.connected;
3131
3600
  }
@@ -3135,16 +3604,26 @@ class FrameController {
3135
3604
  const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3136
3605
  return expandURL(root);
3137
3606
  }
3138
- }
3139
- class SnapshotSubstitution {
3140
- constructor(element) {
3141
- this.visitCachedSnapshot = ({ element }) => {
3142
- var _a;
3143
- const { id, clone } = this;
3144
- (_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
3145
- };
3146
- this.clone = element.cloneNode(true);
3147
- this.id = element.id;
3607
+ frameAllowsVisitingLocation(target, { href: url }, originalEvent) {
3608
+ const event = dispatch("turbo:click", {
3609
+ target,
3610
+ detail: { url, originalEvent },
3611
+ cancelable: true,
3612
+ });
3613
+ return !event.defaultPrevented;
3614
+ }
3615
+ isIgnoringChangesTo(attributeName) {
3616
+ return this.ignoredAttributes.has(attributeName);
3617
+ }
3618
+ ignoringChangesToAttribute(attributeName, callback) {
3619
+ this.ignoredAttributes.add(attributeName);
3620
+ callback();
3621
+ this.ignoredAttributes.delete(attributeName);
3622
+ }
3623
+ withCurrentNavigationElement(element, callback) {
3624
+ this.currentNavigationElement = element;
3625
+ callback();
3626
+ delete this.currentNavigationElement;
3148
3627
  }
3149
3628
  }
3150
3629
  function getFrameElementById(id) {
@@ -3172,36 +3651,10 @@ function activateElement(element, currentURL) {
3172
3651
  }
3173
3652
  }
3174
3653
 
3175
- const StreamActions = {
3176
- after() {
3177
- this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });
3178
- },
3179
- append() {
3180
- this.removeDuplicateTargetChildren();
3181
- this.targetElements.forEach(e => e.append(this.templateContent));
3182
- },
3183
- before() {
3184
- this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });
3185
- },
3186
- prepend() {
3187
- this.removeDuplicateTargetChildren();
3188
- this.targetElements.forEach(e => e.prepend(this.templateContent));
3189
- },
3190
- remove() {
3191
- this.targetElements.forEach(e => e.remove());
3192
- },
3193
- replace() {
3194
- this.targetElements.forEach(e => e.replaceWith(this.templateContent));
3195
- },
3196
- update() {
3197
- this.targetElements.forEach(e => {
3198
- e.innerHTML = "";
3199
- e.append(this.templateContent);
3200
- });
3201
- }
3202
- };
3203
-
3204
3654
  class StreamElement extends HTMLElement {
3655
+ static async renderElement(newElement) {
3656
+ await newElement.performAction();
3657
+ }
3205
3658
  async connectedCallback() {
3206
3659
  try {
3207
3660
  await this.render();
@@ -3215,12 +3668,13 @@ class StreamElement extends HTMLElement {
3215
3668
  }
3216
3669
  async render() {
3217
3670
  var _a;
3218
- return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
3219
- if (this.dispatchEvent(this.beforeRenderEvent)) {
3671
+ return ((_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
3672
+ const event = this.beforeRenderEvent;
3673
+ if (this.dispatchEvent(event)) {
3220
3674
  await nextAnimationFrame();
3221
- this.performAction();
3675
+ await event.detail.render(this);
3222
3676
  }
3223
- })());
3677
+ })()));
3224
3678
  }
3225
3679
  disconnect() {
3226
3680
  try {
@@ -3229,13 +3683,13 @@ class StreamElement extends HTMLElement {
3229
3683
  catch (_a) { }
3230
3684
  }
3231
3685
  removeDuplicateTargetChildren() {
3232
- this.duplicateChildren.forEach(c => c.remove());
3686
+ this.duplicateChildren.forEach((c) => c.remove());
3233
3687
  }
3234
3688
  get duplicateChildren() {
3235
3689
  var _a;
3236
- const existingChildren = this.targetElements.flatMap(e => [...e.children]).filter(c => !!c.id);
3237
- const newChildrenIds = [...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children].filter(c => !!c.id).map(c => c.id);
3238
- return existingChildren.filter(c => newChildrenIds.includes(c.id));
3690
+ const existingChildren = this.targetElements.flatMap((e) => [...e.children]).filter((c) => !!c.id);
3691
+ const newChildrenIds = [...(((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [])].filter((c) => !!c.id).map((c) => c.id);
3692
+ return existingChildren.filter((c) => newChildrenIds.includes(c.id));
3239
3693
  }
3240
3694
  get performAction() {
3241
3695
  if (this.action) {
@@ -3262,7 +3716,12 @@ class StreamElement extends HTMLElement {
3262
3716
  return this.templateElement.content.cloneNode(true);
3263
3717
  }
3264
3718
  get templateElement() {
3265
- if (this.firstElementChild instanceof HTMLTemplateElement) {
3719
+ if (this.firstElementChild === null) {
3720
+ const template = this.ownerDocument.createElement("template");
3721
+ this.appendChild(template);
3722
+ return template;
3723
+ }
3724
+ else if (this.firstElementChild instanceof HTMLTemplateElement) {
3266
3725
  return this.firstElementChild;
3267
3726
  }
3268
3727
  this.raise("first child element must be a <template> element");
@@ -3284,7 +3743,11 @@ class StreamElement extends HTMLElement {
3284
3743
  return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
3285
3744
  }
3286
3745
  get beforeRenderEvent() {
3287
- return new CustomEvent("turbo:before-stream-render", { bubbles: true, cancelable: true });
3746
+ return new CustomEvent("turbo:before-stream-render", {
3747
+ bubbles: true,
3748
+ cancelable: true,
3749
+ detail: { newStream: this, render: StreamElement.renderElement },
3750
+ });
3288
3751
  }
3289
3752
  get targetElementsById() {
3290
3753
  var _a;
@@ -3308,9 +3771,35 @@ class StreamElement extends HTMLElement {
3308
3771
  }
3309
3772
  }
3310
3773
 
3774
+ class StreamSourceElement extends HTMLElement {
3775
+ constructor() {
3776
+ super(...arguments);
3777
+ this.streamSource = null;
3778
+ }
3779
+ connectedCallback() {
3780
+ this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
3781
+ connectStreamSource(this.streamSource);
3782
+ }
3783
+ disconnectedCallback() {
3784
+ if (this.streamSource) {
3785
+ disconnectStreamSource(this.streamSource);
3786
+ }
3787
+ }
3788
+ get src() {
3789
+ return this.getAttribute("src") || "";
3790
+ }
3791
+ }
3792
+
3311
3793
  FrameElement.delegateConstructor = FrameController;
3312
- customElements.define("turbo-frame", FrameElement);
3313
- customElements.define("turbo-stream", StreamElement);
3794
+ if (customElements.get("turbo-frame") === undefined) {
3795
+ customElements.define("turbo-frame", FrameElement);
3796
+ }
3797
+ if (customElements.get("turbo-stream") === undefined) {
3798
+ customElements.define("turbo-stream", StreamElement);
3799
+ }
3800
+ if (customElements.get("turbo-stream-source") === undefined) {
3801
+ customElements.define("turbo-stream-source", StreamSourceElement);
3802
+ }
3314
3803
 
3315
3804
  (() => {
3316
3805
  let element = document.currentScript;
@@ -3318,7 +3807,8 @@ customElements.define("turbo-stream", StreamElement);
3318
3807
  return;
3319
3808
  if (element.hasAttribute("data-turbo-suppress-warning"))
3320
3809
  return;
3321
- while (element = element.parentElement) {
3810
+ element = element.parentElement;
3811
+ while (element) {
3322
3812
  if (element == document.body) {
3323
3813
  return console.warn(unindent `
3324
3814
  You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
@@ -3331,10 +3821,11 @@ customElements.define("turbo-stream", StreamElement);
3331
3821
  Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
3332
3822
  `, element.outerHTML);
3333
3823
  }
3824
+ element = element.parentElement;
3334
3825
  }
3335
3826
  })();
3336
3827
 
3337
3828
  window.Turbo = Turbo;
3338
3829
  start();
3339
3830
 
3340
- export { PageRenderer, PageSnapshot, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setProgressBarDelay, start, visit };
3831
+ export { FrameElement, FrameLoadingStyle, FrameRenderer, PageRenderer, PageSnapshot, StreamActions, StreamElement, StreamSourceElement, cache, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setFormMode, setProgressBarDelay, start, visit };