@hotwired/turbo 7.1.0-rc.3 → 7.2.0-beta.2

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 (66) hide show
  1. package/dist/turbo.es2017-esm.js +1027 -613
  2. package/dist/turbo.es2017-umd.js +1032 -614
  3. package/dist/types/core/bardo.d.ts +7 -2
  4. package/dist/types/core/cache.d.ts +10 -0
  5. package/dist/types/core/drive/error_renderer.d.ts +2 -1
  6. package/dist/types/core/drive/form_submission.d.ts +13 -4
  7. package/dist/types/core/drive/head_snapshot.d.ts +3 -3
  8. package/dist/types/core/drive/history.d.ts +1 -1
  9. package/dist/types/core/drive/navigator.d.ts +4 -3
  10. package/dist/types/core/drive/page_renderer.d.ts +8 -5
  11. package/dist/types/core/drive/page_view.d.ts +8 -5
  12. package/dist/types/core/drive/preloader.d.ts +14 -0
  13. package/dist/types/core/drive/progress_bar.d.ts +1 -0
  14. package/dist/types/core/drive/visit.d.ts +15 -4
  15. package/dist/types/core/frames/frame_controller.d.ts +43 -23
  16. package/dist/types/core/frames/frame_redirector.d.ts +12 -10
  17. package/dist/types/core/frames/frame_renderer.d.ts +8 -1
  18. package/dist/types/core/frames/frame_view.d.ts +2 -1
  19. package/dist/types/core/index.d.ts +14 -4
  20. package/dist/types/core/native/adapter.d.ts +3 -2
  21. package/dist/types/core/native/browser_adapter.d.ts +18 -9
  22. package/dist/types/core/renderer.d.ts +11 -5
  23. package/dist/types/core/session.d.ts +78 -19
  24. package/dist/types/core/snapshot.d.ts +2 -1
  25. package/dist/types/core/streams/stream_message.d.ts +2 -6
  26. package/dist/types/core/types.d.ts +4 -0
  27. package/dist/types/core/view.d.ts +13 -7
  28. package/dist/types/elements/frame_element.d.ts +10 -6
  29. package/dist/types/elements/stream_element.d.ts +3 -0
  30. package/dist/types/elements/stream_source_element.d.ts +7 -0
  31. package/dist/types/http/fetch_request.d.ts +9 -0
  32. package/dist/types/observers/cache_observer.d.ts +1 -1
  33. package/dist/types/observers/form_link_click_observer.d.ts +14 -0
  34. package/dist/types/observers/form_submit_observer.d.ts +2 -1
  35. package/dist/types/observers/link_click_observer.d.ts +5 -4
  36. package/dist/types/polyfills/submit-event.d.ts +1 -7
  37. package/dist/types/tests/functional/async_script_tests.d.ts +1 -6
  38. package/dist/types/tests/functional/autofocus_tests.d.ts +1 -9
  39. package/dist/types/tests/functional/cache_observer_tests.d.ts +1 -5
  40. package/dist/types/tests/functional/drive_disabled_tests.d.ts +1 -9
  41. package/dist/types/tests/functional/drive_tests.d.ts +1 -8
  42. package/dist/types/tests/functional/form_mode_tests.d.ts +1 -0
  43. package/dist/types/tests/functional/form_submission_tests.d.ts +1 -84
  44. package/dist/types/tests/functional/frame_navigation_tests.d.ts +1 -7
  45. package/dist/types/tests/functional/frame_tests.d.ts +1 -51
  46. package/dist/types/tests/functional/import_tests.d.ts +1 -4
  47. package/dist/types/tests/functional/loading_tests.d.ts +1 -13
  48. package/dist/types/tests/functional/navigation_tests.d.ts +1 -37
  49. package/dist/types/tests/functional/pausable_rendering_tests.d.ts +1 -6
  50. package/dist/types/tests/functional/pausable_requests_tests.d.ts +1 -6
  51. package/dist/types/tests/functional/preloader_tests.d.ts +1 -0
  52. package/dist/types/tests/functional/rendering_tests.d.ts +1 -35
  53. package/dist/types/tests/functional/scroll_restoration_tests.d.ts +1 -6
  54. package/dist/types/tests/functional/stream_tests.d.ts +1 -6
  55. package/dist/types/tests/functional/visit_tests.d.ts +1 -14
  56. package/dist/types/tests/helpers/page.d.ts +44 -0
  57. package/dist/types/tests/unit/deprecated_adapter_support_test.d.ts +10 -10
  58. package/dist/types/util.d.ts +12 -3
  59. package/package.json +22 -8
  60. package/CHANGELOG.md +0 -3
  61. package/dist/types/core/frames/form_interceptor.d.ts +0 -12
  62. package/dist/types/core/frames/link_interceptor.d.ts +0 -16
  63. package/dist/types/tests/functional/index.d.ts +0 -17
  64. package/dist/types/tests/helpers/functional_test_case.d.ts +0 -44
  65. package/dist/types/tests/helpers/remote_channel.d.ts +0 -10
  66. package/dist/types/tests/helpers/turbo_drive_test_case.d.ts +0 -21
