@hotwired/turbo 7.1.0 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +5 -1
  2. package/dist/turbo.es2017-esm.js +1284 -655
  3. package/dist/turbo.es2017-umd.js +1298 -662
  4. package/dist/types/core/bardo.d.ts +7 -2
  5. package/dist/types/core/cache.d.ts +10 -0
  6. package/dist/types/core/drive/error_renderer.d.ts +2 -1
  7. package/dist/types/core/drive/form_submission.d.ts +20 -9
  8. package/dist/types/core/drive/head_snapshot.d.ts +6 -6
  9. package/dist/types/core/drive/history.d.ts +4 -4
  10. package/dist/types/core/drive/navigator.d.ts +2 -2
  11. package/dist/types/core/drive/page_renderer.d.ts +12 -7
  12. package/dist/types/core/drive/page_view.d.ts +10 -8
  13. package/dist/types/core/drive/preloader.d.ts +14 -0
  14. package/dist/types/core/drive/progress_bar.d.ts +1 -0
  15. package/dist/types/core/drive/visit.d.ts +17 -6
  16. package/dist/types/core/errors.d.ts +2 -0
  17. package/dist/types/core/frames/frame_controller.d.ts +56 -26
  18. package/dist/types/core/frames/frame_redirector.d.ts +10 -8
  19. package/dist/types/core/frames/frame_renderer.d.ts +8 -1
  20. package/dist/types/core/frames/frame_view.d.ts +3 -2
  21. package/dist/types/core/frames/link_interceptor.d.ts +3 -3
  22. package/dist/types/core/index.d.ts +12 -3
  23. package/dist/types/core/native/adapter.d.ts +2 -1
  24. package/dist/types/core/native/browser_adapter.d.ts +17 -8
  25. package/dist/types/core/renderer.d.ts +12 -6
  26. package/dist/types/core/session.d.ts +72 -17
  27. package/dist/types/core/snapshot.d.ts +6 -3
  28. package/dist/types/core/streams/stream_actions.d.ts +4 -2
  29. package/dist/types/core/streams/stream_message.d.ts +2 -6
  30. package/dist/types/core/streams/stream_message_renderer.d.ts +7 -0
  31. package/dist/types/core/types.d.ts +3 -4
  32. package/dist/types/core/url.d.ts +1 -1
  33. package/dist/types/core/view.d.ts +13 -7
  34. package/dist/types/elements/frame_element.d.ts +12 -6
  35. package/dist/types/elements/index.d.ts +1 -0
  36. package/dist/types/elements/stream_element.d.ts +8 -1
  37. package/dist/types/elements/stream_source_element.d.ts +7 -0
  38. package/dist/types/http/fetch_request.d.ts +18 -4
  39. package/dist/types/http/index.d.ts +1 -0
  40. package/dist/types/index.d.ts +2 -0
  41. package/dist/types/observers/appearance_observer.d.ts +6 -6
  42. package/dist/types/observers/cache_observer.d.ts +5 -1
  43. package/dist/types/observers/form_link_click_observer.d.ts +14 -0
  44. package/dist/types/observers/form_submit_observer.d.ts +2 -1
  45. package/dist/types/observers/link_click_observer.d.ts +5 -4
  46. package/dist/types/polyfills/custom-elements-native-shim.d.ts +1 -0
  47. package/dist/types/polyfills/submit-event.d.ts +1 -7
  48. package/dist/types/tests/functional/async_script_tests.d.ts +1 -6
  49. package/dist/types/tests/functional/autofocus_tests.d.ts +1 -9
  50. package/dist/types/tests/functional/cache_observer_tests.d.ts +1 -5
  51. package/dist/types/tests/functional/drive_disabled_tests.d.ts +1 -9
  52. package/dist/types/tests/functional/drive_tests.d.ts +1 -8
  53. package/dist/types/tests/functional/form_mode_tests.d.ts +1 -0
  54. package/dist/types/tests/functional/form_submission_tests.d.ts +1 -84
  55. package/dist/types/tests/functional/frame_navigation_tests.d.ts +1 -7
  56. package/dist/types/tests/functional/frame_tests.d.ts +7 -51
  57. package/dist/types/tests/functional/import_tests.d.ts +1 -4
  58. package/dist/types/tests/functional/loading_tests.d.ts +1 -13
  59. package/dist/types/tests/functional/navigation_tests.d.ts +1 -38
  60. package/dist/types/tests/functional/pausable_rendering_tests.d.ts +1 -6
  61. package/dist/types/tests/functional/pausable_requests_tests.d.ts +1 -6
  62. package/dist/types/tests/functional/preloader_tests.d.ts +1 -0
  63. package/dist/types/tests/functional/rendering_tests.d.ts +1 -35
  64. package/dist/types/tests/functional/scroll_restoration_tests.d.ts +1 -6
  65. package/dist/types/tests/functional/stream_tests.d.ts +1 -6
  66. package/dist/types/tests/functional/visit_tests.d.ts +1 -15
  67. package/dist/types/tests/helpers/dom_test_case.d.ts +1 -2
  68. package/dist/types/tests/helpers/page.d.ts +60 -0
  69. package/dist/types/tests/integration/ujs_tests.d.ts +1 -0
  70. package/dist/types/tests/unit/deprecated_adapter_support_tests.d.ts +1 -0
  71. package/dist/types/tests/unit/export_tests.d.ts +1 -0
  72. package/dist/types/tests/unit/stream_element_tests.d.ts +0 -10
  73. package/dist/types/util.d.ts +15 -3
  74. package/package.json +32 -13
  75. package/CHANGELOG.md +0 -3
  76. package/dist/types/core/frames/form_interceptor.d.ts +0 -12
  77. package/dist/types/tests/functional/index.d.ts +0 -17
  78. package/dist/types/tests/helpers/functional_test_case.d.ts +0 -44
  79. package/dist/types/tests/helpers/intern_test_case.d.ts +0 -20
  80. package/dist/types/tests/helpers/remote_channel.d.ts +0 -10
  81. package/dist/types/tests/helpers/turbo_drive_test_case.d.ts +0 -21
  82. package/dist/types/tests/unit/deprecated_adapter_support_test.d.ts +0 -24
  83. package/dist/types/tests/unit/index.d.ts +0 -2
@@ -1,20 +1,20 @@
1
1
  /*
2
- Turbo 7.1.0
3
- Copyright © 2021 Basecamp, LLC
2
+ Turbo 7.3.0
3
+ Copyright © 2023 37signals LLC
4
4
  */
5
5
  (function () {
6
- if (window.Reflect === undefined || window.customElements === undefined ||
6
+ if (window.Reflect === undefined ||
7
+ window.customElements === undefined ||
7
8
  window.customElements.polyfillWrapFlushCallback) {
8
9
  return;
9
10
  }
10
11
  const BuiltInHTMLElement = HTMLElement;
11
12
  const wrapperForTheName = {
12
- 'HTMLElement': function HTMLElement() {
13
+ HTMLElement: function HTMLElement() {
13
14
  return Reflect.construct(BuiltInHTMLElement, [], this.constructor);
14
- }
15
+ },
15
16
  };
16
- window.HTMLElement =
17
- wrapperForTheName['HTMLElement'];
17
+ window.HTMLElement = wrapperForTheName["HTMLElement"];
18
18
  HTMLElement.prototype = BuiltInHTMLElement.prototype;
19
19
  HTMLElement.prototype.constructor = HTMLElement;
20
20
  Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);
@@ -72,7 +72,7 @@ Copyright © 2021 Basecamp, LLC
72
72
  }
73
73
  })(HTMLFormElement.prototype);
74
74
 
75
- const submittersByForm = new WeakMap;
75
+ const submittersByForm = new WeakMap();
76
76
  function findSubmitterFromClickTarget(target) {
77
77
  const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
78
78
  const candidate = element ? element.closest("input, button") : null;
@@ -87,23 +87,20 @@ function clickCaptured(event) {
87
87
  (function () {
88
88
  if ("submitter" in Event.prototype)
89
89
  return;
90
- let prototype;
90
+ let prototype = window.Event.prototype;
91
91
  if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
92
92
  prototype = window.SubmitEvent.prototype;
93
93
  }
94
94
  else if ("SubmitEvent" in window) {
95
95
  return;
96
96
  }
97
- else {
98
- prototype = window.Event.prototype;
99
- }
100
97
  addEventListener("click", clickCaptured, true);
101
98
  Object.defineProperty(prototype, "submitter", {
102
99
  get() {
103
100
  if (this.type == "submit" && this.target instanceof HTMLFormElement) {
104
101
  return submittersByForm.get(this.target);
105
102
  }
106
- }
103
+ },
107
104
  });
108
105
  })();
109
106
 
@@ -113,14 +110,14 @@ var FrameLoadingStyle;
113
110
  FrameLoadingStyle["lazy"] = "lazy";
114
111
  })(FrameLoadingStyle || (FrameLoadingStyle = {}));
