@hotwired/turbo 7.1.0 → 7.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +4 -0
  2. package/dist/turbo.es2017-esm.js +1139 -630
  3. package/dist/turbo.es2017-umd.js +1153 -637
  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 +5 -4
  11. package/dist/types/core/drive/page_renderer.d.ts +11 -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 +17 -5
  16. package/dist/types/core/frames/frame_controller.d.ts +50 -24
  17. package/dist/types/core/frames/frame_redirector.d.ts +12 -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 +12 -4
  21. package/dist/types/core/native/adapter.d.ts +3 -2
  22. package/dist/types/core/native/browser_adapter.d.ts +18 -9
  23. package/dist/types/core/renderer.d.ts +11 -5
  24. package/dist/types/core/session.d.ts +81 -21
  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/types.d.ts +4 -0
  30. package/dist/types/core/view.d.ts +13 -7
  31. package/dist/types/elements/frame_element.d.ts +10 -6
  32. package/dist/types/elements/index.d.ts +1 -0
  33. package/dist/types/elements/stream_element.d.ts +8 -1
  34. package/dist/types/elements/stream_source_element.d.ts +7 -0
  35. package/dist/types/http/fetch_request.d.ts +11 -3
  36. package/dist/types/http/index.d.ts +1 -0
  37. package/dist/types/index.d.ts +2 -0
  38. package/dist/types/observers/cache_observer.d.ts +1 -1
  39. package/dist/types/observers/form_link_click_observer.d.ts +14 -0
  40. package/dist/types/observers/form_submit_observer.d.ts +2 -1
  41. package/dist/types/observers/link_click_observer.d.ts +5 -4
  42. package/dist/types/polyfills/submit-event.d.ts +1 -7
  43. package/dist/types/tests/functional/async_script_tests.d.ts +1 -6
  44. package/dist/types/tests/functional/autofocus_tests.d.ts +1 -9
  45. package/dist/types/tests/functional/cache_observer_tests.d.ts +1 -5
  46. package/dist/types/tests/functional/drive_custom_body_tests.d.ts +1 -0
  47. package/dist/types/tests/functional/drive_disabled_tests.d.ts +1 -9
  48. package/dist/types/tests/functional/drive_tests.d.ts +1 -8
  49. package/dist/types/tests/functional/form_mode_tests.d.ts +1 -0
  50. package/dist/types/tests/functional/form_submission_tests.d.ts +1 -84
  51. package/dist/types/tests/functional/frame_navigation_tests.d.ts +1 -7
  52. package/dist/types/tests/functional/frame_tests.d.ts +1 -52
  53. package/dist/types/tests/functional/import_tests.d.ts +1 -4
  54. package/dist/types/tests/functional/loading_tests.d.ts +1 -13
  55. package/dist/types/tests/functional/navigation_tests.d.ts +1 -38
  56. package/dist/types/tests/functional/pausable_rendering_tests.d.ts +1 -6
  57. package/dist/types/tests/functional/pausable_requests_tests.d.ts +1 -6
  58. package/dist/types/tests/functional/preloader_tests.d.ts +1 -0
  59. package/dist/types/tests/functional/rendering_tests.d.ts +1 -35
  60. package/dist/types/tests/functional/scroll_restoration_tests.d.ts +1 -6
  61. package/dist/types/tests/functional/stream_tests.d.ts +1 -6
  62. package/dist/types/tests/functional/visit_tests.d.ts +1 -15
  63. package/dist/types/tests/helpers/page.d.ts +50 -0
  64. package/dist/types/tests/unit/deprecated_adapter_support_test.d.ts +10 -10
  65. package/dist/types/util.d.ts +14 -3
  66. package/package.json +23 -9
  67. package/CHANGELOG.md +0 -3
  68. package/dist/types/core/frames/form_interceptor.d.ts +0 -12
  69. package/dist/types/core/frames/link_interceptor.d.ts +0 -16
  70. package/dist/types/tests/functional/index.d.ts +0 -17
  71. package/dist/types/tests/helpers/functional_test_case.d.ts +0 -44
  72. package/dist/types/tests/helpers/remote_channel.d.ts +0 -10
  73. 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-rc.1
3
+ Copyright © 2022 Basecamp, LLC
4
4
  */
5
5
  (function () {
6
- if (window.Reflect === undefined || window.customElements === undefined ||
6
+ if (window.Reflect === undefined ||
7
+ window.customElements === undefined ||
7
8
  window.customElements.polyfillWrapFlushCallback) {
8
9
  return;
9
10
  }
10
11
  const BuiltInHTMLElement = HTMLElement;
11
12
  const wrapperForTheName = {
12
- 'HTMLElement': function HTMLElement() {
13
+ HTMLElement: function HTMLElement() {
13
14
  return Reflect.construct(BuiltInHTMLElement, [], this.constructor);
14
- }
15
+ },
15
16
  };
16
- window.HTMLElement =
17
- wrapperForTheName['HTMLElement'];
17
+ window.HTMLElement = wrapperForTheName["HTMLElement"];
18
18
  HTMLElement.prototype = BuiltInHTMLElement.prototype;
19
19
  HTMLElement.prototype.constructor = HTMLElement;
20
20
  Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);
@@ -72,7 +72,7 @@ Copyright © 2021 Basecamp, LLC
72
72
  }
73
73
  })(HTMLFormElement.prototype);
74
74
 
75
- const submittersByForm = new WeakMap;
75
+ const submittersByForm = new WeakMap();
76
76
  function findSubmitterFromClickTarget(target) {
77
77
  const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
78
78
  const candidate = element ? element.closest("input, button") : null;
@@ -103,7 +103,7 @@ function clickCaptured(event) {
103
103
  if (this.type == "submit" && this.target instanceof HTMLFormElement) {
104
104
  return submittersByForm.get(this.target);
105
105
  }
106
- }
106
+ },
107
107
  });
108
108
  })();
109
109
 
@@ -119,7 +119,7 @@ class FrameElement extends HTMLElement {
119
119
  this.delegate = new FrameElement.delegateConstructor(this);
120
120
  }
121
121
  static get observedAttributes() {
122
- return ["disabled", "loading", "src"];
122
+ return ["disabled", "complete", "loading", "src"];
123
123
  }
124
124
  connectedCallback() {
125
125
  this.delegate.connect();
@@ -129,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,51 @@ 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 getBodyElementId() {
452
+ return getMetaContent("turbo-body");
453
+ }
454
+ function getMetaElement(name) {
455
+ return document.querySelector(`meta[name="${name}"]`);
456
+ }
457
+ function getMetaContent(name) {
458
+ const element = getMetaElement(name);
459
+ return element && element.content;
460
+ }
461
+ function setMetaContent(name, content) {
462
+ let element = getMetaElement(name);
463
+ if (!element) {
464
+ element = document.createElement("meta");
465
+ element.setAttribute("name", name);
466
+ document.head.appendChild(element);
467
+ }
468
+ element.setAttribute("content", content);
469
+ return element;
470
+ }
382
471
 
383
472
  var FetchMethod;
384
473
  (function (FetchMethod) {
@@ -390,17 +479,22 @@ var FetchMethod;
390
479
  })(FetchMethod || (FetchMethod = {}));
391
480
  function fetchMethodFromString(method) {
392
481
  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;
482
+ case "get":
483
+ return FetchMethod.get;
484
+ case "post":
485
+ return FetchMethod.post;
486
+ case "put":
487
+ return FetchMethod.put;
488
+ case "patch":
489
+ return FetchMethod.patch;
490
+ case "delete":
491
+ return FetchMethod.delete;
398
492
  }
399
493
  }