@@ -1,20 +1,20 @@
1
1
  /*
2
- Turbo 7.1.0-rc.3
3
- Copyright © 2021 Basecamp, LLC
2
+ Turbo 7.2.0-beta.2
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,10 +394,11 @@ 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
  }
@@ -379,6 +420,48 @@ function clearBusyState(...elements) {
379
420
  element.removeAttribute("aria-busy");
380
421
  }
381
422
  }
423
+ function waitForLoad(element, timeoutInMilliseconds = 2000) {
424
+ return new Promise((resolve) => {
425
+ const onComplete = () => {
426
+ element.removeEventListener("error", onComplete);
427
+ element.removeEventListener("load", onComplete);
428
+ resolve();
429
+ };
430
+ element.addEventListener("load", onComplete, { once: true });
431
+ element.addEventListener("error", onComplete, { once: true });
432
+ setTimeout(resolve, timeoutInMilliseconds);
433
+ });
434
+ }
435
+ function getHistoryMethodForAction(action) {
436
+ switch (action) {
437
+ case "replace":
438
+ return history.replaceState;
439
+ case "advance":
440
+ case "restore":
441
+ return history.pushState;
442
+ }
443
+ }
444
+ function getVisitAction(...elements) {
445
+ const action = getAttribute("data-turbo-action", ...elements);
446
+ return isAction(action) ? action : null;
447
+ }
448
+ function getMetaElement(name) {
449
+ return document.querySelector(`meta[name="${name}"]`);
450
+ }
451
+ function getMetaContent(name) {
452
+ const element = getMetaElement(name);
453
+ return element && element.content;
454
+ }
455
+ function setMetaContent(name, content) {
456
+ let element = getMetaElement(name);
457
+ if (!element) {
458
+ element = document.createElement("meta");
459
+ element.setAttribute("name", name);
460
+ document.head.appendChild(element);
461
+ }
462
+ element.setAttribute("content", content);
463
+ return element;
464
+ }
382
465
 
383
466
  var FetchMethod;
384
467
  (function (FetchMethod) {
@@ -390,24 +473,27 @@ var FetchMethod;
390
473
  })(FetchMethod || (FetchMethod = {}));
391
474
  function fetchMethodFromString(method) {
392
475
  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;
476
+ case "get":
477
+ return FetchMethod.get;
478
+ case "post":
479
+ return FetchMethod.post;
480
+ case "put":
481
+ return FetchMethod.put;
482
+ case "patch":
483
+ return FetchMethod.patch;
484
+ case "delete":
485
+ return FetchMethod.delete;
398
486
  }
399
487
  }
400
488
  class FetchRequest {
401
- constructor(delegate, method, location, body = new URLSearchParams, target = null) {
402
- this.abortController = new AbortController;
403
- this.resolveRequestPromise = (value) => { };
489
+ constructor(delegate, method, location, body = new URLSearchParams(), target = null) {
490
+ this.abortController = new AbortController();
491
+ this.resolveRequestPromise = (_value) => { };
404
492
  this.delegate = delegate;
405
493
  this.method = method;
406
494
  this.headers = this.defaultHeaders;
407
495
  this.body = body;
408
- this.url = this.isIdempotent ?
409
- mergeFormDataEntries(new URL(location.href), this.entries) :
410
- location;
496
+ this.url = location;
411
497
  this.target = target;
412
498
  }
413
499
  get location() {
@@ -433,7 +519,7 @@ class FetchRequest {
433
519
  return await this.receive(response);
434
520
  }
435
521
  catch (error) {
436
- if (error.name !== 'AbortError') {
522
+ if (error.name !== "AbortError") {
437
523
  this.delegate.requestErrored(this, error);
438
524
  throw error;
439
525
  }
@@ -444,7 +530,11 @@ class FetchRequest {
444
530
  }
445
531
  async receive(response) {
446
532
  const fetchResponse = new FetchResponse(response);
447
- const event = dispatch("turbo:before-fetch-response", { cancelable: true, detail: { fetchResponse }, target: this.target });
533
+ const event = dispatch("turbo:before-fetch-response", {
534
+ cancelable: true,
535
+ detail: { fetchResponse },
536
+ target: this.target,
537
+ });
448
538
  if (event.defaultPrevented) {
449
539
  this.delegate.requestPreventedHandlingResponse(this, fetchResponse);
450
540
  }
@@ -465,12 +555,12 @@ class FetchRequest {
465
555
  redirect: "follow",
466
556
  body: this.isIdempotent ? null : this.body,
467
557
  signal: this.abortSignal,
468
- referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
558
+ referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href,
469
559
  };
470
560
  }
471
561
  get defaultHeaders() {
472
562
  return {
473
- "Accept": "text/html, application/xhtml+xml"
563
+ Accept: "text/html, application/xhtml+xml",
474
564
  };
475
565
  }
476
566
  get isIdempotent() {
@@ -479,36 +569,29 @@ class FetchRequest {
479
569
  get abortSignal() {
480
570
  return this.abortController.signal;
481
571
  }
572
+ acceptResponseType(mimeType) {
573
+ this.headers["Accept"] = [mimeType, this.headers["Accept"]].join(", ");
574
+ }
482
575
  async allowRequestToBeIntercepted(fetchOptions) {
483
- const requestInterception = new Promise(resolve => this.resolveRequestPromise = resolve);
576
+ const requestInterception = new Promise((resolve) => (this.resolveRequestPromise = resolve));
484
577
  const event = dispatch("turbo:before-fetch-request", {
485
578
  cancelable: true,
486
579
  detail: {
487
580
  fetchOptions,
488
581
  url: this.url,
489
- resume: this.resolveRequestPromise
582
+ resume: this.resolveRequestPromise,
490
583
  },
491
- target: this.target
584
+ target: this.target,
492
585
  });
493
586
  if (event.defaultPrevented)
494
587
  await requestInterception;
495
588
  }
496
589
  }
497
- function mergeFormDataEntries(url, entries) {
498
- const searchParams = new URLSearchParams;
499
- for (const [name, value] of entries) {
500
- if (value instanceof File)
501
- continue;
502
- searchParams.append(name, value);
503
- }
504
- url.search = searchParams.toString();
505
- return url;
506
- }
507
590
 
508
591
  class AppearanceObserver {
509
592
  constructor(delegate, element) {
510
593
  this.started = false;
511
- this.intersect = entries => {
594
+ this.intersect = (entries) => {
512
595
  const lastEntry = entries.slice(-1)[0];
513
596
  if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
514
597
  this.delegate.elementAppearedInViewport(this.element);
@@ -533,40 +616,29 @@ class AppearanceObserver {
533
616
  }
534
617
 
535
618
  class StreamMessage {
536
- constructor(html) {
537
- this.templateElement = document.createElement("template");
538
- this.templateElement.innerHTML = html;
619
+ constructor(fragment) {
620
+ this.fragment = importStreamElements(fragment);
539
621
  }
540
622
  static wrap(message) {
541
623
  if (typeof message == "string") {
542
- return new this(message);
624
+ return new this(createDocumentFragment(message));
543
625
  }
544
626
  else {
545
627
  return message;
546
628
  }
547
629
  }
548
- get fragment() {
549
- const fragment = document.createDocumentFragment();
550
- for (const element of this.foreignElements) {
551
- fragment.appendChild(document.importNode(element, true));
630
+ }
631
+ StreamMessage.contentType = "text/vnd.turbo-stream.html";
632
+ function importStreamElements(fragment) {
633
+ for (const element of fragment.querySelectorAll("turbo-stream")) {
634
+ const streamElement = document.importNode(element, true);
635
+ for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) {
636
+ inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));
552
637
  }
553
- return fragment;
554
- }
555
- get foreignElements() {
556
- return this.templateChildren.reduce((streamElements, child) => {
557
- if (child.tagName.toLowerCase() == "turbo-stream") {
558
- return [...streamElements, child];
559
- }
560
- else {
561
- return streamElements;
562
- }
563
- }, []);
564
- }
565
- get templateChildren() {
566
- return Array.from(this.templateElement.content.children);
638
+ element.replaceWith(streamElement);
567
639
  }
640
+ return fragment;
568
641
  }
569
- StreamMessage.contentType = "text/vnd.turbo-stream.html";
570
642
 
571
643
  var FormSubmissionState;
572
644
  (function (FormSubmissionState) {
@@ -585,9 +657,12 @@ var FormEnctype;
585
657
  })(FormEnctype || (FormEnctype = {}));
586
658
  function formEnctypeFromString(encoding) {
587
659
  switch (encoding.toLowerCase()) {
588
- case FormEnctype.multipart: return FormEnctype.multipart;
589
- case FormEnctype.plain: return FormEnctype.plain;
590
- default: return FormEnctype.urlEncoded;
660
+ case FormEnctype.multipart:
661
+ return FormEnctype.multipart;
662
+ case FormEnctype.plain:
663
+ return FormEnctype.plain;
664
+ default:
665
+ return FormEnctype.urlEncoded;
591
666
  }
592
667
  }
593
668
  class FormSubmission {
@@ -597,11 +672,15 @@ class FormSubmission {
597
672
  this.formElement = formElement;
598
673
  this.submitter = submitter;
599
674
  this.formData = buildFormData(formElement, submitter);
675
+ this.location = expandURL(this.action);
676
+ if (this.method == FetchMethod.get) {
677
+ mergeFormDataEntries(this.location, [...this.body.entries()]);
678
+ }
600
679
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
601
680
  this.mustRedirect = mustRedirect;
602
681
  }
603
- static confirmMethod(message, element) {
604
- return confirm(message);
682
+ static confirmMethod(message, _element) {
683
+ return Promise.resolve(confirm(message));
605
684
  }
606
685
  get method() {
607
686
  var _a;
@@ -610,11 +689,13 @@ class FormSubmission {
610
689
  }
611
690
  get action() {
612
691
  var _a;
613
- const formElementAction = typeof this.formElement.action === 'string' ? this.formElement.action : null;
614
- return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.getAttribute("action") || formElementAction || "";
615
- }
616
- get location() {
617
- return expandURL(this.action);
692
+ const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
693
+ if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
694
+ return this.submitter.getAttribute("formaction") || "";
695
+ }
696
+ else {
697
+ return this.formElement.getAttribute("action") || formElementAction || "";
698
+ }
618
699
  }
619
700
  get body() {
620
701
  if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
@@ -637,7 +718,8 @@ class FormSubmission {
637
718
  }, []);
638
719
  }
639
720
  get confirmationMessage() {
640
- return this.formElement.getAttribute("data-turbo-confirm");
721
+ var _a;
722
+ return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-confirm")) || this.formElement.getAttribute("data-turbo-confirm");
641
723
  }
642
724
  get needsConfirmation() {
643
725
  return this.confirmationMessage !== null;
@@ -645,7 +727,7 @@ class FormSubmission {
645
727
  async start() {
646
728
  const { initialized, requesting } = FormSubmissionState;
647
729
  if (this.needsConfirmation) {
648
- const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
730
+ const answer = await FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
649
731
  if (!answer) {
650
732
  return;
651
733
  }
@@ -669,14 +751,19 @@ class FormSubmission {
669
751
  if (token) {
670
752
  headers["X-CSRF-Token"] = token;
671
753
  }
672
- headers["Accept"] = [StreamMessage.contentType, headers["Accept"]].join(", ");
754
+ }
755
+ if (this.requestAcceptsTurboStreamResponse(request)) {
756
+ request.acceptResponseType(StreamMessage.contentType);
673
757
  }
674
758
  }
675
- requestStarted(request) {
759
+ requestStarted(_request) {
676
760
  var _a;
677
761
  this.state = FormSubmissionState.waiting;
678
762
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
679
- dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } });
763
+ dispatch("turbo:submit-start", {
764
+ target: this.formElement,
765
+ detail: { formSubmission: this },
766
+ });
680
767
  this.delegate.formSubmissionStarted(this);
681
768
  }
682
769
  requestPreventedHandlingResponse(request, response) {
@@ -702,25 +789,35 @@ class FormSubmission {
702
789
  }
703
790
  requestErrored(request, error) {
704
791
  this.result = { success: false, error };
792
+ dispatch("turbo:fetch-request-error", {
793
+ target: this.formElement,
794
+ detail: { request, error },
795
+ });
705
796
  this.delegate.formSubmissionErrored(this, error);
706
797
  }
707
- requestFinished(request) {
798
+ requestFinished(_request) {
708
799
  var _a;
709
800
  this.state = FormSubmissionState.stopped;
710
801
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
711
- 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
+ });
712
806
  this.delegate.formSubmissionFinished(this);
713
807
  }
714
808
  requestMustRedirect(request) {
715
809
  return !request.isIdempotent && this.mustRedirect;
716
810
  }
811
+ requestAcceptsTurboStreamResponse(request) {
812
+ return !request.isIdempotent || this.formElement.hasAttribute("data-turbo-stream");
813
+ }
717
814
  }
718
815
  function buildFormData(formElement, submitter) {
719
816
  const formData = new FormData(formElement);
720
817
  const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
721
818
  const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
722
- if (name && value != null && formData.get(name) != value) {
723
- formData.append(name, value);
819
+ if (name) {
820
+ formData.append(name, value || "");
724
821
  }
725
822
  return formData;
726
823
  }
@@ -734,18 +831,27 @@ function getCookieValue(cookieName) {
734
831
  }
735
832
  }
736
833
  }
737
- function getMetaContent(name) {
738
- const element = document.querySelector(`meta[name="${name}"]`);
739
- return element && element.content;
740
- }
741
834
  function responseSucceededWithoutRedirect(response) {
742
835
  return response.statusCode == 200 && !response.redirected;
743
836
  }
837
+ function mergeFormDataEntries(url, entries) {
838
+ const searchParams = new URLSearchParams();
839
+ for (const [name, value] of entries) {
840
+ if (value instanceof File)
841
+ continue;
842
+ searchParams.append(name, value);
843
+ }
844
+ url.search = searchParams.toString();
845
+ return url;
846
+ }
744
847
 
745
848
  class Snapshot {
746
849
  constructor(element) {
747
850
  this.element = element;
748
851
  }
852
+ get activeElement() {
853
+ return this.element.ownerDocument.activeElement;
854
+ }
749
855
  get children() {
750
856
  return [...this.element.children];
751
857
  }
@@ -759,7 +865,14 @@ class Snapshot {
759
865
  return this.element.isConnected;
760
866
  }
761
867
  get firstAutofocusableElement() {
762
- 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;
763
876
  }
764
877
  get permanentElements() {
765
878
  return [...this.element.querySelectorAll("[id][data-turbo-permanent]")];
@@ -780,35 +893,59 @@ class Snapshot {
780
893
  }
781
894
  }
782
895
 
783
- class FormInterceptor {
784
- constructor(delegate, element) {
896
+ class FormSubmitObserver {
897
+ constructor(delegate, eventTarget) {
898
+ this.started = false;
899
+ this.submitCaptured = () => {
900
+ this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
901
+ this.eventTarget.addEventListener("submit", this.submitBubbled, false);
902
+ };
785
903
  this.submitBubbled = ((event) => {
786
- const form = event.target;
787
- if (!event.defaultPrevented && form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
904
+ if (!event.defaultPrevented) {
905
+ const form = event.target instanceof HTMLFormElement ? event.target : undefined;
788
906
  const submitter = event.submitter || undefined;
789
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
790
- if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
907
+ if (form &&
908
+ submissionDoesNotDismissDialog(form, submitter) &&
909
+ submissionDoesNotTargetIFrame(form, submitter) &&
910
+ this.delegate.willSubmitForm(form, submitter)) {
791
911
  event.preventDefault();
792
- event.stopImmediatePropagation();
793
- this.delegate.formSubmissionIntercepted(form, submitter);
912
+ this.delegate.formSubmitted(form, submitter);
794
913
  }
795
914
  }
796
915
  });
797
916
  this.delegate = delegate;
798
- this.element = element;
917
+ this.eventTarget = eventTarget;
799
918
  }
800
919
  start() {
801
- this.element.addEventListener("submit", this.submitBubbled);
920
+ if (!this.started) {
921
+ this.eventTarget.addEventListener("submit", this.submitCaptured, true);
922
+ this.started = true;
923
+ }
802
924
  }
803
925
  stop() {
804
- this.element.removeEventListener("submit", this.submitBubbled);
926
+ if (this.started) {
927
+ this.eventTarget.removeEventListener("submit", this.submitCaptured, true);
928
+ this.started = false;
929
+ }
930
+ }
931
+ }
932
+ function submissionDoesNotDismissDialog(form, submitter) {
933
+ const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
934
+ return method != "dialog";
935
+ }
936
+ function submissionDoesNotTargetIFrame(form, submitter) {
937
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
938
+ for (const element of document.getElementsByName(target)) {
939
+ if (element instanceof HTMLIFrameElement)
940
+ return false;
805
941
  }
942
+ return true;
806
943
  }
807
944
 
808
945
  class View {
809
946
  constructor(delegate, element) {
810
- this.resolveRenderPromise = (value) => { };
811
- this.resolveInterceptionPromise = (value) => { };
947
+ this.resolveRenderPromise = (_value) => { };
948
+ this.resolveInterceptionPromise = (_value) => { };
812
949
  this.delegate = delegate;
813
950
  this.element = element;
814
951
  }
@@ -853,15 +990,17 @@ class View {
853
990
  const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
854
991
  if (shouldRender) {
855
992
  try {
856
- this.renderPromise = new Promise(resolve => this.resolveRenderPromise = resolve);
993
+ this.renderPromise = new Promise((resolve) => (this.resolveRenderPromise = resolve));
857
994
  this.renderer = renderer;
858
- this.prepareToRenderSnapshot(renderer);
859
- const renderInterception = new Promise(resolve => this.resolveInterceptionPromise = resolve);
860
- const immediateRender = this.delegate.allowsImmediateRender(snapshot, this.resolveInterceptionPromise);
995
+ await this.prepareToRenderSnapshot(renderer);
996
+ const renderInterception = new Promise((resolve) => (this.resolveInterceptionPromise = resolve));
997
+ const options = { resume: this.resolveInterceptionPromise, render: this.renderer.renderElement };
998
+ const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
861
999
  if (!immediateRender)
862
1000
  await renderInterception;
863
1001
  await this.renderSnapshot(renderer);
864
1002
  this.delegate.viewRenderedSnapshot(snapshot, isPreview);
1003
+ this.delegate.preloadOnLoadLinksForView(this.element);
865
1004
  this.finishRenderingSnapshot(renderer);
866
1005
  }
867
1006
  finally {
@@ -871,15 +1010,15 @@ class View {
871
1010
  }
872
1011
  }
873
1012
  else {
874
- this.invalidate();
1013
+ this.invalidate(renderer.reloadReason);
875
1014
  }
876
1015
  }
877
- invalidate() {
878
- this.delegate.viewInvalidated();
1016
+ invalidate(reason) {
1017
+ this.delegate.viewInvalidated(reason);
879
1018
  }
880
- prepareToRenderSnapshot(renderer) {
1019
+ async prepareToRenderSnapshot(renderer) {
881
1020
  this.markAsPreview(renderer.isPreview);
882
- renderer.prepareToRender();
1021
+ await renderer.prepareToRender();
883
1022
  }
884
1023
  markAsPreview(isPreview) {
885
1024
  if (isPreview) {
@@ -906,65 +1045,122 @@ class FrameView extends View {
906
1045
  }
907
1046
  }
908
1047
 
909
- class LinkInterceptor {
910
- constructor(delegate, element) {
911
- this.clickBubbled = (event) => {
912
- if (this.respondsToEventTarget(event.target)) {
913
- this.clickEvent = event;
914
- }
915
- else {
916
- delete this.clickEvent;
917
- }
1048
+ class LinkClickObserver {
1049
+ constructor(delegate, eventTarget) {
1050
+ this.started = false;
1051
+ this.clickCaptured = () => {
1052
+ this.eventTarget.removeEventListener("click", this.clickBubbled, false);
1053
+ this.eventTarget.addEventListener("click", this.clickBubbled, false);
918
1054
  };
919
- this.linkClicked = ((event) => {
920
- if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
921
- if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) {
922
- this.clickEvent.preventDefault();
923
- event.preventDefault();
924
- this.delegate.linkClickIntercepted(event.target, event.detail.url);
1055
+ this.clickBubbled = (event) => {
1056
+ if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
1057
+ const target = (event.composedPath && event.composedPath()[0]) || event.target;
1058
+ const link = this.findLinkFromClickTarget(target);
1059
+ if (link && doesNotTargetIFrame(link)) {
1060
+ const location = this.getLocationForLink(link);
1061
+ if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1062
+ event.preventDefault();
1063
+ this.delegate.followedLinkToLocation(link, location);
1064
+ }
925
1065
  }
926
1066
  }
927
- delete this.clickEvent;
928
- });
929
- this.willVisit = () => {
930
- delete this.clickEvent;
931
1067
  };
932
1068
  this.delegate = delegate;
933
- this.element = element;
1069
+ this.eventTarget = eventTarget;
1070
+ }
1071
+ start() {
1072
+ if (!this.started) {
1073
+ this.eventTarget.addEventListener("click", this.clickCaptured, true);
1074
+ this.started = true;
1075
+ }
1076
+ }
1077
+ stop() {
1078
+ if (this.started) {
1079
+ this.eventTarget.removeEventListener("click", this.clickCaptured, true);
1080
+ this.started = false;
1081
+ }
1082
+ }
1083
+ clickEventIsSignificant(event) {
1084
+ return !((event.target && event.target.isContentEditable) ||
1085
+ event.defaultPrevented ||
1086
+ event.which > 1 ||
1087
+ event.altKey ||
1088
+ event.ctrlKey ||
1089
+ event.metaKey ||
1090
+ event.shiftKey);
1091
+ }
1092
+ findLinkFromClickTarget(target) {
1093
+ if (target instanceof Element) {
1094
+ return target.closest("a[href]:not([target^=_]):not([download])");
1095
+ }
1096
+ }
1097
+ getLocationForLink(link) {
1098
+ return expandURL(link.getAttribute("href") || "");
1099
+ }
1100
+ }
1101
+ function doesNotTargetIFrame(anchor) {
1102
+ for (const element of document.getElementsByName(anchor.target)) {
1103
+ if (element instanceof HTMLIFrameElement)
1104
+ return false;
1105
+ }
1106
+ return true;
1107
+ }
1108
+
1109
+ class FormLinkClickObserver {
1110
+ constructor(delegate, element) {
1111
+ this.delegate = delegate;
1112
+ this.linkClickObserver = new LinkClickObserver(this, element);
934
1113
  }
935
1114
  start() {
936
- this.element.addEventListener("click", this.clickBubbled);
937
- document.addEventListener("turbo:click", this.linkClicked);
938
- document.addEventListener("turbo:before-visit", this.willVisit);
1115
+ this.linkClickObserver.start();
939
1116
  }
940
1117
  stop() {
941
- this.element.removeEventListener("click", this.clickBubbled);
942
- document.removeEventListener("turbo:click", this.linkClicked);
943
- document.removeEventListener("turbo:before-visit", this.willVisit);
1118
+ this.linkClickObserver.stop();
1119
+ }
1120
+ willFollowLinkToLocation(link, location, originalEvent) {
1121
+ return (this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) &&
1122
+ link.hasAttribute("data-turbo-method"));
944
1123
  }
945
- respondsToEventTarget(target) {
946
- const element = target instanceof Element
947
- ? target
948
- : target instanceof Node
949
- ? target.parentElement
950
- : null;
951
- return element && element.closest("turbo-frame, html") == this.element;
1124
+ followedLinkToLocation(link, location) {
1125
+ const action = location.href;
1126
+ const form = document.createElement("form");
1127
+ form.setAttribute("data-turbo", "true");
1128
+ form.setAttribute("action", action);
1129
+ form.setAttribute("hidden", "");
1130
+ const method = link.getAttribute("data-turbo-method");
1131
+ if (method)
1132
+ form.setAttribute("method", method);
1133
+ const turboFrame = link.getAttribute("data-turbo-frame");
1134
+ if (turboFrame)
1135
+ form.setAttribute("data-turbo-frame", turboFrame);
1136
+ const turboConfirm = link.getAttribute("data-turbo-confirm");
1137
+ if (turboConfirm)
1138
+ form.setAttribute("data-turbo-confirm", turboConfirm);
1139
+ const turboStream = link.hasAttribute("data-turbo-stream");
1140
+ if (turboStream)
1141
+ form.setAttribute("data-turbo-stream", "");
1142
+ this.delegate.submittedFormLinkToLocation(link, location, form);
1143
+ document.body.appendChild(form);
1144
+ form.requestSubmit();
1145
+ form.remove();
952
1146
  }
953
1147
  }
954
1148
 
955
1149
  class Bardo {
956
- constructor(permanentElementMap) {
1150
+ constructor(delegate, permanentElementMap) {
1151
+ this.delegate = delegate;
957
1152
  this.permanentElementMap = permanentElementMap;
958
1153
  }
959
- static preservingPermanentElements(permanentElementMap, callback) {
960
- const bardo = new this(permanentElementMap);
1154
+ static preservingPermanentElements(delegate, permanentElementMap, callback) {
1155
+ const bardo = new this(delegate, permanentElementMap);
961
1156
  bardo.enter();
962
1157
  callback();
963
1158
  bardo.leave();
964
1159
  }
965
1160
  enter() {
966
1161
  for (const id in this.permanentElementMap) {
967
- const [, newPermanentElement] = this.permanentElementMap[id];
1162
+ const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
1163
+ this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
968
1164
  this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
969
1165
  }
970
1166
  }
@@ -973,6 +1169,7 @@ class Bardo {
973
1169
  const [currentPermanentElement] = this.permanentElementMap[id];
974
1170
  this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
975
1171
  this.replacePlaceholderWithPermanentElement(currentPermanentElement);
1172
+ this.delegate.leavingBardo(currentPermanentElement);
976
1173
  }
977
1174
  }
978
1175
  replaceNewPermanentElementWithPlaceholder(permanentElement) {
@@ -988,7 +1185,7 @@ class Bardo {
988
1185
  placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
989
1186
  }
990
1187
  getPlaceholderById(id) {
991
- return this.placeholders.find(element => element.content == id);
1188
+ return this.placeholders.find((element) => element.content == id);
992
1189
  }
993
1190
  get placeholders() {
994
1191
  return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")];
@@ -1002,16 +1199,21 @@ function createPlaceholderForPermanentElement(permanentElement) {
1002
1199
  }
1003
1200
 
1004
1201
  class Renderer {
1005
- constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
1202
+ constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1203
+ this.activeElement = null;
1006
1204
  this.currentSnapshot = currentSnapshot;
1007
1205
  this.newSnapshot = newSnapshot;
1008
1206
  this.isPreview = isPreview;
1009
1207
  this.willRender = willRender;
1010
- this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
1208
+ this.renderElement = renderElement;
1209
+ this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));
1011
1210
  }
1012
1211
  get shouldRender() {
1013
1212
  return true;
1014
1213
  }
1214
+ get reloadReason() {
1215
+ return;
1216
+ }
1015
1217
  prepareToRender() {
1016
1218
  return;
1017
1219
  }
@@ -1021,23 +1223,8 @@ class Renderer {
1021
1223
  delete this.resolvingFunctions;
1022
1224
  }
1023
1225
  }
1024
- createScriptElement(element) {
1025
- if (element.getAttribute("data-turbo-eval") == "false") {
1026
- return element;
1027
- }
1028
- else {
1029
- const createdScriptElement = document.createElement("script");
1030
- if (this.cspNonce) {
1031
- createdScriptElement.nonce = this.cspNonce;
1032
- }
1033
- createdScriptElement.textContent = element.textContent;
1034
- createdScriptElement.async = false;
1035
- copyElementAttributes(createdScriptElement, element);
1036
- return createdScriptElement;
1037
- }
1038
- }
1039
1226
  preservingPermanentElements(callback) {
1040
- Bardo.preservingPermanentElements(this.permanentElementMap, callback);
1227
+ Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1041
1228
  }
1042
1229
  focusFirstAutofocusableElement() {
1043
1230
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1045,6 +1232,19 @@ class Renderer {
1045
1232
  element.focus();
1046
1233
  }
1047
1234
  }
1235
+ enteringBardo(currentPermanentElement) {
1236
+ if (this.activeElement)
1237
+ return;
1238
+ if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
1239
+ this.activeElement = this.currentSnapshot.activeElement;
1240
+ }
1241
+ }
1242
+ leavingBardo(currentPermanentElement) {
1243
+ if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
1244
+ this.activeElement.focus();
1245
+ this.activeElement = null;
1246
+ }
1247
+ }
1048
1248
  get connectedSnapshot() {
1049
1249
  return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
1050
1250
  }
@@ -1057,21 +1257,28 @@ class Renderer {
1057
1257
  get permanentElementMap() {
1058
1258
  return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1059
1259
  }
1060
- get cspNonce() {
1061
- var _a;
1062
- return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
1063
- }
1064
- }
1065
- function copyElementAttributes(destinationElement, sourceElement) {
1066
- for (const { name, value } of [...sourceElement.attributes]) {
1067
- destinationElement.setAttribute(name, value);
1068
- }
1069
1260
  }
1070
1261
  function elementIsFocusable(element) {
1071
1262
  return element && typeof element.focus == "function";
1072
1263
  }
1073
1264
 
1074
1265
  class FrameRenderer extends Renderer {
1266
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1267
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1268
+ this.delegate = delegate;
1269
+ }
1270
+ static renderElement(currentElement, newElement) {
1271
+ var _a;
1272
+ const destinationRange = document.createRange();
1273
+ destinationRange.selectNodeContents(currentElement);
1274
+ destinationRange.deleteContents();
1275
+ const frameElement = newElement;
1276
+ const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1277
+ if (sourceRange) {
1278
+ sourceRange.selectNodeContents(frameElement);
1279
+ currentElement.appendChild(sourceRange.extractContents());
1280
+ }
1281
+ }
1075
1282
  get shouldRender() {
1076
1283
  return true;
1077
1284
  }
@@ -1087,23 +1294,16 @@ class FrameRenderer extends Renderer {
1087
1294
  this.activateScriptElements();
1088
1295
  }
1089
1296
  loadFrameElement() {
1090
- var _a;
1091
- const destinationRange = document.createRange();
1092
- destinationRange.selectNodeContents(this.currentElement);
1093
- destinationRange.deleteContents();
1094
- const frameElement = this.newElement;
1095
- const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1096
- if (sourceRange) {
1097
- sourceRange.selectNodeContents(frameElement);
1098
- this.currentElement.appendChild(sourceRange.extractContents());
1099
- }
1297
+ this.delegate.willRenderFrame(this.currentElement, this.newElement);
1298
+ this.renderElement(this.currentElement, this.newElement);
1100
1299
  }
1101
1300
  scrollFrameIntoView() {
1102
1301
  if (this.currentElement.autoscroll || this.newElement.autoscroll) {
1103
1302
  const element = this.currentElement.firstElementChild;
1104
1303
  const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
1304
+ const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
1105
1305
  if (element) {
1106
- element.scrollIntoView({ block });
1306
+ element.scrollIntoView({ block, behavior });
1107
1307
  return true;
1108
1308
  }
1109
1309
  }
@@ -1111,7 +1311,7 @@ class FrameRenderer extends Renderer {
1111
1311
  }
1112
1312
  activateScriptElements() {
1113
1313
  for (const inertScriptElement of this.newScriptElements) {
1114
- const activatedScriptElement = this.createScriptElement(inertScriptElement);
1314
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
1115
1315
  inertScriptElement.replaceWith(activatedScriptElement);
1116
1316
  }
1117
1317
  }
@@ -1127,6 +1327,14 @@ function readScrollLogicalPosition(value, defaultValue) {
1127
1327
  return defaultValue;
1128
1328
  }
1129
1329
  }
1330
+ function readScrollBehavior(value, defaultValue) {
1331
+ if (value == "auto" || value == "smooth") {
1332
+ return value;
1333
+ }
1334
+ else {
1335
+ return defaultValue;
1336
+ }
1337
+ }
1130
1338
 
1131
1339
  class ProgressBar {
1132
1340
  constructor() {
@@ -1150,7 +1358,7 @@ class ProgressBar {
1150
1358
  left: 0;
1151
1359
  height: 3px;
1152
1360
  background: #0076ff;
1153
- z-index: 9999;
1361
+ z-index: 2147483647;
1154
1362
  transition:
1155
1363
  width ${ProgressBar.animationDuration}ms ease-out,
1156
1364
  opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
@@ -1209,13 +1417,16 @@ class ProgressBar {
1209
1417
  }
1210
1418
  refresh() {
1211
1419
  requestAnimationFrame(() => {
1212
- this.progressElement.style.width = `${10 + (this.value * 90)}%`;
1420
+ this.progressElement.style.width = `${10 + this.value * 90}%`;
1213
1421
  });
1214
1422
  }
1215
1423
  createStylesheetElement() {
1216
1424
  const element = document.createElement("style");
1217
1425
  element.type = "text/css";
1218
1426
  element.textContent = ProgressBar.defaultCSS;
1427
+ if (this.cspNonce) {
1428
+ element.nonce = this.cspNonce;
1429
+ }
1219
1430
  return element;
1220
1431
  }
1221
1432
  createProgressElement() {
@@ -1223,6 +1434,9 @@ class ProgressBar {
1223
1434
  element.className = "turbo-progress-bar";
1224
1435
  return element;
1225
1436
  }
1437
+ get cspNonce() {
1438
+ return getMetaContent("csp-nonce");
1439
+ }
1226
1440
  }
1227
1441
  ProgressBar.animationDuration = 300;
1228
1442
 
@@ -1239,14 +1453,14 @@ class HeadSnapshot extends Snapshot {
1239
1453
  : {
1240
1454
  type: elementType(element),
1241
1455
  tracked: elementIsTracked(element),
1242
- elements: []
1456
+ elements: [],
1243
1457
  };
1244
1458
  return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });
1245
1459
  }, {});
1246
1460
  }
1247
1461
  get trackedElementSignature() {
1248
1462
  return Object.keys(this.detailsByOuterHTML)
1249
- .filter(outerHTML => this.detailsByOuterHTML[outerHTML].tracked)
1463
+ .filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked)
1250
1464
  .join("");
1251
1465
  }
1252
1466
  getScriptElementsNotInSnapshot(snapshot) {
@@ -1257,8 +1471,8 @@ class HeadSnapshot extends Snapshot {
1257
1471
  }
1258
1472
  getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
1259
1473
  return Object.keys(this.detailsByOuterHTML)
1260
- .filter(outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))
1261
- .map(outerHTML => this.detailsByOuterHTML[outerHTML])
1474
+ .filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML))
1475
+ .map((outerHTML) => this.detailsByOuterHTML[outerHTML])
1262
1476
  .filter(({ type }) => type == matchedType)
1263
1477
  .map(({ elements: [element] }) => element);
1264
1478
  }
@@ -1278,13 +1492,11 @@ class HeadSnapshot extends Snapshot {
1278
1492
  }
1279
1493
  getMetaValue(name) {
1280
1494
  const element = this.findMetaElementByName(name);
1281
- return element
1282
- ? element.getAttribute("content")
1283
- : null;
1495
+ return element ? element.getAttribute("content") : null;
1284
1496
  }
1285
1497
  findMetaElementByName(name) {
1286
1498
  return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
1287
- const { elements: [element] } = this.detailsByOuterHTML[outerHTML];
1499
+ const { elements: [element], } = this.detailsByOuterHTML[outerHTML];
1288
1500
  return elementIsMetaElementWithName(element, name) ? element : result;
1289
1501
  }, undefined);
1290
1502
  }
@@ -1385,6 +1597,9 @@ const defaultOptions = {
1385
1597
  historyChanged: false,
1386
1598
  visitCachedSnapshot: () => { },
1387
1599
  willRender: true,
1600
+ updateHistory: true,
1601
+ shouldCacheSnapshot: true,
1602
+ acceptsStreamResponse: false,
1388
1603
  };
1389
1604
  var SystemStatusCode;
1390
1605
  (function (SystemStatusCode) {
@@ -1399,12 +1614,15 @@ class Visit {
1399
1614
  this.followedRedirect = false;
1400
1615
  this.historyChanged = false;
1401
1616
  this.scrolled = false;
1617
+ this.shouldCacheSnapshot = true;
1618
+ this.acceptsStreamResponse = false;
1402
1619
  this.snapshotCached = false;
1403
1620
  this.state = VisitState.initialized;
1404
1621
  this.delegate = delegate;
1405
1622
  this.location = location;
1406
1623
  this.restorationIdentifier = restorationIdentifier || uuid();
1407
- const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender } = Object.assign(Object.assign({}, defaultOptions), options);
1624
+ this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));
1625
+ const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, } = Object.assign(Object.assign({}, defaultOptions), options);
1408
1626
  this.action = action;
1409
1627
  this.historyChanged = historyChanged;
1410
1628
  this.referrer = referrer;
@@ -1413,7 +1631,10 @@ class Visit {
1413
1631
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
1414
1632
  this.visitCachedSnapshot = visitCachedSnapshot;
1415
1633
  this.willRender = willRender;
1634
+ this.updateHistory = updateHistory;
1416
1635
  this.scrolled = !willRender;
1636
+ this.shouldCacheSnapshot = shouldCacheSnapshot;
1637
+ this.acceptsStreamResponse = acceptsStreamResponse;
1417
1638
  }
1418
1639
  get adapter() {
1419
1640
  return this.delegate.adapter;
@@ -1445,28 +1666,33 @@ class Visit {
1445
1666
  }
1446
1667
  this.cancelRender();
1447
1668
  this.state = VisitState.canceled;
1669
+ this.resolvingFunctions.reject();
1448
1670
  }
1449
1671
  }
1450
1672
  complete() {
1451
1673
  if (this.state == VisitState.started) {
1452
1674
  this.recordTimingMetric(TimingMetric.visitEnd);
1453
1675
  this.state = VisitState.completed;
1454
- this.adapter.visitCompleted(this);
1455
- this.delegate.visitCompleted(this);
1456
1676
  this.followRedirect();
1677
+ if (!this.followedRedirect) {
1678
+ this.adapter.visitCompleted(this);
1679
+ this.delegate.visitCompleted(this);
1680
+ }
1681
+ this.resolvingFunctions.resolve();
1457
1682
  }
1458
1683
  }
1459
1684
  fail() {
1460
1685
  if (this.state == VisitState.started) {
1461
1686
  this.state = VisitState.failed;
1462
1687
  this.adapter.visitFailed(this);
1688
+ this.resolvingFunctions.reject();
1463
1689
  }
1464
1690
  }
1465
1691
  changeHistory() {
1466
1692
  var _a;
1467
- if (!this.historyChanged) {
1693
+ if (!this.historyChanged && this.updateHistory) {
1468
1694
  const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
1469
- const method = this.getHistoryMethodForAction(actionForHistory);
1695
+ const method = getHistoryMethodForAction(actionForHistory);
1470
1696
  this.history.update(method, this.location, this.restorationIdentifier);
1471
1697
  this.historyChanged = true;
1472
1698
  }
@@ -1511,16 +1737,18 @@ class Visit {
1511
1737
  if (this.response) {
1512
1738
  const { statusCode, responseHTML } = this.response;
1513
1739
  this.render(async () => {
1514
- this.cacheSnapshot();
1740
+ if (this.shouldCacheSnapshot)
1741
+ this.cacheSnapshot();
1515
1742
  if (this.view.renderPromise)
1516
1743
  await this.view.renderPromise;
1517
1744
  if (isSuccessful(statusCode) && responseHTML != null) {
1518
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
1745
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
1746
+ this.performScroll();
1519
1747
  this.adapter.visitRendered(this);
1520
1748
  this.complete();
1521
1749
  }
1522
1750
  else {
1523
- await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
1751
+ await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
1524
1752
  this.adapter.visitRendered(this);
1525
1753
  this.fail();
1526
1754
  }
@@ -1555,7 +1783,8 @@ class Visit {
1555
1783
  else {
1556
1784
  if (this.view.renderPromise)
1557
1785
  await this.view.renderPromise;
1558
- await this.view.renderPage(snapshot, isPreview, this.willRender);
1786
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
1787
+ this.performScroll();
1559
1788
  this.adapter.visitRendered(this);
1560
1789
  if (!isPreview) {
1561
1790
  this.complete();
@@ -1568,8 +1797,9 @@ class Visit {
1568
1797
  var _a;
1569
1798
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1570
1799
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1571
- action: 'replace',
1572
- response: this.response
1800
+ action: "replace",
1801
+ willRender: false,
1802
+ response: this.response,
1573
1803
  });
1574
1804
  this.followedRedirect = true;
1575
1805
  }
@@ -1578,20 +1808,28 @@ class Visit {
1578
1808
  if (this.isSamePage) {
1579
1809
  this.render(async () => {
1580
1810
  this.cacheSnapshot();
1811
+ this.performScroll();
1581
1812
  this.adapter.visitRendered(this);
1582
1813
  });
1583
1814
  }
1584
1815
  }
1816
+ prepareHeadersForRequest(headers, request) {
1817
+ if (this.acceptsStreamResponse) {
1818
+ request.acceptResponseType(StreamMessage.contentType);
1819
+ }
1820
+ }
1585
1821
  requestStarted() {
1586
1822
  this.startRequest();
1587
1823
  }
1588
- requestPreventedHandlingResponse(request, response) {
1589
- }
1824
+ requestPreventedHandlingResponse(_request, _response) { }
1590
1825
  async requestSucceededWithResponse(request, response) {
1591
1826
  const responseHTML = await response.responseHTML;
1592
1827
  const { redirected, statusCode } = response;
1593
1828
  if (responseHTML == undefined) {
1594
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1829
+ this.recordResponse({
1830
+ statusCode: SystemStatusCode.contentTypeMismatch,
1831
+ redirected,
1832
+ });
1595
1833
  }
1596
1834
  else {
1597
1835
  this.redirectedToLocation = response.redirected ? response.location : undefined;
@@ -1602,20 +1840,26 @@ class Visit {
1602
1840
  const responseHTML = await response.responseHTML;
1603
1841
  const { redirected, statusCode } = response;
1604
1842
  if (responseHTML == undefined) {
1605
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1843
+ this.recordResponse({
1844
+ statusCode: SystemStatusCode.contentTypeMismatch,
1845
+ redirected,
1846
+ });
1606
1847
  }
1607
1848
  else {
1608
1849
  this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
1609
1850
  }
1610
1851
  }
1611
- requestErrored(request, error) {
1612
- this.recordResponse({ statusCode: SystemStatusCode.networkFailure, redirected: false });
1852
+ requestErrored(_request, _error) {
1853
+ this.recordResponse({
1854
+ statusCode: SystemStatusCode.networkFailure,
1855
+ redirected: false,
1856
+ });
1613
1857
  }
1614
1858
  requestFinished() {
1615
1859
  this.finishRequest();
1616
1860
  }
1617
1861
  performScroll() {
1618
- if (!this.scrolled) {
1862
+ if (!this.scrolled && !this.view.forceReloaded) {
1619
1863
  if (this.action == "restore") {
1620
1864
  this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
1621
1865
  }
@@ -1650,9 +1894,11 @@ class Visit {
1650
1894
  }
1651
1895
  getHistoryMethodForAction(action) {
1652
1896
  switch (action) {
1653
- case "replace": return history.replaceState;
1897
+ case "replace":
1898
+ return history.replaceState;
1654
1899
  case "advance":
1655
- case "restore": return history.pushState;
1900
+ case "restore":
1901
+ return history.pushState;
1656
1902
  }
1657
1903
  }
1658
1904
  hasPreloadedResponse() {
@@ -1671,18 +1917,17 @@ class Visit {
1671
1917
  }
1672
1918
  cacheSnapshot() {
1673
1919
  if (!this.snapshotCached) {
1674
- this.view.cacheSnapshot().then(snapshot => snapshot && this.visitCachedSnapshot(snapshot));
1920
+ this.view.cacheSnapshot().then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot));
1675
1921
  this.snapshotCached = true;
1676
1922
  }
1677
1923
  }
1678
1924
  async render(callback) {
1679
1925
  this.cancelRender();
1680
- await new Promise(resolve => {
1926
+ await new Promise((resolve) => {
1681
1927
  this.frame = requestAnimationFrame(() => resolve());
1682
1928
  });
1683
1929
  await callback();
1684
1930
  delete this.frame;
1685
- this.performScroll();
1686
1931
  }
1687
1932
  cancelRender() {
1688
1933
  if (this.frame) {
@@ -1697,19 +1942,19 @@ function isSuccessful(statusCode) {
1697
1942
 
1698
1943
  class BrowserAdapter {
1699
1944
  constructor(session) {
1700
- this.progressBar = new ProgressBar;
1945
+ this.progressBar = new ProgressBar();
1701
1946
  this.showProgressBar = () => {
1702
1947
  this.progressBar.show();
1703
1948
  };
1704
1949
  this.session = session;
1705
1950
  }
1706
1951
  visitProposedToLocation(location, options) {
1707
- this.navigator.startVisit(location, uuid(), options);
1952
+ return this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
1708
1953
  }
1709
1954
  visitStarted(visit) {
1955
+ this.location = visit.location;
1710
1956
  visit.loadCachedSnapshot();
1711
1957
  visit.issueRequest();
1712
- visit.changeHistory();
1713
1958
  visit.goToSamePageAnchor();
1714
1959
  }
1715
1960
  visitRequestStarted(visit) {
@@ -1729,29 +1974,31 @@ class BrowserAdapter {
1729
1974
  case SystemStatusCode.networkFailure:
1730
1975
  case SystemStatusCode.timeoutFailure:
1731
1976
  case SystemStatusCode.contentTypeMismatch:
1732
- return this.reload();
1977
+ return this.reload({
1978
+ reason: "request_failed",
1979
+ context: {
1980
+ statusCode,
1981
+ },
1982
+ });
1733
1983
  default:
1734
1984
  return visit.loadResponse();
1735
1985
  }
1736
1986
  }
1737
- visitRequestFinished(visit) {
1987
+ visitRequestFinished(_visit) {
1738
1988
  this.progressBar.setValue(1);
1739
1989
  this.hideVisitProgressBar();
1740
1990
  }
1741
- visitCompleted(visit) {
1742
- }
1743
- pageInvalidated() {
1744
- this.reload();
1745
- }
1746
- visitFailed(visit) {
1991
+ visitCompleted(_visit) { }
1992
+ pageInvalidated(reason) {
1993
+ this.reload(reason);
1747
1994
  }
1748
- visitRendered(visit) {
1749
- }
1750
- formSubmissionStarted(formSubmission) {
1995
+ visitFailed(_visit) { }
1996
+ visitRendered(_visit) { }
1997
+ formSubmissionStarted(_formSubmission) {
1751
1998
  this.progressBar.setValue(0);
1752
1999
  this.showFormProgressBarAfterDelay();
1753
2000
  }
1754
- formSubmissionFinished(formSubmission) {
2001
+ formSubmissionFinished(_formSubmission) {
1755
2002
  this.progressBar.setValue(1);
1756
2003
  this.hideFormProgressBar();
1757
2004
  }
@@ -1777,8 +2024,11 @@ class BrowserAdapter {
1777
2024
  delete this.formProgressBarTimeout;
1778
2025
  }
1779
2026
  }
1780
- reload() {
1781
- window.location.reload();
2027
+ reload(reason) {
2028
+ dispatch("turbo:reload", { detail: reason });
2029
+ if (!this.location)
2030
+ return;
2031
+ window.location.href = this.location.toString();
1782
2032
  }
1783
2033
  get navigator() {
1784
2034
  return this.session.navigator;
@@ -1788,94 +2038,60 @@ class BrowserAdapter {
1788
2038
  class CacheObserver {
1789
2039
  constructor() {
1790
2040
  this.started = false;
1791
- }
1792
- start() {
1793
- if (!this.started) {
1794
- this.started = true;
1795
- addEventListener("turbo:before-cache", this.removeStaleElements, false);
1796
- }
1797
- }
1798
- stop() {
1799
- if (this.started) {
1800
- this.started = false;
1801
- removeEventListener("turbo:before-cache", this.removeStaleElements, false);
1802
- }
1803
- }
1804
- removeStaleElements() {
1805
- const staleElements = [...document.querySelectorAll('[data-turbo-cache="false"]')];
1806
- for (const element of staleElements) {
1807
- element.remove();
1808
- }
1809
- }
1810
- }
1811
-
1812
- class FormSubmitObserver {
1813
- constructor(delegate) {
1814
- this.started = false;
1815
- this.submitCaptured = () => {
1816
- removeEventListener("submit", this.submitBubbled, false);
1817
- addEventListener("submit", this.submitBubbled, false);
1818
- };
1819
- this.submitBubbled = ((event) => {
1820
- if (!event.defaultPrevented) {
1821
- const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1822
- const submitter = event.submitter || undefined;
1823
- if (form) {
1824
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
1825
- if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
1826
- event.preventDefault();
1827
- this.delegate.formSubmitted(form, submitter);
1828
- }
1829
- }
2041
+ this.removeStaleElements = ((_event) => {
2042
+ const staleElements = [...document.querySelectorAll('[data-turbo-cache="false"]')];
2043
+ for (const element of staleElements) {
2044
+ element.remove();
1830
2045
  }
1831
2046
  });
1832
- this.delegate = delegate;
1833
2047
  }
1834
2048
  start() {
1835
2049
  if (!this.started) {
1836
- addEventListener("submit", this.submitCaptured, true);
1837
2050
  this.started = true;
2051
+ addEventListener("turbo:before-cache", this.removeStaleElements, false);
1838
2052
  }
1839
2053
  }
1840
2054
  stop() {
1841
2055
  if (this.started) {
1842
- removeEventListener("submit", this.submitCaptured, true);
1843
2056
  this.started = false;
2057
+ removeEventListener("turbo:before-cache", this.removeStaleElements, false);
1844
2058
  }
1845
2059
  }
1846
2060
  }
1847
2061
 
1848
2062
  class FrameRedirector {
1849
- constructor(element) {
2063
+ constructor(session, element) {
2064
+ this.session = session;
1850
2065
  this.element = element;
1851
- this.linkInterceptor = new LinkInterceptor(this, element);
1852
- this.formInterceptor = new FormInterceptor(this, element);
2066
+ this.linkClickObserver = new LinkClickObserver(this, element);
2067
+ this.formSubmitObserver = new FormSubmitObserver(this, element);
1853
2068
  }
1854
2069
  start() {
1855
- this.linkInterceptor.start();
1856
- this.formInterceptor.start();
2070
+ this.linkClickObserver.start();
2071
+ this.formSubmitObserver.start();
1857
2072
  }
1858
2073
  stop() {
1859
- this.linkInterceptor.stop();
1860
- this.formInterceptor.stop();
2074
+ this.linkClickObserver.stop();
2075
+ this.formSubmitObserver.stop();
1861
2076
  }
1862
- shouldInterceptLinkClick(element, url) {
2077
+ willFollowLinkToLocation(element) {
1863
2078
  return this.shouldRedirect(element);
1864
2079
  }
1865
- linkClickIntercepted(element, url) {
2080
+ followedLinkToLocation(element, url) {
1866
2081
  const frame = this.findFrameElement(element);
1867
2082
  if (frame) {
1868
- frame.delegate.linkClickIntercepted(element, url);
2083
+ frame.delegate.followedLinkToLocation(element, url);
1869
2084
  }
1870
2085
  }
1871
- shouldInterceptFormSubmission(element, submitter) {
1872
- return this.shouldSubmit(element, submitter);
2086
+ willSubmitForm(element, submitter) {
2087
+ return (element.closest("turbo-frame") == null &&
2088
+ this.shouldSubmit(element, submitter) &&
2089
+ this.shouldRedirect(element, submitter));
1873
2090
  }
1874
- formSubmissionIntercepted(element, submitter) {
2091
+ formSubmitted(element, submitter) {
1875
2092
  const frame = this.findFrameElement(element, submitter);
1876
2093
  if (frame) {
1877
- frame.removeAttribute("reloadable");
1878
- frame.delegate.formSubmissionIntercepted(element, submitter);
2094
+ frame.delegate.formSubmitted(element, submitter);
1879
2095
  }
1880
2096
  }
1881
2097
  shouldSubmit(form, submitter) {
@@ -1886,8 +2102,16 @@ class FrameRedirector {
1886
2102
  return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
1887
2103
  }
1888
2104
  shouldRedirect(element, submitter) {
1889
- const frame = this.findFrameElement(element, submitter);
1890
- return frame ? frame != element.closest("turbo-frame") : false;
2105
+ const isNavigatable = element instanceof HTMLFormElement
2106
+ ? this.session.submissionIsNavigatable(element, submitter)
2107
+ : this.session.elementIsNavigatable(element);
2108
+ if (isNavigatable) {
2109
+ const frame = this.findFrameElement(element, submitter);
2110
+ return frame ? frame != element.closest("turbo-frame") : false;
2111
+ }
2112
+ else {
2113
+ return false;
2114
+ }
1891
2115
  }
1892
2116
  findFrameElement(element, submitter) {
1893
2117
  const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
@@ -1917,7 +2141,7 @@ class History {
1917
2141
  }
1918
2142
  }
1919
2143
  };
1920
- this.onPageLoad = async (event) => {
2144
+ this.onPageLoad = async (_event) => {
1921
2145
  await nextMicrotask();
1922
2146
  this.pageLoaded = true;
1923
2147
  };
@@ -1979,81 +2203,30 @@ class History {
1979
2203
  }
1980
2204
  }
1981
2205
 
1982
- class LinkClickObserver {
1983
- constructor(delegate) {
1984
- this.started = false;
1985
- this.clickCaptured = () => {
1986
- removeEventListener("click", this.clickBubbled, false);
1987
- addEventListener("click", this.clickBubbled, false);
1988
- };
1989
- this.clickBubbled = (event) => {
1990
- if (this.clickEventIsSignificant(event)) {
1991
- const target = (event.composedPath && event.composedPath()[0]) || event.target;
1992
- const link = this.findLinkFromClickTarget(target);
1993
- if (link) {
1994
- const location = this.getLocationForLink(link);
1995
- if (this.delegate.willFollowLinkToLocation(link, location)) {
1996
- event.preventDefault();
1997
- this.delegate.followedLinkToLocation(link, location);
1998
- }
1999
- }
2000
- }
2001
- };
2002
- this.delegate = delegate;
2003
- }
2004
- start() {
2005
- if (!this.started) {
2006
- addEventListener("click", this.clickCaptured, true);
2007
- this.started = true;
2008
- }
2009
- }
2010
- stop() {
2011
- if (this.started) {
2012
- removeEventListener("click", this.clickCaptured, true);
2013
- this.started = false;
2014
- }
2015
- }
2016
- clickEventIsSignificant(event) {
2017
- return !((event.target && event.target.isContentEditable)
2018
- || event.defaultPrevented
2019
- || event.which > 1
2020
- || event.altKey
2021
- || event.ctrlKey
2022
- || event.metaKey
2023
- || event.shiftKey);
2024
- }
2025
- findLinkFromClickTarget(target) {
2026
- if (target instanceof Element) {
2027
- return target.closest("a[href]:not([target^=_]):not([download])");
2028
- }
2029
- }
2030
- getLocationForLink(link) {
2031
- return expandURL(link.getAttribute("href") || "");
2032
- }
2033
- }
2034
-
2035
- function isAction(action) {
2036
- return action == "advance" || action == "replace" || action == "restore";
2037
- }
2038
-
2039
- class Navigator {
2206
+ class Navigator {
2040
2207
  constructor(delegate) {
2041
2208
  this.delegate = delegate;
2042
2209
  }
2043
2210
  proposeVisit(location, options = {}) {
2044
2211
  if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
2045
2212
  if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
2046
- this.delegate.visitProposedToLocation(location, options);
2213
+ return this.delegate.visitProposedToLocation(location, options);
2047
2214
  }
2048
2215
  else {
2049
2216
  window.location.href = location.toString();
2217
+ return Promise.resolve();
2050
2218
  }
2051
2219
  }
2220
+ else {
2221
+ return Promise.reject();
2222
+ }
2052
2223
  }
2053
2224
  startVisit(locatable, restorationIdentifier, options = {}) {
2225
+ this.lastVisit = this.currentVisit;
2054
2226
  this.stop();
2055
2227
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));
2056
2228
  this.currentVisit.start();
2229
+ return this.currentVisit.promise;
2057
2230
  }
2058
2231
  submitForm(form, submitter) {
2059
2232
  this.stop();
@@ -2080,7 +2253,7 @@ class Navigator {
2080
2253
  return this.delegate.history;
2081
2254
  }
2082
2255
  formSubmissionStarted(formSubmission) {
2083
- if (typeof this.adapter.formSubmissionStarted === 'function') {
2256
+ if (typeof this.adapter.formSubmissionStarted === "function") {
2084
2257
  this.adapter.formSubmissionStarted(formSubmission);
2085
2258
  }
2086
2259
  }
@@ -2088,12 +2261,17 @@ class Navigator {
2088
2261
  if (formSubmission == this.formSubmission) {
2089
2262
  const responseHTML = await fetchResponse.responseHTML;
2090
2263
  if (responseHTML) {
2091
- if (formSubmission.method != FetchMethod.get) {
2264
+ const shouldCacheSnapshot = formSubmission.method == FetchMethod.get;
2265
+ if (!shouldCacheSnapshot) {
2092
2266
  this.view.clearSnapshotCache();
2093
2267
  }
2094
2268
  const { statusCode, redirected } = fetchResponse;
2095
2269
  const action = this.getActionForFormSubmission(formSubmission);
2096
- const visitOptions = { action, response: { statusCode, responseHTML, redirected } };
2270
+ const visitOptions = {
2271
+ action,
2272
+ shouldCacheSnapshot,
2273
+ response: { statusCode, responseHTML, redirected },
2274
+ };
2097
2275
  this.proposeVisit(fetchResponse.location, visitOptions);
2098
2276
  }
2099
2277
  }
@@ -2103,10 +2281,10 @@ class Navigator {
2103
2281
  if (responseHTML) {
2104
2282
  const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2105
2283
  if (fetchResponse.serverError) {
2106
- await this.view.renderError(snapshot);
2284
+ await this.view.renderError(snapshot, this.currentVisit);
2107
2285
  }
2108
2286
  else {
2109
- await this.view.renderPage(snapshot);
2287
+ await this.view.renderPage(snapshot, false, true, this.currentVisit);
2110
2288
  }
2111
2289
  this.view.scrollToTop();
2112
2290
  this.view.clearSnapshotCache();
@@ -2116,7 +2294,7 @@ class Navigator {
2116
2294
  console.error(error);
2117
2295
  }
2118
2296
  formSubmissionFinished(formSubmission) {
2119
- if (typeof this.adapter.formSubmissionFinished === 'function') {
2297
+ if (typeof this.adapter.formSubmissionFinished === "function") {
2120
2298
  this.adapter.formSubmissionFinished(formSubmission);
2121
2299
  }
2122
2300
  }
@@ -2127,12 +2305,14 @@ class Navigator {
2127
2305
  this.delegate.visitCompleted(visit);
2128
2306
  }
2129
2307
  locationWithActionIsSamePage(location, action) {
2308
+ var _a;
2130
2309
  const anchor = getAnchor(location);
2131
- const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2132
- const isRestorationToTop = action === 'restore' && typeof anchor === 'undefined';
2133
- return action !== "replace" &&
2134
- getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&
2135
- (isRestorationToTop || (anchor != null && anchor !== currentAnchor));
2310
+ const lastLocation = ((_a = this.lastVisit) === null || _a === void 0 ? void 0 : _a.location) || this.view.lastRenderedLocation;
2311
+ const currentAnchor = getAnchor(lastLocation);
2312
+ const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2313
+ return (action !== "replace" &&
2314
+ getRequestURL(location) === getRequestURL(lastLocation) &&
2315
+ (isRestorationToTop || (anchor != null && anchor !== currentAnchor)));
2136
2316
  }
2137
2317
  visitScrolledToSamePageLocation(oldURL, newURL) {
2138
2318
  this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
@@ -2238,7 +2418,7 @@ class ScrollObserver {
2238
2418
 
2239
2419
  class StreamObserver {
2240
2420
  constructor(delegate) {
2241
- this.sources = new Set;
2421
+ this.sources = new Set();
2242
2422
  this.started = false;
2243
2423
  this.inspectFetchResponse = ((event) => {
2244
2424
  const response = fetchResponseFromEvent(event);
@@ -2288,7 +2468,7 @@ class StreamObserver {
2288
2468
  }
2289
2469
  }
2290
2470
  receiveMessageHTML(html) {
2291
- this.delegate.receivedMessageFromStream(new StreamMessage(html));
2471
+ this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
2292
2472
  }
2293
2473
  }
2294
2474
  function fetchResponseFromEvent(event) {
@@ -2305,20 +2485,24 @@ function fetchResponseIsStream(response) {
2305
2485
  }
2306
2486
 
2307
2487
  class ErrorRenderer extends Renderer {
2488
+ static renderElement(currentElement, newElement) {
2489
+ const { documentElement, body } = document;
2490
+ documentElement.replaceChild(newElement, body);
2491
+ }
2308
2492
  async render() {
2309
2493
  this.replaceHeadAndBody();
2310
2494
  this.activateScriptElements();
2311
2495
  }
2312
2496
  replaceHeadAndBody() {
2313
- const { documentElement, head, body } = document;
2497
+ const { documentElement, head } = document;
2314
2498
  documentElement.replaceChild(this.newHead, head);
2315
- documentElement.replaceChild(this.newElement, body);
2499
+ this.renderElement(this.currentElement, this.newElement);
2316
2500
  }
2317
2501
  activateScriptElements() {
2318
2502
  for (const replaceableElement of this.scriptElements) {
2319
2503
  const parentNode = replaceableElement.parentNode;
2320
2504
  if (parentNode) {
2321
- const element = this.createScriptElement(replaceableElement);
2505
+ const element = activateScriptElement(replaceableElement);
2322
2506
  parentNode.replaceChild(element, replaceableElement);
2323
2507
  }
2324
2508
  }
@@ -2327,16 +2511,36 @@ class ErrorRenderer extends Renderer {
2327
2511
  return this.newSnapshot.headSnapshot.element;
2328
2512
  }
2329
2513
  get scriptElements() {
2330
- return [...document.documentElement.querySelectorAll("script")];
2514
+ return document.documentElement.querySelectorAll("script");
2331
2515
  }
2332
2516
  }
2333
2517
 
2334
2518
  class PageRenderer extends Renderer {
2519
+ static renderElement(currentElement, newElement) {
2520
+ if (document.body && newElement instanceof HTMLBodyElement) {
2521
+ document.body.replaceWith(newElement);
2522
+ }
2523
+ else {
2524
+ document.documentElement.appendChild(newElement);
2525
+ }
2526
+ }
2335
2527
  get shouldRender() {
2336
2528
  return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
2337
2529
  }
2338
- prepareToRender() {
2339
- this.mergeHead();
2530
+ get reloadReason() {
2531
+ if (!this.newSnapshot.isVisitable) {
2532
+ return {
2533
+ reason: "turbo_visit_control_is_reload",
2534
+ };
2535
+ }
2536
+ if (!this.trackedElementsAreIdentical) {
2537
+ return {
2538
+ reason: "tracked_element_mismatch",
2539
+ };
2540
+ }
2541
+ }
2542
+ async prepareToRender() {
2543
+ await this.mergeHead();
2340
2544
  }
2341
2545
  async render() {
2342
2546
  if (this.willRender) {
@@ -2358,11 +2562,12 @@ class PageRenderer extends Renderer {
2358
2562
  get newElement() {
2359
2563
  return this.newSnapshot.element;
2360
2564
  }
2361
- mergeHead() {
2362
- this.copyNewHeadStylesheetElements();
2565
+ async mergeHead() {
2566
+ const newStylesheetElements = this.copyNewHeadStylesheetElements();
2363
2567
  this.copyNewHeadScriptElements();
2364
2568
  this.removeCurrentHeadProvisionalElements();
2365
2569
  this.copyNewHeadProvisionalElements();
2570
+ await newStylesheetElements;
2366
2571
  }
2367
2572
  replaceBody() {
2368
2573
  this.preservingPermanentElements(() => {
@@ -2373,14 +2578,17 @@ class PageRenderer extends Renderer {
2373
2578
  get trackedElementsAreIdentical() {
2374
2579
  return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
2375
2580
  }
2376
- copyNewHeadStylesheetElements() {
2581
+ async copyNewHeadStylesheetElements() {
2582
+ const loadingElements = [];
2377
2583
  for (const element of this.newHeadStylesheetElements) {
2584
+ loadingElements.push(waitForLoad(element));
2378
2585
  document.head.appendChild(element);
2379
2586
  }
2587
+ await Promise.all(loadingElements);
2380
2588
  }
2381
2589
  copyNewHeadScriptElements() {
2382
2590
  for (const element of this.newHeadScriptElements) {
2383
- document.head.appendChild(this.createScriptElement(element));
2591
+ document.head.appendChild(activateScriptElement(element));
2384
2592
  }
2385
2593
  }
2386
2594
  removeCurrentHeadProvisionalElements() {
@@ -2399,17 +2607,12 @@ class PageRenderer extends Renderer {
2399
2607
  }
2400
2608
  activateNewBodyScriptElements() {
2401
2609
  for (const inertScriptElement of this.newBodyScriptElements) {
2402
- const activatedScriptElement = this.createScriptElement(inertScriptElement);
2610
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
2403
2611
  inertScriptElement.replaceWith(activatedScriptElement);
2404
2612
  }
2405
2613
  }
2406
2614
  assignNewBody() {
2407
- if (document.body && this.newElement instanceof HTMLBodyElement) {
2408
- document.body.replaceWith(this.newElement);
2409
- }
2410
- else {
2411
- document.documentElement.appendChild(this.newElement);
2412
- }
2615
+ this.renderElement(this.currentElement, this.newElement);
2413
2616
  }
2414
2617
  get newHeadStylesheetElements() {
2415
2618
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -2478,13 +2681,21 @@ class PageView extends View {
2478
2681
  super(...arguments);
2479
2682
  this.snapshotCache = new SnapshotCache(10);
2480
2683
  this.lastRenderedLocation = new URL(location.href);
2684
+ this.forceReloaded = false;
2481
2685
  }
2482
- renderPage(snapshot, isPreview = false, willRender = true) {
2483
- const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
2686
+ renderPage(snapshot, isPreview = false, willRender = true, visit) {
2687
+ const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
2688
+ if (!renderer.shouldRender) {
2689
+ this.forceReloaded = true;
2690
+ }
2691
+ else {
2692
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2693
+ }
2484
2694
  return this.render(renderer);
2485
2695
  }
2486
- renderError(snapshot) {
2487
- const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2696
+ renderError(snapshot, visit) {
2697
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2698
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
2488
2699
  return this.render(renderer);
2489
2700
  }
2490
2701
  clearSnapshotCache() {
@@ -2511,34 +2722,78 @@ class PageView extends View {
2511
2722
  }
2512
2723
  }
2513
2724
 
2725
+ class Preloader {
2726
+ constructor(delegate) {
2727
+ this.selector = "a[data-turbo-preload]";
2728
+ this.delegate = delegate;
2729
+ }
2730
+ get snapshotCache() {
2731
+ return this.delegate.navigator.view.snapshotCache;
2732
+ }
2733
+ start() {
2734
+ if (document.readyState === "loading") {
2735
+ return document.addEventListener("DOMContentLoaded", () => {
2736
+ this.preloadOnLoadLinksForView(document.body);
2737
+ });
2738
+ }
2739
+ else {
2740
+ this.preloadOnLoadLinksForView(document.body);
2741
+ }
2742
+ }
2743
+ preloadOnLoadLinksForView(element) {
2744
+ for (const link of element.querySelectorAll(this.selector)) {
2745
+ this.preloadURL(link);
2746
+ }
2747
+ }
2748
+ async preloadURL(link) {
2749
+ const location = new URL(link.href);
2750
+ if (this.snapshotCache.has(location)) {
2751
+ return;
2752
+ }
2753
+ try {
2754
+ const response = await fetch(location.toString(), { headers: { "VND.PREFETCH": "true", Accept: "text/html" } });
2755
+ const responseText = await response.text();
2756
+ const snapshot = PageSnapshot.fromHTMLString(responseText);
2757
+ this.snapshotCache.put(location, snapshot);
2758
+ }
2759
+ catch (_) {
2760
+ }
2761
+ }
2762
+ }
2763
+
2514
2764
  class Session {
2515
2765
  constructor() {
2516
2766
  this.navigator = new Navigator(this);
2517
2767
  this.history = new History(this);
2768
+ this.preloader = new Preloader(this);
2518
2769
  this.view = new PageView(this, document.documentElement);
2519
2770
  this.adapter = new BrowserAdapter(this);
2520
2771
  this.pageObserver = new PageObserver(this);
2521
2772
  this.cacheObserver = new CacheObserver();
2522
- this.linkClickObserver = new LinkClickObserver(this);
2523
- this.formSubmitObserver = new FormSubmitObserver(this);
2773
+ this.linkClickObserver = new LinkClickObserver(this, window);
2774
+ this.formSubmitObserver = new FormSubmitObserver(this, document);
2524
2775
  this.scrollObserver = new ScrollObserver(this);
2525
2776
  this.streamObserver = new StreamObserver(this);
2526
- this.frameRedirector = new FrameRedirector(document.documentElement);
2777
+ this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
2778
+ this.frameRedirector = new FrameRedirector(this, document.documentElement);
2527
2779
  this.drive = true;
2528
2780
  this.enabled = true;
2529
2781
  this.progressBarDelay = 500;
2530
2782
  this.started = false;
2783
+ this.formMode = "on";
2531
2784
  }
2532
2785
  start() {
2533
2786
  if (!this.started) {
2534
2787
  this.pageObserver.start();
2535
2788
  this.cacheObserver.start();
2789
+ this.formLinkClickObserver.start();
2536
2790
  this.linkClickObserver.start();
2537
2791
  this.formSubmitObserver.start();
2538
2792
  this.scrollObserver.start();
2539
2793
  this.streamObserver.start();
2540
2794
  this.frameRedirector.start();
2541
2795
  this.history.start();
2796
+ this.preloader.start();
2542
2797
  this.started = true;
2543
2798
  this.enabled = true;
2544
2799
  }
@@ -2550,6 +2805,7 @@ class Session {
2550
2805
  if (this.started) {
2551
2806
  this.pageObserver.stop();
2552
2807
  this.cacheObserver.stop();
2808
+ this.formLinkClickObserver.stop();
2553
2809
  this.linkClickObserver.stop();
2554
2810
  this.formSubmitObserver.stop();
2555
2811
  this.scrollObserver.stop();
@@ -2563,7 +2819,14 @@ class Session {
2563
2819
  this.adapter = adapter;
2564
2820
  }
2565
2821
  visit(location, options = {}) {
2566
- this.navigator.proposeVisit(expandURL(location), options);
2822
+ const frameElement = document.getElementById(options.frame || "");
2823
+ if (frameElement instanceof FrameElement) {
2824
+ frameElement.src = location.toString();
2825
+ return frameElement.loaded;
2826
+ }
2827
+ else {
2828
+ return this.navigator.proposeVisit(expandURL(location), options);
2829
+ }
2567
2830
  }
2568
2831
  connectStreamSource(source) {
2569
2832
  this.streamObserver.connectStreamSource(source);
@@ -2580,6 +2843,9 @@ class Session {
2580
2843
  setProgressBarDelay(delay) {
2581
2844
  this.progressBarDelay = delay;
2582
2845
  }
2846
+ setFormMode(mode) {
2847
+ this.formMode = mode;
2848
+ }
2583
2849
  get location() {
2584
2850
  return this.history.location;
2585
2851
  }
@@ -2588,55 +2854,40 @@ class Session {
2588
2854
  }
2589
2855
  historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
2590
2856
  if (this.enabled) {
2591
- this.navigator.startVisit(location, restorationIdentifier, { action: "restore", historyChanged: true });
2857
+ this.navigator.startVisit(location, restorationIdentifier, {
2858
+ action: "restore",
2859
+ historyChanged: true,
2860
+ });
2592
2861
  }
2593
2862
  else {
2594
- this.adapter.pageInvalidated();
2863
+ this.adapter.pageInvalidated({
2864
+ reason: "turbo_disabled",
2865
+ });
2595
2866
  }
2596
2867
  }
2597
2868
  scrollPositionChanged(position) {
2598
2869
  this.history.updateRestorationData({ scrollPosition: position });
2599
2870
  }
2600
- willFollowLinkToLocation(link, location) {
2601
- return this.elementDriveEnabled(link)
2602
- && locationIsVisitable(location, this.snapshot.rootLocation)
2603
- && this.applicationAllowsFollowingLinkToLocation(link, location);
2871
+ willSubmitFormLinkToLocation(link, location) {
2872
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
2873
+ }
2874
+ submittedFormLinkToLocation() { }
2875
+ willFollowLinkToLocation(link, location, event) {
2876
+ return (this.elementIsNavigatable(link) &&
2877
+ locationIsVisitable(location, this.snapshot.rootLocation) &&
2878
+ this.applicationAllowsFollowingLinkToLocation(link, location, event));
2604
2879
  }
2605
2880
  followedLinkToLocation(link, location) {
2606
2881
  const action = this.getActionForLink(link);
2607
- this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, { action });
2608
- }
2609
- convertLinkWithMethodClickToFormSubmission(link) {
2610
- const linkMethod = link.getAttribute("data-turbo-method");
2611
- if (linkMethod) {
2612
- const form = document.createElement("form");
2613
- form.method = linkMethod;
2614
- form.action = link.getAttribute("href") || "undefined";
2615
- form.hidden = true;
2616
- if (link.hasAttribute("data-turbo-confirm")) {
2617
- form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
2618
- }
2619
- const frame = this.getTargetFrameForLink(link);
2620
- if (frame) {
2621
- form.setAttribute("data-turbo-frame", frame);
2622
- form.addEventListener("turbo:submit-start", () => form.remove());
2623
- }
2624
- else {
2625
- form.addEventListener("submit", () => form.remove());
2626
- }
2627
- document.body.appendChild(form);
2628
- return dispatch("submit", { cancelable: true, target: form });
2629
- }
2630
- else {
2631
- return false;
2632
- }
2882
+ const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
2883
+ this.visit(location.href, { action, acceptsStreamResponse });
2633
2884
  }
2634
2885
  allowsVisitingLocationWithAction(location, action) {
2635
2886
  return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
2636
2887
  }
2637
2888
  visitProposedToLocation(location, options) {
2638
2889
  extendURLWithDeprecatedProperties(location);
2639
- this.adapter.visitProposedToLocation(location, options);
2890
+ return this.adapter.visitProposedToLocation(location, options);
2640
2891
  }
2641
2892
  visitStarted(visit) {
2642
2893
  extendURLWithDeprecatedProperties(visit.location);
@@ -2655,9 +2906,8 @@ class Session {
2655
2906
  }
2656
2907
  willSubmitForm(form, submitter) {
2657
2908
  const action = getAction(form, submitter);
2658
- return this.elementDriveEnabled(form)
2659
- && (!submitter || this.elementDriveEnabled(submitter))
2660
- && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
2909
+ return (this.submissionIsNavigatable(form, submitter) &&
2910
+ locationIsVisitable(expandURL(action), this.snapshot.rootLocation));
2661
2911
  }
2662
2912
  formSubmitted(form, submitter) {
2663
2913
  this.navigator.submitForm(form, submitter);
@@ -2681,16 +2931,23 @@ class Session {
2681
2931
  this.notifyApplicationBeforeCachingSnapshot();
2682
2932
  }
2683
2933
  }
2684
- allowsImmediateRender({ element }, resume) {
2685
- const event = this.notifyApplicationBeforeRender(element, resume);
2686
- return !event.defaultPrevented;
2934
+ allowsImmediateRender({ element }, options) {
2935
+ const event = this.notifyApplicationBeforeRender(element, options);
2936
+ const { defaultPrevented, detail: { render }, } = event;
2937
+ if (this.view.renderer && render) {
2938
+ this.view.renderer.renderElement = render;
2939
+ }
2940
+ return !defaultPrevented;
2687
2941
  }
2688
- viewRenderedSnapshot(snapshot, isPreview) {
2942
+ viewRenderedSnapshot(_snapshot, _isPreview) {
2689
2943
  this.view.lastRenderedLocation = this.history.location;
2690
2944
  this.notifyApplicationAfterRender();
2691
2945
  }
2692
- viewInvalidated() {
2693
- this.adapter.pageInvalidated();
2946
+ preloadOnLoadLinksForView(element) {
2947
+ this.preloader.preloadOnLoadLinksForView(element);
2948
+ }
2949
+ viewInvalidated(reason) {
2950
+ this.adapter.pageInvalidated(reason);
2694
2951
  }
2695
2952
  frameLoaded(frame) {
2696
2953
  this.notifyApplicationAfterFrameLoad(frame);
@@ -2698,19 +2955,30 @@ class Session {
2698
2955
  frameRendered(fetchResponse, frame) {
2699
2956
  this.notifyApplicationAfterFrameRender(fetchResponse, frame);
2700
2957
  }
2701
- applicationAllowsFollowingLinkToLocation(link, location) {
2702
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
2958
+ frameMissing(frame, fetchResponse) {
2959
+ console.warn(`Completing full-page visit as matching frame for #${frame.id} was missing from the response`);
2960
+ return this.visit(fetchResponse.location);
2961
+ }
2962
+ applicationAllowsFollowingLinkToLocation(link, location, ev) {
2963
+ const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
2703
2964
  return !event.defaultPrevented;
2704
2965
  }
2705
2966
  applicationAllowsVisitingLocation(location) {
2706
2967
  const event = this.notifyApplicationBeforeVisitingLocation(location);
2707
2968
  return !event.defaultPrevented;
2708
2969
  }
2709
- notifyApplicationAfterClickingLinkToLocation(link, location) {
2710
- return dispatch("turbo:click", { target: link, detail: { url: location.href }, cancelable: true });
2970
+ notifyApplicationAfterClickingLinkToLocation(link, location, event) {
2971
+ return dispatch("turbo:click", {
2972
+ target: link,
2973
+ detail: { url: location.href, originalEvent: event },
2974
+ cancelable: true,
2975
+ });
2711
2976
  }
2712
2977
  notifyApplicationBeforeVisitingLocation(location) {
2713
- return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
2978
+ return dispatch("turbo:before-visit", {
2979
+ detail: { url: location.href },
2980
+ cancelable: true,
2981
+ });
2714
2982
  }
2715
2983
  notifyApplicationAfterVisitingLocation(location, action) {
2716
2984
  markAsBusy(document.documentElement);
@@ -2719,28 +2987,55 @@ class Session {
2719
2987
  notifyApplicationBeforeCachingSnapshot() {
2720
2988
  return dispatch("turbo:before-cache");
2721
2989
  }
2722
- notifyApplicationBeforeRender(newBody, resume) {
2723
- return dispatch("turbo:before-render", { detail: { newBody, resume }, cancelable: true });
2990
+ notifyApplicationBeforeRender(newBody, options) {
2991
+ return dispatch("turbo:before-render", {
2992
+ detail: Object.assign({ newBody }, options),
2993
+ cancelable: true,
2994
+ });
2724
2995
  }
2725
2996
  notifyApplicationAfterRender() {
2726
2997
  return dispatch("turbo:render");
2727
2998
  }
2728
2999
  notifyApplicationAfterPageLoad(timing = {}) {
2729
3000
  clearBusyState(document.documentElement);
2730
- return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
3001
+ return dispatch("turbo:load", {
3002
+ detail: { url: this.location.href, timing },
3003
+ });
2731
3004
  }
2732
3005
  notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
2733
- dispatchEvent(new HashChangeEvent("hashchange", { oldURL: oldURL.toString(), newURL: newURL.toString() }));
3006
+ dispatchEvent(new HashChangeEvent("hashchange", {
3007
+ oldURL: oldURL.toString(),
3008
+ newURL: newURL.toString(),
3009
+ }));
2734
3010
  }
2735
3011
  notifyApplicationAfterFrameLoad(frame) {
2736
3012
  return dispatch("turbo:frame-load", { target: frame });
2737
3013
  }
2738
3014
  notifyApplicationAfterFrameRender(fetchResponse, frame) {
2739
- return dispatch("turbo:frame-render", { detail: { fetchResponse }, target: frame, cancelable: true });
3015
+ return dispatch("turbo:frame-render", {
3016
+ detail: { fetchResponse },
3017
+ target: frame,
3018
+ cancelable: true,
3019
+ });
3020
+ }
3021
+ submissionIsNavigatable(form, submitter) {
3022
+ if (this.formMode == "off") {
3023
+ return false;
3024
+ }
3025
+ else {
3026
+ const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
3027
+ if (this.formMode == "optin") {
3028
+ return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null;
3029
+ }
3030
+ else {
3031
+ return submitterIsNavigatable && this.elementIsNavigatable(form);
3032
+ }
3033
+ }
2740
3034
  }
2741
- elementDriveEnabled(element) {
2742
- const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
2743
- if (this.drive) {
3035
+ elementIsNavigatable(element) {
3036
+ const container = element.closest("[data-turbo]");
3037
+ const withinFrame = element.closest("turbo-frame");
3038
+ if (this.drive || withinFrame) {
2744
3039
  if (container) {
2745
3040
  return container.getAttribute("data-turbo") != "false";
2746
3041
  }
@@ -2761,18 +3056,6 @@ class Session {
2761
3056
  const action = link.getAttribute("data-turbo-action");
2762
3057
  return isAction(action) ? action : "advance";
2763
3058
  }
2764
- getTargetFrameForLink(link) {
2765
- const frame = link.getAttribute("data-turbo-frame");
2766
- if (frame) {
2767
- return frame;
2768
- }
2769
- else {
2770
- const container = link.closest("turbo-frame");
2771
- if (container) {
2772
- return container.id;
2773
- }
2774
- }
2775
- }
2776
3059
  get snapshot() {
2777
3060
  return this.view.snapshot;
2778
3061
  }
@@ -2784,11 +3067,59 @@ const deprecatedLocationPropertyDescriptors = {
2784
3067
  absoluteURL: {
2785
3068
  get() {
2786
3069
  return this.toString();
2787
- }
3070
+ },
3071
+ },
3072
+ };
3073
+
3074
+ class Cache {
3075
+ constructor(session) {
3076
+ this.session = session;
3077
+ }
3078
+ clear() {
3079
+ this.session.clearCache();
2788
3080
  }
3081
+ resetCacheControl() {
3082
+ this.setCacheControl("");
3083
+ }
3084
+ exemptPageFromCache() {
3085
+ this.setCacheControl("no-cache");
3086
+ }
3087
+ exemptPageFromPreview() {
3088
+ this.setCacheControl("no-preview");
3089
+ }
3090
+ setCacheControl(value) {
3091
+ setMetaContent("turbo-cache-control", value);
3092
+ }
3093
+ }
3094
+
3095
+ const StreamActions = {
3096
+ after() {
3097
+ this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });
3098
+ },
3099
+ append() {
3100
+ this.removeDuplicateTargetChildren();
3101
+ this.targetElements.forEach((e) => e.append(this.templateContent));
3102
+ },
3103
+ before() {
3104
+ this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });
3105
+ },
3106
+ prepend() {
3107
+ this.removeDuplicateTargetChildren();
3108
+ this.targetElements.forEach((e) => e.prepend(this.templateContent));
3109
+ },
3110
+ remove() {
3111
+ this.targetElements.forEach((e) => e.remove());
3112
+ },
3113
+ replace() {
3114
+ this.targetElements.forEach((e) => e.replaceWith(this.templateContent));
3115
+ },
3116
+ update() {
3117
+ this.targetElements.forEach((e) => e.replaceChildren(this.templateContent));
3118
+ },
2789
3119
  };
2790
3120
 
2791
- const session = new Session;
3121
+ const session = new Session();
3122
+ const cache = new Cache(session);
2792
3123
  const { navigator: navigator$1 } = session;
2793
3124
  function start() {
2794
3125
  session.start();
@@ -2797,7 +3128,7 @@ function registerAdapter(adapter) {
2797
3128
  session.registerAdapter(adapter);
2798
3129
  }
2799
3130
  function visit(location, options) {
2800
- session.visit(location, options);
3131
+ return session.visit(location, options);
2801
3132
  }
2802
3133
  function connectStreamSource(source) {
2803
3134
  session.connectStreamSource(source);
@@ -2809,6 +3140,7 @@ function renderStreamMessage(message) {
2809
3140
  session.renderStreamMessage(message);
2810
3141
  }
2811
3142
  function clearCache() {
3143
+ 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.`");
2812
3144
  session.clearCache();
2813
3145
  }
2814
3146
  function setProgressBarDelay(delay) {
@@ -2817,13 +3149,18 @@ function setProgressBarDelay(delay) {
2817
3149
  function setConfirmMethod(confirmMethod) {
2818
3150
  FormSubmission.confirmMethod = confirmMethod;
2819
3151
  }
3152
+ function setFormMode(mode) {
3153
+ session.setFormMode(mode);
3154
+ }
2820
3155
 
2821
3156
  var Turbo = /*#__PURE__*/Object.freeze({
2822
3157
  __proto__: null,
2823
3158
  navigator: navigator$1,
2824
3159
  session: session,
3160
+ cache: cache,
2825
3161
  PageRenderer: PageRenderer,
2826
3162
  PageSnapshot: PageSnapshot,
3163
+ FrameRenderer: FrameRenderer,
2827
3164
  start: start,
2828
3165
  registerAdapter: registerAdapter,
2829
3166
  visit: visit,
@@ -2832,41 +3169,56 @@ var Turbo = /*#__PURE__*/Object.freeze({
2832
3169
  renderStreamMessage: renderStreamMessage,
2833
3170
  clearCache: clearCache,
2834
3171
  setProgressBarDelay: setProgressBarDelay,
2835
- setConfirmMethod: setConfirmMethod
3172
+ setConfirmMethod: setConfirmMethod,
3173
+ setFormMode: setFormMode,
3174
+ StreamActions: StreamActions
2836
3175
  });
2837
3176
 
2838
3177
  class FrameController {
2839
3178
  constructor(element) {
2840
- this.fetchResponseLoaded = (fetchResponse) => { };
3179
+ this.fetchResponseLoaded = (_fetchResponse) => { };
2841
3180
  this.currentFetchRequest = null;
2842
3181
  this.resolveVisitPromise = () => { };
2843
3182
  this.connected = false;
2844
3183
  this.hasBeenLoaded = false;
2845
- this.settingSourceURL = false;
3184
+ this.ignoredAttributes = new Set();
3185
+ this.action = null;
3186
+ this.visitCachedSnapshot = ({ element }) => {
3187
+ const frame = element.querySelector("#" + this.element.id);
3188
+ if (frame && this.previousFrameElement) {
3189
+ frame.replaceChildren(...this.previousFrameElement.children);
3190
+ }
3191
+ delete this.previousFrameElement;
3192
+ };
2846
3193
  this.element = element;
2847
3194
  this.view = new FrameView(this, this.element);
2848
3195
  this.appearanceObserver = new AppearanceObserver(this, this.element);
2849
- this.linkInterceptor = new LinkInterceptor(this, this.element);
2850
- this.formInterceptor = new FormInterceptor(this, this.element);
3196
+ this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);
3197
+ this.linkClickObserver = new LinkClickObserver(this, this.element);
3198
+ this.restorationIdentifier = uuid();
3199
+ this.formSubmitObserver = new FormSubmitObserver(this, this.element);
2851
3200
  }
2852
3201
  connect() {
2853
3202
  if (!this.connected) {
2854
3203
  this.connected = true;
2855
- this.reloadable = false;
2856
3204
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2857
3205
  this.appearanceObserver.start();
2858
3206
  }
2859
- this.linkInterceptor.start();
2860
- this.formInterceptor.start();
2861
- this.sourceURLChanged();
3207
+ else {
3208
+ this.loadSourceURL();
3209
+ }
3210
+ this.formLinkClickObserver.start();
3211
+ this.linkClickObserver.start();
3212
+ this.formSubmitObserver.start();
2862
3213
  }
2863
3214
  }
2864
3215
  disconnect() {
2865
3216
  if (this.connected) {
2866
3217
  this.connected = false;
2867
3218
  this.appearanceObserver.stop();
2868
- this.linkInterceptor.stop();
2869
- this.formInterceptor.stop();
3219
+ this.formLinkClickObserver.stop();
3220
+ this.linkClickObserver.stop();
3221
+ this.formSubmitObserver.stop();
2870
3222
  }
2871
3223
  }
2872
3224
  disabledChanged() {
@@ -2875,10 +3227,20 @@ class FrameController {
2875
3227
  }
2876
3228
  }
2877
3229
  sourceURLChanged() {
3230
+ if (this.isIgnoringChangesTo("src"))
3231
+ return;
3232
+ if (this.element.isConnected) {
3233
+ this.complete = false;
3234
+ }
2878
3235
  if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
2879
3236
  this.loadSourceURL();
2880
3237
  }
2881
3238
  }
