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