400
494
  class FetchRequest {
401
- constructor(delegate, method, location, body = new URLSearchParams, target = null) {
402
- this.abortController = new AbortController;
403
- this.resolveRequestPromise = (value) => { };
495
+ constructor(delegate, method, location, body = new URLSearchParams(), target = null) {
496
+ this.abortController = new AbortController();
497
+ this.resolveRequestPromise = (_value) => { };
404
498
  this.delegate = delegate;
405
499
  this.method = method;
406
500
  this.headers = this.defaultHeaders;
@@ -431,7 +525,7 @@ class FetchRequest {
431
525
  return await this.receive(response);
432
526
  }
433
527
  catch (error) {
434
- if (error.name !== 'AbortError') {
528
+ if (error.name !== "AbortError") {
435
529
  this.delegate.requestErrored(this, error);
436
530
  throw error;
437
531
  }
@@ -442,7 +536,11 @@ class FetchRequest {
442
536
  }
443
537
  async receive(response) {
444
538
  const fetchResponse = new FetchResponse(response);
445
- const event = dispatch("turbo:before-fetch-response", { cancelable: true, detail: { fetchResponse }, target: this.target });
539
+ const event = dispatch("turbo:before-fetch-response", {
540
+ cancelable: true,
541
+ detail: { fetchResponse },
542
+ target: this.target,
543
+ });
446
544
  if (event.defaultPrevented) {
447
545
  this.delegate.requestPreventedHandlingResponse(this, fetchResponse);
448
546
  }
@@ -463,12 +561,12 @@ class FetchRequest {
463
561
  redirect: "follow",
464
562
  body: this.isIdempotent ? null : this.body,
465
563
  signal: this.abortSignal,
466
- referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
564
+ referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href,
467
565
  };
468
566
  }
469
567
  get defaultHeaders() {
470
568
  return {
471
- "Accept": "text/html, application/xhtml+xml"
569
+ Accept: "text/html, application/xhtml+xml",
472
570
  };
473
571
  }
474
572
  get isIdempotent() {
@@ -477,16 +575,19 @@ class FetchRequest {
477
575
  get abortSignal() {
478
576
  return this.abortController.signal;
479
577
  }
578
+ acceptResponseType(mimeType) {
579
+ this.headers["Accept"] = [mimeType, this.headers["Accept"]].join(", ");
580
+ }
480
581
  async allowRequestToBeIntercepted(fetchOptions) {
481
- const requestInterception = new Promise(resolve => this.resolveRequestPromise = resolve);
582
+ const requestInterception = new Promise((resolve) => (this.resolveRequestPromise = resolve));
482
583
  const event = dispatch("turbo:before-fetch-request", {
483
584
  cancelable: true,
484
585
  detail: {
485
586
  fetchOptions,
486
587
  url: this.url,
487
- resume: this.resolveRequestPromise
588
+ resume: this.resolveRequestPromise,
488
589
  },
489
- target: this.target
590
+ target: this.target,
490
591
  });
491
592
  if (event.defaultPrevented)
492
593
  await requestInterception;
@@ -496,7 +597,7 @@ class FetchRequest {
496
597
  class AppearanceObserver {
497
598
  constructor(delegate, element) {
498
599
  this.started = false;
499
- this.intersect = entries => {
600
+ this.intersect = (entries) => {
500
601
  const lastEntry = entries.slice(-1)[0];
501
602
  if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
502
603
  this.delegate.elementAppearedInViewport(this.element);
@@ -521,40 +622,29 @@ class AppearanceObserver {
521
622
  }
522
623
 
523
624
  class StreamMessage {
524
- constructor(html) {
525
- this.templateElement = document.createElement("template");
526
- this.templateElement.innerHTML = html;
625
+ constructor(fragment) {
626
+ this.fragment = importStreamElements(fragment);
527
627
  }
528
628
  static wrap(message) {
529
629
  if (typeof message == "string") {
530
- return new this(message);
630
+ return new this(createDocumentFragment(message));
531
631
  }
532
632
  else {
533
633
  return message;
534
634
  }
535
635
  }
536
- get fragment() {
537
- const fragment = document.createDocumentFragment();
538
- for (const element of this.foreignElements) {
539
- fragment.appendChild(document.importNode(element, true));
636
+ }
637
+ StreamMessage.contentType = "text/vnd.turbo-stream.html";
638
+ function importStreamElements(fragment) {
639
+ for (const element of fragment.querySelectorAll("turbo-stream")) {
640
+ const streamElement = document.importNode(element, true);
641
+ for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) {
642
+ inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));
540
643
  }
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);
644
+ element.replaceWith(streamElement);
555
645
  }
646
+ return fragment;
556
647
  }
557
- StreamMessage.contentType = "text/vnd.turbo-stream.html";
558
648
 
559
649
  var FormSubmissionState;
560
650
  (function (FormSubmissionState) {
@@ -573,9 +663,12 @@ var FormEnctype;
573
663
  })(FormEnctype || (FormEnctype = {}));
574
664
  function formEnctypeFromString(encoding) {
575
665
  switch (encoding.toLowerCase()) {
576
- case FormEnctype.multipart: return FormEnctype.multipart;
577
- case FormEnctype.plain: return FormEnctype.plain;
578
- default: return FormEnctype.urlEncoded;
666
+ case FormEnctype.multipart:
667
+ return FormEnctype.multipart;
668
+ case FormEnctype.plain:
669
+ return FormEnctype.plain;
670
+ default:
671
+ return FormEnctype.urlEncoded;
579
672
  }
580
673
  }
581
674
  class FormSubmission {
@@ -592,8 +685,8 @@ class FormSubmission {
592
685
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
593
686
  this.mustRedirect = mustRedirect;
594
687
  }
595
- static confirmMethod(message, element) {
596
- return confirm(message);
688
+ static confirmMethod(message, _element, _submitter) {
689
+ return Promise.resolve(confirm(message));
597
690
  }
598
691
  get method() {
599
692
  var _a;
@@ -602,8 +695,13 @@ class FormSubmission {
602
695
  }
603
696
  get action() {
604
697
  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 || "";
698
+ const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
699
+ if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
700
+ return this.submitter.getAttribute("formaction") || "";
701
+ }
702
+ else {
703
+ return this.formElement.getAttribute("action") || formElementAction || "";
704
+ }
607
705
  }
608
706
  get body() {
609
707
  if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
@@ -625,16 +723,11 @@ class FormSubmission {
625
723
  return entries.concat(typeof value == "string" ? [[name, value]] : []);
626
724
  }, []);
627
725
  }
628
- get confirmationMessage() {
629
- return this.formElement.getAttribute("data-turbo-confirm");
630
- }
631
- get needsConfirmation() {
632
- return this.confirmationMessage !== null;
633
- }
634
726
  async start() {
635
727
  const { initialized, requesting } = FormSubmissionState;
636
- if (this.needsConfirmation) {
637
- const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
728
+ const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
729
+ if (typeof confirmationMessage === "string") {
730
+ const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);
638
731
  if (!answer) {
639
732
  return;
640
733
  }
@@ -658,14 +751,19 @@ class FormSubmission {
658
751
  if (token) {
659
752
  headers["X-CSRF-Token"] = token;
660
753
  }
661
- headers["Accept"] = [StreamMessage.contentType, headers["Accept"]].join(", ");
754
+ }
755
+ if (this.requestAcceptsTurboStreamResponse(request)) {
756
+ request.acceptResponseType(StreamMessage.contentType);
662
757
  }
663
758
  }
664
- requestStarted(request) {
759
+ requestStarted(_request) {
665
760
  var _a;
666
761
  this.state = FormSubmissionState.waiting;
667
762
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
668
- dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } });
763
+ dispatch("turbo:submit-start", {
764
+ target: this.formElement,
765
+ detail: { formSubmission: this },
766
+ });
669
767
  this.delegate.formSubmissionStarted(this);
670
768
  }
671
769
  requestPreventedHandlingResponse(request, response) {
@@ -691,25 +789,35 @@ class FormSubmission {
691
789
  }
692
790
  requestErrored(request, error) {
693
791
  this.result = { success: false, error };
792
+ dispatch("turbo:fetch-request-error", {
793
+ target: this.formElement,
794
+ detail: { request, error },
795
+ });
694
796
  this.delegate.formSubmissionErrored(this, error);
695
797
  }
696
- requestFinished(request) {
798
+ requestFinished(_request) {
697
799
  var _a;
698
800
  this.state = FormSubmissionState.stopped;
699
801
  (_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) });
802
+ dispatch("turbo:submit-end", {
803
+ target: this.formElement,
804
+ detail: Object.assign({ formSubmission: this }, this.result),
805
+ });
701
806
  this.delegate.formSubmissionFinished(this);
702
807
  }
703
808
  requestMustRedirect(request) {
704
809
  return !request.isIdempotent && this.mustRedirect;
705
810
  }
811
+ requestAcceptsTurboStreamResponse(request) {
812
+ return !request.isIdempotent || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
813
+ }
706
814
  }
707
815
  function buildFormData(formElement, submitter) {
708
816
  const formData = new FormData(formElement);
709
817
  const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
710
818
  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);
819
+ if (name) {
820
+ formData.append(name, value || "");
713
821
  }
714
822
  return formData;
715
823
  }
@@ -723,15 +831,11 @@ function getCookieValue(cookieName) {
723
831
  }
724
832
  }
725
833
  }
726
- function getMetaContent(name) {
727
- const element = document.querySelector(`meta[name="${name}"]`);
728
- return element && element.content;
729
- }
730
834
  function responseSucceededWithoutRedirect(response) {
731
835
  return response.statusCode == 200 && !response.redirected;
732
836
  }
733
837
  function mergeFormDataEntries(url, entries) {
734
- const searchParams = new URLSearchParams;
838
+ const searchParams = new URLSearchParams();
735
839
  for (const [name, value] of entries) {
736
840
  if (value instanceof File)
737
841
  continue;
@@ -745,6 +849,9 @@ class Snapshot {
745
849
  constructor(element) {
746
850
  this.element = element;
747
851
  }
852
+ get activeElement() {
853
+ return this.element.ownerDocument.activeElement;
854
+ }
748
855
  get children() {
749
856
  return [...this.element.children];
750
857
  }
@@ -758,13 +865,20 @@ class Snapshot {
758
865
  return this.element.isConnected;
759
866
  }
760
867
  get firstAutofocusableElement() {
761
- return this.element.querySelector("[autofocus]");
868
+ const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
869
+ for (const element of this.element.querySelectorAll("[autofocus]")) {
870
+ if (element.closest(inertDisabledOrHidden) == null)
871
+ return element;
872
+ else
873
+ continue;
874
+ }
875
+ return null;
762
876
  }
763
877
  get permanentElements() {
764
- return [...this.element.querySelectorAll("[id][data-turbo-permanent]")];
878
+ return queryPermanentElementsAll(this.element);
765
879
  }
766
880
  getPermanentElementById(id) {
767
- return this.element.querySelector(`#${id}[data-turbo-permanent]`);
881
+ return getPermanentElementById(this.element, id);
768
882
  }
769
883
  getPermanentElementMapForSnapshot(snapshot) {
770
884
  const permanentElementMap = {};
@@ -778,36 +892,66 @@ class Snapshot {
778
892
  return permanentElementMap;
779
893
  }
780
894
  }
895
+ function getPermanentElementById(node, id) {
896
+ return node.querySelector(`#${id}[data-turbo-permanent]`);
897
+ }
898
+ function queryPermanentElementsAll(node) {
899
+ return node.querySelectorAll("[id][data-turbo-permanent]");
900
+ }
781
901
 
782
- class FormInterceptor {
783
- constructor(delegate, element) {
902
+ class FormSubmitObserver {
903
+ constructor(delegate, eventTarget) {
904
+ this.started = false;
905
+ this.submitCaptured = () => {
906
+ this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
907
+ this.eventTarget.addEventListener("submit", this.submitBubbled, false);
908
+ };
784
909
  this.submitBubbled = ((event) => {
785
- const form = event.target;
786
- if (!event.defaultPrevented && form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
910
+ if (!event.defaultPrevented) {
911
+ const form = event.target instanceof HTMLFormElement ? event.target : undefined;
787
912
  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)) {
913
+ if (form &&
914
+ submissionDoesNotDismissDialog(form, submitter) &&
915
+ submissionDoesNotTargetIFrame(form, submitter) &&
916
+ this.delegate.willSubmitForm(form, submitter)) {
790
917
  event.preventDefault();
791
- event.stopImmediatePropagation();
792
- this.delegate.formSubmissionIntercepted(form, submitter);
918
+ this.delegate.formSubmitted(form, submitter);
793
919
  }
794
920
  }
795
921
  });
796
922
  this.delegate = delegate;
797
- this.element = element;
923
+ this.eventTarget = eventTarget;
798
924
  }
799
925
  start() {
800
- this.element.addEventListener("submit", this.submitBubbled);
926
+ if (!this.started) {
927
+ this.eventTarget.addEventListener("submit", this.submitCaptured, true);
928
+ this.started = true;
929
+ }
801
930
  }
802
931
  stop() {
803
- this.element.removeEventListener("submit", this.submitBubbled);
932
+ if (this.started) {
933
+ this.eventTarget.removeEventListener("submit", this.submitCaptured, true);
934
+ this.started = false;
935
+ }
936
+ }
937
+ }
938
+ function submissionDoesNotDismissDialog(form, submitter) {
939
+ const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
940
+ return method != "dialog";
941
+ }
942
+ function submissionDoesNotTargetIFrame(form, submitter) {
943
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
944
+ for (const element of document.getElementsByName(target)) {
945
+ if (element instanceof HTMLIFrameElement)
946
+ return false;
804
947
  }
948
+ return true;
805
949
  }
806
950
 
807
951
  class View {
808
952
  constructor(delegate, element) {
809
- this.resolveRenderPromise = (value) => { };
810
- this.resolveInterceptionPromise = (value) => { };
953
+ this.resolveRenderPromise = (_value) => { };
954
+ this.resolveInterceptionPromise = (_value) => { };
811
955
  this.delegate = delegate;
812
956
  this.element = element;
813
957
  }
@@ -852,15 +996,17 @@ class View {
852
996
  const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
853
997
  if (shouldRender) {
854
998
  try {
855
- this.renderPromise = new Promise(resolve => this.resolveRenderPromise = resolve);
999
+ this.renderPromise = new Promise((resolve) => (this.resolveRenderPromise = resolve));
856
1000
  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);
1001
+ await this.prepareToRenderSnapshot(renderer);
1002
+ const renderInterception = new Promise((resolve) => (this.resolveInterceptionPromise = resolve));
1003
+ const options = { resume: this.resolveInterceptionPromise, render: this.renderer.renderElement };
1004
+ const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
860
1005
  if (!immediateRender)
861
1006
  await renderInterception;
862
1007
  await this.renderSnapshot(renderer);
863
1008
  this.delegate.viewRenderedSnapshot(snapshot, isPreview);
1009
+ this.delegate.preloadOnLoadLinksForView(this.element);
864
1010
  this.finishRenderingSnapshot(renderer);
865
1011
  }
866
1012
  finally {
@@ -870,15 +1016,15 @@ class View {
870
1016
  }
871
1017
  }
872
1018
  else {
873
- this.invalidate();
1019
+ this.invalidate(renderer.reloadReason);
874
1020
  }
875
1021
  }
876
- invalidate() {
877
- this.delegate.viewInvalidated();
1022
+ invalidate(reason) {
1023
+ this.delegate.viewInvalidated(reason);
878
1024
  }
879
- prepareToRenderSnapshot(renderer) {
1025
+ async prepareToRenderSnapshot(renderer) {
880
1026
  this.markAsPreview(renderer.isPreview);
881
- renderer.prepareToRender();
1027
+ await renderer.prepareToRender();
882
1028
  }
883
1029
  markAsPreview(isPreview) {
884
1030
  if (isPreview) {
@@ -905,65 +1051,125 @@ class FrameView extends View {
905
1051
  }
906
1052
  }
907
1053
 
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
- }
1054
+ class LinkClickObserver {
1055
+ constructor(delegate, eventTarget) {
1056
+ this.started = false;
1057
+ this.clickCaptured = () => {
1058
+ this.eventTarget.removeEventListener("click", this.clickBubbled, false);
1059
+ this.eventTarget.addEventListener("click", this.clickBubbled, false);
917
1060
  };
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);
1061
+ this.clickBubbled = (event) => {
1062
+ if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
1063
+ const target = (event.composedPath && event.composedPath()[0]) || event.target;
1064
+ const link = this.findLinkFromClickTarget(target);
1065
+ if (link && doesNotTargetIFrame(link)) {
1066
+ const location = this.getLocationForLink(link);
1067
+ if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1068
+ event.preventDefault();
1069
+ this.delegate.followedLinkToLocation(link, location);
1070
+ }
924
1071
  }
925
1072
  }
926
- delete this.clickEvent;
927
- });
928
- this.willVisit = () => {
929
- delete this.clickEvent;
930
1073
  };
931
1074
  this.delegate = delegate;
932
- this.element = element;
1075
+ this.eventTarget = eventTarget;
1076
+ }
1077
+ start() {
1078
+ if (!this.started) {
1079
+ this.eventTarget.addEventListener("click", this.clickCaptured, true);
1080
+ this.started = true;
1081
+ }
1082
+ }
1083
+ stop() {
1084
+ if (this.started) {
1085
+ this.eventTarget.removeEventListener("click", this.clickCaptured, true);
1086
+ this.started = false;
1087
+ }
1088
+ }
1089
+ clickEventIsSignificant(event) {
1090
+ return !((event.target && event.target.isContentEditable) ||
1091
+ event.defaultPrevented ||
1092
+ event.which > 1 ||
1093
+ event.altKey ||
1094
+ event.ctrlKey ||
1095
+ event.metaKey ||
1096
+ event.shiftKey);
1097
+ }
1098
+ findLinkFromClickTarget(target) {
1099
+ if (target instanceof Element) {
1100
+ return target.closest("a[href]:not([target^=_]):not([download])");
1101
+ }
1102
+ }
1103
+ getLocationForLink(link) {
1104
+ return expandURL(link.getAttribute("href") || "");
1105
+ }
1106
+ }
1107
+ function doesNotTargetIFrame(anchor) {
1108
+ for (const element of document.getElementsByName(anchor.target)) {
1109
+ if (element instanceof HTMLIFrameElement)
1110
+ return false;
1111
+ }
1112
+ return true;
1113
+ }
1114
+
1115
+ class FormLinkClickObserver {
1116
+ constructor(delegate, element) {
1117
+ this.delegate = delegate;
1118
+ this.linkClickObserver = new LinkClickObserver(this, element);
933
1119
  }
934
1120
  start() {
935
- this.element.addEventListener("click", this.clickBubbled);
936
- document.addEventListener("turbo:click", this.linkClicked);
937
- document.addEventListener("turbo:before-visit", this.willVisit);
1121
+ this.linkClickObserver.start();
938
1122
  }
939
1123
  stop() {
940
- this.element.removeEventListener("click", this.clickBubbled);
941
- document.removeEventListener("turbo:click", this.linkClicked);
942
- document.removeEventListener("turbo:before-visit", this.willVisit);
1124
+ this.linkClickObserver.stop();
1125
+ }
1126
+ willFollowLinkToLocation(link, location, originalEvent) {
1127
+ return (this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) &&
1128
+ link.hasAttribute("data-turbo-method"));
943
1129
  }
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;
1130
+ followedLinkToLocation(link, location) {
1131
+ const action = location.href;
1132
+ const form = document.createElement("form");
1133
+ form.setAttribute("data-turbo", "true");
1134
+ form.setAttribute("action", action);
1135
+ form.setAttribute("hidden", "");
1136
+ const method = link.getAttribute("data-turbo-method");
1137
+ if (method)
1138
+ form.setAttribute("method", method);
1139
+ const turboFrame = link.getAttribute("data-turbo-frame");
1140
+ if (turboFrame)
1141
+ form.setAttribute("data-turbo-frame", turboFrame);
1142
+ const turboAction = link.getAttribute("data-turbo-action");
1143
+ if (turboAction)
1144
+ form.setAttribute("data-turbo-action", turboAction);
1145
+ const turboConfirm = link.getAttribute("data-turbo-confirm");
1146
+ if (turboConfirm)
1147
+ form.setAttribute("data-turbo-confirm", turboConfirm);
1148
+ const turboStream = link.hasAttribute("data-turbo-stream");
1149
+ if (turboStream)
1150
+ form.setAttribute("data-turbo-stream", "");
1151
+ this.delegate.submittedFormLinkToLocation(link, location, form);
1152
+ document.body.appendChild(form);
1153
+ form.addEventListener("turbo:submit-end", () => form.remove(), { once: true });
1154
+ requestAnimationFrame(() => form.requestSubmit());
951
1155
  }
952
1156
  }
953
1157
 
954
1158
  class Bardo {
955
- constructor(permanentElementMap) {
1159
+ constructor(delegate, permanentElementMap) {
1160
+ this.delegate = delegate;
956
1161
  this.permanentElementMap = permanentElementMap;
957
1162
  }
958
- static preservingPermanentElements(permanentElementMap, callback) {
959
- const bardo = new this(permanentElementMap);
1163
+ static preservingPermanentElements(delegate, permanentElementMap, callback) {
1164
+ const bardo = new this(delegate, permanentElementMap);
960
1165
  bardo.enter();
961
1166
  callback();
962
1167
  bardo.leave();
963
1168
  }
964
1169
  enter() {
965
1170
  for (const id in this.permanentElementMap) {
966
- const [, newPermanentElement] = this.permanentElementMap[id];
1171
+ const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
1172
+ this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
967
1173
  this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
968
1174
  }
969
1175
  }
@@ -972,6 +1178,7 @@ class Bardo {
972
1178
  const [currentPermanentElement] = this.permanentElementMap[id];
973
1179
  this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
974
1180
  this.replacePlaceholderWithPermanentElement(currentPermanentElement);
1181
+ this.delegate.leavingBardo(currentPermanentElement);
975
1182
  }
976
1183
  }
977
1184
  replaceNewPermanentElementWithPlaceholder(permanentElement) {
@@ -987,7 +1194,7 @@ class Bardo {
987
1194
  placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
988
1195
  }
989
1196
  getPlaceholderById(id) {
990
- return this.placeholders.find(element => element.content == id);
1197
+ return this.placeholders.find((element) => element.content == id);
991
1198
  }
992
1199
  get placeholders() {
993
1200
  return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")];
@@ -1001,16 +1208,21 @@ function createPlaceholderForPermanentElement(permanentElement) {
1001
1208
  }
1002
1209
 
1003
1210
  class Renderer {
1004
- constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
1211
+ constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1212
+ this.activeElement = null;
1005
1213
  this.currentSnapshot = currentSnapshot;
1006
1214
  this.newSnapshot = newSnapshot;
1007
1215
  this.isPreview = isPreview;
1008
1216
  this.willRender = willRender;
1009
- this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
1217
+ this.renderElement = renderElement;
1218
+ this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));
1010
1219
  }
1011
1220
  get shouldRender() {
1012
1221
  return true;
1013
1222
  }
1223
+ get reloadReason() {
1224
+ return;
1225
+ }
1014
1226
  prepareToRender() {
1015
1227
  return;
1016
1228
  }
@@ -1020,23 +1232,8 @@ class Renderer {
1020
1232
  delete this.resolvingFunctions;
1021
1233
  }
1022
1234
  }
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
1235
  preservingPermanentElements(callback) {
1039
- Bardo.preservingPermanentElements(this.permanentElementMap, callback);
1236
+ Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1040
1237
  }
1041
1238
  focusFirstAutofocusableElement() {
1042
1239
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1044,6 +1241,19 @@ class Renderer {
1044
1241
  element.focus();
1045
1242
  }
1046
1243
  }
1244
+ enteringBardo(currentPermanentElement) {
1245
+ if (this.activeElement)
1246
+ return;
1247
+ if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
1248
+ this.activeElement = this.currentSnapshot.activeElement;
1249
+ }
1250
+ }
1251
+ leavingBardo(currentPermanentElement) {
1252
+ if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
1253
+ this.activeElement.focus();
1254
+ this.activeElement = null;
1255
+ }
1256
+ }
1047
1257
  get connectedSnapshot() {
1048
1258
  return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
1049
1259
  }
@@ -1056,21 +1266,28 @@ class Renderer {
1056
1266
  get permanentElementMap() {
1057
1267
  return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1058
1268
  }
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
1269
  }
1069
1270
  function elementIsFocusable(element) {
1070
1271
  return element && typeof element.focus == "function";
1071
1272
  }
1072
1273
 
1073
1274
  class FrameRenderer extends Renderer {
1275
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1276
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1277
+ this.delegate = delegate;
1278
+ }
1279
+ static renderElement(currentElement, newElement) {
1280
+ var _a;
1281
+ const destinationRange = document.createRange();
1282
+ destinationRange.selectNodeContents(currentElement);
1283
+ destinationRange.deleteContents();
1284
+ const frameElement = newElement;
1285
+ const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1286
+ if (sourceRange) {
1287
+ sourceRange.selectNodeContents(frameElement);
1288
+ currentElement.appendChild(sourceRange.extractContents());
1289
+ }
1290
+ }
1074
1291
  get shouldRender() {
1075
1292
  return true;
1076
1293
  }
@@ -1086,23 +1303,16 @@ class FrameRenderer extends Renderer {
1086
1303
  this.activateScriptElements();
1087
1304
  }
1088
1305
  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
- }
1306
+ this.delegate.willRenderFrame(this.currentElement, this.newElement);
1307
+ this.renderElement(this.currentElement, this.newElement);
1099
1308
  }
1100
1309
  scrollFrameIntoView() {
1101
1310
  if (this.currentElement.autoscroll || this.newElement.autoscroll) {
1102
1311
  const element = this.currentElement.firstElementChild;
1103
1312
  const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
1313
+ const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
1104
1314
  if (element) {
1105
- element.scrollIntoView({ block });
1315
+ element.scrollIntoView({ block, behavior });
1106
1316
  return true;
1107
1317
  }
1108
1318
  }
@@ -1110,7 +1320,7 @@ class FrameRenderer extends Renderer {
1110
1320
  }
1111
1321
  activateScriptElements() {
1112
1322
  for (const inertScriptElement of this.newScriptElements) {
1113
- const activatedScriptElement = this.createScriptElement(inertScriptElement);
1323
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
1114
1324
  inertScriptElement.replaceWith(activatedScriptElement);
1115
1325
  }
1116
1326
  }
@@ -1126,6 +1336,14 @@ function readScrollLogicalPosition(value, defaultValue) {
1126
1336
  return defaultValue;
1127
1337
  }
1128
1338
  }
1339
+ function readScrollBehavior(value, defaultValue) {
1340
+ if (value == "auto" || value == "smooth") {
1341
+ return value;
1342
+ }
1343
+ else {
1344
+ return defaultValue;
1345
+ }
1346
+ }
1129
1347
 
1130
1348
  class ProgressBar {
1131
1349
  constructor() {
@@ -1149,7 +1367,7 @@ class ProgressBar {
1149
1367
  left: 0;
1150
1368
  height: 3px;
1151
1369
  background: #0076ff;
1152
- z-index: 9999;
1370
+ z-index: 2147483647;
1153
1371
  transition:
1154
1372
  width ${ProgressBar.animationDuration}ms ease-out,
1155
1373
  opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
@@ -1208,13 +1426,16 @@ class ProgressBar {
1208
1426
  }
1209
1427
  refresh() {
1210
1428
  requestAnimationFrame(() => {
1211
- this.progressElement.style.width = `${10 + (this.value * 90)}%`;
1429
+ this.progressElement.style.width = `${10 + this.value * 90}%`;
1212
1430
  });
1213
1431
  }
1214
1432
  createStylesheetElement() {
1215
1433
  const element = document.createElement("style");
1216
1434
  element.type = "text/css";
1217
1435
  element.textContent = ProgressBar.defaultCSS;
1436
+ if (this.cspNonce) {
1437
+ element.nonce = this.cspNonce;
1438
+ }
1218
1439
  return element;
1219
1440
  }
1220
1441
  createProgressElement() {
@@ -1222,6 +1443,9 @@ class ProgressBar {
1222
1443
  element.className = "turbo-progress-bar";
1223
1444
  return element;
1224
1445
  }
1446
+ get cspNonce() {
1447
+ return getMetaContent("csp-nonce");
1448
+ }
1225
1449
  }
1226
1450
  ProgressBar.animationDuration = 300;
1227
1451
 
@@ -1238,14 +1462,14 @@ class HeadSnapshot extends Snapshot {
1238
1462
  : {
1239
1463
  type: elementType(element),
1240
1464
  tracked: elementIsTracked(element),
1241
- elements: []
1465
+ elements: [],
1242
1466
  };
1243
1467
  return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });
1244
1468
  }, {});
1245
1469
  }
1246
1470
  get trackedElementSignature() {
1247
1471
  return Object.keys(this.detailsByOuterHTML)
1248
- .filter(outerHTML => this.detailsByOuterHTML[outerHTML].tracked)
1472
+ .filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked)
1249
1473
  .join("");
1250
1474
  }
1251
1475
  getScriptElementsNotInSnapshot(snapshot) {
@@ -1256,8 +1480,8 @@ class HeadSnapshot extends Snapshot {
1256
1480
  }
1257
1481
  getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
1258
1482
  return Object.keys(this.detailsByOuterHTML)
1259
- .filter(outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))
1260
- .map(outerHTML => this.detailsByOuterHTML[outerHTML])
1483
+ .filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML))
1484
+ .map((outerHTML) => this.detailsByOuterHTML[outerHTML])
1261
1485
  .filter(({ type }) => type == matchedType)
1262
1486
  .map(({ elements: [element] }) => element);
1263
1487
  }
@@ -1277,13 +1501,11 @@ class HeadSnapshot extends Snapshot {
1277
1501
  }
1278
1502
  getMetaValue(name) {
1279
1503
  const element = this.findMetaElementByName(name);
1280
- return element
1281
- ? element.getAttribute("content")
1282
- : null;
1504
+ return element ? element.getAttribute("content") : null;
1283
1505
  }
1284
1506
  findMetaElementByName(name) {
1285
1507
  return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
1286
- const { elements: [element] } = this.detailsByOuterHTML[outerHTML];
1508
+ const { elements: [element], } = this.detailsByOuterHTML[outerHTML];
1287
1509
  return elementIsMetaElementWithName(element, name) ? element : result;
1288
1510
  }, undefined);
1289
1511
  }
@@ -1300,19 +1522,19 @@ function elementIsTracked(element) {
1300
1522
  return element.getAttribute("data-turbo-track") == "reload";
1301
1523
  }
1302
1524
  function elementIsScript(element) {
1303
- const tagName = element.tagName.toLowerCase();
1525
+ const tagName = element.localName;
1304
1526
  return tagName == "script";
1305
1527
  }
1306
1528
  function elementIsNoscript(element) {
1307
- const tagName = element.tagName.toLowerCase();
1529
+ const tagName = element.localName;
1308
1530
  return tagName == "noscript";
1309
1531
  }
1310
1532
  function elementIsStylesheet(element) {
1311
- const tagName = element.tagName.toLowerCase();
1533
+ const tagName = element.localName;
1312
1534
  return tagName == "style" || (tagName == "link" && element.getAttribute("rel") == "stylesheet");
1313
1535
  }
1314
1536
  function elementIsMetaElementWithName(element, name) {
1315
- const tagName = element.tagName.toLowerCase();
1537
+ const tagName = element.localName;
1316
1538
  return tagName == "meta" && element.getAttribute("name") == name;
1317
1539
  }
1318
1540
  function elementWithoutNonce(element) {
@@ -1337,7 +1559,20 @@ class PageSnapshot extends Snapshot {
1337
1559
  return new this(body, new HeadSnapshot(head));
1338
1560
  }
1339
1561
  clone() {
1340
- return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
1562
+ const clonedElement = this.element.cloneNode(true);
1563
+ const selectElements = this.element.querySelectorAll("select");
1564
+ const clonedSelectElements = clonedElement.querySelectorAll("select");
1565
+ for (const [index, source] of selectElements.entries()) {
1566
+ const clone = clonedSelectElements[index];
1567
+ for (const option of clone.selectedOptions)
1568
+ option.selected = false;
1569
+ for (const option of source.selectedOptions)
1570
+ clone.options[option.index].selected = true;
1571
+ }
1572
+ for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
1573
+ clonedPasswordInput.value = "";
1574
+ }
1575
+ return new PageSnapshot(clonedElement, this.headSnapshot);
1341
1576
  }
1342
1577
  get headElement() {
1343
1578
  return this.headSnapshot.element;
@@ -1384,6 +1619,10 @@ const defaultOptions = {
1384
1619
  historyChanged: false,
1385
1620
  visitCachedSnapshot: () => { },
1386
1621
  willRender: true,
1622
+ updateHistory: true,
1623
+ shouldCacheSnapshot: true,
1624
+ acceptsStreamResponse: false,
1625
+ initiator: document.documentElement,
1387
1626
  };
1388
1627
  var SystemStatusCode;
1389
1628
  (function (SystemStatusCode) {
@@ -1393,17 +1632,19 @@ var SystemStatusCode;
1393
1632
  })(SystemStatusCode || (SystemStatusCode = {}));
1394
1633
  class Visit {
1395
1634
  constructor(delegate, location, restorationIdentifier, options = {}) {
1396
- this.identifier = uuid();
1397
1635
  this.timingMetrics = {};
1398
1636
  this.followedRedirect = false;
1399
1637
  this.historyChanged = false;
1400
1638
  this.scrolled = false;
1639
+ this.shouldCacheSnapshot = true;
1640
+ this.acceptsStreamResponse = false;
1401
1641
  this.snapshotCached = false;
1402
1642
  this.state = VisitState.initialized;
1403
1643
  this.delegate = delegate;
1404
1644
  this.location = location;
1405
1645
  this.restorationIdentifier = restorationIdentifier || uuid();
1406
- const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender } = Object.assign(Object.assign({}, defaultOptions), options);
1646
+ this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));
1647
+ const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, initiator, } = Object.assign(Object.assign({}, defaultOptions), options);
1407
1648
  this.action = action;