3239
+ completeChanged() {
3240
+ if (this.isIgnoringChangesTo("complete"))
3241
+ return;
3242
+ this.loadSourceURL();
3243
+ }
2882
3244
  loadingStyleChanged() {
2883
3245
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2884
3246
  this.appearanceObserver.start();
@@ -2889,21 +3251,11 @@ class FrameController {
2889
3251
  }
2890
3252
  }
2891
3253
  async loadSourceURL() {
2892
- if (!this.settingSourceURL && this.enabled && this.isActive && (this.reloadable || this.sourceURL != this.currentURL)) {
2893
- const previousURL = this.currentURL;
2894
- this.currentURL = this.sourceURL;
2895
- if (this.sourceURL) {
2896
- try {
2897
- this.element.loaded = this.visit(expandURL(this.sourceURL));
2898
- this.appearanceObserver.stop();
2899
- await this.element.loaded;
2900
- this.hasBeenLoaded = true;
2901
- }
2902
- catch (error) {
2903
- this.currentURL = previousURL;
2904
- throw error;
2905
- }
2906
- }
3254
+ if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
3255
+ this.element.loaded = this.visit(expandURL(this.sourceURL));
3256
+ this.appearanceObserver.stop();
3257
+ await this.element.loaded;
3258
+ this.hasBeenLoaded = true;
2907
3259
  }
2908
3260
  }