115
112
  class FrameElement extends HTMLElement {
113
+ static get observedAttributes() {
114
+ return ["disabled", "complete", "loading", "src"];
115
+ }
116
116
  constructor() {
117
117
  super();
118
118
  this.loaded = Promise.resolve();
119
119
  this.delegate = new FrameElement.delegateConstructor(this);
120
120
  }
121
- static get observedAttributes() {
122
- return ["disabled", "loading", "src"];
123
- }
124
121
  connectedCallback() {
125
122
  this.delegate.connect();
126
123
  }
@@ -128,14 +125,15 @@ class FrameElement extends HTMLElement {
128
125
  this.delegate.disconnect();
129
126
  }
130
127
  reload() {
131
- const { src } = this;
132
- this.src = null;
133
- this.src = src;
128
+ return this.delegate.sourceURLReloaded();
134
129
  }
135
130
  attributeChangedCallback(name) {
136
131
  if (name == "loading") {
137
132
  this.delegate.loadingStyleChanged();
138
133
  }
134
+ else if (name == "complete") {
135
+ this.delegate.completeChanged();
136
+ }
139
137
  else if (name == "src") {
140
138
  this.delegate.sourceURLChanged();
141
139
  }
@@ -200,8 +198,10 @@ class FrameElement extends HTMLElement {
200
198
  }
201
199
  function frameLoadingStyleFromString(style) {
202
200
  switch (style.toLowerCase()) {
203
- case "lazy": return FrameLoadingStyle.lazy;
204
- default: return FrameLoadingStyle.eager;
201
+ case "lazy":
202
+ return FrameLoadingStyle.lazy;
203
+ default:
204
+ return FrameLoadingStyle.eager;
205
205
  }
206
206
  }
207
207
 
@@ -213,7 +213,7 @@ function getAnchor(url) {
213
213
  if (url.hash) {
214
214
  return url.hash.slice(1);
215
215
  }
216
- else if (anchorMatch = url.href.match(/#(.*)$/)) {
216
+ else if ((anchorMatch = url.href.match(/#(.*)$/))) {
217
217
  return anchorMatch[1];
218
218
  }
219
219
  }
@@ -225,7 +225,7 @@ function getExtension(url) {
225
225
  return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
226
226
  }
227
227
  function isHTML(url) {
228
- return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
228
+ return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
229
229
  }
230
230
  function isPrefixedBy(baseURL, url) {
231
231
  const prefix = getPrefix(url);
@@ -236,9 +236,7 @@ function locationIsVisitable(location, rootLocation) {
236
236
  }
237
237
  function getRequestURL(url) {
238
238
  const anchor = getAnchor(url);
239
- return anchor != null
240
- ? url.href.slice(0, -(anchor.length + 1))
241
- : url.href;
239
+ return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
242
240
  }
243
241
  function toCacheKey(url) {
244
242
  return getRequestURL(url);
@@ -306,8 +304,39 @@ class FetchResponse {
306
304
  }
307
305
  }
308
306
 
307
+ function activateScriptElement(element) {
308
+ if (element.getAttribute("data-turbo-eval") == "false") {
309
+ return element;
310
+ }
311
+ else {
312
+ const createdScriptElement = document.createElement("script");
313
+ const cspNonce = getMetaContent("csp-nonce");
314
+ if (cspNonce) {
315
+ createdScriptElement.nonce = cspNonce;
316
+ }
317
+ createdScriptElement.textContent = element.textContent;
318
+ createdScriptElement.async = false;
319
+ copyElementAttributes(createdScriptElement, element);
320
+ return createdScriptElement;
321
+ }
322
+ }
323
+ function copyElementAttributes(destinationElement, sourceElement) {
324
+ for (const { name, value } of sourceElement.attributes) {
325
+ destinationElement.setAttribute(name, value);
326
+ }
327
+ }
328
+ function createDocumentFragment(html) {
329
+ const template = document.createElement("template");
330
+ template.innerHTML = html;
331
+ return template.content;
332
+ }
309
333
  function dispatch(eventName, { target, cancelable, detail } = {}) {
310
- const event = new CustomEvent(eventName, { cancelable, bubbles: true, detail });
334
+ const event = new CustomEvent(eventName, {
335
+ cancelable,
336
+ bubbles: true,
337
+ composed: true,
338
+ detail,
339
+ });
311
340
  if (target && target.isConnected) {
312
341
  target.dispatchEvent(event);
313
342
  }
@@ -317,10 +346,10 @@ function dispatch(eventName, { target, cancelable, detail } = {}) {
317
346
  return event;
318
347
  }
319
348
  function nextAnimationFrame() {
320
- return new Promise(resolve => requestAnimationFrame(() => resolve()));
349
+ return new Promise((resolve) => requestAnimationFrame(() => resolve()));
321
350
  }
322
351
  function nextEventLoopTick() {
323
- return new Promise(resolve => setTimeout(() => resolve(), 0));
352
+ return new Promise((resolve) => setTimeout(() => resolve(), 0));
324
353
  }
325
354
  function nextMicrotask() {
326
355
  return Promise.resolve();
@@ -332,7 +361,7 @@ function unindent(strings, ...values) {
332
361
  const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
333
362
  const match = lines[0].match(/^\s+/);
334
363
  const indent = match ? match[0].length : 0;
335
- return lines.map(line => line.slice(indent)).join("\n");
364
+ return lines.map((line) => line.slice(indent)).join("\n");
336
365
  }
337
366
  function interpolate(strings, values) {
338
367
  return strings.reduce((result, string, i) => {
@@ -341,7 +370,8 @@ function interpolate(strings, values) {
341
370
  }, "");
342
371
  }
343
372
  function uuid() {
344
- return Array.apply(null, { length: 36 }).map((_, i) => {
373
+ return Array.from({ length: 36 })
374
+ .map((_, i) => {
345
375
  if (i == 8 || i == 13 || i == 18 || i == 23) {
346
376
  return "-";
347
377
  }
@@ -354,15 +384,19 @@ function uuid() {
354
384
  else {
355
385
  return Math.floor(Math.random() * 15).toString(16);
356
386
  }
357
- }).join("");
387
+ })
388
+ .join("");
358
389
  }
359
390
  function getAttribute(attributeName, ...elements) {
360
- for (const value of elements.map(element => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {
391
+ for (const value of elements.map((element) => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {
361
392
  if (typeof value == "string")
362
393
  return value;
363
394
  }
364
395
  return null;
365
396
  }
397
+ function hasAttribute(attributeName, ...elements) {
398
+ return elements.some((element) => element && element.hasAttribute(attributeName));
399
+ }
366
400
  function markAsBusy(...elements) {
367
401
  for (const element of elements) {
368
402
  if (element.localName == "turbo-frame") {
@@ -379,6 +413,58 @@ function clearBusyState(...elements) {
379
413
  element.removeAttribute("aria-busy");
380
414
  }
381
415
  }
416
+ function waitForLoad(element, timeoutInMilliseconds = 2000) {
417
+ return new Promise((resolve) => {
418
+ const onComplete = () => {
419
+ element.removeEventListener("error", onComplete);
420
+ element.removeEventListener("load", onComplete);
421
+ resolve();
422
+ };
423
+ element.addEventListener("load", onComplete, { once: true });
424
+ element.addEventListener("error", onComplete, { once: true });
425
+ setTimeout(resolve, timeoutInMilliseconds);
426
+ });
427
+ }
428
+ function getHistoryMethodForAction(action) {
429
+ switch (action) {
430
+ case "replace":
431
+ return history.replaceState;
432
+ case "advance":
433
+ case "restore":
434
+ return history.pushState;
435
+ }
436
+ }
437
+ function isAction(action) {
438
+ return action == "advance" || action == "replace" || action == "restore";
439
+ }
440
+ function getVisitAction(...elements) {
441
+ const action = getAttribute("data-turbo-action", ...elements);
442
+ return isAction(action) ? action : null;
443
+ }
444
+ function getMetaElement(name) {
445
+ return document.querySelector(`meta[name="${name}"]`);
446
+ }
447
+ function getMetaContent(name) {
448
+ const element = getMetaElement(name);
449
+ return element && element.content;
450
+ }
451
+ function setMetaContent(name, content) {
452
+ let element = getMetaElement(name);
453
+ if (!element) {
454
+ element = document.createElement("meta");
455
+ element.setAttribute("name", name);
456
+ document.head.appendChild(element);
457
+ }
458
+ element.setAttribute("content", content);
459
+ return element;
460
+ }
461
+ function findClosestRecursively(element, selector) {
462
+ var _a;
463
+ if (element instanceof Element) {
464
+ return (element.closest(selector) ||
465
+ findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector));
466
+ }
467
+ }
382
468
 
383
469
  var FetchMethod;
384
470
  (function (FetchMethod) {
@@ -390,17 +476,22 @@ var FetchMethod;
390
476
  })(FetchMethod || (FetchMethod = {}));
391
477
  function fetchMethodFromString(method) {
392
478
  switch (method.toLowerCase()) {
393
- case "get": return FetchMethod.get;
394
- case "post": return FetchMethod.post;
395
- case "put": return FetchMethod.put;
396
- case "patch": return FetchMethod.patch;
397
- case "delete": return FetchMethod.delete;
479
+ case "get":
480
+ return FetchMethod.get;
481
+ case "post":
482
+ return FetchMethod.post;
483
+ case "put":
484
+ return FetchMethod.put;
485
+ case "patch":
486
+ return FetchMethod.patch;
487
+ case "delete":
488
+ return FetchMethod.delete;
398
489
  }
399
490
  }
400
491
  class FetchRequest {
401
- constructor(delegate, method, location, body = new URLSearchParams, target = null) {
402
- this.abortController = new AbortController;
403
- this.resolveRequestPromise = (value) => { };
492
+ constructor(delegate, method, location, body = new URLSearchParams(), target = null) {
493
+ this.abortController = new AbortController();
494
+ this.resolveRequestPromise = (_value) => { };
404
495
  this.delegate = delegate;
405
496
  this.method = method;
406
497
  this.headers = this.defaultHeaders;
@@ -421,9 +512,8 @@ class FetchRequest {
421
512
  this.abortController.abort();
422
513
  }
423
514
  async perform() {
424
- var _a, _b;
425
515
  const { fetchOptions } = this;
426
- (_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
516
+ this.delegate.prepareRequest(this);
427
517
  await this.allowRequestToBeIntercepted(fetchOptions);
428
518
  try {
429
519
  this.delegate.requestStarted(this);
@@ -431,8 +521,10 @@ class FetchRequest {
431
521
  return await this.receive(response);
432
522
  }
433
523
  catch (error) {
434
- if (error.name !== 'AbortError') {
435
- this.delegate.requestErrored(this, error);
524
+ if (error.name !== "AbortError") {
525
+ if (this.willDelegateErrorHandling(error)) {
526
+ this.delegate.requestErrored(this, error);
527
+ }
436
528
  throw error;
437
529
  }
438
530
  }
@@ -442,7 +534,11 @@ class FetchRequest {
442
534
  }
443
535
  async receive(response) {
444
536
  const fetchResponse = new FetchResponse(response);
445
- const event = dispatch("turbo:before-fetch-response", { cancelable: true, detail: { fetchResponse }, target: this.target });
537
+ const event = dispatch("turbo:before-fetch-response", {
538
+ cancelable: true,
539
+ detail: { fetchResponse },
540
+ target: this.target,
541
+ });
446
542
  if (event.defaultPrevented) {
447
543
  this.delegate.requestPreventedHandlingResponse(this, fetchResponse);
448
544
  }
@@ -461,42 +557,53 @@ class FetchRequest {
461
557
  credentials: "same-origin",
462
558
  headers: this.headers,
463
559
  redirect: "follow",
464
- body: this.isIdempotent ? null : this.body,
560
+ body: this.isSafe ? null : this.body,
465
561
  signal: this.abortSignal,
466
- referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
562
+ referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href,
467
563
  };
468
564
  }
469
565
  get defaultHeaders() {
470
566
  return {
471
- "Accept": "text/html, application/xhtml+xml"
567
+ Accept: "text/html, application/xhtml+xml",
472
568
  };
473
569
  }
474
- get isIdempotent() {
475
- return this.method == FetchMethod.get;
570
+ get isSafe() {
571
+ return this.method === FetchMethod.get;
476
572
  }
477
573
  get abortSignal() {
478
574
  return this.abortController.signal;
479
575
  }
576
+ acceptResponseType(mimeType) {
577
+ this.headers["Accept"] = [mimeType, this.headers["Accept"]].join(", ");
578
+ }
480
579
  async allowRequestToBeIntercepted(fetchOptions) {
481
- const requestInterception = new Promise(resolve => this.resolveRequestPromise = resolve);
580
+ const requestInterception = new Promise((resolve) => (this.resolveRequestPromise = resolve));
482
581
  const event = dispatch("turbo:before-fetch-request", {
483
582
  cancelable: true,
484
583
  detail: {
485
584
  fetchOptions,
486
585
  url: this.url,
487
- resume: this.resolveRequestPromise
586
+ resume: this.resolveRequestPromise,
488
587
  },
489
- target: this.target
588
+ target: this.target,
490
589
  });
491
590
  if (event.defaultPrevented)
492
591
  await requestInterception;
493
592
  }
593
+ willDelegateErrorHandling(error) {
594
+ const event = dispatch("turbo:fetch-request-error", {
595
+ target: this.target,
596
+ cancelable: true,
597
+ detail: { request: this, error: error },
598
+ });
599
+ return !event.defaultPrevented;
600
+ }
494
601
  }
495
602
 
496
603
  class AppearanceObserver {
497
604
  constructor(delegate, element) {
498
605
  this.started = false;
499
- this.intersect = entries => {
606
+ this.intersect = (entries) => {
500
607
  const lastEntry = entries.slice(-1)[0];
501
608
  if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
502
609
  this.delegate.elementAppearedInViewport(this.element);
@@ -521,40 +628,29 @@ class AppearanceObserver {
521
628
  }
522
629
 
523
630
  class StreamMessage {
524
- constructor(html) {
525
- this.templateElement = document.createElement("template");
526
- this.templateElement.innerHTML = html;
527
- }
528
631
  static wrap(message) {
529
632
  if (typeof message == "string") {
530
- return new this(message);
633
+ return new this(createDocumentFragment(message));
531
634
  }
532
635
  else {
533
636
  return message;
534
637
  }
535
638
  }
536
- get fragment() {
537
- const fragment = document.createDocumentFragment();
538
- for (const element of this.foreignElements) {
539
- fragment.appendChild(document.importNode(element, true));
540
- }
541
- return fragment;
542
- }
543
- get foreignElements() {
544
- return this.templateChildren.reduce((streamElements, child) => {
545
- if (child.tagName.toLowerCase() == "turbo-stream") {
546
- return [...streamElements, child];
547
- }
548
- else {
549
- return streamElements;
550
- }
551
- }, []);
552
- }
553
- get templateChildren() {
554
- return Array.from(this.templateElement.content.children);
639
+ constructor(fragment) {
640
+ this.fragment = importStreamElements(fragment);
555
641
  }
556
642
  }
557
643
  StreamMessage.contentType = "text/vnd.turbo-stream.html";
644
+ function importStreamElements(fragment) {
645
+ for (const element of fragment.querySelectorAll("turbo-stream")) {
646
+ const streamElement = document.importNode(element, true);
647
+ for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) {
648
+ inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));
649
+ }
650
+ element.replaceWith(streamElement);
651
+ }
652
+ return fragment;
653
+ }
558
654
 
559
655
  var FormSubmissionState;
560
656
  (function (FormSubmissionState) {
@@ -573,12 +669,18 @@ var FormEnctype;
573
669
  })(FormEnctype || (FormEnctype = {}));
574
670
  function formEnctypeFromString(encoding) {
575
671
  switch (encoding.toLowerCase()) {
576
- case FormEnctype.multipart: return FormEnctype.multipart;
577
- case FormEnctype.plain: return FormEnctype.plain;
578
- default: return FormEnctype.urlEncoded;
672
+ case FormEnctype.multipart:
673
+ return FormEnctype.multipart;
674
+ case FormEnctype.plain:
675
+ return FormEnctype.plain;
676
+ default:
677
+ return FormEnctype.urlEncoded;
579
678
  }
580
679
  }
581
680
  class FormSubmission {
681
+ static confirmMethod(message, _element, _submitter) {
682
+ return Promise.resolve(confirm(message));
683
+ }
582
684
  constructor(delegate, formElement, submitter, mustRedirect = false) {
583
685
  this.state = FormSubmissionState.initialized;
584
686
  this.delegate = delegate;
@@ -592,9 +694,6 @@ class FormSubmission {
592
694
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
593
695
  this.mustRedirect = mustRedirect;
594
696
  }
595
- static confirmMethod(message, element) {
596
- return confirm(message);
597
- }
598
697
  get method() {
599
698
  var _a;
600
699
  const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
@@ -602,8 +701,13 @@ class FormSubmission {
602
701
  }
603
702
  get action() {
604
703
  var _a;
605
- const formElementAction = typeof this.formElement.action === 'string' ? this.formElement.action : null;
606
- return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.getAttribute("action") || formElementAction || "";
704
+ const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
705
+ if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
706
+ return this.submitter.getAttribute("formaction") || "";
707
+ }
708
+ else {
709
+ return this.formElement.getAttribute("action") || formElementAction || "";
710
+ }
607
711
  }
608
712
  get body() {
609
713
  if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
@@ -617,24 +721,19 @@ class FormSubmission {
617
721
  var _a;
618
722
  return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
619
723
  }
620
- get isIdempotent() {
621
- return this.fetchRequest.isIdempotent;
724
+ get isSafe() {
725
+ return this.fetchRequest.isSafe;
622
726
  }
623
727
  get stringFormData() {
624
728
  return [...this.formData].reduce((entries, [name, value]) => {
625
729
  return entries.concat(typeof value == "string" ? [[name, value]] : []);
626
730
  }, []);
627
731
  }
628
- get confirmationMessage() {
629
- return this.formElement.getAttribute("data-turbo-confirm");
630
- }
631
- get needsConfirmation() {
632
- return this.confirmationMessage !== null;
633
- }
634
732
  async start() {
635
733
  const { initialized, requesting } = FormSubmissionState;
636
- if (this.needsConfirmation) {
637
- const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
734
+ const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
735
+ if (typeof confirmationMessage === "string") {
736
+ const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);
638
737
  if (!answer) {
639
738
  return;
640
739
  }
@@ -652,20 +751,26 @@ class FormSubmission {
652
751
  return true;
653
752
  }
654
753
  }
655
- prepareHeadersForRequest(headers, request) {
656
- if (!request.isIdempotent) {
754
+ prepareRequest(request) {
755
+ if (!request.isSafe) {
657
756
  const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
658
757
  if (token) {
659
- headers["X-CSRF-Token"] = token;
758
+ request.headers["X-CSRF-Token"] = token;
660
759
  }
661
- headers["Accept"] = [StreamMessage.contentType, headers["Accept"]].join(", ");
760
+ }
761
+ if (this.requestAcceptsTurboStreamResponse(request)) {
762
+ request.acceptResponseType(StreamMessage.contentType);
662
763
  }
663
764
  }
664
- requestStarted(request) {
765
+ requestStarted(_request) {
665
766
  var _a;
666
767
  this.state = FormSubmissionState.waiting;
667
768
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
668
- dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } });
769
+ this.setSubmitsWith();
770
+ dispatch("turbo:submit-start", {
771
+ target: this.formElement,
772
+ detail: { formSubmission: this },
773
+ });
669
774
  this.delegate.formSubmissionStarted(this);
670
775
  }
671
776
  requestPreventedHandlingResponse(request, response) {
@@ -693,23 +798,58 @@ class FormSubmission {
693
798
  this.result = { success: false, error };
694
799
  this.delegate.formSubmissionErrored(this, error);
695
800
  }
696
- requestFinished(request) {
801
+ requestFinished(_request) {
697
802
  var _a;
698
803
  this.state = FormSubmissionState.stopped;
699
804
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
700
- dispatch("turbo:submit-end", { target: this.formElement, detail: Object.assign({ formSubmission: this }, this.result) });
805
+ this.resetSubmitterText();
806
+ dispatch("turbo:submit-end", {
807
+ target: this.formElement,
808
+ detail: Object.assign({ formSubmission: this }, this.result),
809
+ });
701
810
  this.delegate.formSubmissionFinished(this);
702
811
  }
812
+ setSubmitsWith() {
813
+ if (!this.submitter || !this.submitsWith)
814
+ return;
815
+ if (this.submitter.matches("button")) {
816
+ this.originalSubmitText = this.submitter.innerHTML;
817
+ this.submitter.innerHTML = this.submitsWith;
818
+ }
819
+ else if (this.submitter.matches("input")) {
820
+ const input = this.submitter;
821
+ this.originalSubmitText = input.value;
822
+ input.value = this.submitsWith;
823
+ }
824
+ }
825
+ resetSubmitterText() {
826
+ if (!this.submitter || !this.originalSubmitText)
827
+ return;
828
+ if (this.submitter.matches("button")) {
829
+ this.submitter.innerHTML = this.originalSubmitText;
830
+ }
831
+ else if (this.submitter.matches("input")) {
832
+ const input = this.submitter;
833
+ input.value = this.originalSubmitText;
834
+ }
835
+ }
703
836
  requestMustRedirect(request) {
704
- return !request.isIdempotent && this.mustRedirect;
837
+ return !request.isSafe && this.mustRedirect;
838
+ }
839
+ requestAcceptsTurboStreamResponse(request) {
840
+ return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
841
+ }
842
+ get submitsWith() {
843
+ var _a;
844
+ return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with");
705
845
  }
706
846
  }
707
847
  function buildFormData(formElement, submitter) {
708
848
  const formData = new FormData(formElement);
709
849
  const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
710
850
  const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
711
- if (name && value != null && formData.get(name) != value) {
712
- formData.append(name, value);
851
+ if (name) {
852
+ formData.append(name, value || "");
713
853
  }
714
854
  return formData;
715
855
  }
@@ -723,15 +863,11 @@ function getCookieValue(cookieName) {
723
863
  }
724
864
  }
725
865
  }
726
- function getMetaContent(name) {
727
- const element = document.querySelector(`meta[name="${name}"]`);
728
- return element && element.content;
729
- }
730
866
  function responseSucceededWithoutRedirect(response) {
731
867
  return response.statusCode == 200 && !response.redirected;
732
868
  }
733
869
  function mergeFormDataEntries(url, entries) {
734
- const searchParams = new URLSearchParams;
870
+ const searchParams = new URLSearchParams();
735
871
  for (const [name, value] of entries) {
736
872
  if (value instanceof File)
737
873
  continue;
@@ -745,6 +881,9 @@ class Snapshot {
745
881
  constructor(element) {
746
882
  this.element = element;
747
883
  }
884
+ get activeElement() {
885
+ return this.element.ownerDocument.activeElement;
886
+ }
748
887
  get children() {
749
888
  return [...this.element.children];
750
889
  }
@@ -758,13 +897,20 @@ class Snapshot {
758
897
  return this.element.isConnected;
759
898
  }
760
899
  get firstAutofocusableElement() {
761
- return this.element.querySelector("[autofocus]");
900
+ const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
901
+ for (const element of this.element.querySelectorAll("[autofocus]")) {
902
+ if (element.closest(inertDisabledOrHidden) == null)
903
+ return element;
904
+ else
905
+ continue;
906
+ }
907
+ return null;
762
908
  }
763
909
  get permanentElements() {
764
- return [...this.element.querySelectorAll("[id][data-turbo-permanent]")];
910
+ return queryPermanentElementsAll(this.element);
765
911
  }
766
912
  getPermanentElementById(id) {
767
- return this.element.querySelector(`#${id}[data-turbo-permanent]`);
913
+ return getPermanentElementById(this.element, id);
768
914
  }
769
915
  getPermanentElementMapForSnapshot(snapshot) {
770
916
  const permanentElementMap = {};
@@ -778,36 +924,72 @@ class Snapshot {
778
924
  return permanentElementMap;
779
925
  }
780
926
  }
927
+ function getPermanentElementById(node, id) {
928
+ return node.querySelector(`#${id}[data-turbo-permanent]`);
929
+ }
930
+ function queryPermanentElementsAll(node) {
931
+ return node.querySelectorAll("[id][data-turbo-permanent]");
932
+ }
781
933
 
782
- class FormInterceptor {
783
- constructor(delegate, element) {
934
+ class FormSubmitObserver {
935
+ constructor(delegate, eventTarget) {
936
+ this.started = false;
937
+ this.submitCaptured = () => {
938
+ this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
939
+ this.eventTarget.addEventListener("submit", this.submitBubbled, false);
940
+ };
784
941
  this.submitBubbled = ((event) => {
785
- const form = event.target;
786
- if (!event.defaultPrevented && form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
942
+ if (!event.defaultPrevented) {
943
+ const form = event.target instanceof HTMLFormElement ? event.target : undefined;
787
944
  const submitter = event.submitter || undefined;
788
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
789
- if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
945
+ if (form &&
946
+ submissionDoesNotDismissDialog(form, submitter) &&
947
+ submissionDoesNotTargetIFrame(form, submitter) &&
948
+ this.delegate.willSubmitForm(form, submitter)) {
790
949
  event.preventDefault();
791
950
  event.stopImmediatePropagation();
792
- this.delegate.formSubmissionIntercepted(form, submitter);
951
+ this.delegate.formSubmitted(form, submitter);
793
952
  }
794
953
  }
795
954
  });
796
955
  this.delegate = delegate;
797
- this.element = element;
956
+ this.eventTarget = eventTarget;
798
957
  }
799
958
  start() {
800
- this.element.addEventListener("submit", this.submitBubbled);
959
+ if (!this.started) {
960
+ this.eventTarget.addEventListener("submit", this.submitCaptured, true);
961
+ this.started = true;
962
+ }
801
963
  }
802
964
  stop() {
803
- this.element.removeEventListener("submit", this.submitBubbled);
965
+ if (this.started) {
966
+ this.eventTarget.removeEventListener("submit", this.submitCaptured, true);
967
+ this.started = false;
968
+ }
969
+ }
970
+ }
971
+ function submissionDoesNotDismissDialog(form, submitter) {
972
+ const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
973
+ return method != "dialog";
974
+ }
975
+ function submissionDoesNotTargetIFrame(form, submitter) {
976
+ if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
977
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
978
+ for (const element of document.getElementsByName(target)) {
979
+ if (element instanceof HTMLIFrameElement)
980
+ return false;
981
+ }
982
+ return true;
983
+ }
984
+ else {
985
+ return true;
804
986
  }
805
987
  }
806
988
 
807
989
  class View {
808
990
  constructor(delegate, element) {
809
- this.resolveRenderPromise = (value) => { };
810
- this.resolveInterceptionPromise = (value) => { };
991
+ this.resolveRenderPromise = (_value) => { };
992
+ this.resolveInterceptionPromise = (_value) => { };
811
993
  this.delegate = delegate;
812
994
  this.element = element;
813
995
  }
@@ -852,15 +1034,17 @@ class View {
852
1034
  const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
853
1035
  if (shouldRender) {
854
1036
  try {
855
- this.renderPromise = new Promise(resolve => this.resolveRenderPromise = resolve);
1037
+ this.renderPromise = new Promise((resolve) => (this.resolveRenderPromise = resolve));
856
1038
  this.renderer = renderer;
857
- this.prepareToRenderSnapshot(renderer);
858
- const renderInterception = new Promise(resolve => this.resolveInterceptionPromise = resolve);
859
- const immediateRender = this.delegate.allowsImmediateRender(snapshot, this.resolveInterceptionPromise);
1039
+ await this.prepareToRenderSnapshot(renderer);
1040
+ const renderInterception = new Promise((resolve) => (this.resolveInterceptionPromise = resolve));
1041
+ const options = { resume: this.resolveInterceptionPromise, render: this.renderer.renderElement };
1042
+ const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
860
1043
  if (!immediateRender)
861
1044
  await renderInterception;
862
1045
  await this.renderSnapshot(renderer);
863
1046
  this.delegate.viewRenderedSnapshot(snapshot, isPreview);
1047
+ this.delegate.preloadOnLoadLinksForView(this.element);
864
1048
  this.finishRenderingSnapshot(renderer);
865
1049
  }
866
1050
  finally {
@@ -870,15 +1054,15 @@ class View {
870
1054
  }
871
1055
  }
872
1056
  else {
873
- this.invalidate();
1057
+ this.invalidate(renderer.reloadReason);
874
1058
  }
875
1059
  }
876
- invalidate() {
877
- this.delegate.viewInvalidated();
1060
+ invalidate(reason) {
1061
+ this.delegate.viewInvalidated(reason);
878
1062
  }
879
- prepareToRenderSnapshot(renderer) {
1063
+ async prepareToRenderSnapshot(renderer) {
880
1064
  this.markAsPreview(renderer.isPreview);
881
- renderer.prepareToRender();
1065
+ await renderer.prepareToRender();
882
1066
  }
883
1067
  markAsPreview(isPreview) {
884
1068
  if (isPreview) {
@@ -897,8 +1081,8 @@ class View {
897
1081
  }
898
1082
 
899
1083
  class FrameView extends View {
900
- invalidate() {
901
- this.element.innerHTML = "";
1084
+ missing() {
1085
+ this.element.innerHTML = `<strong class="turbo-frame-error">Content missing</strong>`;
902
1086
  }
903
1087
  get snapshot() {
904
1088
  return new Snapshot(this.element);
@@ -917,17 +1101,17 @@ class LinkInterceptor {
917
1101
  };
918
1102
  this.linkClicked = ((event) => {
919
1103
  if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
920
- if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) {
1104
+ if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
921
1105
  this.clickEvent.preventDefault();
922
1106
  event.preventDefault();
923
- this.delegate.linkClickIntercepted(event.target, event.detail.url);
1107
+ this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
924
1108
  }
925
1109
  }
926
1110
  delete this.clickEvent;
927
1111
  });
928
- this.willVisit = () => {
1112
+ this.willVisit = ((_event) => {
929
1113
  delete this.clickEvent;
930
- };
1114
+ });
931
1115
  this.delegate = delegate;
932
1116
  this.element = element;
933
1117
  }
@@ -942,28 +1126,137 @@ class LinkInterceptor {
942
1126
  document.removeEventListener("turbo:before-visit", this.willVisit);
943
1127
  }
944
1128
  respondsToEventTarget(target) {
945
- const element = target instanceof Element
946
- ? target
947
- : target instanceof Node
948
- ? target.parentElement
949
- : null;
1129
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
950
1130
  return element && element.closest("turbo-frame, html") == this.element;
951
1131
  }
952
1132
  }
953
1133
 
954
- class Bardo {
955
- constructor(permanentElementMap) {
956
- this.permanentElementMap = permanentElementMap;
1134
+ class LinkClickObserver {
1135
+ constructor(delegate, eventTarget) {
1136
+ this.started = false;
1137
+ this.clickCaptured = () => {
1138
+ this.eventTarget.removeEventListener("click", this.clickBubbled, false);
1139
+ this.eventTarget.addEventListener("click", this.clickBubbled, false);
1140
+ };
1141
+ this.clickBubbled = (event) => {
1142
+ if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
1143
+ const target = (event.composedPath && event.composedPath()[0]) || event.target;
1144
+ const link = this.findLinkFromClickTarget(target);
1145
+ if (link && doesNotTargetIFrame(link)) {
1146
+ const location = this.getLocationForLink(link);
1147
+ if (this.delegate.willFollowLinkToLocation(link, location, event)) {
1148
+ event.preventDefault();
1149
+ this.delegate.followedLinkToLocation(link, location);
1150
+ }
1151
+ }
1152
+ }
1153
+ };
1154
+ this.delegate = delegate;
1155
+ this.eventTarget = eventTarget;
1156
+ }
1157
+ start() {
1158
+ if (!this.started) {
1159
+ this.eventTarget.addEventListener("click", this.clickCaptured, true);
1160
+ this.started = true;
1161
+ }
1162
+ }
1163
+ stop() {
1164
+ if (this.started) {
1165
+ this.eventTarget.removeEventListener("click", this.clickCaptured, true);
1166
+ this.started = false;
1167
+ }
1168
+ }
1169
+ clickEventIsSignificant(event) {
1170
+ return !((event.target && event.target.isContentEditable) ||
1171
+ event.defaultPrevented ||
1172
+ event.which > 1 ||
1173
+ event.altKey ||
1174
+ event.ctrlKey ||
1175
+ event.metaKey ||
1176
+ event.shiftKey);
1177
+ }
1178
+ findLinkFromClickTarget(target) {
1179
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
1180
+ }
1181
+ getLocationForLink(link) {
1182
+ return expandURL(link.getAttribute("href") || "");
1183
+ }
1184
+ }
1185
+ function doesNotTargetIFrame(anchor) {
1186
+ if (anchor.hasAttribute("target")) {
1187
+ for (const element of document.getElementsByName(anchor.target)) {
1188
+ if (element instanceof HTMLIFrameElement)
1189
+ return false;
1190
+ }
1191
+ return true;
1192
+ }
1193
+ else {
1194
+ return true;
1195
+ }
1196
+ }
1197
+
1198
+ class FormLinkClickObserver {
1199
+ constructor(delegate, element) {
1200
+ this.delegate = delegate;
1201
+ this.linkInterceptor = new LinkClickObserver(this, element);
957
1202
  }
958
- static preservingPermanentElements(permanentElementMap, callback) {
959
- const bardo = new this(permanentElementMap);
1203
+ start() {
1204
+ this.linkInterceptor.start();
1205
+ }
1206
+ stop() {
1207
+ this.linkInterceptor.stop();
1208
+ }
1209
+ willFollowLinkToLocation(link, location, originalEvent) {
1210
+ return (this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) &&
1211
+ link.hasAttribute("data-turbo-method"));
1212
+ }
1213
+ followedLinkToLocation(link, location) {
1214
+ const form = document.createElement("form");
1215
+ const type = "hidden";
1216
+ for (const [name, value] of location.searchParams) {
1217
+ form.append(Object.assign(document.createElement("input"), { type, name, value }));
1218
+ }
1219
+ const action = Object.assign(location, { search: "" });
1220
+ form.setAttribute("data-turbo", "true");
1221
+ form.setAttribute("action", action.href);
1222
+ form.setAttribute("hidden", "");
1223
+ const method = link.getAttribute("data-turbo-method");
1224
+ if (method)
1225
+ form.setAttribute("method", method);
1226
+ const turboFrame = link.getAttribute("data-turbo-frame");
1227
+ if (turboFrame)
1228
+ form.setAttribute("data-turbo-frame", turboFrame);
1229
+ const turboAction = getVisitAction(link);
1230
+ if (turboAction)
1231
+ form.setAttribute("data-turbo-action", turboAction);
1232
+ const turboConfirm = link.getAttribute("data-turbo-confirm");
1233
+ if (turboConfirm)
1234
+ form.setAttribute("data-turbo-confirm", turboConfirm);
1235
+ const turboStream = link.hasAttribute("data-turbo-stream");
1236
+ if (turboStream)
1237
+ form.setAttribute("data-turbo-stream", "");
1238
+ this.delegate.submittedFormLinkToLocation(link, location, form);
1239
+ document.body.appendChild(form);
1240
+ form.addEventListener("turbo:submit-end", () => form.remove(), { once: true });
1241
+ requestAnimationFrame(() => form.requestSubmit());
1242
+ }
1243
+ }
1244
+
1245
+ class Bardo {
1246
+ static async preservingPermanentElements(delegate, permanentElementMap, callback) {
1247
+ const bardo = new this(delegate, permanentElementMap);
960
1248
  bardo.enter();
961
- callback();
1249
+ await callback();
962
1250
  bardo.leave();
963
1251
  }
1252
+ constructor(delegate, permanentElementMap) {
1253
+ this.delegate = delegate;
1254
+ this.permanentElementMap = permanentElementMap;
1255
+ }
964
1256
  enter() {
965
1257
  for (const id in this.permanentElementMap) {
966
- const [, newPermanentElement] = this.permanentElementMap[id];
1258
+ const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
1259
+ this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
967
1260
  this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
968
1261
  }
969
1262
  }
@@ -972,6 +1265,7 @@ class Bardo {
972
1265
  const [currentPermanentElement] = this.permanentElementMap[id];
973
1266
  this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
974
1267
  this.replacePlaceholderWithPermanentElement(currentPermanentElement);
1268
+ this.delegate.leavingBardo(currentPermanentElement);
975
1269
  }
976
1270
  }
977
1271
  replaceNewPermanentElementWithPlaceholder(permanentElement) {
@@ -987,7 +1281,7 @@ class Bardo {
987
1281
  placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
988
1282
  }
989
1283
  getPlaceholderById(id) {
990
- return this.placeholders.find(element => element.content == id);
1284
+ return this.placeholders.find((element) => element.content == id);
991
1285
  }
992
1286
  get placeholders() {
993
1287
  return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")];
@@ -1001,16 +1295,21 @@ function createPlaceholderForPermanentElement(permanentElement) {
1001
1295
  }
1002
1296
 
1003
1297
  class Renderer {
1004
- constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
1298
+ constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1299
+ this.activeElement = null;
1005
1300
  this.currentSnapshot = currentSnapshot;
1006
1301
  this.newSnapshot = newSnapshot;
1007
1302
  this.isPreview = isPreview;
1008
1303
  this.willRender = willRender;
1009
- this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
1304
+ this.renderElement = renderElement;
1305
+ this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));
1010
1306
  }
1011
1307
  get shouldRender() {
1012
1308
  return true;
1013
1309
  }
1310
+ get reloadReason() {
1311
+ return;
1312
+ }
1014
1313
  prepareToRender() {
1015
1314
  return;
1016
1315
  }
@@ -1020,23 +1319,8 @@ class Renderer {
1020
1319
  delete this.resolvingFunctions;
1021
1320
  }
1022
1321
  }
1023
- createScriptElement(element) {
1024
- if (element.getAttribute("data-turbo-eval") == "false") {
1025
- return element;
1026
- }
1027
- else {
1028
- const createdScriptElement = document.createElement("script");
1029
- if (this.cspNonce) {
1030
- createdScriptElement.nonce = this.cspNonce;
1031
- }
1032
- createdScriptElement.textContent = element.textContent;
1033
- createdScriptElement.async = false;
1034
- copyElementAttributes(createdScriptElement, element);
1035
- return createdScriptElement;
1036
- }
1037
- }
1038
- preservingPermanentElements(callback) {
1039
- Bardo.preservingPermanentElements(this.permanentElementMap, callback);
1322
+ async preservingPermanentElements(callback) {
1323
+ await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1040
1324
  }
1041
1325
  focusFirstAutofocusableElement() {
1042
1326
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1044,6 +1328,19 @@ class Renderer {
1044
1328
  element.focus();
1045
1329
  }
1046
1330
  }
1331
+ enteringBardo(currentPermanentElement) {
1332
+ if (this.activeElement)
1333
+ return;
1334
+ if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
1335
+ this.activeElement = this.currentSnapshot.activeElement;
1336
+ }
1337
+ }
1338
+ leavingBardo(currentPermanentElement) {
1339
+ if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
1340
+ this.activeElement.focus();
1341
+ this.activeElement = null;
1342
+ }
1343
+ }
1047
1344
  get connectedSnapshot() {
1048
1345
  return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
1049
1346
  }
@@ -1056,21 +1353,28 @@ class Renderer {
1056
1353
  get permanentElementMap() {
1057
1354
  return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1058
1355
  }
1059
- get cspNonce() {
1060
- var _a;
1061
- return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
1062
- }
1063
- }
1064
- function copyElementAttributes(destinationElement, sourceElement) {
1065
- for (const { name, value } of [...sourceElement.attributes]) {
1066
- destinationElement.setAttribute(name, value);
1067
- }
1068
1356
  }
1069
1357
  function elementIsFocusable(element) {
1070
1358
  return element && typeof element.focus == "function";
1071
1359
  }
1072
1360
 
1073
1361
  class FrameRenderer extends Renderer {
1362
+ static renderElement(currentElement, newElement) {
1363
+ var _a;
1364
+ const destinationRange = document.createRange();
1365
+ destinationRange.selectNodeContents(currentElement);
1366
+ destinationRange.deleteContents();
1367
+ const frameElement = newElement;
1368
+ const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1369
+ if (sourceRange) {
1370
+ sourceRange.selectNodeContents(frameElement);
1371
+ currentElement.appendChild(sourceRange.extractContents());
1372
+ }
1373
+ }
1374
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1375
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1376
+ this.delegate = delegate;
1377
+ }
1074
1378
  get shouldRender() {
1075
1379
  return true;
1076
1380
  }
@@ -1086,23 +1390,16 @@ class FrameRenderer extends Renderer {
1086
1390
  this.activateScriptElements();
1087
1391
  }
1088
1392
  loadFrameElement() {
1089
- var _a;
1090
- const destinationRange = document.createRange();
1091
- destinationRange.selectNodeContents(this.currentElement);
1092
- destinationRange.deleteContents();
1093
- const frameElement = this.newElement;
1094
- const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1095
- if (sourceRange) {
1096
- sourceRange.selectNodeContents(frameElement);
1097
- this.currentElement.appendChild(sourceRange.extractContents());
1098
- }
1393
+ this.delegate.willRenderFrame(this.currentElement, this.newElement);
1394
+ this.renderElement(this.currentElement, this.newElement);
1099
1395
  }
1100
1396
  scrollFrameIntoView() {
1101
1397
  if (this.currentElement.autoscroll || this.newElement.autoscroll) {
1102
1398
  const element = this.currentElement.firstElementChild;
1103
1399
  const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
1400
+ const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
1104
1401
  if (element) {
1105
- element.scrollIntoView({ block });
1402
+ element.scrollIntoView({ block, behavior });
1106
1403
  return true;
1107
1404
  }
1108
1405
  }
@@ -1110,7 +1407,7 @@ class FrameRenderer extends Renderer {
1110
1407
  }
1111
1408
  activateScriptElements() {
1112
1409
  for (const inertScriptElement of this.newScriptElements) {
1113
- const activatedScriptElement = this.createScriptElement(inertScriptElement);
1410
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
1114
1411
  inertScriptElement.replaceWith(activatedScriptElement);
1115
1412
  }
1116
1413
  }
@@ -1126,20 +1423,16 @@ function readScrollLogicalPosition(value, defaultValue) {
1126
1423
  return defaultValue;
1127
1424
  }
1128
1425
  }
1426
+ function readScrollBehavior(value, defaultValue) {
1427
+ if (value == "auto" || value == "smooth") {
1428
+ return value;
1429
+ }
1430
+ else {
1431
+ return defaultValue;
1432
+ }
1433
+ }
1129
1434
 
1130
1435
  class ProgressBar {
1131
- constructor() {
1132
- this.hiding = false;
1133
- this.value = 0;
1134
- this.visible = false;
1135
- this.trickle = () => {
1136
- this.setValue(this.value + Math.random() / 100);
1137
- };
1138
- this.stylesheetElement = this.createStylesheetElement();
1139
- this.progressElement = this.createProgressElement();
1140
- this.installStylesheetElement();
1141
- this.setValue(0);
1142
- }
1143
1436
  static get defaultCSS() {
1144
1437
  return unindent `
1145
1438
  .turbo-progress-bar {
@@ -1149,7 +1442,7 @@ class ProgressBar {
1149
1442
  left: 0;
1150
1443
  height: 3px;
1151
1444
  background: #0076ff;
1152
- z-index: 9999;
1445
+ z-index: 2147483647;
1153
1446
  transition:
1154
1447
  width ${ProgressBar.animationDuration}ms ease-out,
1155
1448
  opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
@@ -1157,6 +1450,18 @@ class ProgressBar {
1157
1450
  }
1158
1451
  `;
1159
1452
  }
1453
+ constructor() {
1454
+ this.hiding = false;
1455
+ this.value = 0;
1456
+ this.visible = false;
1457
+ this.trickle = () => {
1458
+ this.setValue(this.value + Math.random() / 100);
1459
+ };
1460
+ this.stylesheetElement = this.createStylesheetElement();
1461
+ this.progressElement = this.createProgressElement();
1462
+ this.installStylesheetElement();
1463
+ this.setValue(0);
1464
+ }
1160
1465
  show() {
1161
1466
  if (!this.visible) {
1162
1467
  this.visible = true;
@@ -1208,13 +1513,16 @@ class ProgressBar {
1208
1513
  }
1209
1514
  refresh() {
1210
1515
  requestAnimationFrame(() => {
1211
- this.progressElement.style.width = `${10 + (this.value * 90)}%`;
1516
+ this.progressElement.style.width = `${10 + this.value * 90}%`;
1212
1517
  });
1213
1518
  }
1214
1519
  createStylesheetElement() {
1215
1520
  const element = document.createElement("style");
1216
1521
  element.type = "text/css";
1217
1522
  element.textContent = ProgressBar.defaultCSS;
1523
+ if (this.cspNonce) {
1524
+ element.nonce = this.cspNonce;
1525
+ }
1218
1526
  return element;
1219
1527
  }
1220
1528
  createProgressElement() {
@@ -1222,6 +1530,9 @@ class ProgressBar {
1222
1530
  element.className = "turbo-progress-bar";
1223
1531
  return element;
1224
1532
  }
1533
+ get cspNonce() {
1534
+ return getMetaContent("csp-nonce");
1535
+ }
1225
1536
  }
1226
1537
  ProgressBar.animationDuration = 300;
1227
1538
 
@@ -1238,14 +1549,14 @@ class HeadSnapshot extends Snapshot {
1238
1549
  : {
1239
1550
  type: elementType(element),
1240
1551
  tracked: elementIsTracked(element),
1241
- elements: []
1552
+ elements: [],
1242
1553
  };
1243
1554
  return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });
1244
1555
  }, {});
1245
1556
  }
1246
1557
  get trackedElementSignature() {
1247
1558
  return Object.keys(this.detailsByOuterHTML)
1248
- .filter(outerHTML => this.detailsByOuterHTML[outerHTML].tracked)
1559
+ .filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked)
1249
1560
  .join("");
1250
1561
  }
1251
1562
  getScriptElementsNotInSnapshot(snapshot) {
@@ -1256,8 +1567,8 @@ class HeadSnapshot extends Snapshot {
1256
1567
  }
1257
1568
  getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
1258
1569
  return Object.keys(this.detailsByOuterHTML)
1259
- .filter(outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))
1260
- .map(outerHTML => this.detailsByOuterHTML[outerHTML])
1570
+ .filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML))
1571
+ .map((outerHTML) => this.detailsByOuterHTML[outerHTML])
1261
1572
  .filter(({ type }) => type == matchedType)
1262
1573
  .map(({ elements: [element] }) => element);
1263
1574
  }
@@ -1277,13 +1588,11 @@ class HeadSnapshot extends Snapshot {
1277
1588
  }
1278
1589
  getMetaValue(name) {
1279
1590
  const element = this.findMetaElementByName(name);
1280
- return element
1281
- ? element.getAttribute("content")
1282
- : null;
1591
+ return element ? element.getAttribute("content") : null;
1283
1592
  }
1284
1593
  findMetaElementByName(name) {
1285
1594
  return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
1286
- const { elements: [element] } = this.detailsByOuterHTML[outerHTML];
1595
+ const { elements: [element], } = this.detailsByOuterHTML[outerHTML];
1287
1596
  return elementIsMetaElementWithName(element, name) ? element : result;
1288
1597
  }, undefined);
1289
1598
  }
@@ -1300,19 +1609,19 @@ function elementIsTracked(element) {
1300
1609
  return element.getAttribute("data-turbo-track") == "reload";
1301
1610
  }
1302
1611
  function elementIsScript(element) {
1303
- const tagName = element.tagName.toLowerCase();
1612
+ const tagName = element.localName;
1304
1613
  return tagName == "script";
1305
1614
  }
1306
1615
  function elementIsNoscript(element) {
1307
- const tagName = element.tagName.toLowerCase();
1616
+ const tagName = element.localName;
1308
1617
  return tagName == "noscript";
1309
1618
  }
1310
1619
  function elementIsStylesheet(element) {
1311
- const tagName = element.tagName.toLowerCase();
1620
+ const tagName = element.localName;
1312
1621
  return tagName == "style" || (tagName == "link" && element.getAttribute("rel") == "stylesheet");
1313
1622
  }
1314
1623
  function elementIsMetaElementWithName(element, name) {
1315
- const tagName = element.tagName.toLowerCase();
1624
+ const tagName = element.localName;
1316
1625
  return tagName == "meta" && element.getAttribute("name") == name;
1317
1626
  }
1318
1627
  function elementWithoutNonce(element) {
@@ -1323,10 +1632,6 @@ function elementWithoutNonce(element) {
1323
1632
  }
1324
1633
 
1325
1634
  class PageSnapshot extends Snapshot {
1326
- constructor(element, headSnapshot) {
1327
- super(element);
1328
- this.headSnapshot = headSnapshot;
1329
- }
1330
1635
  static fromHTMLString(html = "") {
1331
1636
  return this.fromDocument(parseHTMLDocument(html));
1332
1637
  }
@@ -1336,8 +1641,25 @@ class PageSnapshot extends Snapshot {
1336
1641
  static fromDocument({ head, body }) {
1337
1642
  return new this(body, new HeadSnapshot(head));
1338
1643
  }
1644
+ constructor(element, headSnapshot) {
1645
+ super(element);
1646
+ this.headSnapshot = headSnapshot;
1647
+ }
1339
1648
  clone() {
1340
- return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
1649
+ const clonedElement = this.element.cloneNode(true);
1650
+ const selectElements = this.element.querySelectorAll("select");
1651
+ const clonedSelectElements = clonedElement.querySelectorAll("select");
1652
+ for (const [index, source] of selectElements.entries()) {
1653
+ const clone = clonedSelectElements[index];
1654
+ for (const option of clone.selectedOptions)
1655
+ option.selected = false;
1656
+ for (const option of source.selectedOptions)
1657
+ clone.options[option.index].selected = true;
1658
+ }
1659
+ for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
1660
+ clonedPasswordInput.value = "";
1661
+ }
1662
+ return new PageSnapshot(clonedElement, this.headSnapshot);
1341
1663
  }
1342
1664
  get headElement() {
1343
1665
  return this.headSnapshot.element;
@@ -1384,6 +1706,9 @@ const defaultOptions = {
1384
1706
  historyChanged: false,
1385
1707
  visitCachedSnapshot: () => { },
1386
1708
  willRender: true,
1709
+ updateHistory: true,
1710
+ shouldCacheSnapshot: true,
1711
+ acceptsStreamResponse: false,
1387
1712
  };
1388
1713
  var SystemStatusCode;
1389
1714
  (function (SystemStatusCode) {
@@ -1398,21 +1723,27 @@ class Visit {
1398
1723
  this.followedRedirect = false;
1399
1724
  this.historyChanged = false;
1400
1725
  this.scrolled = false;
1726
+ this.shouldCacheSnapshot = true;
1727
+ this.acceptsStreamResponse = false;
1401
1728
  this.snapshotCached = false;
1402
1729
  this.state = VisitState.initialized;
1403
1730
  this.delegate = delegate;
1404
1731
  this.location = location;
1405
1732
  this.restorationIdentifier = restorationIdentifier || uuid();
1406
- const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender } = Object.assign(Object.assign({}, defaultOptions), options);
1733
+ const { action, historyChanged, referrer, snapshot, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, } = Object.assign(Object.assign({}, defaultOptions), options);
1407
1734
  this.action = action;
1408
1735
  this.historyChanged = historyChanged;
1409
1736
  this.referrer = referrer;
1737
+ this.snapshot = snapshot;
1410
1738
  this.snapshotHTML = snapshotHTML;
1411
1739
  this.response = response;
1412
1740
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
1413
1741
  this.visitCachedSnapshot = visitCachedSnapshot;
1414
1742
  this.willRender = willRender;
1743
+ this.updateHistory = updateHistory;
1415
1744
  this.scrolled = !willRender;
1745
+ this.shouldCacheSnapshot = shouldCacheSnapshot;
1746
+ this.acceptsStreamResponse = acceptsStreamResponse;
1416
1747
  }
1417
1748
  get adapter() {
1418
1749
  return this.delegate.adapter;
@@ -1450,9 +1781,11 @@ class Visit {
1450
1781
  if (this.state == VisitState.started) {
1451
1782
  this.recordTimingMetric(TimingMetric.visitEnd);
1452
1783
  this.state = VisitState.completed;
1453
- this.adapter.visitCompleted(this);
1454
- this.delegate.visitCompleted(this);
1455
1784
  this.followRedirect();
1785
+ if (!this.followedRedirect) {
1786
+ this.adapter.visitCompleted(this);
1787
+ this.delegate.visitCompleted(this);
1788
+ }
1456
1789
  }
1457
1790
  }
1458
1791
  fail() {
@@ -1463,9 +1796,9 @@ class Visit {
1463
1796
  }
1464
1797
  changeHistory() {
1465
1798
  var _a;
1466
- if (!this.historyChanged) {
1799
+ if (!this.historyChanged && this.updateHistory) {
1467
1800
  const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
1468
- const method = this.getHistoryMethodForAction(actionForHistory);
1801
+ const method = getHistoryMethodForAction(actionForHistory);
1469
1802
  this.history.update(method, this.location, this.restorationIdentifier);
1470
1803
  this.historyChanged = true;
1471
1804
  }
@@ -1510,16 +1843,18 @@ class Visit {
1510
1843
  if (this.response) {
1511
1844
  const { statusCode, responseHTML } = this.response;
1512
1845
  this.render(async () => {
1513
- this.cacheSnapshot();
1846
+ if (this.shouldCacheSnapshot)
1847
+ this.cacheSnapshot();
1514
1848
  if (this.view.renderPromise)
1515
1849
  await this.view.renderPromise;
1516
1850
  if (isSuccessful(statusCode) && responseHTML != null) {
1517
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
1851
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
1852
+ this.performScroll();
1518
1853
  this.adapter.visitRendered(this);
1519
1854
  this.complete();
1520
1855
  }
1521
1856
  else {
1522
- await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
1857
+ await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
1523
1858
  this.adapter.visitRendered(this);
1524
1859
  this.fail();
1525
1860
  }
@@ -1554,7 +1889,8 @@ class Visit {
1554
1889
  else {
1555
1890
  if (this.view.renderPromise)
1556
1891
  await this.view.renderPromise;
1557
- await this.view.renderPage(snapshot, isPreview, this.willRender);
1892
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
1893
+ this.performScroll();
1558
1894
  this.adapter.visitRendered(this);
1559
1895
  if (!isPreview) {
1560
1896
  this.complete();
@@ -1567,8 +1903,10 @@ class Visit {
1567
1903
  var _a;
1568
1904
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1569
1905
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1570
- action: 'replace',
1571
- response: this.response
1906
+ action: "replace",
1907
+ response: this.response,
1908
+ shouldCacheSnapshot: false,
1909
+ willRender: false,
1572
1910
  });
1573
1911
  this.followedRedirect = true;
1574
1912
  }
@@ -1577,20 +1915,29 @@ class Visit {
1577
1915
  if (this.isSamePage) {
1578
1916
  this.render(async () => {
1579
1917
  this.cacheSnapshot();
1918
+ this.performScroll();
1919
+ this.changeHistory();
1580
1920
  this.adapter.visitRendered(this);
1581
1921
  });
1582
1922
  }
1583
1923
  }
1924
+ prepareRequest(request) {
1925
+ if (this.acceptsStreamResponse) {
1926
+ request.acceptResponseType(StreamMessage.contentType);
1927
+ }
1928
+ }
1584
1929
  requestStarted() {
1585
1930
  this.startRequest();
1586
1931
  }
1587
- requestPreventedHandlingResponse(request, response) {
1588
- }
1932
+ requestPreventedHandlingResponse(_request, _response) { }
1589
1933
  async requestSucceededWithResponse(request, response) {
1590
1934
  const responseHTML = await response.responseHTML;
1591
1935
  const { redirected, statusCode } = response;
1592
1936
  if (responseHTML == undefined) {
1593
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1937
+ this.recordResponse({
1938
+ statusCode: SystemStatusCode.contentTypeMismatch,
1939
+ redirected,
1940
+ });
1594
1941
  }
1595
1942
  else {
1596
1943
  this.redirectedToLocation = response.redirected ? response.location : undefined;
@@ -1601,20 +1948,26 @@ class Visit {
1601
1948
  const responseHTML = await response.responseHTML;
1602
1949
  const { redirected, statusCode } = response;
1603
1950
  if (responseHTML == undefined) {
1604
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1951
+ this.recordResponse({
1952
+ statusCode: SystemStatusCode.contentTypeMismatch,
1953
+ redirected,
1954
+ });
1605
1955
  }
1606
1956
  else {
1607
1957
  this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
1608
1958
  }
1609
1959
  }
1610
- requestErrored(request, error) {
1611
- this.recordResponse({ statusCode: SystemStatusCode.networkFailure, redirected: false });
1960
+ requestErrored(_request, _error) {
1961
+ this.recordResponse({
1962
+ statusCode: SystemStatusCode.networkFailure,
1963
+ redirected: false,
1964
+ });
1612
1965
  }
1613
1966
  requestFinished() {
1614
1967
  this.finishRequest();
1615
1968
  }
1616
1969
  performScroll() {
1617
- if (!this.scrolled) {
1970
+ if (!this.scrolled && !this.view.forceReloaded) {
1618
1971
  if (this.action == "restore") {
1619
1972
  this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
1620
1973
  }
@@ -1649,9 +2002,11 @@ class Visit {
1649
2002
  }
1650
2003
  getHistoryMethodForAction(action) {
1651
2004
  switch (action) {
1652
- case "replace": return history.replaceState;
2005
+ case "replace":
2006
+ return history.replaceState;
1653
2007
  case "advance":
1654
- case "restore": return history.pushState;
2008
+ case "restore":
2009
+ return history.pushState;
1655
2010
  }
1656
2011
  }
1657
2012
  hasPreloadedResponse() {
@@ -1670,18 +2025,17 @@ class Visit {
1670
2025
  }
1671
2026
  cacheSnapshot() {
1672
2027
  if (!this.snapshotCached) {
1673
- this.view.cacheSnapshot().then(snapshot => snapshot && this.visitCachedSnapshot(snapshot));
2028
+ this.view.cacheSnapshot(this.snapshot).then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot));
1674
2029
  this.snapshotCached = true;
1675
2030
  }
1676
2031
  }
1677
2032
  async render(callback) {
1678
2033
  this.cancelRender();
1679
- await new Promise(resolve => {
2034
+ await new Promise((resolve) => {
1680
2035
  this.frame = requestAnimationFrame(() => resolve());
1681
2036
  });
1682
2037
  await callback();
1683
2038
  delete this.frame;
1684
- this.performScroll();
1685
2039
  }
1686
2040
  cancelRender() {
1687
2041
  if (this.frame) {
@@ -1696,19 +2050,19 @@ function isSuccessful(statusCode) {
1696
2050
 
1697
2051
  class BrowserAdapter {
1698
2052
  constructor(session) {
1699
- this.progressBar = new ProgressBar;
2053
+ this.progressBar = new ProgressBar();
1700
2054
  this.showProgressBar = () => {
1701
2055
  this.progressBar.show();
1702
2056
  };
1703
2057
  this.session = session;
1704
2058
  }
1705
2059
  visitProposedToLocation(location, options) {
1706
- this.navigator.startVisit(location, uuid(), options);
2060
+ this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
1707
2061
  }
1708
2062
  visitStarted(visit) {
2063
+ this.location = visit.location;
1709
2064
  visit.loadCachedSnapshot();
1710
2065
  visit.issueRequest();
1711
- visit.changeHistory();
1712
2066
  visit.goToSamePageAnchor();
1713
2067
  }
1714
2068
  visitRequestStarted(visit) {
@@ -1728,29 +2082,31 @@ class BrowserAdapter {
1728
2082
  case SystemStatusCode.networkFailure:
1729
2083
  case SystemStatusCode.timeoutFailure:
1730
2084
  case SystemStatusCode.contentTypeMismatch:
1731
- return this.reload();
2085
+ return this.reload({
2086
+ reason: "request_failed",
2087
+ context: {
2088
+ statusCode,
2089
+ },
2090
+ });
1732
2091
  default:
1733
2092
  return visit.loadResponse();
1734
2093
  }
1735
2094
  }
1736
- visitRequestFinished(visit) {
2095
+ visitRequestFinished(_visit) {
1737
2096
  this.progressBar.setValue(1);
1738
2097
  this.hideVisitProgressBar();
1739
2098
  }
1740
- visitCompleted(visit) {
1741
- }
1742
- pageInvalidated() {
1743
- this.reload();
2099
+ visitCompleted(_visit) { }
2100
+ pageInvalidated(reason) {
2101
+ this.reload(reason);
1744
2102
  }
1745
- visitFailed(visit) {
1746
- }
1747
- visitRendered(visit) {
1748
- }
1749
- formSubmissionStarted(formSubmission) {
2103
+ visitFailed(_visit) { }
2104
+ visitRendered(_visit) { }
2105
+ formSubmissionStarted(_formSubmission) {
1750
2106
  this.progressBar.setValue(0);
1751
2107
  this.showFormProgressBarAfterDelay();
1752
2108
  }
1753
- formSubmissionFinished(formSubmission) {
2109
+ formSubmissionFinished(_formSubmission) {
1754
2110
  this.progressBar.setValue(1);
1755
2111
  this.hideFormProgressBar();
1756
2112
  }
@@ -1776,8 +2132,10 @@ class BrowserAdapter {
1776
2132
  delete this.formProgressBarTimeout;
1777
2133
  }
1778
2134
  }
1779
- reload() {
1780
- window.location.reload();
2135
+ reload(reason) {
2136
+ var _a;
2137
+ dispatch("turbo:reload", { detail: reason });
2138
+ window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;
1781
2139
  }
1782
2140
  get navigator() {
1783
2141
  return this.session.navigator;
@@ -1786,95 +2144,72 @@ class BrowserAdapter {
1786
2144
 
1787
2145
  class CacheObserver {
1788
2146
  constructor() {
2147
+ this.selector = "[data-turbo-temporary]";
2148
+ this.deprecatedSelector = "[data-turbo-cache=false]";
1789
2149
  this.started = false;
2150
+ this.removeTemporaryElements = ((_event) => {
2151
+ for (const element of this.temporaryElements) {
2152
+ element.remove();
2153
+ }
2154
+ });
1790
2155
  }
1791
2156
  start() {
1792
2157
  if (!this.started) {
1793
2158
  this.started = true;
1794
- addEventListener("turbo:before-cache", this.removeStaleElements, false);
2159
+ addEventListener("turbo:before-cache", this.removeTemporaryElements, false);
1795
2160
  }
1796
2161
  }
1797
2162
  stop() {
1798
2163
  if (this.started) {
1799
2164
  this.started = false;
1800
- removeEventListener("turbo:before-cache", this.removeStaleElements, false);
2165
+ removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
1801
2166
  }
1802
2167
  }
1803
- removeStaleElements() {
1804
- const staleElements = [...document.querySelectorAll('[data-turbo-cache="false"]')];
1805
- for (const element of staleElements) {
1806
- element.remove();
1807
- }
2168
+ get temporaryElements() {
2169
+ return [...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation];
1808
2170
  }
1809
- }
1810
-
1811
- class FormSubmitObserver {
1812
- constructor(delegate) {
1813
- this.started = false;
1814
- this.submitCaptured = () => {
1815
- removeEventListener("submit", this.submitBubbled, false);
1816
- addEventListener("submit", this.submitBubbled, false);
1817
- };
1818
- this.submitBubbled = ((event) => {
1819
- if (!event.defaultPrevented) {
1820
- const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1821
- const submitter = event.submitter || undefined;
1822
- if (form) {
1823
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
1824
- if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
1825
- event.preventDefault();
1826
- this.delegate.formSubmitted(form, submitter);
1827
- }
1828
- }
1829
- }
1830
- });
1831
- this.delegate = delegate;
1832
- }
1833
- start() {
1834
- if (!this.started) {
1835
- addEventListener("submit", this.submitCaptured, true);
1836
- this.started = true;
1837
- }
1838
- }
1839
- stop() {
1840
- if (this.started) {
1841
- removeEventListener("submit", this.submitCaptured, true);
1842
- this.started = false;
2171
+ get temporaryElementsWithDeprecation() {
2172
+ const elements = document.querySelectorAll(this.deprecatedSelector);
2173
+ if (elements.length) {
2174
+ console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);
1843
2175
  }
2176
+ return [...elements];
1844
2177
  }
1845
2178
  }
1846
2179
 
1847
2180
  class FrameRedirector {
1848
- constructor(element) {
2181
+ constructor(session, element) {
2182
+ this.session = session;
1849
2183
  this.element = element;
1850
2184
  this.linkInterceptor = new LinkInterceptor(this, element);
1851
- this.formInterceptor = new FormInterceptor(this, element);
2185
+ this.formSubmitObserver = new FormSubmitObserver(this, element);
1852
2186
  }
1853
2187
  start() {
1854
2188
  this.linkInterceptor.start();
1855
- this.formInterceptor.start();
2189
+ this.formSubmitObserver.start();
1856
2190
  }
1857
2191
  stop() {
1858
2192
  this.linkInterceptor.stop();
1859
- this.formInterceptor.stop();
2193
+ this.formSubmitObserver.stop();
1860
2194
  }
1861
- shouldInterceptLinkClick(element, url) {
2195
+ shouldInterceptLinkClick(element, _location, _event) {
1862
2196
  return this.shouldRedirect(element);
1863
2197
  }
1864
- linkClickIntercepted(element, url) {
2198
+ linkClickIntercepted(element, url, event) {
1865
2199
  const frame = this.findFrameElement(element);
1866
2200
  if (frame) {
1867
- frame.delegate.linkClickIntercepted(element, url);
2201
+ frame.delegate.linkClickIntercepted(element, url, event);
1868
2202
  }
1869
2203
  }
1870
- shouldInterceptFormSubmission(element, submitter) {
1871
- return this.shouldSubmit(element, submitter);
2204
+ willSubmitForm(element, submitter) {
2205
+ return (element.closest("turbo-frame") == null &&
2206
+ this.shouldSubmit(element, submitter) &&
2207
+ this.shouldRedirect(element, submitter));
1872
2208
  }
1873
- formSubmissionIntercepted(element, submitter) {
2209
+ formSubmitted(element, submitter) {
1874
2210
  const frame = this.findFrameElement(element, submitter);
1875
2211
  if (frame) {
1876
- frame.removeAttribute("reloadable");
1877
- frame.delegate.formSubmissionIntercepted(element, submitter);
2212
+ frame.delegate.formSubmitted(element, submitter);
1878
2213
  }
1879
2214
  }
1880
2215
  shouldSubmit(form, submitter) {
@@ -1885,8 +2220,16 @@ class FrameRedirector {
1885
2220
  return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
1886
2221
  }
1887
2222
  shouldRedirect(element, submitter) {
1888
- const frame = this.findFrameElement(element, submitter);
1889
- return frame ? frame != element.closest("turbo-frame") : false;
2223
+ const isNavigatable = element instanceof HTMLFormElement
2224
+ ? this.session.submissionIsNavigatable(element, submitter)
2225
+ : this.session.elementIsNavigatable(element);
2226
+ if (isNavigatable) {
2227
+ const frame = this.findFrameElement(element, submitter);
2228
+ return frame ? frame != element.closest("turbo-frame") : false;
2229
+ }
2230
+ else {
2231
+ return false;
2232
+ }
1890
2233
  }
1891
2234
  findFrameElement(element, submitter) {
1892
2235
  const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
@@ -1916,7 +2259,7 @@ class History {
1916
2259
  }
1917
2260
  }
1918
2261
  };
1919
- this.onPageLoad = async (event) => {
2262
+ this.onPageLoad = async (_event) => {
1920
2263
  await nextMicrotask();
1921
2264
  this.pageLoaded = true;
1922
2265
  };
@@ -1978,63 +2321,6 @@ class History {
1978
2321
  }
1979
2322
  }
1980
2323
 
1981
- class LinkClickObserver {
1982
- constructor(delegate) {
1983
- this.started = false;
1984
- this.clickCaptured = () => {
1985
- removeEventListener("click", this.clickBubbled, false);
1986
- addEventListener("click", this.clickBubbled, false);
1987
- };
1988
- this.clickBubbled = (event) => {
1989
- if (this.clickEventIsSignificant(event)) {
1990
- const target = (event.composedPath && event.composedPath()[0]) || event.target;
1991
- const link = this.findLinkFromClickTarget(target);
1992
- if (link) {
1993
- const location = this.getLocationForLink(link);
1994
- if (this.delegate.willFollowLinkToLocation(link, location)) {
1995
- event.preventDefault();
1996
- this.delegate.followedLinkToLocation(link, location);
1997
- }
1998
- }
1999
- }
2000
- };
2001
- this.delegate = delegate;
2002
- }
2003
- start() {
2004
- if (!this.started) {
2005
- addEventListener("click", this.clickCaptured, true);
2006
- this.started = true;
2007
- }
2008
- }
2009
- stop() {
2010
- if (this.started) {
2011
- removeEventListener("click", this.clickCaptured, true);
2012
- this.started = false;
2013
- }
2014
- }
2015
- clickEventIsSignificant(event) {
2016
- return !((event.target && event.target.isContentEditable)
2017
- || event.defaultPrevented
2018
- || event.which > 1
2019
- || event.altKey
2020
- || event.ctrlKey
2021
- || event.metaKey
2022
- || event.shiftKey);
2023
- }
2024
- findLinkFromClickTarget(target) {
2025
- if (target instanceof Element) {
2026
- return target.closest("a[href]:not([target^=_]):not([download])");
2027
- }
2028
- }
2029
- getLocationForLink(link) {
2030
- return expandURL(link.getAttribute("href") || "");
2031
- }
2032
- }
2033
-
2034
- function isAction(action) {
2035
- return action == "advance" || action == "replace" || action == "restore";
2036
- }
2037
-
2038
2324
  class Navigator {
2039
2325
  constructor(delegate) {
2040
2326
  this.delegate = delegate;
@@ -2079,7 +2365,7 @@ class Navigator {
2079
2365
  return this.delegate.history;
2080
2366
  }
2081
2367
  formSubmissionStarted(formSubmission) {
2082
- if (typeof this.adapter.formSubmissionStarted === 'function') {
2368
+ if (typeof this.adapter.formSubmissionStarted === "function") {
2083
2369
  this.adapter.formSubmissionStarted(formSubmission);
2084
2370
  }
2085
2371
  }
@@ -2087,12 +2373,17 @@ class Navigator {
2087
2373
  if (formSubmission == this.formSubmission) {
2088
2374
  const responseHTML = await fetchResponse.responseHTML;
2089
2375
  if (responseHTML) {
2090
- if (formSubmission.method != FetchMethod.get) {
2376
+ const shouldCacheSnapshot = formSubmission.isSafe;
2377
+ if (!shouldCacheSnapshot) {
2091
2378
  this.view.clearSnapshotCache();
2092
2379
  }
2093
2380
  const { statusCode, redirected } = fetchResponse;
2094
2381
  const action = this.getActionForFormSubmission(formSubmission);
2095
- const visitOptions = { action, response: { statusCode, responseHTML, redirected } };
2382
+ const visitOptions = {
2383
+ action,
2384
+ shouldCacheSnapshot,
2385
+ response: { statusCode, responseHTML, redirected },
2386
+ };
2096
2387
  this.proposeVisit(fetchResponse.location, visitOptions);
2097
2388
  }
2098
2389
  }
@@ -2102,10 +2393,10 @@ class Navigator {
2102
2393
  if (responseHTML) {
2103
2394
  const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2104
2395
  if (fetchResponse.serverError) {
2105
- await this.view.renderError(snapshot);
2396
+ await this.view.renderError(snapshot, this.currentVisit);
2106
2397
  }
2107
2398
  else {
2108
- await this.view.renderPage(snapshot);
2399
+ await this.view.renderPage(snapshot, false, true, this.currentVisit);
2109
2400
  }
2110
2401
  this.view.scrollToTop();
2111
2402
  this.view.clearSnapshotCache();
@@ -2115,7 +2406,7 @@ class Navigator {
2115
2406
  console.error(error);
2116
2407
  }
2117
2408
  formSubmissionFinished(formSubmission) {
2118
- if (typeof this.adapter.formSubmissionFinished === 'function') {
2409
+ if (typeof this.adapter.formSubmissionFinished === "function") {
2119
2410
  this.adapter.formSubmissionFinished(formSubmission);
2120
2411
  }
2121
2412
  }
@@ -2128,10 +2419,10 @@ class Navigator {
2128
2419
  locationWithActionIsSamePage(location, action) {
2129
2420
  const anchor = getAnchor(location);
2130
2421
  const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2131
- const isRestorationToTop = action === 'restore' && typeof anchor === 'undefined';
2132
- return action !== "replace" &&
2422
+ const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2423
+ return (action !== "replace" &&
2133
2424
  getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&
2134
- (isRestorationToTop || (anchor != null && anchor !== currentAnchor));
2425
+ (isRestorationToTop || (anchor != null && anchor !== currentAnchor)));
2135
2426
  }
2136
2427
  visitScrolledToSamePageLocation(oldURL, newURL) {
2137
2428
  this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
@@ -2142,10 +2433,8 @@ class Navigator {
2142
2433
  get restorationIdentifier() {
2143
2434
  return this.history.restorationIdentifier;
2144
2435
  }
2145
- getActionForFormSubmission(formSubmission) {
2146
- const { formElement, submitter } = formSubmission;
2147
- const action = getAttribute("data-turbo-action", submitter, formElement);
2148
- return isAction(action) ? action : "advance";
2436
+ getActionForFormSubmission({ submitter, formElement }) {
2437
+ return getVisitAction(submitter, formElement) || "advance";
2149
2438
  }
2150
2439
  }
2151
2440
 
@@ -2235,9 +2524,33 @@ class ScrollObserver {
2235
2524
  }
2236
2525
  }
2237
2526
 
2527
+ class StreamMessageRenderer {
2528
+ render({ fragment }) {
2529
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => document.documentElement.appendChild(fragment));
2530
+ }
2531
+ enteringBardo(currentPermanentElement, newPermanentElement) {
2532
+ newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
2533
+ }
2534
+ leavingBardo() { }
2535
+ }
2536
+ function getPermanentElementMapForFragment(fragment) {
2537
+ const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
2538
+ const permanentElementMap = {};
2539
+ for (const permanentElementInDocument of permanentElementsInDocument) {
2540
+ const { id } = permanentElementInDocument;
2541
+ for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
2542
+ const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
2543
+ if (elementInStream) {
2544
+ permanentElementMap[id] = [permanentElementInDocument, elementInStream];
2545
+ }
2546
+ }
2547
+ }
2548
+ return permanentElementMap;
2549
+ }
2550
+
2238
2551
  class StreamObserver {
2239
2552
  constructor(delegate) {
2240
- this.sources = new Set;
2553
+ this.sources = new Set();
2241
2554
  this.started = false;
2242
2555
  this.inspectFetchResponse = ((event) => {
2243
2556
  const response = fetchResponseFromEvent(event);
@@ -2287,7 +2600,7 @@ class StreamObserver {
2287
2600
  }
2288
2601
  }
2289
2602
  receiveMessageHTML(html) {
2290
- this.delegate.receivedMessageFromStream(new StreamMessage(html));
2603
+ this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
2291
2604
  }
2292
2605
  }
2293
2606
  function fetchResponseFromEvent(event) {
@@ -2304,20 +2617,24 @@ function fetchResponseIsStream(response) {
2304
2617
  }
2305
2618
 
2306
2619
  class ErrorRenderer extends Renderer {
2620
+ static renderElement(currentElement, newElement) {
2621
+ const { documentElement, body } = document;
2622
+ documentElement.replaceChild(newElement, body);
2623
+ }
2307
2624
  async render() {
2308
2625
  this.replaceHeadAndBody();
2309
2626
  this.activateScriptElements();
2310
2627
  }
2311
2628
  replaceHeadAndBody() {
2312
- const { documentElement, head, body } = document;
2629
+ const { documentElement, head } = document;
2313
2630
  documentElement.replaceChild(this.newHead, head);
2314
- documentElement.replaceChild(this.newElement, body);
2631
+ this.renderElement(this.currentElement, this.newElement);
2315
2632
  }
2316
2633
  activateScriptElements() {
2317
2634
  for (const replaceableElement of this.scriptElements) {
2318
2635
  const parentNode = replaceableElement.parentNode;
2319
2636
  if (parentNode) {
2320
- const element = this.createScriptElement(replaceableElement);
2637
+ const element = activateScriptElement(replaceableElement);
2321
2638
  parentNode.replaceChild(element, replaceableElement);
2322
2639
  }
2323
2640
  }
@@ -2326,20 +2643,40 @@ class ErrorRenderer extends Renderer {
2326
2643
  return this.newSnapshot.headSnapshot.element;
2327
2644
  }
2328
2645
  get scriptElements() {
2329
- return [...document.documentElement.querySelectorAll("script")];
2646
+ return document.documentElement.querySelectorAll("script");
2330
2647
  }
2331
2648
  }
2332
2649
 
2333
2650
  class PageRenderer extends Renderer {
2651
+ static renderElement(currentElement, newElement) {
2652
+ if (document.body && newElement instanceof HTMLBodyElement) {
2653
+ document.body.replaceWith(newElement);
2654
+ }
2655
+ else {
2656
+ document.documentElement.appendChild(newElement);
2657
+ }
2658
+ }
2334
2659
  get shouldRender() {
2335
2660
  return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
2336
2661
  }
2337
- prepareToRender() {
2338
- this.mergeHead();
2662
+ get reloadReason() {
2663
+ if (!this.newSnapshot.isVisitable) {
2664
+ return {
2665
+ reason: "turbo_visit_control_is_reload",
2666
+ };
2667
+ }
2668
+ if (!this.trackedElementsAreIdentical) {
2669
+ return {
2670
+ reason: "tracked_element_mismatch",
2671
+ };
2672
+ }
2673
+ }
2674
+ async prepareToRender() {
2675
+ await this.mergeHead();
2339
2676
  }
2340
2677
  async render() {
2341
2678
  if (this.willRender) {
2342
- this.replaceBody();
2679
+ await this.replaceBody();
2343
2680
  }
2344
2681
  }
2345
2682
  finishRendering() {
@@ -2357,30 +2694,63 @@ class PageRenderer extends Renderer {
2357
2694
  get newElement() {
2358
2695
  return this.newSnapshot.element;
2359
2696
  }
2360
- mergeHead() {
2361
- this.copyNewHeadStylesheetElements();
2697
+ async mergeHead() {
2698
+ const mergedHeadElements = this.mergeProvisionalElements();
2699
+ const newStylesheetElements = this.copyNewHeadStylesheetElements();
2362
2700
  this.copyNewHeadScriptElements();
2363
- this.removeCurrentHeadProvisionalElements();
2364
- this.copyNewHeadProvisionalElements();
2701
+ await mergedHeadElements;
2702
+ await newStylesheetElements;
2365
2703
  }
2366
- replaceBody() {
2367
- this.preservingPermanentElements(() => {
2704
+ async replaceBody() {
2705
+ await this.preservingPermanentElements(async () => {
2368
2706
  this.activateNewBody();
2369
- this.assignNewBody();
2707
+ await this.assignNewBody();
2370
2708
  });
2371
2709
  }
2372
2710
  get trackedElementsAreIdentical() {
2373
2711
  return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
2374
2712
  }
2375
- copyNewHeadStylesheetElements() {
2713
+ async copyNewHeadStylesheetElements() {
2714
+ const loadingElements = [];
2376
2715
  for (const element of this.newHeadStylesheetElements) {
2716
+ loadingElements.push(waitForLoad(element));
2377
2717
  document.head.appendChild(element);
2378
2718
  }
2719
+ await Promise.all(loadingElements);
2379
2720
  }
2380
2721
  copyNewHeadScriptElements() {
2381
2722
  for (const element of this.newHeadScriptElements) {
2382
- document.head.appendChild(this.createScriptElement(element));
2723
+ document.head.appendChild(activateScriptElement(element));
2724
+ }
2725
+ }
2726
+ async mergeProvisionalElements() {
2727
+ const newHeadElements = [...this.newHeadProvisionalElements];
2728
+ for (const element of this.currentHeadProvisionalElements) {
2729
+ if (!this.isCurrentElementInElementList(element, newHeadElements)) {
2730
+ document.head.removeChild(element);
2731
+ }
2732
+ }
2733
+ for (const element of newHeadElements) {
2734
+ document.head.appendChild(element);
2735
+ }
2736
+ }
2737
+ isCurrentElementInElementList(element, elementList) {
2738
+ for (const [index, newElement] of elementList.entries()) {
2739
+ if (element.tagName == "TITLE") {
2740
+ if (newElement.tagName != "TITLE") {
2741
+ continue;
2742
+ }
2743
+ if (element.innerHTML == newElement.innerHTML) {
2744
+ elementList.splice(index, 1);
2745
+ return true;
2746
+ }
2747
+ }
2748
+ if (newElement.isEqualNode(element)) {
2749
+ elementList.splice(index, 1);
2750
+ return true;
2751
+ }
2383
2752
  }
2753
+ return false;
2384
2754
  }
2385
2755
  removeCurrentHeadProvisionalElements() {
2386
2756
  for (const element of this.currentHeadProvisionalElements) {
@@ -2398,17 +2768,12 @@ class PageRenderer extends Renderer {
2398
2768
  }
2399
2769
  activateNewBodyScriptElements() {
2400
2770
  for (const inertScriptElement of this.newBodyScriptElements) {
2401
- const activatedScriptElement = this.createScriptElement(inertScriptElement);
2771
+ const activatedScriptElement = activateScriptElement(inertScriptElement);
2402
2772
  inertScriptElement.replaceWith(activatedScriptElement);
2403
2773
  }
2404
2774
  }
2405
- assignNewBody() {
2406
- if (document.body && this.newElement instanceof HTMLBodyElement) {
2407
- document.body.replaceWith(this.newElement);
2408
- }
2409
- else {
2410
- document.documentElement.appendChild(this.newElement);
2411
- }
2775
+ async assignNewBody() {
2776
+ await this.renderElement(this.currentElement, this.newElement);
2412
2777
  }
2413
2778
  get newHeadStylesheetElements() {
2414
2779
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -2477,22 +2842,30 @@ class PageView extends View {
2477
2842
  super(...arguments);
2478
2843
  this.snapshotCache = new SnapshotCache(10);
2479
2844
  this.lastRenderedLocation = new URL(location.href);
2845
+ this.forceReloaded = false;
2480
2846
  }
2481
- renderPage(snapshot, isPreview = false, willRender = true) {
2482
- const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
2847
+ renderPage(snapshot, isPreview = false, willRender = true, visit) {
2848
+ const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
2849
+ if (!renderer.shouldRender) {
2850
+ this.forceReloaded = true;
2851
+ }
2852
+ else {
2853
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2854
+ }
2483
2855
  return this.render(renderer);
2484
2856
  }
2485
- renderError(snapshot) {
2486
- const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2857
+ renderError(snapshot, visit) {
2858
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2859
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
2487
2860
  return this.render(renderer);
2488
2861
  }
2489
2862
  clearSnapshotCache() {
2490
2863
  this.snapshotCache.clear();
2491
2864
  }
2492
- async cacheSnapshot() {
2493
- if (this.shouldCacheSnapshot) {
2865
+ async cacheSnapshot(snapshot = this.snapshot) {
2866
+ if (snapshot.isCacheable) {
2494
2867
  this.delegate.viewWillCacheSnapshot();
2495
- const { snapshot, lastRenderedLocation: location } = this;
2868
+ const { lastRenderedLocation: location } = this;
2496
2869
  await nextEventLoopTick();
2497
2870
  const cachedSnapshot = snapshot.clone();
2498
2871
  this.snapshotCache.put(location, cachedSnapshot);
@@ -2505,8 +2878,44 @@ class PageView extends View {
2505
2878
  get snapshot() {
2506
2879
  return PageSnapshot.fromElement(this.element);
2507
2880
  }
2508
- get shouldCacheSnapshot() {
2509
- return this.snapshot.isCacheable;
2881
+ }
2882
+
2883
+ class Preloader {
2884
+ constructor(delegate) {
2885
+ this.selector = "a[data-turbo-preload]";
2886
+ this.delegate = delegate;
2887
+ }
2888
+ get snapshotCache() {
2889
+ return this.delegate.navigator.view.snapshotCache;
2890
+ }
2891
+ start() {
2892
+ if (document.readyState === "loading") {
2893
+ return document.addEventListener("DOMContentLoaded", () => {
2894
+ this.preloadOnLoadLinksForView(document.body);
2895
+ });
2896
+ }
2897
+ else {
2898
+ this.preloadOnLoadLinksForView(document.body);
2899
+ }
2900
+ }
2901
+ preloadOnLoadLinksForView(element) {
2902
+ for (const link of element.querySelectorAll(this.selector)) {
2903
+ this.preloadURL(link);
2904
+ }
2905
+ }
2906
+ async preloadURL(link) {
2907
+ const location = new URL(link.href);
2908
+ if (this.snapshotCache.has(location)) {
2909
+ return;
2910
+ }
2911
+ try {
2912
+ const response = await fetch(location.toString(), { headers: { "VND.PREFETCH": "true", Accept: "text/html" } });
2913
+ const responseText = await response.text();
2914
+ const snapshot = PageSnapshot.fromHTMLString(responseText);
2915
+ this.snapshotCache.put(location, snapshot);
2916
+ }
2917
+ catch (_) {
2918
+ }
2510
2919
  }
2511
2920
  }
2512
2921
 
@@ -2514,30 +2923,36 @@ class Session {
2514
2923
  constructor() {
2515
2924
  this.navigator = new Navigator(this);
2516
2925
  this.history = new History(this);
2926
+ this.preloader = new Preloader(this);
2517
2927
  this.view = new PageView(this, document.documentElement);
2518
2928
  this.adapter = new BrowserAdapter(this);
2519
2929
  this.pageObserver = new PageObserver(this);
2520
2930
  this.cacheObserver = new CacheObserver();
2521
- this.linkClickObserver = new LinkClickObserver(this);
2522
- this.formSubmitObserver = new FormSubmitObserver(this);
2931
+ this.linkClickObserver = new LinkClickObserver(this, window);
2932
+ this.formSubmitObserver = new FormSubmitObserver(this, document);
2523
2933
  this.scrollObserver = new ScrollObserver(this);
2524
2934
  this.streamObserver = new StreamObserver(this);
2525
- this.frameRedirector = new FrameRedirector(document.documentElement);
2935
+ this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
2936
+ this.frameRedirector = new FrameRedirector(this, document.documentElement);
2937
+ this.streamMessageRenderer = new StreamMessageRenderer();
2526
2938
  this.drive = true;
2527
2939
  this.enabled = true;
2528
2940
  this.progressBarDelay = 500;
2529
2941
  this.started = false;
2942
+ this.formMode = "on";
2530
2943
  }
2531
2944
  start() {
2532
2945
  if (!this.started) {
2533
2946
  this.pageObserver.start();
2534
2947
  this.cacheObserver.start();
2948
+ this.formLinkClickObserver.start();
2535
2949
  this.linkClickObserver.start();
2536
2950
  this.formSubmitObserver.start();
2537
2951
  this.scrollObserver.start();
2538
2952
  this.streamObserver.start();
2539
2953
  this.frameRedirector.start();
2540
2954
  this.history.start();
2955
+ this.preloader.start();
2541
2956
  this.started = true;
2542
2957
  this.enabled = true;
2543
2958
  }
@@ -2549,6 +2964,7 @@ class Session {
2549
2964
  if (this.started) {
2550
2965
  this.pageObserver.stop();
2551
2966
  this.cacheObserver.stop();
2967
+ this.formLinkClickObserver.stop();
2552
2968
  this.linkClickObserver.stop();
2553
2969
  this.formSubmitObserver.stop();
2554
2970
  this.scrollObserver.stop();
@@ -2562,7 +2978,14 @@ class Session {
2562
2978
  this.adapter = adapter;
2563
2979
  }
2564
2980
  visit(location, options = {}) {
2565
- this.navigator.proposeVisit(expandURL(location), options);
2981
+ const frameElement = options.frame ? document.getElementById(options.frame) : null;
2982
+ if (frameElement instanceof FrameElement) {
2983
+ frameElement.src = location.toString();
2984
+ frameElement.loaded;
2985
+ }
2986
+ else {
2987
+ this.navigator.proposeVisit(expandURL(location), options);
2988
+ }
2566
2989
  }
2567
2990
  connectStreamSource(source) {
2568
2991
  this.streamObserver.connectStreamSource(source);
@@ -2571,7 +2994,7 @@ class Session {
2571
2994
  this.streamObserver.disconnectStreamSource(source);
2572
2995
  }
2573
2996
  renderStreamMessage(message) {
2574
- document.documentElement.appendChild(StreamMessage.wrap(message).fragment);
2997
+ this.streamMessageRenderer.render(StreamMessage.wrap(message));
2575
2998
  }
2576
2999
  clearCache() {
2577
3000
  this.view.clearSnapshotCache();
@@ -2579,6 +3002,9 @@ class Session {
2579
3002
  setProgressBarDelay(delay) {
2580
3003
  this.progressBarDelay = delay;
2581
3004
  }
3005
+ setFormMode(mode) {
3006
+ this.formMode = mode;
3007
+ }
2582
3008
  get location() {
2583
3009
  return this.history.location;
2584
3010
  }
@@ -2587,48 +3013,33 @@ class Session {
2587
3013
  }
2588
3014
  historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
2589
3015
  if (this.enabled) {
2590
- this.navigator.startVisit(location, restorationIdentifier, { action: "restore", historyChanged: true });
3016
+ this.navigator.startVisit(location, restorationIdentifier, {
3017
+ action: "restore",
3018
+ historyChanged: true,
3019
+ });
2591
3020
  }
2592
3021
  else {
2593
- this.adapter.pageInvalidated();
3022
+ this.adapter.pageInvalidated({
3023
+ reason: "turbo_disabled",
3024
+ });
2594
3025
  }
2595
3026
  }
2596
3027
  scrollPositionChanged(position) {
2597
3028
  this.history.updateRestorationData({ scrollPosition: position });
2598
3029
  }
2599
- willFollowLinkToLocation(link, location) {
2600
- return this.elementDriveEnabled(link)
2601
- && locationIsVisitable(location, this.snapshot.rootLocation)
2602
- && this.applicationAllowsFollowingLinkToLocation(link, location);
3030
+ willSubmitFormLinkToLocation(link, location) {
3031
+ return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
3032
+ }
3033
+ submittedFormLinkToLocation() { }
3034
+ willFollowLinkToLocation(link, location, event) {
3035
+ return (this.elementIsNavigatable(link) &&
3036
+ locationIsVisitable(location, this.snapshot.rootLocation) &&
3037
+ this.applicationAllowsFollowingLinkToLocation(link, location, event));
2603
3038
  }
2604
3039
  followedLinkToLocation(link, location) {
2605
3040
  const action = this.getActionForLink(link);
2606
- this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, { action });
2607
- }
2608
- convertLinkWithMethodClickToFormSubmission(link) {
2609
- const linkMethod = link.getAttribute("data-turbo-method");
2610
- if (linkMethod) {
2611
- const form = document.createElement("form");
2612
- form.method = linkMethod;
2613
- form.action = link.getAttribute("href") || "undefined";
2614
- form.hidden = true;
2615
- if (link.hasAttribute("data-turbo-confirm")) {
2616
- form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
2617
- }
2618
- const frame = this.getTargetFrameForLink(link);
2619
- if (frame) {
2620
- form.setAttribute("data-turbo-frame", frame);
2621
- form.addEventListener("turbo:submit-start", () => form.remove());
2622
- }
2623
- else {
2624
- form.addEventListener("submit", () => form.remove());
2625
- }
2626
- document.body.appendChild(form);
2627
- return dispatch("submit", { cancelable: true, target: form });
2628
- }
2629
- else {
2630
- return false;
2631
- }
3041
+ const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
3042
+ this.visit(location.href, { action, acceptsStreamResponse });
2632
3043
  }
2633
3044
  allowsVisitingLocationWithAction(location, action) {
2634
3045
  return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
@@ -2638,12 +3049,16 @@ class Session {
2638
3049
  this.adapter.visitProposedToLocation(location, options);
2639
3050
  }
2640
3051
  visitStarted(visit) {
3052
+ if (!visit.acceptsStreamResponse) {
3053
+ markAsBusy(document.documentElement);
3054
+ }
2641
3055
  extendURLWithDeprecatedProperties(visit.location);
2642
3056
  if (!visit.silent) {
2643
3057
  this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
2644
3058
  }
2645
3059
  }
2646
3060
  visitCompleted(visit) {
3061
+ clearBusyState(document.documentElement);
2647
3062
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2648
3063
  }
2649
3064
  locationWithActionIsSamePage(location, action) {
@@ -2654,9 +3069,8 @@ class Session {
2654
3069
  }
2655
3070
  willSubmitForm(form, submitter) {
2656
3071
  const action = getAction(form, submitter);
2657
- return this.elementDriveEnabled(form)
2658
- && (!submitter || this.elementDriveEnabled(submitter))
2659
- && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
3072
+ return (this.submissionIsNavigatable(form, submitter) &&
3073
+ locationIsVisitable(expandURL(action), this.snapshot.rootLocation));
2660
3074
  }
2661
3075
  formSubmitted(form, submitter) {
2662
3076
  this.navigator.submitForm(form, submitter);
@@ -2680,16 +3094,23 @@ class Session {
2680
3094
  this.notifyApplicationBeforeCachingSnapshot();
2681
3095
  }
2682
3096
  }
2683
- allowsImmediateRender({ element }, resume) {
2684
- const event = this.notifyApplicationBeforeRender(element, resume);
2685
- return !event.defaultPrevented;
3097
+ allowsImmediateRender({ element }, options) {
3098
+ const event = this.notifyApplicationBeforeRender(element, options);
3099
+ const { defaultPrevented, detail: { render }, } = event;
3100
+ if (this.view.renderer && render) {
3101
+ this.view.renderer.renderElement = render;
3102
+ }
3103
+ return !defaultPrevented;
2686
3104
  }
2687
- viewRenderedSnapshot(snapshot, isPreview) {
3105
+ viewRenderedSnapshot(_snapshot, _isPreview) {
2688
3106
  this.view.lastRenderedLocation = this.history.location;
2689
3107
  this.notifyApplicationAfterRender();
2690
3108
  }
2691
- viewInvalidated() {
2692
- this.adapter.pageInvalidated();
3109
+ preloadOnLoadLinksForView(element) {
3110
+ this.preloader.preloadOnLoadLinksForView(element);
3111
+ }
3112
+ viewInvalidated(reason) {
3113
+ this.adapter.pageInvalidated(reason);
2693
3114
  }
2694
3115
  frameLoaded(frame) {
2695
3116
  this.notifyApplicationAfterFrameLoad(frame);
@@ -2697,49 +3118,81 @@ class Session {
2697
3118
  frameRendered(fetchResponse, frame) {
2698
3119
  this.notifyApplicationAfterFrameRender(fetchResponse, frame);
2699
3120
  }
2700
- applicationAllowsFollowingLinkToLocation(link, location) {
2701
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
3121
+ applicationAllowsFollowingLinkToLocation(link, location, ev) {
3122
+ const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
2702
3123
  return !event.defaultPrevented;
2703
3124
  }
2704
3125
  applicationAllowsVisitingLocation(location) {
2705
3126
  const event = this.notifyApplicationBeforeVisitingLocation(location);
2706
3127
  return !event.defaultPrevented;
2707
3128
  }
2708
- notifyApplicationAfterClickingLinkToLocation(link, location) {
2709
- return dispatch("turbo:click", { target: link, detail: { url: location.href }, cancelable: true });
3129
+ notifyApplicationAfterClickingLinkToLocation(link, location, event) {
3130
+ return dispatch("turbo:click", {
3131
+ target: link,
3132
+ detail: { url: location.href, originalEvent: event },
3133
+ cancelable: true,
3134
+ });
2710
3135
  }
2711
3136
  notifyApplicationBeforeVisitingLocation(location) {
2712
- return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
3137
+ return dispatch("turbo:before-visit", {
3138
+ detail: { url: location.href },
3139
+ cancelable: true,
3140
+ });
2713
3141
  }
2714
3142
  notifyApplicationAfterVisitingLocation(location, action) {
2715
- markAsBusy(document.documentElement);
2716
3143
  return dispatch("turbo:visit", { detail: { url: location.href, action } });
2717
3144
  }
2718
3145
  notifyApplicationBeforeCachingSnapshot() {
2719
3146
  return dispatch("turbo:before-cache");
2720
3147
  }
2721
- notifyApplicationBeforeRender(newBody, resume) {
2722
- return dispatch("turbo:before-render", { detail: { newBody, resume }, cancelable: true });
3148
+ notifyApplicationBeforeRender(newBody, options) {
3149
+ return dispatch("turbo:before-render", {
3150
+ detail: Object.assign({ newBody }, options),
3151
+ cancelable: true,
3152
+ });
2723
3153
  }
2724
3154
  notifyApplicationAfterRender() {
2725
3155
  return dispatch("turbo:render");
2726
3156
  }
2727
3157
  notifyApplicationAfterPageLoad(timing = {}) {
2728
- clearBusyState(document.documentElement);
2729
- return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
3158
+ return dispatch("turbo:load", {
3159
+ detail: { url: this.location.href, timing },
3160
+ });
2730
3161
  }
2731
3162
  notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
2732
- dispatchEvent(new HashChangeEvent("hashchange", { oldURL: oldURL.toString(), newURL: newURL.toString() }));
3163
+ dispatchEvent(new HashChangeEvent("hashchange", {
3164
+ oldURL: oldURL.toString(),
3165
+ newURL: newURL.toString(),
3166
+ }));
2733
3167
  }
2734
3168
  notifyApplicationAfterFrameLoad(frame) {
2735
3169
  return dispatch("turbo:frame-load", { target: frame });
2736
3170
  }
2737
3171
  notifyApplicationAfterFrameRender(fetchResponse, frame) {
2738
- return dispatch("turbo:frame-render", { detail: { fetchResponse }, target: frame, cancelable: true });
3172
+ return dispatch("turbo:frame-render", {
3173
+ detail: { fetchResponse },
3174
+ target: frame,
3175
+ cancelable: true,
3176
+ });
3177
+ }
3178
+ submissionIsNavigatable(form, submitter) {
3179
+ if (this.formMode == "off") {
3180
+ return false;
3181
+ }
3182
+ else {
3183
+ const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
3184
+ if (this.formMode == "optin") {
3185
+ return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null;
3186
+ }
3187
+ else {
3188
+ return submitterIsNavigatable && this.elementIsNavigatable(form);
3189
+ }
3190
+ }
2739
3191
  }
2740
- elementDriveEnabled(element) {
2741
- const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
2742
- if (this.drive) {
3192
+ elementIsNavigatable(element) {
3193
+ const container = findClosestRecursively(element, "[data-turbo]");
3194
+ const withinFrame = findClosestRecursively(element, "turbo-frame");
3195
+ if (this.drive || withinFrame) {
2743
3196
  if (container) {
2744
3197
  return container.getAttribute("data-turbo") != "false";
2745
3198
  }
@@ -2757,20 +3210,7 @@ class Session {
2757
3210
  }
2758
3211
  }
2759
3212
  getActionForLink(link) {
2760
- const action = link.getAttribute("data-turbo-action");
2761
- return isAction(action) ? action : "advance";
2762
- }
2763
- getTargetFrameForLink(link) {
2764
- const frame = link.getAttribute("data-turbo-frame");
2765
- if (frame) {
2766
- return frame;
2767
- }
2768
- else {
2769
- const container = link.closest("turbo-frame");
2770
- if (container) {
2771
- return container.id;
2772
- }
2773
- }
3213
+ return getVisitAction(link) || "advance";
2774
3214
  }
2775
3215
  get snapshot() {
2776
3216
  return this.view.snapshot;
@@ -2783,11 +3223,62 @@ const deprecatedLocationPropertyDescriptors = {
2783
3223
  absoluteURL: {
2784
3224
  get() {
2785
3225
  return this.toString();
2786
- }
3226
+ },
3227
+ },
3228
+ };
3229
+
3230
+ class Cache {
3231
+ constructor(session) {
3232
+ this.session = session;
2787
3233
  }
3234
+ clear() {
3235
+ this.session.clearCache();
3236
+ }
3237
+ resetCacheControl() {
3238
+ this.setCacheControl("");
3239
+ }
3240
+ exemptPageFromCache() {
3241
+ this.setCacheControl("no-cache");
3242
+ }
3243
+ exemptPageFromPreview() {
3244
+ this.setCacheControl("no-preview");
3245
+ }
3246
+ setCacheControl(value) {
3247
+ setMetaContent("turbo-cache-control", value);
3248
+ }
3249
+ }
3250
+
3251
+ const StreamActions = {
3252
+ after() {
3253
+ this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });
3254
+ },
3255
+ append() {
3256
+ this.removeDuplicateTargetChildren();
3257
+ this.targetElements.forEach((e) => e.append(this.templateContent));
3258
+ },
3259
+ before() {
3260
+ this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });
3261
+ },
3262
+ prepend() {
3263
+ this.removeDuplicateTargetChildren();
3264
+ this.targetElements.forEach((e) => e.prepend(this.templateContent));
3265
+ },
3266
+ remove() {
3267
+ this.targetElements.forEach((e) => e.remove());
3268
+ },
3269
+ replace() {
3270
+ this.targetElements.forEach((e) => e.replaceWith(this.templateContent));
3271
+ },
3272
+ update() {
3273
+ this.targetElements.forEach((targetElement) => {
3274
+ targetElement.innerHTML = "";
3275
+ targetElement.append(this.templateContent);
3276
+ });
3277
+ },
2788
3278
  };
2789
3279
 
2790
- const session = new Session;
3280
+ const session = new Session();
3281
+ const cache = new Cache(session);
2791
3282
  const { navigator: navigator$1 } = session;
2792
3283
  function start() {
2793
3284
  session.start();
@@ -2808,6 +3299,7 @@ function renderStreamMessage(message) {
2808
3299
  session.renderStreamMessage(message);
2809
3300
  }
2810
3301
  function clearCache() {
3302
+ console.warn("Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
2811
3303
  session.clearCache();
2812
3304
  }
2813
3305
  function setProgressBarDelay(delay) {
@@ -2816,13 +3308,18 @@ function setProgressBarDelay(delay) {
2816
3308
  function setConfirmMethod(confirmMethod) {
2817
3309
  FormSubmission.confirmMethod = confirmMethod;
2818
3310
  }
3311
+ function setFormMode(mode) {
3312
+ session.setFormMode(mode);
3313
+ }
2819
3314
 
2820
3315
  var Turbo = /*#__PURE__*/Object.freeze({
2821
3316
  __proto__: null,
2822
3317
  navigator: navigator$1,
2823
3318
  session: session,
3319
+ cache: cache,
2824
3320
  PageRenderer: PageRenderer,
2825
3321
  PageSnapshot: PageSnapshot,
3322
+ FrameRenderer: FrameRenderer,
2826
3323
  start: start,
2827
3324
  registerAdapter: registerAdapter,
2828
3325
  visit: visit,
@@ -2831,41 +3328,59 @@ var Turbo = /*#__PURE__*/Object.freeze({
2831
3328
  renderStreamMessage: renderStreamMessage,
2832
3329
  clearCache: clearCache,
2833
3330
  setProgressBarDelay: setProgressBarDelay,
2834
- setConfirmMethod: setConfirmMethod
3331
+ setConfirmMethod: setConfirmMethod,
3332
+ setFormMode: setFormMode,
3333
+ StreamActions: StreamActions
2835
3334
  });
2836
3335
 
3336
+ class TurboFrameMissingError extends Error {
3337
+ }
3338
+
2837
3339
  class FrameController {
2838
3340
  constructor(element) {
2839
- this.fetchResponseLoaded = (fetchResponse) => { };
3341
+ this.fetchResponseLoaded = (_fetchResponse) => { };
2840
3342
  this.currentFetchRequest = null;
2841
3343
  this.resolveVisitPromise = () => { };
2842
3344
  this.connected = false;
2843
3345
  this.hasBeenLoaded = false;
2844
- this.settingSourceURL = false;
3346
+ this.ignoredAttributes = new Set();
3347
+ this.action = null;
3348
+ this.visitCachedSnapshot = ({ element }) => {
3349
+ const frame = element.querySelector("#" + this.element.id);
3350
+ if (frame && this.previousFrameElement) {
3351
+ frame.replaceChildren(...this.previousFrameElement.children);
3352
+ }
3353
+ delete this.previousFrameElement;
3354
+ };
2845
3355
  this.element = element;
2846
3356
  this.view = new FrameView(this, this.element);
2847
3357
  this.appearanceObserver = new AppearanceObserver(this, this.element);
3358
+ this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);
2848
3359
  this.linkInterceptor = new LinkInterceptor(this, this.element);
2849
- this.formInterceptor = new FormInterceptor(this, this.element);
3360
+ this.restorationIdentifier = uuid();
3361
+ this.formSubmitObserver = new FormSubmitObserver(this, this.element);
2850
3362
  }
2851
3363
  connect() {
2852
3364
  if (!this.connected) {
2853
3365
  this.connected = true;
2854
- this.reloadable = false;
2855
3366
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2856
3367
  this.appearanceObserver.start();
2857
3368
  }
3369
+ else {
3370
+ this.loadSourceURL();
3371
+ }
3372
+ this.formLinkClickObserver.start();
2858
3373
  this.linkInterceptor.start();
2859
- this.formInterceptor.start();
2860
- this.sourceURLChanged();
3374
+ this.formSubmitObserver.start();
2861
3375
  }
2862
3376
  }
2863
3377
  disconnect() {
2864
3378
  if (this.connected) {
2865
3379
  this.connected = false;
2866
3380
  this.appearanceObserver.stop();
3381
+ this.formLinkClickObserver.stop();
2867
3382
  this.linkInterceptor.stop();
2868
- this.formInterceptor.stop();
3383
+ this.formSubmitObserver.stop();
2869
3384
  }
2870
3385
  }
2871
3386
  disabledChanged() {
@@ -2874,10 +3389,29 @@ class FrameController {
2874
3389
  }
2875
3390
  }
2876
3391
  sourceURLChanged() {
3392
+ if (this.isIgnoringChangesTo("src"))
3393
+ return;
3394
+ if (this.element.isConnected) {
3395
+ this.complete = false;
3396
+ }
2877
3397
  if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
2878
3398
  this.loadSourceURL();
2879
3399
  }
2880
3400
  }
3401
+ sourceURLReloaded() {
3402
+ const { src } = this.element;
3403
+ this.ignoringChangesToAttribute("complete", () => {
3404
+ this.element.removeAttribute("complete");
3405
+ });
3406
+ this.element.src = null;
3407
+ this.element.src = src;
3408
+ return this.element.loaded;
3409
+ }
3410
+ completeChanged() {
3411
+ if (this.isIgnoringChangesTo("complete"))
3412
+ return;
3413
+ this.loadSourceURL();
3414
+ }
2881
3415
  loadingStyleChanged() {
2882
3416
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2883
3417
  this.appearanceObserver.start();
@@ -2888,21 +3422,11 @@ class FrameController {
2888
3422
  }
2889
3423
  }
2890
3424
  async loadSourceURL() {
2891
- if (!this.settingSourceURL && this.enabled && this.isActive && (this.reloadable || this.sourceURL != this.currentURL)) {
2892
- const previousURL = this.currentURL;
2893
- this.currentURL = this.sourceURL;
2894
- if (this.sourceURL) {
2895
- try {
2896
- this.element.loaded = this.visit(expandURL(this.sourceURL));
2897
- this.appearanceObserver.stop();
2898
- await this.element.loaded;
2899
- this.hasBeenLoaded = true;
2900
- }
2901
- catch (error) {
2902
- this.currentURL = previousURL;
2903
- throw error;
2904
- }
2905
- }
3425
+ if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
3426
+ this.element.loaded = this.visit(expandURL(this.sourceURL));
3427
+ this.appearanceObserver.stop();
3428
+ await this.element.loaded;
3429
+ this.hasBeenLoaded = true;
2906
3430
  }
2907
3431
  }
2908
3432
  async loadResponse(fetchResponse) {
@@ -2912,75 +3436,76 @@ class FrameController {
2912
3436
  try {
2913
3437
  const html = await fetchResponse.responseHTML;
2914
3438
  if (html) {
2915
- const { body } = parseHTMLDocument(html);
2916
- const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
2917
- const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
2918
- if (this.view.renderPromise)
2919
- await this.view.renderPromise;
2920
- await this.view.render(renderer);
2921
- session.frameRendered(fetchResponse, this.element);
2922
- session.frameLoaded(this.element);
2923
- this.fetchResponseLoaded(fetchResponse);
3439
+ const document = parseHTMLDocument(html);
3440
+ const pageSnapshot = PageSnapshot.fromDocument(document);
3441
+ if (pageSnapshot.isVisitable) {
3442
+ await this.loadFrameResponse(fetchResponse, document);
3443
+ }
3444
+ else {
3445
+ await this.handleUnvisitableFrameResponse(fetchResponse);
3446
+ }
2924
3447
  }
2925
3448
  }
2926
- catch (error) {
2927
- console.error(error);
2928
- this.view.invalidate();
2929
- }
2930
3449
  finally {
2931
3450
  this.fetchResponseLoaded = () => { };
2932
3451
  }
2933
3452
  }
2934
3453
  elementAppearedInViewport(element) {
3454
+ this.proposeVisitIfNavigatedWithAction(element, element);
2935
3455
  this.loadSourceURL();
2936
3456
  }
2937
- shouldInterceptLinkClick(element, url) {
2938
- if (element.hasAttribute("data-turbo-method")) {
2939
- return false;
2940
- }
2941
- else {
2942
- return this.shouldInterceptNavigation(element);
2943
- }
3457
+ willSubmitFormLinkToLocation(link) {
3458
+ return this.shouldInterceptNavigation(link);
2944
3459
  }
2945
- linkClickIntercepted(element, url) {
2946
- this.reloadable = true;
2947
- this.navigateFrame(element, url);
3460
+ submittedFormLinkToLocation(link, _location, form) {
3461
+ const frame = this.findFrameElement(link);
3462
+ if (frame)
3463
+ form.setAttribute("data-turbo-frame", frame.id);
2948
3464
  }
2949
- shouldInterceptFormSubmission(element, submitter) {
2950
- return this.shouldInterceptNavigation(element, submitter);
3465
+ shouldInterceptLinkClick(element, _location, _event) {
3466
+ return this.shouldInterceptNavigation(element);
2951
3467
  }
2952
- formSubmissionIntercepted(element, submitter) {
3468
+ linkClickIntercepted(element, location) {
3469
+ this.navigateFrame(element, location);
3470
+ }
3471
+ willSubmitForm(element, submitter) {
3472
+ return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
3473
+ }
3474
+ formSubmitted(element, submitter) {
2953
3475
  if (this.formSubmission) {
2954
3476
  this.formSubmission.stop();
2955
3477
  }
2956
- this.reloadable = false;
2957
3478
  this.formSubmission = new FormSubmission(this, element, submitter);
2958
3479
  const { fetchRequest } = this.formSubmission;
2959
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3480
+ this.prepareRequest(fetchRequest);
2960
3481
  this.formSubmission.start();
2961
3482
  }
2962
- prepareHeadersForRequest(headers, request) {
2963
- headers["Turbo-Frame"] = this.id;
3483
+ prepareRequest(request) {
3484
+ var _a;
3485
+ request.headers["Turbo-Frame"] = this.id;
3486
+ if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3487
+ request.acceptResponseType(StreamMessage.contentType);
3488
+ }
2964
3489
  }
2965
- requestStarted(request) {
3490
+ requestStarted(_request) {
2966
3491
  markAsBusy(this.element);
2967
3492
  }
2968
- requestPreventedHandlingResponse(request, response) {
3493
+ requestPreventedHandlingResponse(_request, _response) {
2969
3494
  this.resolveVisitPromise();
2970
3495
  }
2971
3496
  async requestSucceededWithResponse(request, response) {
2972
3497
  await this.loadResponse(response);
2973
3498
  this.resolveVisitPromise();
2974
3499
  }
2975
- requestFailedWithResponse(request, response) {
2976
- console.error(response);
3500
+ async requestFailedWithResponse(request, response) {
3501
+ await this.loadResponse(response);
2977
3502
  this.resolveVisitPromise();
2978
3503
  }
2979
3504
  requestErrored(request, error) {
2980
3505
  console.error(error);
2981
3506
  this.resolveVisitPromise();
2982
3507
  }
2983
- requestFinished(request) {
3508
+ requestFinished(_request) {
2984
3509
  clearBusyState(this.element);
2985
3510
  }
2986
3511
  formSubmissionStarted({ formElement }) {
@@ -2988,11 +3513,15 @@ class FrameController {
2988
3513
  }
2989
3514
  formSubmissionSucceededWithResponse(formSubmission, response) {
2990
3515
  const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
2991
- this.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
3516
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
2992
3517
  frame.delegate.loadResponse(response);
3518
+ if (!formSubmission.isSafe) {
3519
+ session.clearCache();
3520
+ }
2993
3521
  }
2994
3522
  formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
2995
3523
  this.element.delegate.loadResponse(fetchResponse);
3524
+ session.clearCache();
2996
3525
  }
2997
3526
  formSubmissionErrored(formSubmission, error) {
2998
3527
  console.error(error);
@@ -3000,19 +3529,50 @@ class FrameController {
3000
3529
  formSubmissionFinished({ formElement }) {
3001
3530
  clearBusyState(formElement, this.findFrameElement(formElement));
3002
3531
  }
3003
- allowsImmediateRender(snapshot, resume) {
3004
- return true;
3005
- }
3006
- viewRenderedSnapshot(snapshot, isPreview) {
3007
- }
3008
- viewInvalidated() {
3532
+ allowsImmediateRender({ element: newFrame }, options) {
3533
+ const event = dispatch("turbo:before-frame-render", {
3534
+ target: this.element,
3535
+ detail: Object.assign({ newFrame }, options),
3536
+ cancelable: true,
3537
+ });
3538
+ const { defaultPrevented, detail: { render }, } = event;
3539
+ if (this.view.renderer && render) {
3540
+ this.view.renderer.renderElement = render;
3541
+ }
3542
+ return !defaultPrevented;
3543
+ }
3544
+ viewRenderedSnapshot(_snapshot, _isPreview) { }
3545
+ preloadOnLoadLinksForView(element) {
3546
+ session.preloadOnLoadLinksForView(element);
3547
+ }
3548
+ viewInvalidated() { }
3549
+ willRenderFrame(currentElement, _newElement) {
3550
+ this.previousFrameElement = currentElement.cloneNode(true);
3551
+ }
3552
+ async loadFrameResponse(fetchResponse, document) {
3553
+ const newFrameElement = await this.extractForeignFrameElement(document.body);
3554
+ if (newFrameElement) {
3555
+ const snapshot = new Snapshot(newFrameElement);
3556
+ const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
3557
+ if (this.view.renderPromise)
3558
+ await this.view.renderPromise;
3559
+ this.changeHistory();
3560
+ await this.view.render(renderer);
3561
+ this.complete = true;
3562
+ session.frameRendered(fetchResponse, this.element);
3563
+ session.frameLoaded(this.element);
3564
+ this.fetchResponseLoaded(fetchResponse);
3565
+ }
3566
+ else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3567
+ this.handleFrameMissingFromResponse(fetchResponse);
3568
+ }
3009
3569
  }
3010
3570
  async visit(url) {
3011
3571
  var _a;
3012
- const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
3572
+ const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);
3013
3573
  (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
3014
3574
  this.currentFetchRequest = request;
3015
- return new Promise(resolve => {
3575
+ return new Promise((resolve) => {
3016
3576
  this.resolveVisitPromise = () => {
3017
3577
  this.resolveVisitPromise = () => { };
3018
3578
  this.currentFetchRequest = null;
@@ -3023,24 +3583,78 @@ class FrameController {
3023
3583
  }
3024
3584
  navigateFrame(element, url, submitter) {
3025
3585
  const frame = this.findFrameElement(element, submitter);
3026
- this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3027
- frame.setAttribute("reloadable", "");
3028
- frame.src = url;
3586
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3587
+ this.withCurrentNavigationElement(element, () => {
3588
+ frame.src = url;
3589
+ });
3029
3590
  }
3030
3591
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3031
- const action = getAttribute("data-turbo-action", submitter, element, frame);
3032
- if (isAction(action)) {
3033
- const { visitCachedSnapshot } = new SnapshotSubstitution(frame);
3592
+ this.action = getVisitAction(submitter, element, frame);
3593
+ if (this.action) {
3594
+ const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3595
+ const { visitCachedSnapshot } = frame.delegate;
3034
3596
  frame.delegate.fetchResponseLoaded = (fetchResponse) => {
3035
3597
  if (frame.src) {
3036
3598
  const { statusCode, redirected } = fetchResponse;
3037
3599
  const responseHTML = frame.ownerDocument.documentElement.outerHTML;
3038
3600
  const response = { statusCode, redirected, responseHTML };
3039
- session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false });
3601
+ const options = {
3602
+ response,
3603
+ visitCachedSnapshot,
3604
+ willRender: false,
3605
+ updateHistory: false,
3606
+ restorationIdentifier: this.restorationIdentifier,
3607
+ snapshot: pageSnapshot,
3608
+ };
3609
+ if (this.action)
3610
+ options.action = this.action;
3611
+ session.visit(frame.src, options);
3040
3612
  }
3041
3613
  };
3042
3614
  }
3043
3615
  }
3616
+ changeHistory() {
3617
+ if (this.action) {
3618
+ const method = getHistoryMethodForAction(this.action);
3619
+ session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
3620
+ }
3621
+ }
3622
+ async handleUnvisitableFrameResponse(fetchResponse) {
3623
+ console.warn(`The response (${fetchResponse.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.`);
3624
+ await this.visitResponse(fetchResponse.response);
3625
+ }
3626
+ willHandleFrameMissingFromResponse(fetchResponse) {
3627
+ this.element.setAttribute("complete", "");
3628
+ const response = fetchResponse.response;
3629
+ const visit = async (url, options = {}) => {
3630
+ if (url instanceof Response) {
3631
+ this.visitResponse(url);
3632
+ }
3633
+ else {
3634
+ session.visit(url, options);
3635
+ }
3636
+ };
3637
+ const event = dispatch("turbo:frame-missing", {
3638
+ target: this.element,
3639
+ detail: { response, visit },
3640
+ cancelable: true,
3641
+ });
3642
+ return !event.defaultPrevented;
3643
+ }
3644
+ handleFrameMissingFromResponse(fetchResponse) {
3645
+ this.view.missing();
3646
+ this.throwFrameMissingError(fetchResponse);
3647
+ }
3648
+ throwFrameMissingError(fetchResponse) {
3649
+ const message = `The response (${fetchResponse.statusCode}) did not contain the expected <turbo-frame id="${this.element.id}"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;
3650
+ throw new TurboFrameMissingError(message);
3651
+ }
3652
+ async visitResponse(response) {
3653
+ const wrapped = new FetchResponse(response);
3654
+ const responseHTML = await wrapped.responseHTML;
3655
+ const { location, redirected, statusCode } = wrapped;
3656
+ return session.visit(location, { response: { redirected, statusCode, responseHTML } });
3657
+ }
3044
3658
  findFrameElement(element, submitter) {
3045
3659
  var _a;
3046
3660
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
@@ -3050,19 +3664,21 @@ class FrameController {
3050
3664
  let element;
3051
3665
  const id = CSS.escape(this.id);
3052
3666
  try {
3053
- if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
3667
+ element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
3668
+ if (element) {
3054
3669
  return element;
3055
3670
  }
3056
- if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
3671
+ element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);
3672
+ if (element) {
3057
3673
  await element.loaded;
3058
3674
  return await this.extractForeignFrameElement(element);
3059
3675
  }
3060
- console.error(`Response has no matching <turbo-frame id="${id}"> element`);
3061
3676
  }
3062
3677
  catch (error) {
3063
3678
  console.error(error);
3679
+ return new FrameElement();
3064
3680
  }
3065
- return new FrameElement();
3681
+ return null;
3066
3682
  }
3067
3683
  formActionIsVisitable(form, submitter) {
3068
3684
  const action = getAction(form, submitter);
@@ -3082,10 +3698,10 @@ class FrameController {
3082
3698
  return !frameElement.disabled;
3083
3699
  }
3084
3700
  }
3085
- if (!session.elementDriveEnabled(element)) {
3701
+ if (!session.elementIsNavigatable(element)) {
3086
3702
  return false;
3087
3703
  }
3088
- if (submitter && !session.elementDriveEnabled(submitter)) {
3704
+ if (submitter && !session.elementIsNavigatable(submitter)) {
3089
3705
  return false;
3090
3706
  }
3091
3707
  return true;
@@ -3101,24 +3717,10 @@ class FrameController {
3101
3717
  return this.element.src;
3102
3718
  }
3103
3719
  }
3104
- get reloadable() {
3105
- const frame = this.findFrameElement(this.element);
3106
- return frame.hasAttribute("reloadable");
3107
- }
3108
- set reloadable(value) {
3109
- const frame = this.findFrameElement(this.element);
3110
- if (value) {
3111
- frame.setAttribute("reloadable", "");
3112
- }
3113
- else {
3114
- frame.removeAttribute("reloadable");
3115
- }
3116
- }
3117
3720
  set sourceURL(sourceURL) {
3118
- this.settingSourceURL = true;
3119
- this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3120
- this.currentURL = this.element.src;
3121
- this.settingSourceURL = false;
3721
+ this.ignoringChangesToAttribute("src", () => {
3722
+ this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3723
+ });
3122
3724
  }
3123
3725
  get loadingStyle() {
3124
3726
  return this.element.loading;
@@ -3126,6 +3728,19 @@ class FrameController {
3126
3728
  get isLoading() {
3127
3729
  return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
3128
3730
  }
3731
+ get complete() {
3732
+ return this.element.hasAttribute("complete");
3733
+ }
3734
+ set complete(value) {
3735
+ this.ignoringChangesToAttribute("complete", () => {
3736
+ if (value) {
3737
+ this.element.setAttribute("complete", "");
3738
+ }
3739
+ else {
3740
+ this.element.removeAttribute("complete");
3741
+ }
3742
+ });
3743
+ }
3129
3744
  get isActive() {
3130
3745
  return this.element.isActive && this.connected;
3131
3746
  }
@@ -3135,16 +3750,18 @@ class FrameController {
3135
3750
  const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3136
3751
  return expandURL(root);
3137
3752
  }
3138
- }
3139
- class SnapshotSubstitution {
3140
- constructor(element) {
3141
- this.visitCachedSnapshot = ({ element }) => {
3142
- var _a;
3143
- const { id, clone } = this;
3144
- (_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
3145
- };
3146
- this.clone = element.cloneNode(true);
3147
- this.id = element.id;
3753
+ isIgnoringChangesTo(attributeName) {
3754
+ return this.ignoredAttributes.has(attributeName);
3755
+ }
3756
+ ignoringChangesToAttribute(attributeName, callback) {
3757
+ this.ignoredAttributes.add(attributeName);
3758
+ callback();
3759
+ this.ignoredAttributes.delete(attributeName);
3760
+ }
3761
+ withCurrentNavigationElement(element, callback) {
3762
+ this.currentNavigationElement = element;
3763
+ callback();
3764
+ delete this.currentNavigationElement;
3148
3765
  }
3149
3766
  }
3150
3767
  function getFrameElementById(id) {
@@ -3172,36 +3789,10 @@ function activateElement(element, currentURL) {
3172
3789
  }
3173
3790
  }
3174
3791
 
3175
- const StreamActions = {
3176
- after() {
3177
- this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });
3178
- },
3179
- append() {
3180
- this.removeDuplicateTargetChildren();
3181
- this.targetElements.forEach(e => e.append(this.templateContent));
3182
- },
3183
- before() {
3184
- this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });
3185
- },
3186
- prepend() {
3187
- this.removeDuplicateTargetChildren();
3188
- this.targetElements.forEach(e => e.prepend(this.templateContent));
3189
- },
3190
- remove() {
3191
- this.targetElements.forEach(e => e.remove());
3192
- },
3193
- replace() {
3194
- this.targetElements.forEach(e => e.replaceWith(this.templateContent));
3195
- },
3196
- update() {
3197
- this.targetElements.forEach(e => {
3198
- e.innerHTML = "";
3199
- e.append(this.templateContent);
3200
- });
3201
- }
3202
- };
3203
-
3204
3792
  class StreamElement extends HTMLElement {
3793
+ static async renderElement(newElement) {
3794
+ await newElement.performAction();
3795
+ }
3205
3796
  async connectedCallback() {
3206
3797
  try {
3207
3798
  await this.render();
@@ -3215,12 +3806,13 @@ class StreamElement extends HTMLElement {
3215
3806
  }
3216
3807
  async render() {
3217
3808
  var _a;
3218
- return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
3219
- if (this.dispatchEvent(this.beforeRenderEvent)) {
3809
+ return ((_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
3810
+ const event = this.beforeRenderEvent;
3811
+ if (this.dispatchEvent(event)) {
3220
3812
  await nextAnimationFrame();
3221
- this.performAction();
3813
+ await event.detail.render(this);
3222
3814
  }
3223
- })());
3815
+ })()));
3224
3816
  }
3225
3817
  disconnect() {
3226
3818
  try {
@@ -3229,13 +3821,13 @@ class StreamElement extends HTMLElement {
3229
3821
  catch (_a) { }
3230
3822
  }
3231
3823
  removeDuplicateTargetChildren() {
3232
- this.duplicateChildren.forEach(c => c.remove());
3824
+ this.duplicateChildren.forEach((c) => c.remove());
3233
3825
  }
3234
3826
  get duplicateChildren() {
3235
3827
  var _a;
3236
- const existingChildren = this.targetElements.flatMap(e => [...e.children]).filter(c => !!c.id);
3237
- const newChildrenIds = [...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children].filter(c => !!c.id).map(c => c.id);
3238
- return existingChildren.filter(c => newChildrenIds.includes(c.id));
3828
+ const existingChildren = this.targetElements.flatMap((e) => [...e.children]).filter((c) => !!c.id);
3829
+ const newChildrenIds = [...(((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [])].filter((c) => !!c.id).map((c) => c.id);
3830
+ return existingChildren.filter((c) => newChildrenIds.includes(c.id));
3239
3831
  }
3240
3832
  get performAction() {
3241
3833
  if (this.action) {
@@ -3262,7 +3854,12 @@ class StreamElement extends HTMLElement {
3262
3854
  return this.templateElement.content.cloneNode(true);
3263
3855
  }
3264
3856
  get templateElement() {
3265
- if (this.firstElementChild instanceof HTMLTemplateElement) {
3857
+ if (this.firstElementChild === null) {
3858
+ const template = this.ownerDocument.createElement("template");
3859
+ this.appendChild(template);
3860
+ return template;
3861
+ }
3862
+ else if (this.firstElementChild instanceof HTMLTemplateElement) {
3266
3863
  return this.firstElementChild;
3267
3864
  }
3268
3865
  this.raise("first child element must be a <template> element");
@@ -3284,7 +3881,11 @@ class StreamElement extends HTMLElement {
3284
3881
  return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
3285
3882
  }
3286
3883
  get beforeRenderEvent() {
3287
- return new CustomEvent("turbo:before-stream-render", { bubbles: true, cancelable: true });
3884
+ return new CustomEvent("turbo:before-stream-render", {
3885
+ bubbles: true,
3886
+ cancelable: true,
3887
+ detail: { newStream: this, render: StreamElement.renderElement },
3888
+ });
3288
3889
  }
3289
3890
  get targetElementsById() {
3290
3891
  var _a;
@@ -3308,9 +3909,35 @@ class StreamElement extends HTMLElement {
3308
3909
  }
3309
3910
  }
3310
3911
 
3912
+ class StreamSourceElement extends HTMLElement {
3913
+ constructor() {
3914
+ super(...arguments);
3915
+ this.streamSource = null;
3916
+ }
3917
+ connectedCallback() {
3918
+ this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
3919
+ connectStreamSource(this.streamSource);
3920
+ }
3921
+ disconnectedCallback() {
3922
+ if (this.streamSource) {
3923
+ disconnectStreamSource(this.streamSource);
3924
+ }
3925
+ }
3926
+ get src() {
3927
+ return this.getAttribute("src") || "";
3928
+ }
3929
+ }
3930
+
3311
3931
  FrameElement.delegateConstructor = FrameController;
3312
- customElements.define("turbo-frame", FrameElement);
3313
- customElements.define("turbo-stream", StreamElement);
3932
+ if (customElements.get("turbo-frame") === undefined) {
3933
+ customElements.define("turbo-frame", FrameElement);
3934
+ }
3935
+ if (customElements.get("turbo-stream") === undefined) {
3936
+ customElements.define("turbo-stream", StreamElement);
3937
+ }
3938
+ if (customElements.get("turbo-stream-source") === undefined) {
3939
+ customElements.define("turbo-stream-source", StreamSourceElement);
3940
+ }
3314
3941
 
3315
3942
  (() => {
3316
3943
  let element = document.currentScript;
@@ -3318,7 +3945,8 @@ customElements.define("turbo-stream", StreamElement);
3318
3945
  return;
3319
3946
  if (element.hasAttribute("data-turbo-suppress-warning"))
3320
3947
  return;
3321
- while (element = element.parentElement) {
3948
+ element = element.parentElement;
3949
+ while (element) {
3322
3950
  if (element == document.body) {
3323
3951
  return console.warn(unindent `
3324
3952
  You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
@@ -3331,10 +3959,11 @@ customElements.define("turbo-stream", StreamElement);
3331
3959
  Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
3332
3960
  `, element.outerHTML);
3333
3961
  }
3962
+ element = element.parentElement;
3334
3963
  }
3335
3964
  })();
3336
3965
 
3337
3966
  window.Turbo = Turbo;
3338
3967
  start();
3339
3968
 
3340
- export { PageRenderer, PageSnapshot, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setProgressBarDelay, start, visit };
3969
+ export { FrameElement, FrameLoadingStyle, FrameRenderer, PageRenderer, PageSnapshot, StreamActions, StreamElement, StreamSourceElement, cache, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setFormMode, setProgressBarDelay, start, visit };