1408
1649
  this.historyChanged = historyChanged;
1409
1650
  this.referrer = referrer;
@@ -1412,7 +1653,11 @@ class Visit {
1412
1653
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
1413
1654
  this.visitCachedSnapshot = visitCachedSnapshot;
1414
1655
  this.willRender = willRender;
1656
+ this.updateHistory = updateHistory;
1415
1657
  this.scrolled = !willRender;
1658
+ this.shouldCacheSnapshot = shouldCacheSnapshot;
1659
+ this.acceptsStreamResponse = acceptsStreamResponse;
1660
+ this.initiator = initiator;
1416
1661
  }
1417
1662
  get adapter() {
1418
1663
  return this.delegate.adapter;
@@ -1444,28 +1689,33 @@ class Visit {
1444
1689
  }
1445
1690
  this.cancelRender();
1446
1691
  this.state = VisitState.canceled;
1692
+ this.resolvingFunctions.reject();
1447
1693
  }
1448
1694
  }
1449
1695
  complete() {
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
+ }
1704
+ this.resolvingFunctions.resolve();
1456
1705
  }
1457
1706
  }
1458
1707
  fail() {
1459
1708
  if (this.state == VisitState.started) {
1460
1709
  this.state = VisitState.failed;
1461
1710
  this.adapter.visitFailed(this);
1711
+ this.resolvingFunctions.reject();
1462
1712
  }
1463
1713
  }