2909
3261
  async loadResponse(fetchResponse) {
@@ -2914,14 +3266,22 @@ class FrameController {
2914
3266
  const html = await fetchResponse.responseHTML;
2915
3267
  if (html) {
2916
3268
  const { body } = parseHTMLDocument(html);
2917
- const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
2918
- const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
2919
- if (this.view.renderPromise)
2920
- await this.view.renderPromise;
2921
- await this.view.render(renderer);
2922
- session.frameRendered(fetchResponse, this.element);
2923
- session.frameLoaded(this.element);
2924
- this.fetchResponseLoaded(fetchResponse);
3269
+ const newFrameElement = await this.extractForeignFrameElement(body);
3270
+ if (newFrameElement) {
3271
+ const snapshot = new Snapshot(newFrameElement);
3272
+ const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
3273
+ if (this.view.renderPromise)
3274
+ await this.view.renderPromise;
3275
+ this.changeHistory();
3276
+ await this.view.render(renderer);
3277
+ this.complete = true;
3278
+ session.frameRendered(fetchResponse, this.element);
3279
+ session.frameLoaded(this.element);
3280
+ this.fetchResponseLoaded(fetchResponse);
3281
+ }
3282
+ else if (this.sessionWillHandleMissingFrame(fetchResponse)) {
3283
+ await session.frameMissing(this.element, fetchResponse);
3284
+ }
2925
3285
  }
2926
3286
  }
2927
3287
  catch (error) {
@@ -2932,41 +3292,46 @@ class FrameController {
2932
3292
  this.fetchResponseLoaded = () => { };
2933
3293
  }
2934
3294
  }
2935
- elementAppearedInViewport(element) {
3295
+ elementAppearedInViewport(_element) {
2936
3296
  this.loadSourceURL();
2937
3297
  }
2938
- shouldInterceptLinkClick(element, url) {
2939
- if (element.hasAttribute("data-turbo-method")) {
2940
- return false;
2941
- }
2942
- else {
2943
- return this.shouldInterceptNavigation(element);
2944
- }
3298
+ willSubmitFormLinkToLocation(link) {
3299
+ return link.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(link);
3300
+ }
3301
+ submittedFormLinkToLocation(link, _location, form) {
3302
+ const frame = this.findFrameElement(link);
3303
+ if (frame)
3304
+ form.setAttribute("data-turbo-frame", frame.id);
3305
+ }
3306
+ willFollowLinkToLocation(element) {
3307
+ return this.shouldInterceptNavigation(element);
2945
3308
  }
2946
- linkClickIntercepted(element, url) {
2947
- this.reloadable = true;
2948
- this.navigateFrame(element, url);
3309
+ followedLinkToLocation(element, location) {
3310
+ this.navigateFrame(element, location.href);
2949
3311
  }
2950
- shouldInterceptFormSubmission(element, submitter) {
2951
- return this.shouldInterceptNavigation(element, submitter);
3312
+ willSubmitForm(element, submitter) {
3313
+ return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
2952
3314
  }
2953
- formSubmissionIntercepted(element, submitter) {
3315
+ formSubmitted(element, submitter) {
2954
3316
  if (this.formSubmission) {
2955
3317
  this.formSubmission.stop();
2956
3318
  }
2957
- this.reloadable = false;
2958
3319
  this.formSubmission = new FormSubmission(this, element, submitter);
2959
3320
  const { fetchRequest } = this.formSubmission;
2960
3321
  this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
2961
3322
  this.formSubmission.start();
2962
3323
  }
2963
3324
  prepareHeadersForRequest(headers, request) {
3325
+ var _a;
2964
3326
  headers["Turbo-Frame"] = this.id;
3327
+ if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3328
+ request.acceptResponseType(StreamMessage.contentType);
3329
+ }
2965
3330
  }
2966
- requestStarted(request) {
3331
+ requestStarted(_request) {
2967
3332
  markAsBusy(this.element);
2968
3333
  }
2969
- requestPreventedHandlingResponse(request, response) {
3334
+ requestPreventedHandlingResponse(_request, _response) {
2970
3335
  this.resolveVisitPromise();
2971
3336
  }
2972
3337
  async requestSucceededWithResponse(request, response) {
@@ -2979,9 +3344,13 @@ class FrameController {
2979
3344
  }
2980
3345
  requestErrored(request, error) {
2981
3346
  console.error(error);
3347
+ dispatch("turbo:fetch-request-error", {
3348
+ target: this.element,
3349
+ detail: { request, error },
3350
+ });
2982
3351
  this.resolveVisitPromise();
2983
3352
  }
2984
- requestFinished(request) {
3353
+ requestFinished(_request) {
2985
3354
  clearBusyState(this.element);
2986
3355
  }
2987
3356
  formSubmissionStarted({ formElement }) {
@@ -3001,19 +3370,32 @@ class FrameController {
3001
3370
  formSubmissionFinished({ formElement }) {
3002
3371
  clearBusyState(formElement, this.findFrameElement(formElement));
3003
3372
  }
3004
- allowsImmediateRender(snapshot, resume) {
3005
- return true;
3373
+ allowsImmediateRender({ element: newFrame }, options) {
3374
+ const event = dispatch("turbo:before-frame-render", {
3375
+ target: this.element,
3376
+ detail: Object.assign({ newFrame }, options),
3377
+ cancelable: true,
3378
+ });
3379
+ const { defaultPrevented, detail: { render }, } = event;
3380
+ if (this.view.renderer && render) {
3381
+ this.view.renderer.renderElement = render;
3382
+ }
3383
+ return !defaultPrevented;
3006
3384
  }
3007
- viewRenderedSnapshot(snapshot, isPreview) {
3385
+ viewRenderedSnapshot(_snapshot, _isPreview) { }
3386
+ preloadOnLoadLinksForView(element) {
3387
+ session.preloadOnLoadLinksForView(element);
3008
3388
  }
3009
- viewInvalidated() {
3389
+ viewInvalidated() { }
3390
+ willRenderFrame(currentElement, _newElement) {
3391
+ this.previousFrameElement = currentElement.cloneNode(true);
3010
3392
  }
3011
3393
  async visit(url) {
3012
3394
  var _a;
3013
- const request = new FetchRequest(this, FetchMethod.get, url, url.searchParams, this.element);
3395
+ const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);
3014
3396
  (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
3015
3397
  this.currentFetchRequest = request;
3016
- return new Promise(resolve => {
3398
+ return new Promise((resolve) => {
3017
3399
  this.resolveVisitPromise = () => {
3018
3400
  this.resolveVisitPromise = () => { };
3019
3401
  this.currentFetchRequest = null;
@@ -3025,23 +3407,49 @@ class FrameController {
3025
3407
  navigateFrame(element, url, submitter) {
3026
3408
  const frame = this.findFrameElement(element, submitter);
3027
3409
  this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3028
- frame.setAttribute("reloadable", "");
3029
- frame.src = url;
3410
+ this.withCurrentNavigationElement(element, () => {
3411
+ frame.src = url;
3412
+ });
3030
3413
  }
3031
3414
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3032
- const action = getAttribute("data-turbo-action", submitter, element, frame);
3033
- if (isAction(action)) {
3034
- const { visitCachedSnapshot } = new SnapshotSubstitution(frame);
3415
+ this.action = getVisitAction(submitter, element, frame);
3416
+ this.frame = frame;
3417
+ if (isAction(this.action)) {
3418
+ const { visitCachedSnapshot } = frame.delegate;
3035
3419
  frame.delegate.fetchResponseLoaded = (fetchResponse) => {
3036
3420
  if (frame.src) {
3037
3421
  const { statusCode, redirected } = fetchResponse;
3038
3422
  const responseHTML = frame.ownerDocument.documentElement.outerHTML;
3039
3423
  const response = { statusCode, redirected, responseHTML };
3040
- session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false });
3424
+ const options = {
3425
+ response,
3426
+ visitCachedSnapshot,
3427
+ willRender: false,
3428
+ updateHistory: false,
3429
+ restorationIdentifier: this.restorationIdentifier,
3430
+ };
3431
+ if (this.action)
3432
+ options.action = this.action;
3433
+ session.visit(frame.src, options);
3041
3434
  }
3042
3435
  };
3043
3436
  }
3044
3437
  }
3438
+ changeHistory() {
3439
+ if (this.action && this.frame) {
3440
+ const method = getHistoryMethodForAction(this.action);
3441
+ session.history.update(method, expandURL(this.frame.src || ""), this.restorationIdentifier);
3442
+ }
3443
+ }
3444
+ sessionWillHandleMissingFrame(fetchResponse) {
3445
+ this.element.setAttribute("complete", "");
3446
+ const event = dispatch("turbo:frame-missing", {
3447
+ target: this.element,
3448
+ detail: { fetchResponse },
3449
+ cancelable: true,
3450
+ });
3451
+ return !event.defaultPrevented;
3452
+ }
3045
3453
  findFrameElement(element, submitter) {
3046
3454
  var _a;
3047
3455
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
@@ -3051,19 +3459,21 @@ class FrameController {
3051
3459
  let element;
3052
3460
  const id = CSS.escape(this.id);
3053
3461
  try {
3054
- if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
3462
+ element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
3463
+ if (element) {
3055
3464
  return element;
3056
3465
  }
3057
- if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
3466
+ element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);
3467
+ if (element) {
3058
3468
  await element.loaded;
3059
3469
  return await this.extractForeignFrameElement(element);
3060
3470
  }
3061
- console.error(`Response has no matching <turbo-frame id="${id}"> element`);
3062
3471
  }
3063
3472
  catch (error) {
3064
3473
  console.error(error);
3474
+ return new FrameElement();
3065
3475
  }
3066
- return new FrameElement();
3476
+ return null;
3067
3477
  }
3068
3478
  formActionIsVisitable(form, submitter) {
3069
3479
  const action = getAction(form, submitter);
@@ -3083,10 +3493,10 @@ class FrameController {
3083
3493
  return !frameElement.disabled;
3084
3494
  }
3085
3495
  }
3086
- if (!session.elementDriveEnabled(element)) {
3496
+ if (!session.elementIsNavigatable(element)) {
3087
3497
  return false;
3088
3498
  }
3089
- if (submitter && !session.elementDriveEnabled(submitter)) {
3499
+ if (submitter && !session.elementIsNavigatable(submitter)) {
3090
3500
  return false;
3091
3501
  }
3092
3502
  return true;
@@ -3102,24 +3512,10 @@ class FrameController {
3102
3512
  return this.element.src;
3103
3513
  }
3104
3514
  }
3105
- get reloadable() {
3106
- const frame = this.findFrameElement(this.element);
3107
- return frame.hasAttribute("reloadable");
3108
- }
3109
- set reloadable(value) {
3110
- const frame = this.findFrameElement(this.element);
3111
- if (value) {
3112
- frame.setAttribute("reloadable", "");
3113
- }
3114
- else {
3115
- frame.removeAttribute("reloadable");
3116
- }
3117
- }
3118
3515
  set sourceURL(sourceURL) {
3119
- this.settingSourceURL = true;
3120
- this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3121
- this.currentURL = this.element.src;
3122
- this.settingSourceURL = false;
3516
+ this.ignoringChangesToAttribute("src", () => {
3517
+ this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3518
+ });
3123
3519
  }
3124
3520
  get loadingStyle() {
3125
3521
  return this.element.loading;
@@ -3127,6 +3523,19 @@ class FrameController {
3127
3523
  get isLoading() {
3128
3524
  return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
3129
3525
  }
3526
+ get complete() {
3527
+ return this.element.hasAttribute("complete");
3528
+ }
3529
+ set complete(value) {
3530
+ this.ignoringChangesToAttribute("complete", () => {
3531
+ if (value) {
3532
+ this.element.setAttribute("complete", "");
3533
+ }
3534
+ else {
3535
+ this.element.removeAttribute("complete");
3536
+ }
3537
+ });
3538
+ }
3130
3539
  get isActive() {
3131
3540
  return this.element.isActive && this.connected;
3132
3541
  }
@@ -3136,16 +3545,18 @@ class FrameController {
3136
3545
  const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3137
3546
  return expandURL(root);
3138
3547
  }
3139
- }
3140
- class SnapshotSubstitution {
3141
- constructor(element) {
3142
- this.visitCachedSnapshot = ({ element }) => {
3143
- var _a;
3144
- const { id, clone } = this;
3145
- (_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
3146
- };
3147
- this.clone = element.cloneNode(true);
3148
- this.id = element.id;
3548
+ isIgnoringChangesTo(attributeName) {
3549
+ return this.ignoredAttributes.has(attributeName);
3550
+ }
3551
+ ignoringChangesToAttribute(attributeName, callback) {
3552
+ this.ignoredAttributes.add(attributeName);
3553
+ callback();
3554
+ this.ignoredAttributes.delete(attributeName);
3555
+ }
3556
+ withCurrentNavigationElement(element, callback) {
3557
+ this.currentNavigationElement = element;
3558
+ callback();
3559
+ delete this.currentNavigationElement;
3149
3560
  }
3150
3561
  }
3151
3562
  function getFrameElementById(id) {
@@ -3173,35 +3584,6 @@ function activateElement(element, currentURL) {
3173
3584
  }
3174
3585
  }
3175
3586
 
3176
- const StreamActions = {
3177
- after() {
3178
- this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });
3179
- },
3180
- append() {
3181
- this.removeDuplicateTargetChildren();
3182
- this.targetElements.forEach(e => e.append(this.templateContent));
3183
- },
3184
- before() {
3185
- this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });
3186
- },
3187
- prepend() {
3188
- this.removeDuplicateTargetChildren();
3189
- this.targetElements.forEach(e => e.prepend(this.templateContent));
3190
- },
3191
- remove() {
3192
- this.targetElements.forEach(e => e.remove());
3193
- },
3194
- replace() {
3195
- this.targetElements.forEach(e => e.replaceWith(this.templateContent));
3196
- },
3197
- update() {
3198
- this.targetElements.forEach(e => {
3199
- e.innerHTML = "";
3200
- e.append(this.templateContent);
3201
- });
3202
- }
3203
- };
3204
-
3205
3587
  class StreamElement extends HTMLElement {
3206
3588
  async connectedCallback() {
3207
3589
  try {
@@ -3216,12 +3598,12 @@ class StreamElement extends HTMLElement {
3216
3598
  }
3217
3599
  async render() {
3218
3600
  var _a;
3219
- return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
3601
+ return ((_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
3220
3602
  if (this.dispatchEvent(this.beforeRenderEvent)) {
3221
3603
  await nextAnimationFrame();
3222
3604
  this.performAction();
3223
3605
  }
3224
- })());
3606
+ })()));
3225
3607
  }
3226
3608
  disconnect() {
3227
3609
  try {
@@ -3230,13 +3612,13 @@ class StreamElement extends HTMLElement {
3230
3612
  catch (_a) { }
3231
3613
  }
3232
3614
  removeDuplicateTargetChildren() {
3233
- this.duplicateChildren.forEach(c => c.remove());
3615
+ this.duplicateChildren.forEach((c) => c.remove());
3234
3616
  }
3235
3617
  get duplicateChildren() {
3236
3618
  var _a;
3237
- const existingChildren = this.targetElements.flatMap(e => [...e.children]).filter(c => !!c.id);
3238
- const newChildrenIds = [...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children].filter(c => !!c.id).map(c => c.id);
3239
- return existingChildren.filter(c => newChildrenIds.includes(c.id));
3619
+ const existingChildren = this.targetElements.flatMap((e) => [...e.children]).filter((c) => !!c.id);
3620
+ const newChildrenIds = [...(((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [])].filter((c) => !!c.id).map((c) => c.id);
3621
+ return existingChildren.filter((c) => newChildrenIds.includes(c.id));
3240
3622
  }
3241
3623
  get performAction() {
3242
3624
  if (this.action) {
@@ -3285,7 +3667,11 @@ class StreamElement extends HTMLElement {
3285
3667
  return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
3286
3668
  }
3287
3669
  get beforeRenderEvent() {
3288
- return new CustomEvent("turbo:before-stream-render", { bubbles: true, cancelable: true });
3670
+ return new CustomEvent("turbo:before-stream-render", {
3671
+ bubbles: true,
3672
+ cancelable: true,
3673
+ detail: { newStream: this },
3674
+ });
3289
3675
  }
3290
3676
  get targetElementsById() {
3291
3677
  var _a;
@@ -3309,9 +3695,35 @@ class StreamElement extends HTMLElement {
3309
3695
  }
3310
3696
  }
3311
3697
 
3698
+ class StreamSourceElement extends HTMLElement {
3699
+ constructor() {
3700
+ super(...arguments);
3701
+ this.streamSource = null;
3702
+ }
3703
+ connectedCallback() {
3704
+ this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
3705
+ connectStreamSource(this.streamSource);
3706
+ }
3707
+ disconnectedCallback() {
3708
+ if (this.streamSource) {
3709
+ disconnectStreamSource(this.streamSource);
3710
+ }
3711
+ }
3712
+ get src() {
3713
+ return this.getAttribute("src") || "";
3714
+ }
3715
+ }
3716
+
3312
3717
  FrameElement.delegateConstructor = FrameController;
3313
- customElements.define("turbo-frame", FrameElement);
3314
- customElements.define("turbo-stream", StreamElement);
3718
+ if (customElements.get("turbo-frame") === undefined) {
3719
+ customElements.define("turbo-frame", FrameElement);
3720
+ }
3721
+ if (customElements.get("turbo-stream") === undefined) {
3722
+ customElements.define("turbo-stream", StreamElement);
3723
+ }
3724
+ if (customElements.get("turbo-stream-source") === undefined) {
3725
+ customElements.define("turbo-stream-source", StreamSourceElement);
3726
+ }
3315
3727
 
3316
3728
  (() => {
3317
3729
  let element = document.currentScript;
@@ -3319,7 +3731,8 @@ customElements.define("turbo-stream", StreamElement);
3319
3731
  return;
3320
3732
  if (element.hasAttribute("data-turbo-suppress-warning"))
3321
3733
  return;
3322
- while (element = element.parentElement) {
3734
+ element = element.parentElement;
3735
+ while (element) {
3323
3736
  if (element == document.body) {
3324
3737
  return console.warn(unindent `
3325
3738
  You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
@@ -3332,10 +3745,11 @@ customElements.define("turbo-stream", StreamElement);
3332
3745
  Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
3333
3746
  `, element.outerHTML);
3334
3747
  }
3748
+ element = element.parentElement;
3335
3749
  }
3336
3750
  })();
3337
3751
 
3338
3752
  window.Turbo = Turbo;
3339
3753
  start();
3340
3754
 
3341
- export { PageRenderer, PageSnapshot, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setProgressBarDelay, start, visit };
3755
+ export { FrameRenderer, PageRenderer, PageSnapshot, StreamActions, cache, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setFormMode, setProgressBarDelay, start, visit };