@hotwired/turbo-rails 7.1.3 → 7.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -88,7 +88,7 @@ class FrameElement extends HTMLElement {
88
88
  this.delegate = new FrameElement.delegateConstructor(this);
89
89
  }
90
90
  static get observedAttributes() {
91
- return [ "disabled", "loading", "src" ];
91
+ return [ "disabled", "complete", "loading", "src" ];
92
92
  }
93
93
  connectedCallback() {
94
94
  this.delegate.connect();
@@ -98,12 +98,15 @@ class FrameElement extends HTMLElement {
98
98
  }
99
99
  reload() {
100
100
  const {src: src} = this;
101
+ this.removeAttribute("complete");
101
102
  this.src = null;
102
103
  this.src = src;
103
104
  }
104
105
  attributeChangedCallback(name) {
105
106
  if (name == "loading") {
106
107
  this.delegate.loadingStyleChanged();
108
+ } else if (name == "complete") {
109
+ this.delegate.completeChanged();
107
110
  } else if (name == "src") {
108
111
  this.delegate.sourceURLChanged();
109
112
  } else {
@@ -195,7 +198,7 @@ function getExtension(url) {
195
198
  }
196
199
 
197
200
  function isHTML(url) {
198
- return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
201
+ return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
199
202
  }
200
203
 
201
204
  function isPrefixedBy(baseURL, url) {
@@ -327,7 +330,7 @@ function interpolate(strings, values) {
327
330
  }
328
331
 
329
332
  function uuid() {
330
- return Array.apply(null, {
333
+ return Array.from({
331
334
  length: 36
332
335
  }).map(((_, i) => {
333
336
  if (i == 8 || i == 13 || i == 18 || i == 23) {
@@ -367,6 +370,26 @@ function clearBusyState(...elements) {
367
370
  }
368
371
  }
369
372
 
373
+ function getMetaElement(name) {
374
+ return document.querySelector(`meta[name="${name}"]`);
375
+ }
376
+
377
+ function getMetaContent(name) {
378
+ const element = getMetaElement(name);
379
+ return element && element.content;
380
+ }
381
+
382
+ function setMetaContent(name, content) {
383
+ let element = getMetaElement(name);
384
+ if (!element) {
385
+ element = document.createElement("meta");
386
+ element.setAttribute("name", name);
387
+ document.head.appendChild(element);
388
+ }
389
+ element.setAttribute("content", content);
390
+ return element;
391
+ }
392
+
370
393
  var FetchMethod;
371
394
 
372
395
  (function(FetchMethod) {
@@ -399,7 +422,7 @@ function fetchMethodFromString(method) {
399
422
  class FetchRequest {
400
423
  constructor(delegate, method, location, body = new URLSearchParams, target = null) {
401
424
  this.abortController = new AbortController;
402
- this.resolveRequestPromise = value => {};
425
+ this.resolveRequestPromise = _value => {};
403
426
  this.delegate = delegate;
404
427
  this.method = method;
405
428
  this.headers = this.defaultHeaders;
@@ -601,8 +624,8 @@ class FormSubmission {
601
624
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
602
625
  this.mustRedirect = mustRedirect;
603
626
  }
604
- static confirmMethod(message, element) {
605
- return confirm(message);
627
+ static confirmMethod(message, _element) {
628
+ return Promise.resolve(confirm(message));
606
629
  }
607
630
  get method() {
608
631
  var _a;
@@ -612,7 +635,11 @@ class FormSubmission {
612
635
  get action() {
613
636
  var _a;
614
637
  const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
615
- return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.getAttribute("action") || formElementAction || "";
638
+ if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
639
+ return this.submitter.getAttribute("formaction") || "";
640
+ } else {
641
+ return this.formElement.getAttribute("action") || formElementAction || "";
642
+ }
616
643
  }
617
644
  get body() {
618
645
  if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
@@ -632,7 +659,8 @@ class FormSubmission {
632
659
  return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
633
660
  }
634
661
  get confirmationMessage() {
635
- return this.formElement.getAttribute("data-turbo-confirm");
662
+ var _a;
663
+ return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-confirm")) || this.formElement.getAttribute("data-turbo-confirm");
636
664
  }
637
665
  get needsConfirmation() {
638
666
  return this.confirmationMessage !== null;
@@ -640,7 +668,7 @@ class FormSubmission {
640
668
  async start() {
641
669
  const {initialized: initialized, requesting: requesting} = FormSubmissionState;
642
670
  if (this.needsConfirmation) {
643
- const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
671
+ const answer = await FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
644
672
  if (!answer) {
645
673
  return;
646
674
  }
@@ -664,10 +692,12 @@ class FormSubmission {
664
692
  if (token) {
665
693
  headers["X-CSRF-Token"] = token;
666
694
  }
695
+ }
696
+ if (this.requestAcceptsTurboStreamResponse(request)) {
667
697
  headers["Accept"] = [ StreamMessage.contentType, headers["Accept"] ].join(", ");
668
698
  }
669
699
  }
670
- requestStarted(request) {
700
+ requestStarted(_request) {
671
701
  var _a;
672
702
  this.state = FormSubmissionState.waiting;
673
703
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
@@ -714,7 +744,7 @@ class FormSubmission {
714
744
  };
715
745
  this.delegate.formSubmissionErrored(this, error);
716
746
  }
717
- requestFinished(request) {
747
+ requestFinished(_request) {
718
748
  var _a;
719
749
  this.state = FormSubmissionState.stopped;
720
750
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
@@ -729,6 +759,9 @@ class FormSubmission {
729
759
  requestMustRedirect(request) {
730
760
  return !request.isIdempotent && this.mustRedirect;
731
761
  }
762
+ requestAcceptsTurboStreamResponse(request) {
763
+ return !request.isIdempotent || this.formElement.hasAttribute("data-turbo-stream");
764
+ }
732
765
  }
733
766
 
734
767
  function buildFormData(formElement, submitter) {
@@ -752,11 +785,6 @@ function getCookieValue(cookieName) {
752
785
  }
753
786
  }
754
787
 
755
- function getMetaContent(name) {
756
- const element = document.querySelector(`meta[name="${name}"]`);
757
- return element && element.content;
758
- }
759
-
760
788
  function responseSucceededWithoutRedirect(response) {
761
789
  return response.statusCode == 200 && !response.redirected;
762
790
  }
@@ -775,6 +803,9 @@ class Snapshot {
775
803
  constructor(element) {
776
804
  this.element = element;
777
805
  }
806
+ get activeElement() {
807
+ return this.element.ownerDocument.activeElement;
808
+ }
778
809
  get children() {
779
810
  return [ ...this.element.children ];
780
811
  }
@@ -836,8 +867,8 @@ class FormInterceptor {
836
867
 
837
868
  class View {
838
869
  constructor(delegate, element) {
839
- this.resolveRenderPromise = value => {};
840
- this.resolveInterceptionPromise = value => {};
870
+ this.resolveRenderPromise = _value => {};
871
+ this.resolveInterceptionPromise = _value => {};
841
872
  this.delegate = delegate;
842
873
  this.element = element;
843
874
  }
@@ -890,10 +921,15 @@ class View {
890
921
  this.renderer = renderer;
891
922
  this.prepareToRenderSnapshot(renderer);
892
923
  const renderInterception = new Promise((resolve => this.resolveInterceptionPromise = resolve));
893
- const immediateRender = this.delegate.allowsImmediateRender(snapshot, this.resolveInterceptionPromise);
924
+ const options = {
925
+ resume: this.resolveInterceptionPromise,
926
+ render: this.renderer.renderElement
927
+ };
928
+ const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
894
929
  if (!immediateRender) await renderInterception;
895
930
  await this.renderSnapshot(renderer);
896
931
  this.delegate.viewRenderedSnapshot(snapshot, isPreview);
932
+ this.delegate.preloadOnLoadLinksForView(this.element);
897
933
  this.finishRenderingSnapshot(renderer);
898
934
  } finally {
899
935
  delete this.renderer;
@@ -901,11 +937,11 @@ class View {
901
937
  delete this.renderPromise;
902
938
  }
903
939
  } else {
904
- this.invalidate();
940
+ this.invalidate(renderer.reloadReason);
905
941
  }
906
942
  }
907
- invalidate() {
908
- this.delegate.viewInvalidated();
943
+ invalidate(reason) {
944
+ this.delegate.viewInvalidated(reason);
909
945
  }
910
946
  prepareToRenderSnapshot(renderer) {
911
947
  this.markAsPreview(renderer.isPreview);
@@ -954,7 +990,7 @@ class LinkInterceptor {
954
990
  }
955
991
  delete this.clickEvent;
956
992
  };
957
- this.willVisit = () => {
993
+ this.willVisit = _event => {
958
994
  delete this.clickEvent;
959
995
  };
960
996
  this.delegate = delegate;
@@ -976,19 +1012,55 @@ class LinkInterceptor {
976
1012
  }
977
1013
  }
978
1014
 
1015
+ class FormLinkInterceptor {
1016
+ constructor(delegate, element) {
1017
+ this.delegate = delegate;
1018
+ this.linkInterceptor = new LinkInterceptor(this, element);
1019
+ }
1020
+ start() {
1021
+ this.linkInterceptor.start();
1022
+ }
1023
+ stop() {
1024
+ this.linkInterceptor.stop();
1025
+ }
1026
+ shouldInterceptLinkClick(link) {
1027
+ return this.delegate.shouldInterceptFormLinkClick(link) && (link.hasAttribute("data-turbo-method") || link.hasAttribute("data-turbo-stream"));
1028
+ }
1029
+ linkClickIntercepted(link, action) {
1030
+ const form = document.createElement("form");
1031
+ form.setAttribute("data-turbo", "true");
1032
+ form.setAttribute("action", action);
1033
+ form.setAttribute("hidden", "");
1034
+ const method = link.getAttribute("data-turbo-method");
1035
+ if (method) form.setAttribute("method", method);
1036
+ const turboFrame = link.getAttribute("data-turbo-frame");
1037
+ if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
1038
+ const turboConfirm = link.getAttribute("data-turbo-confirm");
1039
+ if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
1040
+ const turboStream = link.hasAttribute("data-turbo-stream");
1041
+ if (turboStream) form.setAttribute("data-turbo-stream", "");
1042
+ this.delegate.formLinkClickIntercepted(link, form);
1043
+ document.body.appendChild(form);
1044
+ form.requestSubmit();
1045
+ form.remove();
1046
+ }
1047
+ }
1048
+
979
1049
  class Bardo {
980
- constructor(permanentElementMap) {
1050
+ constructor(delegate, permanentElementMap) {
1051
+ this.delegate = delegate;
981
1052
  this.permanentElementMap = permanentElementMap;
982
1053
  }
983
- static preservingPermanentElements(permanentElementMap, callback) {
984
- const bardo = new this(permanentElementMap);
1054
+ static preservingPermanentElements(delegate, permanentElementMap, callback) {
1055
+ const bardo = new this(delegate, permanentElementMap);
985
1056
  bardo.enter();
986
1057
  callback();
987
1058
  bardo.leave();
988
1059
  }
989
1060
  enter() {
990
1061
  for (const id in this.permanentElementMap) {
991
- const [, newPermanentElement] = this.permanentElementMap[id];
1062
+ const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
1063
+ this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
992
1064
  this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
993
1065
  }
994
1066
  }
@@ -997,6 +1069,7 @@ class Bardo {
997
1069
  const [currentPermanentElement] = this.permanentElementMap[id];
998
1070
  this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
999
1071
  this.replacePlaceholderWithPermanentElement(currentPermanentElement);
1072
+ this.delegate.leavingBardo(currentPermanentElement);
1000
1073
  }
1001
1074
  }
1002
1075
  replaceNewPermanentElementWithPlaceholder(permanentElement) {
@@ -1027,11 +1100,13 @@ function createPlaceholderForPermanentElement(permanentElement) {
1027
1100
  }
1028
1101
 
1029
1102
  class Renderer {
1030
- constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
1103
+ constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1104
+ this.activeElement = null;
1031
1105
  this.currentSnapshot = currentSnapshot;
1032
1106
  this.newSnapshot = newSnapshot;
1033
1107
  this.isPreview = isPreview;
1034
1108
  this.willRender = willRender;
1109
+ this.renderElement = renderElement;
1035
1110
  this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
1036
1111
  resolve: resolve,
1037
1112
  reject: reject
@@ -1040,6 +1115,9 @@ class Renderer {
1040
1115
  get shouldRender() {
1041
1116
  return true;
1042
1117
  }
1118
+ get reloadReason() {
1119
+ return;
1120
+ }
1043
1121
  prepareToRender() {
1044
1122
  return;
1045
1123
  }
@@ -1064,7 +1142,7 @@ class Renderer {
1064
1142
  }
1065
1143
  }
1066
1144
  preservingPermanentElements(callback) {
1067
- Bardo.preservingPermanentElements(this.permanentElementMap, callback);
1145
+ Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1068
1146
  }
1069
1147
  focusFirstAutofocusableElement() {
1070
1148
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1072,6 +1150,18 @@ class Renderer {
1072
1150
  element.focus();
1073
1151
  }
1074
1152
  }
1153
+ enteringBardo(currentPermanentElement) {
1154
+ if (this.activeElement) return;
1155
+ if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
1156
+ this.activeElement = this.currentSnapshot.activeElement;
1157
+ }
1158
+ }
1159
+ leavingBardo(currentPermanentElement) {
1160
+ if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
1161
+ this.activeElement.focus();
1162
+ this.activeElement = null;
1163
+ }
1164
+ }
1075
1165
  get connectedSnapshot() {
1076
1166
  return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
1077
1167
  }
@@ -1085,8 +1175,7 @@ class Renderer {
1085
1175
  return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
1086
1176
  }
1087
1177
  get cspNonce() {
1088
- var _a;
1089
- return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
1178
+ return getMetaContent("csp-nonce");
1090
1179
  }
1091
1180
  }
1092
1181
 
@@ -1101,6 +1190,22 @@ function elementIsFocusable(element) {
1101
1190
  }
1102
1191
 
1103
1192
  class FrameRenderer extends Renderer {
1193
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1194
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1195
+ this.delegate = delegate;
1196
+ }
1197
+ static renderElement(currentElement, newElement) {
1198
+ var _a;
1199
+ const destinationRange = document.createRange();
1200
+ destinationRange.selectNodeContents(currentElement);
1201
+ destinationRange.deleteContents();
1202
+ const frameElement = newElement;
1203
+ const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1204
+ if (sourceRange) {
1205
+ sourceRange.selectNodeContents(frameElement);
1206
+ currentElement.appendChild(sourceRange.extractContents());
1207
+ }
1208
+ }
1104
1209
  get shouldRender() {
1105
1210
  return true;
1106
1211
  }
@@ -1116,24 +1221,18 @@ class FrameRenderer extends Renderer {
1116
1221
  this.activateScriptElements();
1117
1222
  }
1118
1223
  loadFrameElement() {
1119
- var _a;
1120
- const destinationRange = document.createRange();
1121
- destinationRange.selectNodeContents(this.currentElement);
1122
- destinationRange.deleteContents();
1123
- const frameElement = this.newElement;
1124
- const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
1125
- if (sourceRange) {
1126
- sourceRange.selectNodeContents(frameElement);
1127
- this.currentElement.appendChild(sourceRange.extractContents());
1128
- }
1224
+ this.delegate.frameExtracted(this.newElement.cloneNode(true));
1225
+ this.renderElement(this.currentElement, this.newElement);
1129
1226
  }
1130
1227
  scrollFrameIntoView() {
1131
1228
  if (this.currentElement.autoscroll || this.newElement.autoscroll) {
1132
1229
  const element = this.currentElement.firstElementChild;
1133
1230
  const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
1231
+ const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
1134
1232
  if (element) {
1135
1233
  element.scrollIntoView({
1136
- block: block
1234
+ block: block,
1235
+ behavior: behavior
1137
1236
  });
1138
1237
  return true;
1139
1238
  }
@@ -1159,6 +1258,14 @@ function readScrollLogicalPosition(value, defaultValue) {
1159
1258
  }
1160
1259
  }
1161
1260
 
1261
+ function readScrollBehavior(value, defaultValue) {
1262
+ if (value == "auto" || value == "smooth") {
1263
+ return value;
1264
+ } else {
1265
+ return defaultValue;
1266
+ }
1267
+ }
1268
+
1162
1269
  class ProgressBar {
1163
1270
  constructor() {
1164
1271
  this.hiding = false;
@@ -1181,7 +1288,7 @@ class ProgressBar {
1181
1288
  left: 0;
1182
1289
  height: 3px;
1183
1290
  background: #0076ff;
1184
- z-index: 9999;
1291
+ z-index: 2147483647;
1185
1292
  transition:
1186
1293
  width ${ProgressBar.animationDuration}ms ease-out,
1187
1294
  opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
@@ -1247,6 +1354,9 @@ class ProgressBar {
1247
1354
  const element = document.createElement("style");
1248
1355
  element.type = "text/css";
1249
1356
  element.textContent = ProgressBar.defaultCSS;
1357
+ if (this.cspNonce) {
1358
+ element.nonce = this.cspNonce;
1359
+ }
1250
1360
  return element;
1251
1361
  }
1252
1362
  createProgressElement() {
@@ -1254,6 +1364,9 @@ class ProgressBar {
1254
1364
  element.className = "turbo-progress-bar";
1255
1365
  return element;
1256
1366
  }
1367
+ get cspNonce() {
1368
+ return getMetaContent("csp-nonce");
1369
+ }
1257
1370
  }
1258
1371
 
1259
1372
  ProgressBar.animationDuration = 300;
@@ -1485,9 +1598,11 @@ class Visit {
1485
1598
  if (this.state == VisitState.started) {
1486
1599
  this.recordTimingMetric(TimingMetric.visitEnd);
1487
1600
  this.state = VisitState.completed;
1488
- this.adapter.visitCompleted(this);
1489
- this.delegate.visitCompleted(this);
1490
1601
  this.followRedirect();
1602
+ if (!this.followedRedirect) {
1603
+ this.adapter.visitCompleted(this);
1604
+ this.delegate.visitCompleted(this);
1605
+ }
1491
1606
  }
1492
1607
  }
1493
1608
  fail() {
@@ -1546,11 +1661,11 @@ class Visit {
1546
1661
  this.cacheSnapshot();
1547
1662
  if (this.view.renderPromise) await this.view.renderPromise;
1548
1663
  if (isSuccessful(statusCode) && responseHTML != null) {
1549
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
1664
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
1550
1665
  this.adapter.visitRendered(this);
1551
1666
  this.complete();
1552
1667
  } else {
1553
- await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
1668
+ await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
1554
1669
  this.adapter.visitRendered(this);
1555
1670
  this.fail();
1556
1671
  }
@@ -1583,7 +1698,7 @@ class Visit {
1583
1698
  this.adapter.visitRendered(this);
1584
1699
  } else {
1585
1700
  if (this.view.renderPromise) await this.view.renderPromise;
1586
- await this.view.renderPage(snapshot, isPreview, this.willRender);
1701
+ await this.view.renderPage(snapshot, isPreview, this.willRender, this);
1587
1702
  this.adapter.visitRendered(this);
1588
1703
  if (!isPreview) {
1589
1704
  this.complete();
@@ -1597,6 +1712,7 @@ class Visit {
1597
1712
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1598
1713
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1599
1714
  action: "replace",
1715
+ willRender: false,
1600
1716
  response: this.response
1601
1717
  });
1602
1718
  this.followedRedirect = true;
@@ -1613,7 +1729,7 @@ class Visit {
1613
1729
  requestStarted() {
1614
1730
  this.startRequest();
1615
1731
  }
1616
- requestPreventedHandlingResponse(request, response) {}
1732
+ requestPreventedHandlingResponse(_request, _response) {}
1617
1733
  async requestSucceededWithResponse(request, response) {
1618
1734
  const responseHTML = await response.responseHTML;
1619
1735
  const {redirected: redirected, statusCode: statusCode} = response;
@@ -1647,7 +1763,7 @@ class Visit {
1647
1763
  });
1648
1764
  }
1649
1765
  }
1650
- requestErrored(request, error) {
1766
+ requestErrored(_request, _error) {
1651
1767
  this.recordResponse({
1652
1768
  statusCode: SystemStatusCode.networkFailure,
1653
1769
  redirected: false
@@ -1724,7 +1840,9 @@ class Visit {
1724
1840
  }));
1725
1841
  await callback();
1726
1842
  delete this.frame;
1727
- this.performScroll();
1843
+ if (!this.view.forceReloaded) {
1844
+ this.performScroll();
1845
+ }
1728
1846
  }
1729
1847
  cancelRender() {
1730
1848
  if (this.frame) {
@@ -1750,9 +1868,9 @@ class BrowserAdapter {
1750
1868
  this.navigator.startVisit(location, uuid(), options);
1751
1869
  }
1752
1870
  visitStarted(visit) {
1871
+ this.location = visit.location;
1753
1872
  visit.loadCachedSnapshot();
1754
1873
  visit.issueRequest();
1755
- visit.changeHistory();
1756
1874
  visit.goToSamePageAnchor();
1757
1875
  }
1758
1876
  visitRequestStarted(visit) {
@@ -1771,27 +1889,32 @@ class BrowserAdapter {
1771
1889
  case SystemStatusCode.networkFailure:
1772
1890
  case SystemStatusCode.timeoutFailure:
1773
1891
  case SystemStatusCode.contentTypeMismatch:
1774
- return this.reload();
1892
+ return this.reload({
1893
+ reason: "request_failed",
1894
+ context: {
1895
+ statusCode: statusCode
1896
+ }
1897
+ });
1775
1898
 
1776
1899
  default:
1777
1900
  return visit.loadResponse();
1778
1901
  }
1779
1902
  }
1780
- visitRequestFinished(visit) {
1903
+ visitRequestFinished(_visit) {
1781
1904
  this.progressBar.setValue(1);
1782
1905
  this.hideVisitProgressBar();
1783
1906
  }
1784
- visitCompleted(visit) {}
1785
- pageInvalidated() {
1786
- this.reload();
1907
+ visitCompleted(_visit) {}
1908
+ pageInvalidated(reason) {
1909
+ this.reload(reason);
1787
1910
  }
1788
- visitFailed(visit) {}
1789
- visitRendered(visit) {}
1790
- formSubmissionStarted(formSubmission) {
1911
+ visitFailed(_visit) {}
1912
+ visitRendered(_visit) {}
1913
+ formSubmissionStarted(_formSubmission) {
1791
1914
  this.progressBar.setValue(0);
1792
1915
  this.showFormProgressBarAfterDelay();
1793
1916
  }
1794
- formSubmissionFinished(formSubmission) {
1917
+ formSubmissionFinished(_formSubmission) {
1795
1918
  this.progressBar.setValue(1);
1796
1919
  this.hideFormProgressBar();
1797
1920
  }
@@ -1817,8 +1940,12 @@ class BrowserAdapter {
1817
1940
  delete this.formProgressBarTimeout;
1818
1941
  }
1819
1942
  }
1820
- reload() {
1821
- window.location.reload();
1943
+ reload(reason) {
1944
+ dispatch("turbo:reload", {
1945
+ detail: reason
1946
+ });
1947
+ if (!this.location) return;
1948
+ window.location.href = this.location.toString();
1822
1949
  }
1823
1950
  get navigator() {
1824
1951
  return this.session.navigator;
@@ -1828,6 +1955,12 @@ class BrowserAdapter {
1828
1955
  class CacheObserver {
1829
1956
  constructor() {
1830
1957
  this.started = false;
1958
+ this.removeStaleElements = _event => {
1959
+ const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
1960
+ for (const element of staleElements) {
1961
+ element.remove();
1962
+ }
1963
+ };
1831
1964
  }
1832
1965
  start() {
1833
1966
  if (!this.started) {
@@ -1841,12 +1974,6 @@ class CacheObserver {
1841
1974
  removeEventListener("turbo:before-cache", this.removeStaleElements, false);
1842
1975
  }
1843
1976
  }
1844
- removeStaleElements() {
1845
- const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
1846
- for (const element of staleElements) {
1847
- element.remove();
1848
- }
1849
- }
1850
1977
  }
1851
1978
 
1852
1979
  class FormSubmitObserver {
@@ -1860,12 +1987,9 @@ class FormSubmitObserver {
1860
1987
  if (!event.defaultPrevented) {
1861
1988
  const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1862
1989
  const submitter = event.submitter || undefined;
1863
- if (form) {
1864
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
1865
- if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
1866
- event.preventDefault();
1867
- this.delegate.formSubmitted(form, submitter);
1868
- }
1990
+ if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
1991
+ event.preventDefault();
1992
+ this.delegate.formSubmitted(form, submitter);
1869
1993
  }
1870
1994
  }
1871
1995
  };
@@ -1885,6 +2009,19 @@ class FormSubmitObserver {
1885
2009
  }
1886
2010
  }
1887
2011
 
2012
+ function submissionDoesNotDismissDialog(form, submitter) {
2013
+ const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
2014
+ return method != "dialog";
2015
+ }
2016
+
2017
+ function submissionDoesNotTargetIFrame(form, submitter) {
2018
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
2019
+ for (const element of document.getElementsByName(target)) {
2020
+ if (element instanceof HTMLIFrameElement) return false;
2021
+ }
2022
+ return true;
2023
+ }
2024
+
1888
2025
  class FrameRedirector {
1889
2026
  constructor(element) {
1890
2027
  this.element = element;
@@ -1899,7 +2036,7 @@ class FrameRedirector {
1899
2036
  this.linkInterceptor.stop();
1900
2037
  this.formInterceptor.stop();
1901
2038
  }
1902
- shouldInterceptLinkClick(element, url) {
2039
+ shouldInterceptLinkClick(element, _url) {
1903
2040
  return this.shouldRedirect(element);
1904
2041
  }
1905
2042
  linkClickIntercepted(element, url) {
@@ -1914,7 +2051,6 @@ class FrameRedirector {
1914
2051
  formSubmissionIntercepted(element, submitter) {
1915
2052
  const frame = this.findFrameElement(element, submitter);
1916
2053
  if (frame) {
1917
- frame.removeAttribute("reloadable");
1918
2054
  frame.delegate.formSubmissionIntercepted(element, submitter);
1919
2055
  }
1920
2056
  }
@@ -1957,7 +2093,7 @@ class History {
1957
2093
  }
1958
2094
  }
1959
2095
  };
1960
- this.onPageLoad = async event => {
2096
+ this.onPageLoad = async _event => {
1961
2097
  await nextMicrotask();
1962
2098
  this.pageLoaded = true;
1963
2099
  };
@@ -2034,9 +2170,9 @@ class LinkClickObserver {
2034
2170
  if (this.clickEventIsSignificant(event)) {
2035
2171
  const target = event.composedPath && event.composedPath()[0] || event.target;
2036
2172
  const link = this.findLinkFromClickTarget(target);
2037
- if (link) {
2173
+ if (link && doesNotTargetIFrame(link)) {
2038
2174
  const location = this.getLocationForLink(link);
2039
- if (this.delegate.willFollowLinkToLocation(link, location)) {
2175
+ if (this.delegate.willFollowLinkToLocation(link, location, event)) {
2040
2176
  event.preventDefault();
2041
2177
  this.delegate.followedLinkToLocation(link, location);
2042
2178
  }
@@ -2070,6 +2206,13 @@ class LinkClickObserver {
2070
2206
  }
2071
2207
  }
2072
2208
 
2209
+ function doesNotTargetIFrame(anchor) {
2210
+ for (const element of document.getElementsByName(anchor.target)) {
2211
+ if (element instanceof HTMLIFrameElement) return false;
2212
+ }
2213
+ return true;
2214
+ }
2215
+
2073
2216
  function isAction(action) {
2074
2217
  return action == "advance" || action == "replace" || action == "restore";
2075
2218
  }
@@ -2088,6 +2231,7 @@ class Navigator {
2088
2231
  }
2089
2232
  }
2090
2233
  startVisit(locatable, restorationIdentifier, options = {}) {
2234
+ this.lastVisit = this.currentVisit;
2091
2235
  this.stop();
2092
2236
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
2093
2237
  referrer: this.location
@@ -2149,9 +2293,9 @@ class Navigator {
2149
2293
  if (responseHTML) {
2150
2294
  const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2151
2295
  if (fetchResponse.serverError) {
2152
- await this.view.renderError(snapshot);
2296
+ await this.view.renderError(snapshot, this.currentVisit);
2153
2297
  } else {
2154
- await this.view.renderPage(snapshot);
2298
+ await this.view.renderPage(snapshot, false, true, this.currentVisit);
2155
2299
  }
2156
2300
  this.view.scrollToTop();
2157
2301
  this.view.clearSnapshotCache();
@@ -2172,10 +2316,12 @@ class Navigator {
2172
2316
  this.delegate.visitCompleted(visit);
2173
2317
  }
2174
2318
  locationWithActionIsSamePage(location, action) {
2319
+ var _a;
2175
2320
  const anchor = getAnchor(location);
2176
- const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2321
+ const lastLocation = ((_a = this.lastVisit) === null || _a === void 0 ? void 0 : _a.location) || this.view.lastRenderedLocation;
2322
+ const currentAnchor = getAnchor(lastLocation);
2177
2323
  const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2178
- return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
2324
+ return action !== "replace" && getRequestURL(location) === getRequestURL(lastLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
2179
2325
  }
2180
2326
  visitScrolledToSamePageLocation(oldURL, newURL) {
2181
2327
  this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
@@ -2354,14 +2500,18 @@ function fetchResponseIsStream(response) {
2354
2500
  }
2355
2501
 
2356
2502
  class ErrorRenderer extends Renderer {
2503
+ static renderElement(currentElement, newElement) {
2504
+ const {documentElement: documentElement, body: body} = document;
2505
+ documentElement.replaceChild(newElement, body);
2506
+ }
2357
2507
  async render() {
2358
2508
  this.replaceHeadAndBody();
2359
2509
  this.activateScriptElements();
2360
2510
  }
2361
2511
  replaceHeadAndBody() {
2362
- const {documentElement: documentElement, head: head, body: body} = document;
2512
+ const {documentElement: documentElement, head: head} = document;
2363
2513
  documentElement.replaceChild(this.newHead, head);
2364
- documentElement.replaceChild(this.newElement, body);
2514
+ this.renderElement(this.currentElement, this.newElement);
2365
2515
  }
2366
2516
  activateScriptElements() {
2367
2517
  for (const replaceableElement of this.scriptElements) {
@@ -2381,9 +2531,28 @@ class ErrorRenderer extends Renderer {
2381
2531
  }
2382
2532
 
2383
2533
  class PageRenderer extends Renderer {
2534
+ static renderElement(currentElement, newElement) {
2535
+ if (document.body && newElement instanceof HTMLBodyElement) {
2536
+ document.body.replaceWith(newElement);
2537
+ } else {
2538
+ document.documentElement.appendChild(newElement);
2539
+ }
2540
+ }
2384
2541
  get shouldRender() {
2385
2542
  return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
2386
2543
  }
2544
+ get reloadReason() {
2545
+ if (!this.newSnapshot.isVisitable) {
2546
+ return {
2547
+ reason: "turbo_visit_control_is_reload"
2548
+ };
2549
+ }
2550
+ if (!this.trackedElementsAreIdentical) {
2551
+ return {
2552
+ reason: "tracked_element_mismatch"
2553
+ };
2554
+ }
2555
+ }
2387
2556
  prepareToRender() {
2388
2557
  this.mergeHead();
2389
2558
  }
@@ -2453,11 +2622,7 @@ class PageRenderer extends Renderer {
2453
2622
  }
2454
2623
  }
2455
2624
  assignNewBody() {
2456
- if (document.body && this.newElement instanceof HTMLBodyElement) {
2457
- document.body.replaceWith(this.newElement);
2458
- } else {
2459
- document.documentElement.appendChild(this.newElement);
2460
- }
2625
+ this.renderElement(this.currentElement, this.newElement);
2461
2626
  }
2462
2627
  get newHeadStylesheetElements() {
2463
2628
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -2525,13 +2690,20 @@ class PageView extends View {
2525
2690
  super(...arguments);
2526
2691
  this.snapshotCache = new SnapshotCache(10);
2527
2692
  this.lastRenderedLocation = new URL(location.href);
2693
+ this.forceReloaded = false;
2528
2694
  }
2529
- renderPage(snapshot, isPreview = false, willRender = true) {
2530
- const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
2695
+ renderPage(snapshot, isPreview = false, willRender = true, visit) {
2696
+ const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
2697
+ if (!renderer.shouldRender) {
2698
+ this.forceReloaded = true;
2699
+ } else {
2700
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2701
+ }
2531
2702
  return this.render(renderer);
2532
2703
  }
2533
- renderError(snapshot) {
2534
- const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2704
+ renderError(snapshot, visit) {
2705
+ visit === null || visit === void 0 ? void 0 : visit.changeHistory();
2706
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
2535
2707
  return this.render(renderer);
2536
2708
  }
2537
2709
  clearSnapshotCache() {
@@ -2558,10 +2730,52 @@ class PageView extends View {
2558
2730
  }
2559
2731
  }
2560
2732
 
2733
+ class Preloader {
2734
+ constructor(delegate) {
2735
+ this.selector = "a[data-turbo-preload]";
2736
+ this.delegate = delegate;
2737
+ }
2738
+ get snapshotCache() {
2739
+ return this.delegate.navigator.view.snapshotCache;
2740
+ }
2741
+ start() {
2742
+ if (document.readyState === "loading") {
2743
+ return document.addEventListener("DOMContentLoaded", (() => {
2744
+ this.preloadOnLoadLinksForView(document.body);
2745
+ }));
2746
+ } else {
2747
+ this.preloadOnLoadLinksForView(document.body);
2748
+ }
2749
+ }
2750
+ preloadOnLoadLinksForView(element) {
2751
+ for (const link of element.querySelectorAll(this.selector)) {
2752
+ this.preloadURL(link);
2753
+ }
2754
+ }
2755
+ async preloadURL(link) {
2756
+ const location = new URL(link.href);
2757
+ if (this.snapshotCache.has(location)) {
2758
+ return;
2759
+ }
2760
+ try {
2761
+ const response = await fetch(location.toString(), {
2762
+ headers: {
2763
+ "VND.PREFETCH": "true",
2764
+ Accept: "text/html"
2765
+ }
2766
+ });
2767
+ const responseText = await response.text();
2768
+ const snapshot = PageSnapshot.fromHTMLString(responseText);
2769
+ this.snapshotCache.put(location, snapshot);
2770
+ } catch (_) {}
2771
+ }
2772
+ }
2773
+
2561
2774
  class Session {
2562
2775
  constructor() {
2563
2776
  this.navigator = new Navigator(this);
2564
2777
  this.history = new History(this);
2778
+ this.preloader = new Preloader(this);
2565
2779
  this.view = new PageView(this, document.documentElement);
2566
2780
  this.adapter = new BrowserAdapter(this);
2567
2781
  this.pageObserver = new PageObserver(this);
@@ -2570,22 +2784,26 @@ class Session {
2570
2784
  this.formSubmitObserver = new FormSubmitObserver(this);
2571
2785
  this.scrollObserver = new ScrollObserver(this);
2572
2786
  this.streamObserver = new StreamObserver(this);
2787
+ this.formLinkInterceptor = new FormLinkInterceptor(this, document.documentElement);
2573
2788
  this.frameRedirector = new FrameRedirector(document.documentElement);
2574
2789
  this.drive = true;
2575
2790
  this.enabled = true;
2576
2791
  this.progressBarDelay = 500;
2577
2792
  this.started = false;
2793
+ this.formMode = "on";
2578
2794
  }
2579
2795
  start() {
2580
2796
  if (!this.started) {
2581
2797
  this.pageObserver.start();
2582
2798
  this.cacheObserver.start();
2799
+ this.formLinkInterceptor.start();
2583
2800
  this.linkClickObserver.start();
2584
2801
  this.formSubmitObserver.start();
2585
2802
  this.scrollObserver.start();
2586
2803
  this.streamObserver.start();
2587
2804
  this.frameRedirector.start();
2588
2805
  this.history.start();
2806
+ this.preloader.start();
2589
2807
  this.started = true;
2590
2808
  this.enabled = true;
2591
2809
  }
@@ -2597,6 +2815,7 @@ class Session {
2597
2815
  if (this.started) {
2598
2816
  this.pageObserver.stop();
2599
2817
  this.cacheObserver.stop();
2818
+ this.formLinkInterceptor.stop();
2600
2819
  this.linkClickObserver.stop();
2601
2820
  this.formSubmitObserver.stop();
2602
2821
  this.scrollObserver.stop();
@@ -2627,6 +2846,9 @@ class Session {
2627
2846
  setProgressBarDelay(delay) {
2628
2847
  this.progressBarDelay = delay;
2629
2848
  }
2849
+ setFormMode(mode) {
2850
+ this.formMode = mode;
2851
+ }
2630
2852
  get location() {
2631
2853
  return this.history.location;
2632
2854
  }
@@ -2640,7 +2862,9 @@ class Session {
2640
2862
  historyChanged: true
2641
2863
  });
2642
2864
  } else {
2643
- this.adapter.pageInvalidated();
2865
+ this.adapter.pageInvalidated({
2866
+ reason: "turbo_disabled"
2867
+ });
2644
2868
  }
2645
2869
  }
2646
2870
  scrollPositionChanged(position) {
@@ -2648,41 +2872,19 @@ class Session {
2648
2872
  scrollPosition: position
2649
2873
  });
2650
2874
  }
2651
- willFollowLinkToLocation(link, location) {
2652
- return this.elementDriveEnabled(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location);
2875
+ shouldInterceptFormLinkClick(_link) {
2876
+ return true;
2877
+ }
2878
+ formLinkClickIntercepted(_link, _form) {}
2879
+ willFollowLinkToLocation(link, location, event) {
2880
+ return this.elementDriveEnabled(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
2653
2881
  }
2654
2882
  followedLinkToLocation(link, location) {
2655
2883
  const action = this.getActionForLink(link);
2656
- this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, {
2884
+ this.visit(location.href, {
2657
2885
  action: action
2658
2886
  });
2659
2887
  }
2660
- convertLinkWithMethodClickToFormSubmission(link) {
2661
- const linkMethod = link.getAttribute("data-turbo-method");
2662
- if (linkMethod) {
2663
- const form = document.createElement("form");
2664
- form.method = linkMethod;
2665
- form.action = link.getAttribute("href") || "undefined";
2666
- form.hidden = true;
2667
- if (link.hasAttribute("data-turbo-confirm")) {
2668
- form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
2669
- }
2670
- const frame = this.getTargetFrameForLink(link);
2671
- if (frame) {
2672
- form.setAttribute("data-turbo-frame", frame);
2673
- form.addEventListener("turbo:submit-start", (() => form.remove()));
2674
- } else {
2675
- form.addEventListener("submit", (() => form.remove()));
2676
- }
2677
- document.body.appendChild(form);
2678
- return dispatch("submit", {
2679
- cancelable: true,
2680
- target: form
2681
- });
2682
- } else {
2683
- return false;
2684
- }
2685
- }
2686
2888
  allowsVisitingLocationWithAction(location, action) {
2687
2889
  return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
2688
2890
  }
@@ -2707,7 +2909,7 @@ class Session {
2707
2909
  }
2708
2910
  willSubmitForm(form, submitter) {
2709
2911
  const action = getAction(form, submitter);
2710
- return this.elementDriveEnabled(form) && (!submitter || this.elementDriveEnabled(submitter)) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
2912
+ return this.elementDriveEnabled(form) && (!submitter || this.formElementDriveEnabled(submitter)) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
2711
2913
  }
2712
2914
  formSubmitted(form, submitter) {
2713
2915
  this.navigator.submitForm(form, submitter);
@@ -2731,16 +2933,23 @@ class Session {
2731
2933
  this.notifyApplicationBeforeCachingSnapshot();
2732
2934
  }
2733
2935
  }
2734
- allowsImmediateRender({element: element}, resume) {
2735
- const event = this.notifyApplicationBeforeRender(element, resume);
2736
- return !event.defaultPrevented;
2936
+ allowsImmediateRender({element: element}, options) {
2937
+ const event = this.notifyApplicationBeforeRender(element, options);
2938
+ const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
2939
+ if (this.view.renderer && render) {
2940
+ this.view.renderer.renderElement = render;
2941
+ }
2942
+ return !defaultPrevented;
2737
2943
  }
2738
- viewRenderedSnapshot(snapshot, isPreview) {
2944
+ viewRenderedSnapshot(_snapshot, _isPreview) {
2739
2945
  this.view.lastRenderedLocation = this.history.location;
2740
2946
  this.notifyApplicationAfterRender();
2741
2947
  }
2742
- viewInvalidated() {
2743
- this.adapter.pageInvalidated();
2948
+ preloadOnLoadLinksForView(element) {
2949
+ this.preloader.preloadOnLoadLinksForView(element);
2950
+ }
2951
+ viewInvalidated(reason) {
2952
+ this.adapter.pageInvalidated(reason);
2744
2953
  }
2745
2954
  frameLoaded(frame) {
2746
2955
  this.notifyApplicationAfterFrameLoad(frame);
@@ -2748,19 +2957,20 @@ class Session {
2748
2957
  frameRendered(fetchResponse, frame) {
2749
2958
  this.notifyApplicationAfterFrameRender(fetchResponse, frame);
2750
2959
  }
2751
- applicationAllowsFollowingLinkToLocation(link, location) {
2752
- const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
2960
+ applicationAllowsFollowingLinkToLocation(link, location, ev) {
2961
+ const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
2753
2962
  return !event.defaultPrevented;
2754
2963
  }
2755
2964
  applicationAllowsVisitingLocation(location) {
2756
2965
  const event = this.notifyApplicationBeforeVisitingLocation(location);
2757
2966
  return !event.defaultPrevented;
2758
2967
  }
2759
- notifyApplicationAfterClickingLinkToLocation(link, location) {
2968
+ notifyApplicationAfterClickingLinkToLocation(link, location, event) {
2760
2969
  return dispatch("turbo:click", {
2761
2970
  target: link,
2762
2971
  detail: {
2763
- url: location.href
2972
+ url: location.href,
2973
+ originalEvent: event
2764
2974
  },
2765
2975
  cancelable: true
2766
2976
  });
@@ -2785,12 +2995,11 @@ class Session {
2785
2995
  notifyApplicationBeforeCachingSnapshot() {
2786
2996
  return dispatch("turbo:before-cache");
2787
2997
  }
2788
- notifyApplicationBeforeRender(newBody, resume) {
2998
+ notifyApplicationBeforeRender(newBody, options) {
2789
2999
  return dispatch("turbo:before-render", {
2790
- detail: {
2791
- newBody: newBody,
2792
- resume: resume
2793
- },
3000
+ detail: Object.assign({
3001
+ newBody: newBody
3002
+ }, options),
2794
3003
  cancelable: true
2795
3004
  });
2796
3005
  }
@@ -2826,6 +3035,16 @@ class Session {
2826
3035
  cancelable: true
2827
3036
  });
2828
3037
  }
3038
+ formElementDriveEnabled(element) {
3039
+ if (this.formMode == "off") {
3040
+ return false;
3041
+ }
3042
+ if (this.formMode == "optin") {
3043
+ const form = element === null || element === void 0 ? void 0 : element.closest("form[data-turbo]");
3044
+ return (form === null || form === void 0 ? void 0 : form.getAttribute("data-turbo")) == "true";
3045
+ }
3046
+ return this.elementDriveEnabled(element);
3047
+ }
2829
3048
  elementDriveEnabled(element) {
2830
3049
  const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
2831
3050
  if (this.drive) {
@@ -2846,17 +3065,6 @@ class Session {
2846
3065
  const action = link.getAttribute("data-turbo-action");
2847
3066
  return isAction(action) ? action : "advance";
2848
3067
  }
2849
- getTargetFrameForLink(link) {
2850
- const frame = link.getAttribute("data-turbo-frame");
2851
- if (frame) {
2852
- return frame;
2853
- } else {
2854
- const container = link.closest("turbo-frame");
2855
- if (container) {
2856
- return container.id;
2857
- }
2858
- }
2859
- }
2860
3068
  get snapshot() {
2861
3069
  return this.view.snapshot;
2862
3070
  }
@@ -2874,8 +3082,63 @@ const deprecatedLocationPropertyDescriptors = {
2874
3082
  }
2875
3083
  };
2876
3084
 
3085
+ class Cache {
3086
+ constructor(session) {
3087
+ this.session = session;
3088
+ }
3089
+ clear() {
3090
+ this.session.clearCache();
3091
+ }
3092
+ resetCacheControl() {
3093
+ this.setCacheControl("");
3094
+ }
3095
+ exemptPageFromCache() {
3096
+ this.setCacheControl("no-cache");
3097
+ }
3098
+ exemptPageFromPreview() {
3099
+ this.setCacheControl("no-preview");
3100
+ }
3101
+ setCacheControl(value) {
3102
+ setMetaContent("turbo-cache-control", value);
3103
+ }
3104
+ }
3105
+
3106
+ const StreamActions = {
3107
+ after() {
3108
+ this.targetElements.forEach((e => {
3109
+ var _a;
3110
+ return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
3111
+ }));
3112
+ },
3113
+ append() {
3114
+ this.removeDuplicateTargetChildren();
3115
+ this.targetElements.forEach((e => e.append(this.templateContent)));
3116
+ },
3117
+ before() {
3118
+ this.targetElements.forEach((e => {
3119
+ var _a;
3120
+ return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
3121
+ }));
3122
+ },
3123
+ prepend() {
3124
+ this.removeDuplicateTargetChildren();
3125
+ this.targetElements.forEach((e => e.prepend(this.templateContent)));
3126
+ },
3127
+ remove() {
3128
+ this.targetElements.forEach((e => e.remove()));
3129
+ },
3130
+ replace() {
3131
+ this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3132
+ },
3133
+ update() {
3134
+ this.targetElements.forEach((e => e.replaceChildren(this.templateContent)));
3135
+ }
3136
+ };
3137
+
2877
3138
  const session = new Session;
2878
3139
 
3140
+ const cache = new Cache(session);
3141
+
2879
3142
  const {navigator: navigator$1} = session;
2880
3143
 
2881
3144
  function start() {
@@ -2903,6 +3166,7 @@ function renderStreamMessage(message) {
2903
3166
  }
2904
3167
 
2905
3168
  function clearCache() {
3169
+ 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.`");
2906
3170
  session.clearCache();
2907
3171
  }
2908
3172
 
@@ -2914,12 +3178,18 @@ function setConfirmMethod(confirmMethod) {
2914
3178
  FormSubmission.confirmMethod = confirmMethod;
2915
3179
  }
2916
3180
 
3181
+ function setFormMode(mode) {
3182
+ session.setFormMode(mode);
3183
+ }
3184
+
2917
3185
  var Turbo = Object.freeze({
2918
3186
  __proto__: null,
2919
3187
  navigator: navigator$1,
2920
3188
  session: session,
3189
+ cache: cache,
2921
3190
  PageRenderer: PageRenderer,
2922
3191
  PageSnapshot: PageSnapshot,
3192
+ FrameRenderer: FrameRenderer,
2923
3193
  start: start,
2924
3194
  registerAdapter: registerAdapter,
2925
3195
  visit: visit,
@@ -2928,39 +3198,51 @@ var Turbo = Object.freeze({
2928
3198
  renderStreamMessage: renderStreamMessage,
2929
3199
  clearCache: clearCache,
2930
3200
  setProgressBarDelay: setProgressBarDelay,
2931
- setConfirmMethod: setConfirmMethod
3201
+ setConfirmMethod: setConfirmMethod,
3202
+ setFormMode: setFormMode,
3203
+ StreamActions: StreamActions
2932
3204
  });
2933
3205
 
2934
3206
  class FrameController {
2935
3207
  constructor(element) {
2936
- this.fetchResponseLoaded = fetchResponse => {};
3208
+ this.fetchResponseLoaded = _fetchResponse => {};
2937
3209
  this.currentFetchRequest = null;
2938
3210
  this.resolveVisitPromise = () => {};
2939
3211
  this.connected = false;
2940
3212
  this.hasBeenLoaded = false;
2941
- this.settingSourceURL = false;
3213
+ this.ignoredAttributes = new Set;
3214
+ this.visitCachedSnapshot = ({element: element}) => {
3215
+ const frame = element.querySelector("#" + this.element.id);
3216
+ if (frame && this.previousFrameElement) {
3217
+ frame.replaceChildren(...this.previousFrameElement.children);
3218
+ }
3219
+ delete this.previousFrameElement;
3220
+ };
2942
3221
  this.element = element;
2943
3222
  this.view = new FrameView(this, this.element);
2944
3223
  this.appearanceObserver = new AppearanceObserver(this, this.element);
3224
+ this.formLinkInterceptor = new FormLinkInterceptor(this, this.element);
2945
3225
  this.linkInterceptor = new LinkInterceptor(this, this.element);
2946
3226
  this.formInterceptor = new FormInterceptor(this, this.element);
2947
3227
  }
2948
3228
  connect() {
2949
3229
  if (!this.connected) {
2950
3230
  this.connected = true;
2951
- this.reloadable = false;
2952
3231
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2953
3232
  this.appearanceObserver.start();
3233
+ } else {
3234
+ this.loadSourceURL();
2954
3235
  }
3236
+ this.formLinkInterceptor.start();
2955
3237
  this.linkInterceptor.start();
2956
3238
  this.formInterceptor.start();
2957
- this.sourceURLChanged();
2958
3239
  }
2959
3240
  }
2960
3241
  disconnect() {
2961
3242
  if (this.connected) {
2962
3243
  this.connected = false;
2963
3244
  this.appearanceObserver.stop();
3245
+ this.formLinkInterceptor.stop();
2964
3246
  this.linkInterceptor.stop();
2965
3247
  this.formInterceptor.stop();
2966
3248
  }
@@ -2971,10 +3253,18 @@ class FrameController {
2971
3253
  }
2972
3254
  }
2973
3255
  sourceURLChanged() {
3256
+ if (this.isIgnoringChangesTo("src")) return;
3257
+ if (this.element.isConnected) {
3258
+ this.complete = false;
3259
+ }
2974
3260
  if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
2975
3261
  this.loadSourceURL();
2976
3262
  }
2977
3263
  }
3264
+ completeChanged() {
3265
+ if (this.isIgnoringChangesTo("complete")) return;
3266
+ this.loadSourceURL();
3267
+ }
2978
3268
  loadingStyleChanged() {
2979
3269
  if (this.loadingStyle == FrameLoadingStyle.lazy) {
2980
3270
  this.appearanceObserver.start();
@@ -2984,20 +3274,11 @@ class FrameController {
2984
3274
  }
2985
3275
  }
2986
3276
  async loadSourceURL() {
2987
- if (!this.settingSourceURL && this.enabled && this.isActive && (this.reloadable || this.sourceURL != this.currentURL)) {
2988
- const previousURL = this.currentURL;
2989
- this.currentURL = this.sourceURL;
2990
- if (this.sourceURL) {
2991
- try {
2992
- this.element.loaded = this.visit(expandURL(this.sourceURL));
2993
- this.appearanceObserver.stop();
2994
- await this.element.loaded;
2995
- this.hasBeenLoaded = true;
2996
- } catch (error) {
2997
- this.currentURL = previousURL;
2998
- throw error;
2999
- }
3000
- }
3277
+ if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
3278
+ this.element.loaded = this.visit(expandURL(this.sourceURL));
3279
+ this.appearanceObserver.stop();
3280
+ await this.element.loaded;
3281
+ this.hasBeenLoaded = true;
3001
3282
  }
3002
3283
  }
3003
3284
  async loadResponse(fetchResponse) {
@@ -3009,9 +3290,10 @@ class FrameController {
3009
3290
  if (html) {
3010
3291
  const {body: body} = parseHTMLDocument(html);
3011
3292
  const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
3012
- const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
3293
+ const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
3013
3294
  if (this.view.renderPromise) await this.view.renderPromise;
3014
3295
  await this.view.render(renderer);
3296
+ this.complete = true;
3015
3297
  session.frameRendered(fetchResponse, this.element);
3016
3298
  session.frameLoaded(this.element);
3017
3299
  this.fetchResponseLoaded(fetchResponse);
@@ -3023,18 +3305,20 @@ class FrameController {
3023
3305
  this.fetchResponseLoaded = () => {};
3024
3306
  }
3025
3307
  }
3026
- elementAppearedInViewport(element) {
3308
+ elementAppearedInViewport(_element) {
3027
3309
  this.loadSourceURL();
3028
3310
  }
3029
- shouldInterceptLinkClick(element, url) {
3030
- if (element.hasAttribute("data-turbo-method")) {
3031
- return false;
3032
- } else {
3033
- return this.shouldInterceptNavigation(element);
3034
- }
3311
+ shouldInterceptFormLinkClick(link) {
3312
+ return this.shouldInterceptNavigation(link);
3313
+ }
3314
+ formLinkClickIntercepted(link, form) {
3315
+ const frame = this.findFrameElement(link);
3316
+ if (frame) form.setAttribute("data-turbo-frame", frame.id);
3317
+ }
3318
+ shouldInterceptLinkClick(element, _url) {
3319
+ return this.shouldInterceptNavigation(element);
3035
3320
  }
3036
3321
  linkClickIntercepted(element, url) {
3037
- this.reloadable = true;
3038
3322
  this.navigateFrame(element, url);
3039
3323
  }
3040
3324
  shouldInterceptFormSubmission(element, submitter) {
@@ -3044,19 +3328,18 @@ class FrameController {
3044
3328
  if (this.formSubmission) {
3045
3329
  this.formSubmission.stop();
3046
3330
  }
3047
- this.reloadable = false;
3048
3331
  this.formSubmission = new FormSubmission(this, element, submitter);
3049
3332
  const {fetchRequest: fetchRequest} = this.formSubmission;
3050
3333
  this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3051
3334
  this.formSubmission.start();
3052
3335
  }
3053
- prepareHeadersForRequest(headers, request) {
3336
+ prepareHeadersForRequest(headers, _request) {
3054
3337
  headers["Turbo-Frame"] = this.id;
3055
3338
  }
3056
- requestStarted(request) {
3339
+ requestStarted(_request) {
3057
3340
  markAsBusy(this.element);
3058
3341
  }
3059
- requestPreventedHandlingResponse(request, response) {
3342
+ requestPreventedHandlingResponse(_request, _response) {
3060
3343
  this.resolveVisitPromise();
3061
3344
  }
3062
3345
  async requestSucceededWithResponse(request, response) {
@@ -3071,7 +3354,7 @@ class FrameController {
3071
3354
  console.error(error);
3072
3355
  this.resolveVisitPromise();
3073
3356
  }
3074
- requestFinished(request) {
3357
+ requestFinished(_request) {
3075
3358
  clearBusyState(this.element);
3076
3359
  }
3077
3360
  formSubmissionStarted({formElement: formElement}) {
@@ -3091,11 +3374,28 @@ class FrameController {
3091
3374
  formSubmissionFinished({formElement: formElement}) {
3092
3375
  clearBusyState(formElement, this.findFrameElement(formElement));
3093
3376
  }
3094
- allowsImmediateRender(snapshot, resume) {
3095
- return true;
3377
+ allowsImmediateRender({element: newFrame}, options) {
3378
+ const event = dispatch("turbo:before-frame-render", {
3379
+ target: this.element,
3380
+ detail: Object.assign({
3381
+ newFrame: newFrame
3382
+ }, options),
3383
+ cancelable: true
3384
+ });
3385
+ const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
3386
+ if (this.view.renderer && render) {
3387
+ this.view.renderer.renderElement = render;
3388
+ }
3389
+ return !defaultPrevented;
3390
+ }
3391
+ viewRenderedSnapshot(_snapshot, _isPreview) {}
3392
+ preloadOnLoadLinksForView(element) {
3393
+ session.preloadOnLoadLinksForView(element);
3096
3394
  }
3097
- viewRenderedSnapshot(snapshot, isPreview) {}
3098
3395
  viewInvalidated() {}
3396
+ frameExtracted(element) {
3397
+ this.previousFrameElement = element;
3398
+ }
3099
3399
  async visit(url) {
3100
3400
  var _a;
3101
3401
  const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
@@ -3113,13 +3413,12 @@ class FrameController {
3113
3413
  navigateFrame(element, url, submitter) {
3114
3414
  const frame = this.findFrameElement(element, submitter);
3115
3415
  this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3116
- frame.setAttribute("reloadable", "");
3117
3416
  frame.src = url;
3118
3417
  }
3119
3418
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3120
3419
  const action = getAttribute("data-turbo-action", submitter, element, frame);
3121
3420
  if (isAction(action)) {
3122
- const {visitCachedSnapshot: visitCachedSnapshot} = new SnapshotSubstitution(frame);
3421
+ const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
3123
3422
  frame.delegate.fetchResponseLoaded = fetchResponse => {
3124
3423
  if (frame.src) {
3125
3424
  const {statusCode: statusCode, redirected: redirected} = fetchResponse;
@@ -3148,10 +3447,12 @@ class FrameController {
3148
3447
  let element;
3149
3448
  const id = CSS.escape(this.id);
3150
3449
  try {
3151
- if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
3450
+ element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
3451
+ if (element) {
3152
3452
  return element;
3153
3453
  }
3154
- if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
3454
+ element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);
3455
+ if (element) {
3155
3456
  await element.loaded;
3156
3457
  return await this.extractForeignFrameElement(element);
3157
3458
  }
@@ -3198,23 +3499,10 @@ class FrameController {
3198
3499
  return this.element.src;
3199
3500
  }
3200
3501
  }
3201
- get reloadable() {
3202
- const frame = this.findFrameElement(this.element);
3203
- return frame.hasAttribute("reloadable");
3204
- }
3205
- set reloadable(value) {
3206
- const frame = this.findFrameElement(this.element);
3207
- if (value) {
3208
- frame.setAttribute("reloadable", "");
3209
- } else {
3210
- frame.removeAttribute("reloadable");
3211
- }
3212
- }
3213
3502
  set sourceURL(sourceURL) {
3214
- this.settingSourceURL = true;
3215
- this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3216
- this.currentURL = this.element.src;
3217
- this.settingSourceURL = false;
3503
+ this.ignoringChangesToAttribute("src", (() => {
3504
+ this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
3505
+ }));
3218
3506
  }
3219
3507
  get loadingStyle() {
3220
3508
  return this.element.loading;
@@ -3222,6 +3510,18 @@ class FrameController {
3222
3510
  get isLoading() {
3223
3511
  return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
3224
3512
  }
3513
+ get complete() {
3514
+ return this.element.hasAttribute("complete");
3515
+ }
3516
+ set complete(value) {
3517
+ this.ignoringChangesToAttribute("complete", (() => {
3518
+ if (value) {
3519
+ this.element.setAttribute("complete", "");
3520
+ } else {
3521
+ this.element.removeAttribute("complete");
3522
+ }
3523
+ }));
3524
+ }
3225
3525
  get isActive() {
3226
3526
  return this.element.isActive && this.connected;
3227
3527
  }
@@ -3231,17 +3531,13 @@ class FrameController {
3231
3531
  const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3232
3532
  return expandURL(root);
3233
3533
  }
3234
- }
3235
-
3236
- class SnapshotSubstitution {
3237
- constructor(element) {
3238
- this.visitCachedSnapshot = ({element: element}) => {
3239
- var _a;
3240
- const {id: id, clone: clone} = this;
3241
- (_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
3242
- };
3243
- this.clone = element.cloneNode(true);
3244
- this.id = element.id;
3534
+ isIgnoringChangesTo(attributeName) {
3535
+ return this.ignoredAttributes.has(attributeName);
3536
+ }
3537
+ ignoringChangesToAttribute(attributeName, callback) {
3538
+ this.ignoredAttributes.add(attributeName);
3539
+ callback();
3540
+ this.ignoredAttributes.delete(attributeName);
3245
3541
  }
3246
3542
  }
3247
3543
 
@@ -3271,41 +3567,6 @@ function activateElement(element, currentURL) {
3271
3567
  }
3272
3568
  }
3273
3569
 
3274
- const StreamActions = {
3275
- after() {
3276
- this.targetElements.forEach((e => {
3277
- var _a;
3278
- return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
3279
- }));
3280
- },
3281
- append() {
3282
- this.removeDuplicateTargetChildren();
3283
- this.targetElements.forEach((e => e.append(this.templateContent)));
3284
- },
3285
- before() {
3286
- this.targetElements.forEach((e => {
3287
- var _a;
3288
- return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
3289
- }));
3290
- },
3291
- prepend() {
3292
- this.removeDuplicateTargetChildren();
3293
- this.targetElements.forEach((e => e.prepend(this.templateContent)));
3294
- },
3295
- remove() {
3296
- this.targetElements.forEach((e => e.remove()));
3297
- },
3298
- replace() {
3299
- this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3300
- },
3301
- update() {
3302
- this.targetElements.forEach((e => {
3303
- e.innerHTML = "";
3304
- e.append(this.templateContent);
3305
- }));
3306
- }
3307
- };
3308
-
3309
3570
  class StreamElement extends HTMLElement {
3310
3571
  async connectedCallback() {
3311
3572
  try {
@@ -3336,7 +3597,7 @@ class StreamElement extends HTMLElement {
3336
3597
  get duplicateChildren() {
3337
3598
  var _a;
3338
3599
  const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
3339
- const newChildrenIds = [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].filter((c => !!c.id)).map((c => c.id));
3600
+ const newChildrenIds = [ ...((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [] ].filter((c => !!c.id)).map((c => c.id));
3340
3601
  return existingChildren.filter((c => newChildrenIds.includes(c.id)));
3341
3602
  }
3342
3603
  get performAction() {
@@ -3409,17 +3670,45 @@ class StreamElement extends HTMLElement {
3409
3670
  }
3410
3671
  }
3411
3672
 
3673
+ class StreamSourceElement extends HTMLElement {
3674
+ constructor() {
3675
+ super(...arguments);
3676
+ this.streamSource = null;
3677
+ }
3678
+ connectedCallback() {
3679
+ this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
3680
+ connectStreamSource(this.streamSource);
3681
+ }
3682
+ disconnectedCallback() {
3683
+ if (this.streamSource) {
3684
+ disconnectStreamSource(this.streamSource);
3685
+ }
3686
+ }
3687
+ get src() {
3688
+ return this.getAttribute("src") || "";
3689
+ }
3690
+ }
3691
+
3412
3692
  FrameElement.delegateConstructor = FrameController;
3413
3693
 
3414
- customElements.define("turbo-frame", FrameElement);
3694
+ if (customElements.get("turbo-frame") === undefined) {
3695
+ customElements.define("turbo-frame", FrameElement);
3696
+ }
3697
+
3698
+ if (customElements.get("turbo-stream") === undefined) {
3699
+ customElements.define("turbo-stream", StreamElement);
3700
+ }
3415
3701
 
3416
- customElements.define("turbo-stream", StreamElement);
3702
+ if (customElements.get("turbo-stream-source") === undefined) {
3703
+ customElements.define("turbo-stream-source", StreamSourceElement);
3704
+ }
3417
3705
 
3418
3706
  (() => {
3419
3707
  let element = document.currentScript;
3420
3708
  if (!element) return;
3421
3709
  if (element.hasAttribute("data-turbo-suppress-warning")) return;
3422
- while (element = element.parentElement) {
3710
+ element = element.parentElement;
3711
+ while (element) {
3423
3712
  if (element == document.body) {
3424
3713
  return console.warn(unindent`
3425
3714
  You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
@@ -3432,6 +3721,7 @@ customElements.define("turbo-stream", StreamElement);
3432
3721
  Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
3433
3722
  `, element.outerHTML);
3434
3723
  }
3724
+ element = element.parentElement;
3435
3725
  }
3436
3726
  })();
3437
3727
 
@@ -3441,8 +3731,11 @@ start();
3441
3731
 
3442
3732
  var turbo_es2017Esm = Object.freeze({
3443
3733
  __proto__: null,
3734
+ FrameRenderer: FrameRenderer,
3444
3735
  PageRenderer: PageRenderer,
3445
3736
  PageSnapshot: PageSnapshot,
3737
+ StreamActions: StreamActions,
3738
+ cache: cache,
3446
3739
  clearCache: clearCache,
3447
3740
  connectStreamSource: connectStreamSource,
3448
3741
  disconnectStreamSource: disconnectStreamSource,
@@ -3451,6 +3744,7 @@ var turbo_es2017Esm = Object.freeze({
3451
3744
  renderStreamMessage: renderStreamMessage,
3452
3745
  session: session,
3453
3746
  setConfirmMethod: setConfirmMethod,
3747
+ setFormMode: setFormMode,
3454
3748
  setProgressBarDelay: setProgressBarDelay,
3455
3749
  start: start,
3456
3750
  visit: visit