1464
1714
  changeHistory() {
1465
1715
  var _a;
1466
- if (!this.historyChanged) {
1716
+ if (!this.historyChanged && this.updateHistory) {
1467
1717
  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);
1718
+ const method = getHistoryMethodForAction(actionForHistory);
1469
1719
  this.history.update(method, this.location, this.restorationIdentifier);
1470
1720
  this.historyChanged = true;
1471
1721
  }
@@ -1475,7 +1725,7 @@ class Visit {
1475
1725
  this.simulateRequest();
1476
1726
  }
1477
1727
  else if (this.shouldIssueRequest() && !this.request) {
1478
- this.request = new FetchRequest(this, FetchMethod.get, this.location);
1728
+ this.request = new FetchRequest(this, FetchMethod.get, this.location, undefined, this.initiator);
1479
1729
  this.request.perform();
1480
1730
  }
1481
1731
  }
@@ -1510,16 +1760,18 @@ class Visit {
1510
1760
  if (this.response) {
1511
1761
  const { statusCode, responseHTML } = this.response;
1512
1762
  this.render(async () => {
1513
- this.cacheSnapshot();
1763
+ if (this.shouldCacheSnapshot)
1764
+ this.cacheSnapshot();
1514
1765
  if (this.view.renderPromise)
1515
1766
  await this.view.renderPromise;
1516
1767
  if (isSuccessful(statusCode) && responseHTML != null) {
1517
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
1768
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
1769
+ this.performScroll();
1518
1770
  this.adapter.visitRendered(this);
1519
1771
  this.complete();
1520
1772
  }
1521
1773
  else {
1522
- await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
1774
+ await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
1523
1775
  this.adapter.visitRendered(this);
1524
1776
  this.fail();
1525
1777
  }
@@ -1554,7 +1806,8 @@ class Visit {
1554
1806
  else {
1555
1807
  if (this.view.renderPromise)
1556
1808
  await this.view.renderPromise;
1557
- await this.view.renderPage(snapshot, isPreview, this.willRender);
1809
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
1810
+ this.performScroll();
1558
1811
  this.adapter.visitRendered(this);
1559
1812
  if (!isPreview) {
1560
1813
  this.complete();
@@ -1567,8 +1820,8 @@ class Visit {
1567
1820
  var _a;
1568
1821
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1569
1822
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1570
- action: 'replace',
1571
- response: this.response
1823
+ action: "replace",
1824
+ response: this.response,
1572
1825
  });
1573
1826
  this.followedRedirect = true;
1574
1827
  }
@@ -1577,20 +1830,28 @@ class Visit {
1577
1830
  if (this.isSamePage) {
1578
1831
  this.render(async () => {
1579
1832
  this.cacheSnapshot();
1833
+ this.performScroll();
1580
1834
  this.adapter.visitRendered(this);
1581
1835
  });
1582
1836
  }
1583
1837
  }
1838
+ prepareHeadersForRequest(headers, request) {
1839
+ if (this.acceptsStreamResponse) {
1840
+ request.acceptResponseType(StreamMessage.contentType);
1841
+ }
1842
+ }
1584
1843
  requestStarted() {
1585
1844
  this.startRequest();
1586
1845
  }
1587
- requestPreventedHandlingResponse(request, response) {
1588
- }
1846
+ requestPreventedHandlingResponse(_request, _response) { }
1589
1847
  async requestSucceededWithResponse(request, response) {
1590
1848
  const responseHTML = await response.responseHTML;
1591
1849
  const { redirected, statusCode } = response;
1592
1850
  if (responseHTML == undefined) {
1593
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1851
+ this.recordResponse({
1852
+ statusCode: SystemStatusCode.contentTypeMismatch,
1853
+ redirected,
1854
+ });
1594
1855
  }
1595
1856
  else {
1596
1857
  this.redirectedToLocation = response.redirected ? response.location : undefined;
@@ -1601,20 +1862,26 @@ class Visit {
1601
1862
  const responseHTML = await response.responseHTML;
1602
1863
  const { redirected, statusCode } = response;
1603
1864
  if (responseHTML == undefined) {
1604
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1865
+ this.recordResponse({
1866
+ statusCode: SystemStatusCode.contentTypeMismatch,
1867
+ redirected,
1868
+ });
1605
1869
  }
1606
1870
  else {
1607
1871
  this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
1608
1872
  }
1609
1873
  }
1610
- requestErrored(request, error) {
1611
- this.recordResponse({ statusCode: SystemStatusCode.networkFailure, redirected: false });
1874
+ requestErrored(_request, _error) {
1875
+ this.recordResponse({
1876
+ statusCode: SystemStatusCode.networkFailure,
1877
+ redirected: false,
1878
+ });
1612
1879
  }
1613
1880
  requestFinished() {
1614
1881
  this.finishRequest();
1615
1882
  }
1616
1883
  performScroll() {
1617
- if (!this.scrolled) {
1884
+ if (!this.scrolled && !this.view.forceReloaded) {
1618
1885
  if (this.action == "restore") {
1619
1886
  this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
1620
1887
  }
@@ -1649,9 +1916,11 @@ class Visit {
1649
1916
  }
1650
1917
  getHistoryMethodForAction(action) {
1651
1918
  switch (action) {
1652
- case "replace": return history.replaceState;
1919
+ case "replace":
1920
+ return history.replaceState;
1653
1921
  case "advance":
1654
- case "restore": return history.pushState;
1922
+ case "restore":
1923
+ return history.pushState;
1655
1924
  }
1656
1925
  }
1657
1926
  hasPreloadedResponse() {
@@ -1670,18 +1939,17 @@ class Visit {
1670
1939
  }
1671
1940
  cacheSnapshot() {
1672
1941
  if (!this.snapshotCached) {
1673
- this.view.cacheSnapshot().then(snapshot => snapshot && this.visitCachedSnapshot(snapshot));
1942
+ this.view.cacheSnapshot().then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot));
1674
1943
  this.snapshotCached = true;
1675
1944
  }
1676
1945
  }
1677
1946
  async render(callback) {
1678
1947
  this.cancelRender();
1679
- await new Promise(resolve => {
1948
+ await new Promise((resolve) => {
1680
1949
  this.frame = requestAnimationFrame(() => resolve());
1681
1950
  });
1682
1951
  await callback();
1683
1952
  delete this.frame;
1684
- this.performScroll();
1685
1953
  }
1686
1954
  cancelRender() {
1687
1955
  if (this.frame) {
@@ -1696,19 +1964,19 @@ function isSuccessful(statusCode) {
1696
1964
 
1697
1965
  class BrowserAdapter {
1698
1966
  constructor(session) {
1699
- this.progressBar = new ProgressBar;
1967
+ this.progressBar = new ProgressBar();
1700
1968
  this.showProgressBar = () => {
1701
1969
  this.progressBar.show();
1702
1970
  };
1703
1971
  this.session = session;
1704
1972
  }
1705
1973
  visitProposedToLocation(location, options) {
1706
- this.navigator.startVisit(location, uuid(), options);
1974
+ return this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
1707
1975
  }
1708
1976
  visitStarted(visit) {
1977
+ this.location = visit.location;
1709
1978
  visit.loadCachedSnapshot();
1710
1979
  visit.issueRequest();
1711
- visit.changeHistory();
1712
1980
  visit.goToSamePageAnchor();
1713
1981
  }
1714
1982
  visitRequestStarted(visit) {
@@ -1728,29 +1996,31 @@ class BrowserAdapter {
1728
1996
  case SystemStatusCode.networkFailure:
1729
1997
  case SystemStatusCode.timeoutFailure:
1730
1998
  case SystemStatusCode.contentTypeMismatch:
1731
- return this.reload();
1999
+ return this.reload({
2000
+ reason: "request_failed",
2001
+ context: {
2002
+ statusCode,
2003
+ },
2004
+ });
1732
2005
  default:
1733
2006
  return visit.loadResponse();
1734
2007
  }
1735
2008
  }
1736
- visitRequestFinished(visit) {
2009
+ visitRequestFinished(_visit) {
1737
2010
  this.progressBar.setValue(1);
1738
2011
  this.hideVisitProgressBar();
1739
2012
  }
1740
- visitCompleted(visit) {
1741
- }
1742
- pageInvalidated() {
1743
- this.reload();
1744
- }
1745
- visitFailed(visit) {
2013
+ visitCompleted(_visit) { }
2014
+ pageInvalidated(reason) {
2015
+ this.reload(reason);
1746
2016
  }
1747
- visitRendered(visit) {
1748
- }
1749
- formSubmissionStarted(formSubmission) {
2017
+ visitFailed(_visit) { }
2018
+ visitRendered(_visit) { }
2019
+ formSubmissionStarted(_formSubmission) {
1750
2020
  this.progressBar.setValue(0);
1751
2021
  this.showFormProgressBarAfterDelay();
1752
2022
  }
1753
- formSubmissionFinished(formSubmission) {
2023
+ formSubmissionFinished(_formSubmission) {
1754
2024
  this.progressBar.setValue(1);
1755
2025
  this.hideFormProgressBar();
1756
2026
  }
@@ -1776,8 +2046,11 @@ class BrowserAdapter {
1776
2046
  delete this.formProgressBarTimeout;
1777
2047
  }
1778
2048
  }
1779
- reload() {
1780
- window.location.reload();
2049
+ reload(reason) {
2050
+ dispatch("turbo:reload", { detail: reason });
2051
+ if (!this.location)
2052
+ return;
2053
+ window.location.href = this.location.toString();
1781
2054
  }
1782
2055
  get navigator() {
1783
2056
  return this.session.navigator;
@@ -1787,94 +2060,60 @@ class BrowserAdapter {
1787
2060
  class CacheObserver {
1788
2061
  constructor() {
1789
2062
  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
- }
2063
+ this.removeStaleElements = ((_event) => {
2064
+ const staleElements = [...document.querySelectorAll('[data-turbo-cache="false"]')];
2065
+ for (const element of staleElements) {
2066
+ element.remove();
1829
2067
  }
1830
2068
  });
1831
- this.delegate = delegate;
1832
2069
  }
1833
2070
  start() {
1834
2071
  if (!this.started) {
1835
- addEventListener("submit", this.submitCaptured, true);
1836
2072
  this.started = true;
2073
+ addEventListener("turbo:before-cache", this.removeStaleElements, false);
1837
2074
  }
1838
2075
  }
1839
2076
  stop() {
1840
2077
  if (this.started) {
1841
- removeEventListener("submit", this.submitCaptured, true);
1842
2078
  this.started = false;
2079
+ removeEventListener("turbo:before-cache", this.removeStaleElements, false);
1843
2080
  }
1844
2081
  }
1845
2082
  }
1846
2083
 
1847
2084
  class FrameRedirector {
1848
- constructor(element) {
2085
+ constructor(session, element) {
2086
+ this.session = session;
1849
2087
  this.element = element;
1850
- this.linkInterceptor = new LinkInterceptor(this, element);
1851
- this.formInterceptor = new FormInterceptor(this, element);
2088
+ this.linkClickObserver = new LinkClickObserver(this, element);
2089
+ this.formSubmitObserver = new FormSubmitObserver(this, element);
1852
2090
  }
1853
2091
  start() {
1854
- this.linkInterceptor.start();
1855
- this.formInterceptor.start();
2092
+ this.linkClickObserver.start();
2093
+ this.formSubmitObserver.start();
1856
2094
  }
1857
2095
  stop() {
1858
- this.linkInterceptor.stop();
1859
- this.formInterceptor.stop();
2096
+ this.linkClickObserver.stop();
2097
+ this.formSubmitObserver.stop();
1860
2098
  }
1861
- shouldInterceptLinkClick(element, url) {
2099
+ willFollowLinkToLocation(element) {
1862
2100
  return this.shouldRedirect(element);
1863
2101
  }
1864
- linkClickIntercepted(element, url) {
2102
+ followedLinkToLocation(element, url) {
1865
2103
  const frame = this.findFrameElement(element);
1866
2104
  if (frame) {
1867
- frame.delegate.linkClickIntercepted(element, url);
2105
+ frame.delegate.followedLinkToLocation(element, url);
1868
2106
  }
1869
2107
  }
1870
- shouldInterceptFormSubmission(element, submitter) {
1871
- return this.shouldSubmit(element, submitter);
2108
+ willSubmitForm(element, submitter) {
2109
+ return (element.closest("turbo-frame") == null &&
2110
+ this.shouldSubmit(element, submitter) &&
2111
+ this.shouldRedirect(element, submitter));
1872
2112
  }
1873
- formSubmissionIntercepted(element, submitter) {
2113
+ formSubmitted(element, submitter) {
1874
2114
  const frame = this.findFrameElement(element, submitter);
1875
2115
  if (frame) {
1876
- frame.removeAttribute("reloadable");
1877
- frame.delegate.formSubmissionIntercepted(element, submitter);
2116
+ frame.delegate.formSubmitted(element, submitter);
1878
2117
  }
1879
2118
  }
1880
2119
  shouldSubmit(form, submitter) {
@@ -1885,8 +2124,16 @@ class FrameRedirector {
1885
2124
  return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
1886
2125
  }
1887
2126
  shouldRedirect(element, submitter) {
1888
- const frame = this.findFrameElement(element, submitter);
1889
- return frame ? frame != element.closest("turbo-frame") : false;
2127
+ const isNavigatable = element instanceof HTMLFormElement
2128
+ ? this.session.submissionIsNavigatable(element, submitter)
2129
+ : this.session.elementIsNavigatable(element);
2130
+ if (isNavigatable) {
2131
+ const frame = this.findFrameElement(element, submitter);
2132
+ return frame ? frame != element.closest("turbo-frame") : false;
2133
+ }
2134
+ else {
2135
+ return false;
2136
+ }
1890
2137
  }
1891
2138
  findFrameElement(element, submitter) {
1892
2139
  const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
@@ -1916,7 +2163,7 @@ class History {
1916
2163
  }
1917
2164
  }
1918
2165
  };
1919
- this.onPageLoad = async (event) => {
2166
+ this.onPageLoad = async (_event) => {
1920
2167
  await nextMicrotask();
1921
2168
  this.pageLoaded = true;
1922
2169
  };
@@ -1978,81 +2225,30 @@ class History {
1978
2225
  }
1979
2226
  }
1980
2227
 
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 {
2228
+ class Navigator {
2039
2229
  constructor(delegate) {
2040
2230
  this.delegate = delegate;
2041
2231
  }
2042
2232
  proposeVisit(location, options = {}) {
2043
- if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
2233
+ if (this.delegate.allowsVisitingLocation(location, options)) {
2044
2234
  if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
2045
- this.delegate.visitProposedToLocation(location, options);
2235
+ return this.delegate.visitProposedToLocation(location, options);
2046
2236
  }
2047
2237
  else {
2048
2238
  window.location.href = location.toString();
2239
+ return Promise.resolve();
2049
2240
  }
2050
2241
  }
2242
+ else {
2243
+ return Promise.reject();
2244
+ }
2051
2245
  }
2052
2246
  startVisit(locatable, restorationIdentifier, options = {}) {
2247
+ this.lastVisit = this.currentVisit;
2053
2248
  this.stop();
2054
2249
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));
2055
2250
  this.currentVisit.start();
2251
+ return this.currentVisit.promise;
2056
2252
  }
2057
2253
  submitForm(form, submitter) {
2058
2254
  this.stop();
@@ -2079,7 +2275,7 @@ class Navigator {
2079
2275
  return this.delegate.history;
2080
2276
  }
2081
2277
  formSubmissionStarted(formSubmission) {
2082
- if (typeof this.adapter.formSubmissionStarted === 'function') {
2278
+ if (typeof this.adapter.formSubmissionStarted === "function") {
2083
2279
  this.adapter.formSubmissionStarted(formSubmission);
2084
2280
  }
2085
2281
  }
@@ -2087,12 +2283,17 @@ class Navigator {
2087
2283
  if (formSubmission == this.formSubmission) {
2088
2284
  const responseHTML = await fetchResponse.responseHTML;
2089
2285
  if (responseHTML) {
2090
- if (formSubmission.method != FetchMethod.get) {
2286
+ const shouldCacheSnapshot = formSubmission.method == FetchMethod.get;
2287
+ if (!shouldCacheSnapshot) {
2091
2288
  this.view.clearSnapshotCache();
2092
2289
  }
2093
2290
  const { statusCode, redirected } = fetchResponse;
2094
2291
  const action = this.getActionForFormSubmission(formSubmission);
2095
- const visitOptions = { action, response: { statusCode, responseHTML, redirected } };
2292
+ const visitOptions = {
2293
+ action,
2294
+ shouldCacheSnapshot,
2295
+ response: { statusCode, responseHTML, redirected },
2296
+ };
2096
2297
  this.proposeVisit(fetchResponse.location, visitOptions);
2097
2298
  }
2098
2299
  }
@@ -2102,10 +2303,10 @@ class Navigator {
2102
2303
  if (responseHTML) {
2103
2304
  const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2104
2305
  if (fetchResponse.serverError) {
2105
- await this.view.renderError(snapshot);
2306
+ await this.view.renderError(snapshot, this.currentVisit);
2106
2307
  }
2107
2308
  else {
2108
- await this.view.renderPage(snapshot);
2309
+ await this.view.renderPage(snapshot, false, true, this.currentVisit);
2109
2310
  }
2110
2311
  this.view.scrollToTop();
2111
2312
  this.view.clearSnapshotCache();
@@ -2115,7 +2316,7 @@ class Navigator {
2115
2316
  console.error(error);
2116
2317
  }
2117
2318
  formSubmissionFinished(formSubmission) {
2118
- if (typeof this.adapter.formSubmissionFinished === 'function') {
2319
+ if (typeof this.adapter.formSubmissionFinished === "function") {
2119
2320
  this.adapter.formSubmissionFinished(formSubmission);
2120
2321
  }
2121
2322
  }
@@ -2126,12 +2327,14 @@ class Navigator {
2126
2327
  this.delegate.visitCompleted(visit);
2127
2328
  }
2128
2329
  locationWithActionIsSamePage(location, action) {
2330
+ var _a;
2129
2331
  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));
2332
+ const lastLocation = ((_a = this.lastVisit) === null || _a === void 0 ? void 0 : _a.location) || this.view.lastRenderedLocation;
2333
+ const currentAnchor = getAnchor(lastLocation);
2334
+ const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2335
+ return (action !== "replace" &&
2336
+ getRequestURL(location) === getRequestURL(lastLocation) &&
2337
+ (isRestorationToTop || (anchor != null && anchor !== currentAnchor)));
2135
2338
  }
2136
2339
  visitScrolledToSamePageLocation(oldURL, newURL) {
2137
2340
  this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
@@ -2235,9 +2438,33 @@ class ScrollObserver {
2235
2438
  }
2236
2439
  }
2237
2440
 
2441
+ class StreamMessageRenderer {
2442
+ render({ fragment }) {
2443
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => document.documentElement.appendChild(fragment));
2444
+ }
2445
+ enteringBardo(currentPermanentElement, newPermanentElement) {
2446
+ newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
2447
+ }
2448
+ leavingBardo() { }
2449
+ }
2450
+ function getPermanentElementMapForFragment(fragment) {
2451
+ const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
2452
+ const permanentElementMap = {};
2453
+ for (const permanentElementInDocument of permanentElementsInDocument) {
2454
+ const { id } = permanentElementInDocument;
2455
+ for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
2456
+ const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
2457
+ if (elementInStream) {
2458
+ permanentElementMap[id] = [permanentElementInDocument, elementInStream];
2459
+ }
2460
+ }
2461
+ }
2462
+ return permanentElementMap;
2463
+ }
2464
+
2238
2465
  class StreamObserver {
2239
2466
  constructor(delegate) {
2240
- this.sources = new Set;
2467
+ this.sources = new Set();
2241
2468
  this.started = false;
2242
2469
  this.inspectFetchResponse = ((event) => {
2243
2470
  const response = fetchResponseFromEvent(event);
@@ -2287,7 +2514,7 @@ class StreamObserver {
2287
2514
  }
2288
2515
  }
2289
2516
  receiveMessageHTML(html) {
2290
- this.delegate.receivedMessageFromStream(new StreamMessage(html));
2517
+ this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
2291
2518
  }
2292
2519
  }
2293
2520
  function fetchResponseFromEvent(event) {
@@ -2304,20 +2531,24 @@ function fetchResponseIsStream(response) {
2304
2531
  }
2305
2532
 
2306
2533
  class ErrorRenderer extends Renderer {
2534
+ static renderElement(currentElement, newElement) {
2535
+ const { documentElement, body } = document;
2536
+ documentElement.replaceChild(newElement, body);
2537
+ }
2307
2538
  async render() {
2308
2539
  this.replaceHeadAndBody();
2309
2540
  this.activateScriptElements();
2310
2541
  }
2311
2542
  replaceHeadAndBody() {
2312
- const { documentElement, head, body } = document;
2543
+ const { documentElement, head } = document;
2313
2544
  documentElement.replaceChild(this.newHead, head);
2314
- documentElement.replaceChild(this.newElement, body);
2545
+ this.renderElement(this.currentElement, this.newElement);
2315
2546
  }
2316
2547
  activateScriptElements() {
2317
2548
  for (const replaceableElement of this.scriptElements) {
2318
2549
  const parentNode = replaceableElement.parentNode;
2319
2550
  if (parentNode) {
2320
- const element = this.createScriptElement(replaceableElement);
2551
+ const element = activateScriptElement(replaceableElement);
2321
2552
  parentNode.replaceChild(element, replaceableElement);
2322
2553
  }
2323
2554
  }
@@ -2326,16 +2557,44 @@ class ErrorRenderer extends Renderer {
2326
2557
  return this.newSnapshot.headSnapshot.element;
2327
2558
  }
2328
2559
  get scriptElements() {
2329
- return [...document.documentElement.querySelectorAll("script")];
2560
+ return document.documentElement.querySelectorAll("script");
2330
2561
  }
2331
2562
  }
2332
2563
 
2333
2564
  class PageRenderer extends Renderer {
2565
+ static async renderElement(currentElement, newElement) {
2566
+ await nextEventLoopTick();
2567
+ if (document.body && newElement instanceof HTMLBodyElement) {
2568
+ const currentBody = PageRenderer.getBodyElement(currentElement);
2569
+ const newBody = PageRenderer.getBodyElement(newElement);
2570
+ currentBody.replaceWith(newBody);
2571
+ }
2572
+ else {
2573
+ document.documentElement.appendChild(newElement);
2574
+ }
2575
+ }
2334
2576
  get shouldRender() {
2335
- return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
2577
+ return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical && this.bodyElementMatches;
2336
2578
  }
2337
- prepareToRender() {
2338
- this.mergeHead();
2579
+ get reloadReason() {
2580
+ if (!this.newSnapshot.isVisitable) {
2581
+ return {
2582
+ reason: "turbo_visit_control_is_reload",
2583
+ };
2584
+ }
2585
+ if (!this.trackedElementsAreIdentical) {
2586
+ return {
2587
+ reason: "tracked_element_mismatch",
2588
+ };
2589
+ }
2590
+ if (!this.bodyElementMatches) {
2591
+ return {
2592
+ reason: "body_element_mismatch",
2593
+ };
2594
+ }
2595
+ }
2596
+ async prepareToRender() {
2597
+ await this.mergeHead();
2339
2598
  }
2340
2599
  async render() {
2341
2600
  if (this.willRender) {
@@ -2357,11 +2616,12 @@ class PageRenderer extends Renderer {
2357
2616
  get newElement() {
2358
2617
  return this.newSnapshot.element;
2359
2618
  }
2360
- mergeHead() {
2361
- this.copyNewHeadStylesheetElements();
2619
+ async mergeHead() {
2620
+ const newStylesheetElements = this.copyNewHeadStylesheetElements();
2362
2621
  this.copyNewHeadScriptElements();
2363
2622
  this.removeCurrentHeadProvisionalElements();
2364
2623
  this.copyNewHeadProvisionalElements();
2624
+ await newStylesheetElements;
2365
2625
  }
2366
2626
  replaceBody() {
2367
2627
  this.preservingPermanentElements(() => {
@@ -2372,14 +2632,27 @@ class PageRenderer extends Renderer {
2372
2632
  get trackedElementsAreIdentical() {
2373
2633
  return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
2374
2634
  }
2375
- copyNewHeadStylesheetElements() {
2635
+ get bodyElementMatches() {
2636
+ return PageRenderer.getBodyElement(this.newElement) !== null;
2637
+ }
2638
+ static get bodySelector() {
2639
+ const bodyId = getBodyElementId();
2640
+ return bodyId ? `#${bodyId}` : "body";
2641
+ }
2642
+ static getBodyElement(element) {
2643
+ return element.querySelector(this.bodySelector) || element;
2644
+ }
2645
+ async copyNewHeadStylesheetElements() {
2646
+ const loadingElements = [];
2376
2647
  for (const element of this.newHeadStylesheetElements) {
2648
+ loadingElements.push(waitForLoad(element));
2377
2649
  document.head.appendChild(element);
2378
2650
  }
2651
+ await Promise.all(loadingElements);
2379
2652
  }
2380
2653
  copyNewHeadScriptElements() {
2381
2654
  for (const element of this.newHeadScriptElements) {
2382
- document.head.appendChild(this.createScriptElement(element));
2655
+ document.head.appendChild(activateScriptElement(element));
2383
2656
  }
2384
2657
  }
2385
2658
  removeCurrentHeadProvisionalElements() {
@@ -2398,17 +2671,12 @@ class PageRenderer extends Renderer {
2398
2671
  }
2399
2672
  activateNewBodyScriptElements() {
2400
2673
  for (const inertScriptElement of this.newBodyScriptElements) {
2401
- const activatedScriptElement = this.createScriptElement(inertScriptElement);
2674
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
2402
2675
  inertScriptElement.replaceWith(activatedScriptElement);
2403
2676
  }
2404
2677
  }
2405
2678
  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
- }
2679
+ this.renderElement(this.currentElement, this.newElement);
2412
2680
  }
2413
2681
  get newHeadStylesheetElements() {
2414
2682
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -2477,13 +2745,21 @@ class PageView extends View {
2477
2745
  super(...arguments);
2478
2746
  this.snapshotCache = new SnapshotCache(10);
2479
2747
  this.lastRenderedLocation = new URL(location.href);
2748
+ this.forceReloaded = false;
2480
2749
  }
2481
- renderPage(snapshot, isPreview = false, willRender = true) {
2482
- const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
2750
+ renderPage(snapshot, isPreview = false, willRender = true, visit) {
2751
+ const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
2752
+ if (!renderer.shouldRender) {
2753
+ this.forceReloaded = true;
2754
+ }
2755
+ else {
2756
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2757
+ }
2483
2758
  return this.render(renderer);
2484
2759
  }
2485
- renderError(snapshot) {
2486
- const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2760
+ renderError(snapshot, visit) {
2761
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2762
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
2487
2763
  return this.render(renderer);
2488
2764
  }
2489
2765
  clearSnapshotCache() {
@@ -2510,34 +2786,79 @@ class PageView extends View {
2510
2786
  }
2511
2787
  }
2512
2788
 
2789
+ class Preloader {
2790
+ constructor(delegate) {
2791
+ this.selector = "a[data-turbo-preload]";
2792
+ this.delegate = delegate;
2793
+ }
2794
+ get snapshotCache() {
2795
+ return this.delegate.navigator.view.snapshotCache;
2796
+ }
2797
+ start() {
2798
+ if (document.readyState === "loading") {
2799
+ return document.addEventListener("DOMContentLoaded", () => {
2800
+ this.preloadOnLoadLinksForView(document.body);
2801
+ });
2802
+ }
2803
+ else {
2804
+ this.preloadOnLoadLinksForView(document.body);
2805
+ }
2806
+ }
2807
+ preloadOnLoadLinksForView(element) {
2808
+ for (const link of element.querySelectorAll(this.selector)) {
2809
+ this.preloadURL(link);
2810
+ }
2811
+ }
2812
+ async preloadURL(link) {
2813
+ const location = new URL(link.href);
2814
+ if (this.snapshotCache.has(location)) {
2815
+ return;
2816
+ }
2817
+ try {
2818
+ const response = await fetch(location.toString(), { headers: { "VND.PREFETCH": "true", Accept: "text/html" } });
2819
+ const responseText = await response.text();
2820
+ const snapshot = PageSnapshot.fromHTMLString(responseText);
2821
+ this.snapshotCache.put(location, snapshot);
2822
+ }
2823
+ catch (_) {
2824
+ }
2825
+ }
2826
+ }
2827
+
2513
2828
  class Session {
2514
2829
  constructor() {
2515
2830
  this.navigator = new Navigator(this);
2516
2831
  this.history = new History(this);
2832
+ this.preloader = new Preloader(this);
2517
2833
  this.view = new PageView(this, document.documentElement);
2518
2834
  this.adapter = new BrowserAdapter(this);
2519
2835
  this.pageObserver = new PageObserver(this);
2520
2836
  this.cacheObserver = new CacheObserver();
2521
- this.linkClickObserver = new LinkClickObserver(this);
2522
- this.formSubmitObserver = new FormSubmitObserver(this);
2837
+ this.linkClickObserver = new LinkClickObserver(this, window);
2838
+ this.formSubmitObserver = new FormSubmitObserver(this, document);
2523
2839
  this.scrollObserver = new ScrollObserver(this);
2524
2840
  this.streamObserver = new StreamObserver(this);
2525
- this.frameRedirector = new FrameRedirector(document.documentElement);
2841
+ this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
2842
+ this.frameRedirector = new FrameRedirector(this, document.documentElement);
2843
+ this.streamMessageRenderer = new StreamMessageRenderer();
2526
2844
  this.drive = true;
2527
2845
  this.enabled = true;
2528
2846
  this.progressBarDelay = 500;
2529
2847
  this.started = false;
2848
+ this.formMode = "on";
2530
2849
  }
2531
2850
  start() {
2532
2851
  if (!this.started) {
2533
2852
  this.pageObserver.start();
2534
2853
  this.cacheObserver.start();
2854
+ this.formLinkClickObserver.start();
2535
2855
  this.linkClickObserver.start();
2536
2856
  this.formSubmitObserver.start();
2537
2857
  this.scrollObserver.start();
2538
2858
  this.streamObserver.start();
2539
2859
  this.frameRedirector.start();
2540
2860
  this.history.start();
2861
+ this.preloader.start();
2541
2862
  this.started = true;
2542
2863
  this.enabled = true;
2543
2864
  }
@@ -2549,6 +2870,7 @@ class Session {
2549
2870
  if (this.started) {
2550
2871
  this.pageObserver.stop();
2551
2872
  this.cacheObserver.stop();
2873
+ this.formLinkClickObserver.stop();
2552
2874
  this.linkClickObserver.stop();
2553
2875
  this.formSubmitObserver.stop();
2554
2876
  this.scrollObserver.stop();
@@ -2562,7 +2884,14 @@ class Session {
2562
2884
  this.adapter = adapter;
2563
2885
  }
2564
2886
  visit(location, options = {}) {
2565
- this.navigator.proposeVisit(expandURL(location), options);
2887
+ const frameElement = options.frame ? document.getElementById(options.frame) : null;
2888
+ if (frameElement instanceof FrameElement) {
2889
+ frameElement.src = location.toString();
2890
+ return frameElement.loaded;
2891
+ }
2892
+ else {
2893
+ return this.navigator.proposeVisit(expandURL(location), options);
2894
+ }
2566
2895
  }
2567
2896
  connectStreamSource(source) {
2568
2897
  this.streamObserver.connectStreamSource(source);
@@ -2571,7 +2900,7 @@ class Session {
2571
2900
  this.streamObserver.disconnectStreamSource(source);
2572
2901
  }
2573
2902
  renderStreamMessage(message) {
2574
- document.documentElement.appendChild(StreamMessage.wrap(message).fragment);
2903
+ this.streamMessageRenderer.render(StreamMessage.wrap(message));
2575
2904
  }
2576
2905
  clearCache() {
2577
2906
  this.view.clearSnapshotCache();
@@ -2579,6 +2908,9 @@ class Session {
2579
2908
  setProgressBarDelay(delay) {
2580
2909
  this.progressBarDelay = delay;
2581
2910
  }
2911
+ setFormMode(mode) {
2912
+ this.formMode = mode;
2913
+ }
2582
2914
  get location() {
2583
2915
  return this.history.location;
2584
2916
  }
@@ -2587,63 +2919,53 @@ class Session {
2587
2919
  }
2588
2920
  historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
2589
2921
  if (this.enabled) {
2590
- this.navigator.startVisit(location, restorationIdentifier, { action: "restore", historyChanged: true });
2922
+ this.navigator.startVisit(location, restorationIdentifier, {
2923
+ action: "restore",
2924
+ historyChanged: true,
2925
+ });
2591
2926
  }
2592
2927
  else {
2593
- this.adapter.pageInvalidated();
2928
+ this.adapter.pageInvalidated({
2929
+ reason: "turbo_disabled",
2930
+ });
2594
2931
  }
2595
2932
  }
2596
2933
  scrollPositionChanged(position) {
2597
2934
  this.history.updateRestorationData({ scrollPosition: position });
2598
2935
  }
2599
- willFollowLinkToLocation(link, location) {
2600
- return this.elementDriveEnabled(link)
2601
- && locationIsVisitable(location, this.snapshot.rootLocation)
2602
- && this.applicationAllowsFollowingLinkToLocation(link, location);
2936
+ willSubmitFormLinkToLocation(link, location) {
2937
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
2938
+ }
2939
+ submittedFormLinkToLocation() { }
2940
+ willFollowLinkToLocation(link, location, event) {
2941
+ return (this.elementIsNavigatable(link) &&
2942
+ locationIsVisitable(location, this.snapshot.rootLocation) &&
2943
+ this.applicationAllowsFollowingLinkToLocation(link, location, event));
2603
2944
  }
2604
2945
  followedLinkToLocation(link, location) {
2605
2946
  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
- }
2947
+ const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
2948
+ this.visit(location.href, { action, acceptsStreamResponse, initiator: link });
2632
2949
  }
2633
- allowsVisitingLocationWithAction(location, action) {
2634
- return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
2950
+ allowsVisitingLocation(location, options = {}) {
2951
+ return (this.locationWithActionIsSamePage(location, options.action) ||
2952
+ this.applicationAllowsVisitingLocation(location, options));
2635
2953
  }
2636
2954
  visitProposedToLocation(location, options) {
2637
2955
  extendURLWithDeprecatedProperties(location);
2638
- this.adapter.visitProposedToLocation(location, options);
2956
+ return this.adapter.visitProposedToLocation(location, options);
2639
2957
  }
2640
2958
  visitStarted(visit) {
2959
+ if (!visit.acceptsStreamResponse) {
2960
+ markAsBusy(document.documentElement);
2961
+ }
2641
2962
  extendURLWithDeprecatedProperties(visit.location);
2642
2963
  if (!visit.silent) {
2643
- this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
2964
+ this.notifyApplicationAfterVisitingLocation(visit.location, visit.action, visit.initiator);
2644
2965
  }
2645
2966
  }
2646
2967
  visitCompleted(visit) {
2968
+ clearBusyState(document.documentElement);
2647
2969
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2648
2970
  }
2649
2971
  locationWithActionIsSamePage(location, action) {
@@ -2654,9 +2976,8 @@ class Session {
2654
2976
  }
2655
2977
  willSubmitForm(form, submitter) {
2656
2978
  const action = getAction(form, submitter);
2657
- return this.elementDriveEnabled(form)
2658
- && (!submitter || this.elementDriveEnabled(submitter))
2659
- && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
2979
+ return (this.submissionIsNavigatable(form, submitter) &&
2980
+ locationIsVisitable(expandURL(action), this.snapshot.rootLocation));
2660
2981
  }
2661
2982
  formSubmitted(form, submitter) {
2662
2983
  this.navigator.submitForm(form, submitter);
@@ -2680,16 +3001,23 @@ class Session {
2680
3001
  this.notifyApplicationBeforeCachingSnapshot();
2681
3002
  }
2682
3003
  }
2683
- allowsImmediateRender({ element }, resume) {
2684
- const event = this.notifyApplicationBeforeRender(element, resume);
2685
- return !event.defaultPrevented;
3004
+ allowsImmediateRender({ element }, options) {
3005
+ const event = this.notifyApplicationBeforeRender(element, options);
3006
+ const { defaultPrevented, detail: { render }, } = event;
3007
+ if (this.view.renderer && render) {
3008
+ this.view.renderer.renderElement = render;
3009
+ }
3010
+ return !defaultPrevented;
2686
3011
  }
2687
- viewRenderedSnapshot(snapshot, isPreview) {
3012
+ viewRenderedSnapshot(_snapshot, _isPreview) {
2688
3013
  this.view.lastRenderedLocation = this.history.location;
2689
3014
  this.notifyApplicationAfterRender();
2690
3015
  }
2691
- viewInvalidated() {
2692
- this.adapter.pageInvalidated();
3016
+ preloadOnLoadLinksForView(element) {
3017
+ this.preloader.preloadOnLoadLinksForView(element);
3018
+ }
3019
+ viewInvalidated(reason) {
3020
+ this.adapter.pageInvalidated(reason);
2693
3021
  }
2694
3022
  frameLoaded(frame) {
2695
3023
  this.notifyApplicationAfterFrameLoad(frame);
@@ -2697,49 +3025,85 @@ class Session {
2697
3025
  frameRendered(fetchResponse, frame) {
2698
3026
  this.notifyApplicationAfterFrameRender(fetchResponse, frame);
2699
3027
  }
2700
- applicationAllowsFollowingLinkToLocation(link, location) {
2701
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
3028
+ applicationAllowsFollowingLinkToLocation(link, location, ev) {
3029
+ const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
2702
3030
  return !event.defaultPrevented;
2703
3031
  }
2704
- applicationAllowsVisitingLocation(location) {
2705
- const event = this.notifyApplicationBeforeVisitingLocation(location);
3032
+ applicationAllowsVisitingLocation(location, options = {}) {
3033
+ const event = this.notifyApplicationBeforeVisitingLocation(location, options.initiator);
2706
3034
  return !event.defaultPrevented;
2707
3035
  }
2708
- notifyApplicationAfterClickingLinkToLocation(link, location) {
2709
- return dispatch("turbo:click", { target: link, detail: { url: location.href }, cancelable: true });
3036
+ notifyApplicationAfterClickingLinkToLocation(link, location, event) {
3037
+ return dispatch("turbo:click", {
3038
+ target: link,
3039
+ detail: { url: location.href, originalEvent: event },
3040
+ cancelable: true,
3041
+ });
2710
3042
  }
2711
- notifyApplicationBeforeVisitingLocation(location) {
2712
- return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
3043
+ notifyApplicationBeforeVisitingLocation(location, element) {
3044
+ return dispatch("turbo:before-visit", {
3045
+ target: element,
3046
+ detail: { url: location.href },
3047
+ cancelable: true,
3048
+ });
2713
3049
  }
2714
- notifyApplicationAfterVisitingLocation(location, action) {
2715
- markAsBusy(document.documentElement);
2716
- return dispatch("turbo:visit", { detail: { url: location.href, action } });
3050
+ notifyApplicationAfterVisitingLocation(location, action, element) {
3051
+ return dispatch("turbo:visit", {
3052
+ target: element,
3053
+ detail: { url: location.href, action },
3054
+ });
2717
3055
  }
2718
3056
  notifyApplicationBeforeCachingSnapshot() {
2719
3057
  return dispatch("turbo:before-cache");
2720
3058
  }
2721
- notifyApplicationBeforeRender(newBody, resume) {
2722
- return dispatch("turbo:before-render", { detail: { newBody, resume }, cancelable: true });
3059
+ notifyApplicationBeforeRender(newBody, options) {
3060
+ return dispatch("turbo:before-render", {
3061
+ detail: Object.assign({ newBody }, options),
3062
+ cancelable: true,
3063
+ });
2723
3064
  }
2724
3065
  notifyApplicationAfterRender() {
2725
3066
  return dispatch("turbo:render");
2726
3067
  }
2727
3068
  notifyApplicationAfterPageLoad(timing = {}) {
2728
- clearBusyState(document.documentElement);
2729
- return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
3069
+ return dispatch("turbo:load", {
3070
+ detail: { url: this.location.href, timing },
3071
+ });
2730
3072
  }
2731
3073
  notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
2732
- dispatchEvent(new HashChangeEvent("hashchange", { oldURL: oldURL.toString(), newURL: newURL.toString() }));
3074
+ dispatchEvent(new HashChangeEvent("hashchange", {
3075
+ oldURL: oldURL.toString(),
3076
+ newURL: newURL.toString(),
3077
+ }));
2733
3078
  }
2734
3079
  notifyApplicationAfterFrameLoad(frame) {
2735
3080
  return dispatch("turbo:frame-load", { target: frame });
2736
3081
  }
2737
3082
  notifyApplicationAfterFrameRender(fetchResponse, frame) {
2738
- return dispatch("turbo:frame-render", { detail: { fetchResponse }, target: frame, cancelable: true });
3083
+ return dispatch("turbo:frame-render", {
3084
+ detail: { fetchResponse },
3085
+ target: frame,
3086
+ cancelable: true,
3087
+ });
2739
3088
  }
2740
- elementDriveEnabled(element) {
2741
- const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
2742
- if (this.drive) {
3089
+ submissionIsNavigatable(form, submitter) {
3090
+ if (this.formMode == "off") {
3091
+ return false;
3092
+ }
3093
+ else {
3094
+ const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
3095
+ if (this.formMode == "optin") {
3096
+ return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null;
3097
+ }
3098
+ else {
3099
+ return submitterIsNavigatable && this.elementIsNavigatable(form);
3100
+ }
3101
+ }
3102
+ }
3103
+ elementIsNavigatable(element) {
3104
+ const container = element.closest("[data-turbo]");
3105
+ const withinFrame = element.closest("turbo-frame");
3106
+ if (this.drive || withinFrame) {
2743
3107
  if (container) {
2744
3108
  return container.getAttribute("data-turbo") != "false";
2745
3109
  }
@@ -2760,18 +3124,6 @@ class Session {
2760
3124
  const action = link.getAttribute("data-turbo-action");
2761
3125
  return isAction(action) ? action : "advance";
2762
3126
  }
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
3127
  get snapshot() {
2776
3128
  return this.view.snapshot;
2777
3129
  }
@@ -2783,11 +3135,59 @@ const deprecatedLocationPropertyDescriptors = {
2783
3135
  absoluteURL: {
2784
3136
  get() {
2785
3137
  return this.toString();
2786
- }
3138
+ },
3139
+ },
3140
+ };
3141
+
3142
+ class Cache {
3143
+ constructor(session) {
3144
+ this.session = session;
3145
+ }
3146
+ clear() {
3147
+ this.session.clearCache();
3148
+ }
3149
+ resetCacheControl() {
3150
+ this.setCacheControl("");
2787
3151
  }
3152
+ exemptPageFromCache() {
3153
+ this.setCacheControl("no-cache");
3154
+ }
3155
+ exemptPageFromPreview() {
3156
+ this.setCacheControl("no-preview");
3157
+ }
3158
+ setCacheControl(value) {
3159
+ setMetaContent("turbo-cache-control", value);
3160
+ }
3161
+ }
3162
+
3163
+ const StreamActions = {
3164
+ after() {
3165
+ this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });
3166
+ },
3167
+ append() {
3168
+ this.removeDuplicateTargetChildren();
3169
+ this.targetElements.forEach((e) => e.append(this.templateContent));
3170
+ },
3171
+ before() {
3172
+ this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });
3173
+ },
3174
+ prepend() {
3175
+ this.removeDuplicateTargetChildren();
3176
+ this.targetElements.forEach((e) => e.prepend(this.templateContent));
3177
+ },
3178
+ remove() {
3179
+ this.targetElements.forEach((e) => e.remove());
3180
+ },
3181
+ replace() {
3182
+ this.targetElements.forEach((e) => e.replaceWith(this.templateContent));
3183
+ },
3184
+ update() {
3185
+ this.targetElements.forEach((e) => e.replaceChildren(this.templateContent));
3186
+ },
2788
3187
  };
2789
3188
 
2790
- const session = new Session;
3189
+ const session = new Session();
3190
+ const cache = new Cache(session);
2791
3191
  const { navigator: navigator$1 } = session;
2792
3192
  function start() {
2793
3193
  session.start();
@@ -2796,7 +3196,7 @@ function registerAdapter(adapter) {
2796
3196
  session.registerAdapter(adapter);
2797
3197
  }
2798
3198
  function visit(location, options) {
2799
- session.visit(location, options);
3199
+ return session.visit(location, options);
2800
3200
  }
2801
3201
  function connectStreamSource(source) {
2802
3202
  session.connectStreamSource(source);
@@ -2808,6 +3208,7 @@ function renderStreamMessage(message) {
2808
3208
  session.renderStreamMessage(message);
2809
3209
  }
2810
3210
  function clearCache() {
3211
+ 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
3212
  session.clearCache();
2812
3213
  }
2813
3214
  function setProgressBarDelay(delay) {
@@ -2816,13 +3217,18 @@ function setProgressBarDelay(delay) {
2816
3217
  function setConfirmMethod(confirmMethod) {
2817
3218
  FormSubmission.confirmMethod = confirmMethod;
2818
3219
  }
3220
+ function setFormMode(mode) {
3221
+ session.setFormMode(mode);
3222
+ }
2819
3223
 
2820
3224
  var Turbo = /*#__PURE__*/Object.freeze({
2821
3225
  __proto__: null,
2822
3226
  navigator: navigator$1,
2823
3227
  session: session,
3228
+ cache: cache,
2824
3229
  PageRenderer: PageRenderer,
2825
3230
  PageSnapshot: PageSnapshot,
3231
+ FrameRenderer: FrameRenderer,
2826
3232
  start: start,
2827
3233
  registerAdapter: registerAdapter,
2828
3234
  visit: visit,
@@ -2831,41 +3237,56 @@ var Turbo = /*#__PURE__*/Object.freeze({
2831
3237
  renderStreamMessage: renderStreamMessage,
2832
3238
  clearCache: clearCache,
2833
3239
  setProgressBarDelay: setProgressBarDelay,
2834
- setConfirmMethod: setConfirmMethod
3240
+ setConfirmMethod: setConfirmMethod,
3241
+ setFormMode: setFormMode,
3242
+ StreamActions: StreamActions
2835
3243
  });
2836
3244
 
2837
3245
  class FrameController {
2838
3246
  constructor(element) {
2839
- this.fetchResponseLoaded = (fetchResponse) => { };
3247
+ this.fetchResponseLoaded = (_fetchResponse) => { };
2840
3248
  this.currentFetchRequest = null;
2841
3249
  this.resolveVisitPromise = () => { };
2842
3250
  this.connected = false;
2843
3251
  this.hasBeenLoaded = false;
2844
- this.settingSourceURL = false;
3252
+ this.ignoredAttributes = new Set();
3253
+ this.action = null;
3254
+ this.visitCachedSnapshot = ({ element }) => {
3255
+ const frame = element.querySelector("#" + this.element.id);
3256
+ if (frame && this.previousFrameElement) {
3257
+ frame.replaceChildren(...this.previousFrameElement.children);
3258
+ }
3259
+ delete this.previousFrameElement;
3260
+ };
2845
3261
  this.element = element;
2846
3262
  this.view = new FrameView(this, this.element);
2847
3263
  this.appearanceObserver = new AppearanceObserver(this, this.element);
2848
- this.linkInterceptor = new LinkInterceptor(this, this.element);
2849
- this.formInterceptor = new FormInterceptor(this, this.element);
3264
+ this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);
3265
+ this.linkClickObserver = new LinkClickObserver(this, this.element);
3266
+ this.restorationIdentifier = uuid();
3267
+ this.formSubmitObserver = new FormSubmitObserver(this, this.element);
2850
3268
  }
2851
3269
  connect() {
2852
3270
  if (!this.connected) {
2853
3271
  this.connected = true;
2854
- this.reloadable = false;
2855
3272
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2856
3273
  this.appearanceObserver.start();
2857
3274
  }
2858
- this.linkInterceptor.start();
2859
- this.formInterceptor.start();
2860
- this.sourceURLChanged();
3275
+ else {
3276
+ this.loadSourceURL();
3277
+ }
3278
+ this.formLinkClickObserver.start();
3279
+ this.linkClickObserver.start();
3280
+ this.formSubmitObserver.start();
2861
3281
  }
2862
3282
  }
2863
3283
  disconnect() {
2864
3284
  if (this.connected) {
2865
3285
  this.connected = false;
2866
3286
  this.appearanceObserver.stop();
2867
- this.linkInterceptor.stop();
2868
- this.formInterceptor.stop();
3287
+ this.formLinkClickObserver.stop();
3288
+ this.linkClickObserver.stop();
3289
+ this.formSubmitObserver.stop();
2869
3290
  }
2870
3291
  }
2871
3292
  disabledChanged() {
@@ -2874,10 +3295,20 @@ class FrameController {
2874
3295
  }
2875
3296
  }
2876
3297
  sourceURLChanged() {
3298
+ if (this.isIgnoringChangesTo("src"))
3299
+ return;
3300
+ if (this.element.isConnected) {
3301
+ this.complete = false;
3302
+ }
2877
3303
  if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
2878
3304
  this.loadSourceURL();
2879
3305
  }
2880
3306
  }
3307
+ completeChanged() {
3308
+ if (this.isIgnoringChangesTo("complete"))
3309
+ return;
3310
+ this.loadSourceURL();
3311
+ }
2881
3312
  loadingStyleChanged() {
2882
3313
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2883
3314
  this.appearanceObserver.start();
@@ -2888,21 +3319,11 @@ class FrameController {
2888
3319
  }
2889
3320
  }
2890
3321
  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
- }
3322
+ if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
3323
+ this.element.loaded = this.visit(expandURL(this.sourceURL));
3324
+ this.appearanceObserver.stop();
3325
+ await this.element.loaded;
3326
+ this.hasBeenLoaded = true;
2906
3327
  }
2907
3328
  }
2908
3329
  async loadResponse(fetchResponse) {
@@ -2913,14 +3334,23 @@ class FrameController {
2913
3334
  const html = await fetchResponse.responseHTML;
2914
3335
  if (html) {
2915
3336
  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);
3337
+ const newFrameElement = await this.extractForeignFrameElement(body);
3338
+ if (newFrameElement) {
3339
+ const snapshot = new Snapshot(newFrameElement);
3340
+ const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
3341
+ if (this.view.renderPromise)
3342
+ await this.view.renderPromise;
3343
+ this.changeHistory();
3344
+ await this.view.render(renderer);
3345
+ this.complete = true;
3346
+ session.frameRendered(fetchResponse, this.element);
3347
+ session.frameLoaded(this.element);
3348
+ this.fetchResponseLoaded(fetchResponse);
3349
+ }
3350
+ else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3351
+ console.warn(`A matching frame for #${this.element.id} was missing from the response, transforming into full-page Visit.`);
3352
+ this.visitResponse(fetchResponse.response);
3353
+ }
2924
3354
  }
2925
3355
  }
2926
3356
  catch (error) {
@@ -2931,56 +3361,66 @@ class FrameController {
2931
3361
  this.fetchResponseLoaded = () => { };
2932
3362
  }
2933
3363
  }
2934
- elementAppearedInViewport(element) {
3364
+ elementAppearedInViewport(_element) {
2935
3365
  this.loadSourceURL();
2936
3366
  }
2937
- shouldInterceptLinkClick(element, url) {
2938
- if (element.hasAttribute("data-turbo-method")) {
2939
- return false;
2940
- }
2941
- else {
2942
- return this.shouldInterceptNavigation(element);
2943
- }
3367
+ willSubmitFormLinkToLocation(link) {
3368
+ return link.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(link);
3369
+ }
3370
+ submittedFormLinkToLocation(link, _location, form) {
3371
+ const frame = this.findFrameElement(link);
3372
+ if (frame)
3373
+ form.setAttribute("data-turbo-frame", frame.id);
2944
3374
  }
2945
- linkClickIntercepted(element, url) {
2946
- this.reloadable = true;
2947
- this.navigateFrame(element, url);
3375
+ willFollowLinkToLocation(element) {
3376
+ return this.shouldInterceptNavigation(element);
2948
3377
  }
2949
- shouldInterceptFormSubmission(element, submitter) {
2950
- return this.shouldInterceptNavigation(element, submitter);
3378
+ followedLinkToLocation(element, location) {
3379
+ this.navigateFrame(element, location.href);
2951
3380
  }
2952
- formSubmissionIntercepted(element, submitter) {
3381
+ willSubmitForm(element, submitter) {
3382
+ return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
3383
+ }
3384
+ formSubmitted(element, submitter) {
2953
3385
  if (this.formSubmission) {
2954
3386
  this.formSubmission.stop();
2955
3387
  }
2956
- this.reloadable = false;
2957
3388
  this.formSubmission = new FormSubmission(this, element, submitter);
2958
3389
  const { fetchRequest } = this.formSubmission;
2959
3390
  this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
2960
3391
  this.formSubmission.start();
2961
3392
  }
2962
3393
  prepareHeadersForRequest(headers, request) {
3394
+ var _a;
2963
3395
  headers["Turbo-Frame"] = this.id;
3396
+ if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3397
+ request.acceptResponseType(StreamMessage.contentType);
3398
+ }
2964
3399
  }
2965
- requestStarted(request) {
3400
+ requestStarted(_request) {
2966
3401
  markAsBusy(this.element);
2967
3402
  }
2968
- requestPreventedHandlingResponse(request, response) {
3403
+ requestPreventedHandlingResponse(_request, _response) {
2969
3404
  this.resolveVisitPromise();
2970
3405
  }
2971
3406
  async requestSucceededWithResponse(request, response) {
2972
3407
  await this.loadResponse(response);
2973
3408
  this.resolveVisitPromise();
2974
3409
  }
2975
- requestFailedWithResponse(request, response) {
3410
+ async requestFailedWithResponse(request, response) {
2976
3411
  console.error(response);
3412
+ await this.loadResponse(response);
2977
3413
  this.resolveVisitPromise();
2978
3414
  }
2979
3415
  requestErrored(request, error) {
2980
3416
  console.error(error);
3417
+ dispatch("turbo:fetch-request-error", {
3418
+ target: this.element,
3419
+ detail: { request, error },
3420
+ });
2981
3421
  this.resolveVisitPromise();
2982
3422
  }
2983
- requestFinished(request) {
3423
+ requestFinished(_request) {
2984
3424
  clearBusyState(this.element);
2985
3425
  }
2986
3426
  formSubmissionStarted({ formElement }) {
@@ -3000,19 +3440,32 @@ class FrameController {
3000
3440
  formSubmissionFinished({ formElement }) {
3001
3441
  clearBusyState(formElement, this.findFrameElement(formElement));
3002
3442
  }
3003
- allowsImmediateRender(snapshot, resume) {
3004
- return true;
3443
+ allowsImmediateRender({ element: newFrame }, options) {
3444
+ const event = dispatch("turbo:before-frame-render", {
3445
+ target: this.element,
3446
+ detail: Object.assign({ newFrame }, options),
3447
+ cancelable: true,
3448
+ });
3449
+ const { defaultPrevented, detail: { render }, } = event;
3450
+ if (this.view.renderer && render) {
3451
+ this.view.renderer.renderElement = render;
3452
+ }
3453
+ return !defaultPrevented;
3005
3454
  }
3006
- viewRenderedSnapshot(snapshot, isPreview) {
3455
+ viewRenderedSnapshot(_snapshot, _isPreview) { }
3456
+ preloadOnLoadLinksForView(element) {
3457
+ session.preloadOnLoadLinksForView(element);
3007
3458
  }
3008
- viewInvalidated() {
3459
+ viewInvalidated() { }
3460
+ willRenderFrame(currentElement, _newElement) {
3461
+ this.previousFrameElement = currentElement.cloneNode(true);
3009
3462
  }
3010
3463
  async visit(url) {
3011
3464
  var _a;
3012
- const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
3465
+ const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);
3013
3466
  (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
3014
3467
  this.currentFetchRequest = request;
3015
- return new Promise(resolve => {
3468
+ return new Promise((resolve) => {
3016
3469
  this.resolveVisitPromise = () => {
3017
3470
  this.resolveVisitPromise = () => { };
3018
3471
  this.currentFetchRequest = null;
@@ -3024,23 +3477,64 @@ class FrameController {
3024
3477
  navigateFrame(element, url, submitter) {
3025
3478
  const frame = this.findFrameElement(element, submitter);
3026
3479
  this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3027
- frame.setAttribute("reloadable", "");
3028
- frame.src = url;
3480
+ this.withCurrentNavigationElement(element, () => {
3481
+ frame.src = url;
3482
+ });
3029
3483
  }
3030
3484
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3031
- const action = getAttribute("data-turbo-action", submitter, element, frame);
3032
- if (isAction(action)) {
3033
- const { visitCachedSnapshot } = new SnapshotSubstitution(frame);
3485
+ this.action = getVisitAction(submitter, element, frame);
3486
+ this.frame = frame;
3487
+ if (isAction(this.action)) {
3488
+ const { visitCachedSnapshot } = frame.delegate;
3034
3489
  frame.delegate.fetchResponseLoaded = (fetchResponse) => {
3035
3490
  if (frame.src) {
3036
3491
  const { statusCode, redirected } = fetchResponse;
3037
3492
  const responseHTML = frame.ownerDocument.documentElement.outerHTML;
3038
3493
  const response = { statusCode, redirected, responseHTML };
3039
- session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false });
3494
+ const options = {
3495
+ response,
3496
+ visitCachedSnapshot,
3497
+ willRender: false,
3498
+ updateHistory: false,
3499
+ restorationIdentifier: this.restorationIdentifier,
3500
+ };
3501
+ if (this.action)
3502
+ options.action = this.action;
3503
+ session.visit(frame.src, options);
3040
3504
  }
3041
3505
  };
3042
3506
  }
3043
3507
  }
3508
+ changeHistory() {
3509
+ if (this.action && this.frame) {
3510
+ const method = getHistoryMethodForAction(this.action);
3511
+ session.history.update(method, expandURL(this.frame.src || ""), this.restorationIdentifier);
3512
+ }
3513
+ }
3514
+ willHandleFrameMissingFromResponse(fetchResponse) {
3515
+ this.element.setAttribute("complete", "");
3516
+ const response = fetchResponse.response;
3517
+ const visit = async (url, options = {}) => {
3518
+ if (url instanceof Response) {
3519
+ this.visitResponse(url);
3520
+ }
3521
+ else {
3522
+ session.visit(url, options);
3523
+ }
3524
+ };
3525
+ const event = dispatch("turbo:frame-missing", {
3526
+ target: this.element,
3527
+ detail: { response, visit },
3528
+ cancelable: true,
3529
+ });
3530
+ return !event.defaultPrevented;
3531
+ }
3532
+ async visitResponse(response) {
3533
+ const wrapped = new FetchResponse(response);
3534
+ const responseHTML = await wrapped.responseHTML;
3535
+ const { location, redirected, statusCode } = wrapped;
3536
+ return session.visit(location, { response: { redirected, statusCode, responseHTML } });
3537
+ }
3044
3538
  findFrameElement(element, submitter) {
3045
3539
  var _a;
3046
3540
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
@@ -3050,19 +3544,21 @@ class FrameController {
3050
3544
  let element;
3051
3545
  const id = CSS.escape(this.id);
3052
3546
  try {
3053
- if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
3547
+ element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
3548
+ if (element) {
3054
3549
  return element;
3055
3550
  }
3056
- if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
3551
+ element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);
3552
+ if (element) {
3057
3553
  await element.loaded;
3058
3554
  return await this.extractForeignFrameElement(element);
3059
3555
  }
3060
- console.error(`Response has no matching <turbo-frame id="${id}"> element`);
3061
3556
  }
3062
3557
  catch (error) {
3063
3558
  console.error(error);
3559
+ return new FrameElement();
3064
3560
  }
3065
- return new FrameElement();
3561
+ return null;
3066
3562
  }
3067
3563
  formActionIsVisitable(form, submitter) {
3068
3564
  const action = getAction(form, submitter);
@@ -3082,10 +3578,10 @@ class FrameController {
3082
3578
  return !frameElement.disabled;
3083
3579
  }
3084
3580
  }
3085
- if (!session.elementDriveEnabled(element)) {
3581
+ if (!session.elementIsNavigatable(element)) {
3086
3582
  return false;
3087
3583
  }
3088
- if (submitter && !session.elementDriveEnabled(submitter)) {
3584
+ if (submitter && !session.elementIsNavigatable(submitter)) {
3089
3585
  return false;
3090
3586
  }
3091
3587
  return true;
@@ -3101,24 +3597,10 @@ class FrameController {
3101
3597
  return this.element.src;
3102
3598
  }
3103
3599
  }
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
3600
  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;
3601
+ this.ignoringChangesToAttribute("src", () => {
3602
+ this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3603
+ });
3122
3604
  }
3123
3605
  get loadingStyle() {
3124
3606
  return this.element.loading;
@@ -3126,6 +3608,19 @@ class FrameController {
3126
3608
  get isLoading() {
3127
3609
  return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
3128
3610
  }
3611
+ get complete() {
3612
+ return this.element.hasAttribute("complete");
3613
+ }
3614
+ set complete(value) {
3615
+ this.ignoringChangesToAttribute("complete", () => {
3616
+ if (value) {
3617
+ this.element.setAttribute("complete", "");
3618
+ }
3619
+ else {
3620
+ this.element.removeAttribute("complete");
3621
+ }
3622
+ });
3623
+ }
3129
3624
  get isActive() {
3130
3625
  return this.element.isActive && this.connected;
3131
3626
  }
@@ -3135,16 +3630,18 @@ class FrameController {
3135
3630
  const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3136
3631
  return expandURL(root);
3137
3632
  }
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;
3633
+ isIgnoringChangesTo(attributeName) {
3634
+ return this.ignoredAttributes.has(attributeName);
3635
+ }
3636
+ ignoringChangesToAttribute(attributeName, callback) {
3637
+ this.ignoredAttributes.add(attributeName);
3638
+ callback();
3639
+ this.ignoredAttributes.delete(attributeName);
3640
+ }
3641
+ withCurrentNavigationElement(element, callback) {
3642
+ this.currentNavigationElement = element;
3643
+ callback();
3644
+ delete this.currentNavigationElement;
3148
3645
  }
3149
3646
  }
3150
3647
  function getFrameElementById(id) {
@@ -3172,36 +3669,10 @@ function activateElement(element, currentURL) {
3172
3669
  }
3173
3670
  }
3174
3671
 
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
3672
  class StreamElement extends HTMLElement {
3673
+ static async renderElement(newElement) {
3674
+ await newElement.performAction();
3675
+ }
3205
3676
  async connectedCallback() {
3206
3677
  try {
3207
3678
  await this.render();
@@ -3215,12 +3686,13 @@ class StreamElement extends HTMLElement {
3215
3686
  }
3216
3687
  async render() {
3217
3688
  var _a;
3218
- return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
3219
- if (this.dispatchEvent(this.beforeRenderEvent)) {
3689
+ return ((_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
3690
+ const event = this.beforeRenderEvent;
3691
+ if (this.dispatchEvent(event)) {
3220
3692
  await nextAnimationFrame();
3221
- this.performAction();
3693
+ await event.detail.render(this);
3222
3694
  }
3223
- })());
3695
+ })()));
3224
3696
  }
3225
3697
  disconnect() {
3226
3698
  try {
@@ -3229,13 +3701,13 @@ class StreamElement extends HTMLElement {
3229
3701
  catch (_a) { }
3230
3702
  }
3231
3703
  removeDuplicateTargetChildren() {
3232
- this.duplicateChildren.forEach(c => c.remove());
3704
+ this.duplicateChildren.forEach((c) => c.remove());
3233
3705
  }
3234
3706
  get duplicateChildren() {
3235
3707
  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));
3708
+ const existingChildren = this.targetElements.flatMap((e) => [...e.children]).filter((c) => !!c.id);
3709
+ const newChildrenIds = [...(((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [])].filter((c) => !!c.id).map((c) => c.id);
3710
+ return existingChildren.filter((c) => newChildrenIds.includes(c.id));
3239
3711
  }
3240
3712
  get performAction() {
3241
3713
  if (this.action) {
@@ -3262,7 +3734,12 @@ class StreamElement extends HTMLElement {
3262
3734
  return this.templateElement.content.cloneNode(true);
3263
3735
  }
3264
3736
  get templateElement() {
3265
- if (this.firstElementChild instanceof HTMLTemplateElement) {
3737
+ if (this.firstElementChild === null) {
3738
+ const template = this.ownerDocument.createElement("template");
3739
+ this.appendChild(template);
3740
+ return template;
3741
+ }
3742
+ else if (this.firstElementChild instanceof HTMLTemplateElement) {
3266
3743
  return this.firstElementChild;
3267
3744
  }
3268
3745
  this.raise("first child element must be a <template> element");
@@ -3284,7 +3761,11 @@ class StreamElement extends HTMLElement {
3284
3761
  return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
3285
3762
  }
3286
3763
  get beforeRenderEvent() {
3287
- return new CustomEvent("turbo:before-stream-render", { bubbles: true, cancelable: true });
3764
+ return new CustomEvent("turbo:before-stream-render", {
3765
+ bubbles: true,
3766
+ cancelable: true,
3767
+ detail: { newStream: this, render: StreamElement.renderElement },
3768
+ });
3288
3769
  }
3289
3770
  get targetElementsById() {
3290
3771
  var _a;
@@ -3308,9 +3789,35 @@ class StreamElement extends HTMLElement {
3308
3789
  }
3309
3790
  }
3310
3791
 
3792
+ class StreamSourceElement extends HTMLElement {
3793
+ constructor() {
3794
+ super(...arguments);
3795
+ this.streamSource = null;
3796
+ }
3797
+ connectedCallback() {
3798
+ this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
3799
+ connectStreamSource(this.streamSource);
3800
+ }
3801
+ disconnectedCallback() {
3802
+ if (this.streamSource) {
3803
+ disconnectStreamSource(this.streamSource);
3804
+ }
3805
+ }
3806
+ get src() {
3807
+ return this.getAttribute("src") || "";
3808
+ }
3809
+ }
3810
+
3311
3811
  FrameElement.delegateConstructor = FrameController;
3312
- customElements.define("turbo-frame", FrameElement);
3313
- customElements.define("turbo-stream", StreamElement);
3812
+ if (customElements.get("turbo-frame") === undefined) {
3813
+ customElements.define("turbo-frame", FrameElement);
3814
+ }
3815
+ if (customElements.get("turbo-stream") === undefined) {
3816
+ customElements.define("turbo-stream", StreamElement);
3817
+ }
3818
+ if (customElements.get("turbo-stream-source") === undefined) {
3819
+ customElements.define("turbo-stream-source", StreamSourceElement);
3820
+ }
3314
3821
 
3315
3822
  (() => {
3316
3823
  let element = document.currentScript;
@@ -3318,7 +3825,8 @@ customElements.define("turbo-stream", StreamElement);
3318
3825
  return;
3319
3826
  if (element.hasAttribute("data-turbo-suppress-warning"))
3320
3827
  return;
3321
- while (element = element.parentElement) {
3828
+ element = element.parentElement;
3829
+ while (element) {
3322
3830
  if (element == document.body) {
3323
3831
  return console.warn(unindent `
3324
3832
  You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
@@ -3331,10 +3839,11 @@ customElements.define("turbo-stream", StreamElement);
3331
3839
  Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
3332
3840
  `, element.outerHTML);
3333
3841
  }
3842
+ element = element.parentElement;
3334
3843
  }
3335
3844
  })();
3336
3845
 
3337
3846
  window.Turbo = Turbo;
3338
3847
  start();
3339
3848
 
3340
- export { PageRenderer, PageSnapshot, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setProgressBarDelay, start, visit };
3849
+ 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 };