turbo-rails 1.3.0 → 1.5.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.
@@ -56,13 +56,11 @@ function clickCaptured(event) {
56
56
 
57
57
  (function() {
58
58
  if ("submitter" in Event.prototype) return;
59
- let prototype;
59
+ let prototype = window.Event.prototype;
60
60
  if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
61
61
  prototype = window.SubmitEvent.prototype;
62
62
  } else if ("SubmitEvent" in window) {
63
63
  return;
64
- } else {
65
- prototype = window.Event.prototype;
66
64
  }
67
65
  addEventListener("click", clickCaptured, true);
68
66
  Object.defineProperty(prototype, "submitter", {
@@ -82,14 +80,14 @@ var FrameLoadingStyle;
82
80
  })(FrameLoadingStyle || (FrameLoadingStyle = {}));
83
81
 
84
82
  class FrameElement extends HTMLElement {
83
+ static get observedAttributes() {
84
+ return [ "disabled", "complete", "loading", "src" ];
85
+ }
85
86
  constructor() {
86
87
  super();
87
88
  this.loaded = Promise.resolve();
88
89
  this.delegate = new FrameElement.delegateConstructor(this);
89
90
  }
90
- static get observedAttributes() {
91
- return [ "disabled", "complete", "loading", "src" ];
92
- }
93
91
  connectedCallback() {
94
92
  this.delegate.connect();
95
93
  }
@@ -97,11 +95,7 @@ class FrameElement extends HTMLElement {
97
95
  this.delegate.disconnect();
98
96
  }
99
97
  reload() {
100
- const {src: src} = this;
101
- this.removeAttribute("complete");
102
- this.src = null;
103
- this.src = src;
104
- return this.loaded;
98
+ return this.delegate.sourceURLReloaded();
105
99
  }
106
100
  attributeChangedCallback(name) {
107
101
  if (name == "loading") {
@@ -286,10 +280,6 @@ class FetchResponse {
286
280
  }
287
281
  }
288
282
 
289
- function isAction(action) {
290
- return action == "advance" || action == "replace" || action == "restore";
291
- }
292
-
293
283
  function activateScriptElement(element) {
294
284
  if (element.getAttribute("data-turbo-eval") == "false") {
295
285
  return element;
@@ -322,6 +312,7 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
322
312
  const event = new CustomEvent(eventName, {
323
313
  cancelable: cancelable,
324
314
  bubbles: true,
315
+ composed: true,
325
316
  detail: detail
326
317
  });
327
318
  if (target && target.isConnected) {
@@ -435,6 +426,10 @@ function getHistoryMethodForAction(action) {
435
426
  }
436
427
  }
437
428
 
429
+ function isAction(action) {
430
+ return action == "advance" || action == "replace" || action == "restore";
431
+ }
432
+
438
433
  function getVisitAction(...elements) {
439
434
  const action = getAttribute("data-turbo-action", ...elements);
440
435
  return isAction(action) ? action : null;
@@ -460,6 +455,13 @@ function setMetaContent(name, content) {
460
455
  return element;
461
456
  }
462
457
 
458
+ function findClosestRecursively(element, selector) {
459
+ var _a;
460
+ if (element instanceof Element) {
461
+ return element.closest(selector) || findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector);
462
+ }
463
+ }
464
+
463
465
  var FetchMethod;
464
466
 
465
467
  (function(FetchMethod) {
@@ -513,9 +515,8 @@ class FetchRequest {
513
515
  this.abortController.abort();
514
516
  }
515
517
  async perform() {
516
- var _a, _b;
517
518
  const {fetchOptions: fetchOptions} = this;
518
- (_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
519
+ this.delegate.prepareRequest(this);
519
520
  await this.allowRequestToBeIntercepted(fetchOptions);
520
521
  try {
521
522
  this.delegate.requestStarted(this);
@@ -557,7 +558,7 @@ class FetchRequest {
557
558
  credentials: "same-origin",
558
559
  headers: this.headers,
559
560
  redirect: "follow",
560
- body: this.isIdempotent ? null : this.body,
561
+ body: this.isSafe ? null : this.body,
561
562
  signal: this.abortSignal,
562
563
  referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
563
564
  };
@@ -567,8 +568,8 @@ class FetchRequest {
567
568
  Accept: "text/html, application/xhtml+xml"
568
569
  };
569
570
  }
570
- get isIdempotent() {
571
- return this.method == FetchMethod.get;
571
+ get isSafe() {
572
+ return this.method === FetchMethod.get;
572
573
  }
573
574
  get abortSignal() {
574
575
  return this.abortController.signal;
@@ -630,9 +631,6 @@ class AppearanceObserver {
630
631
  }
631
632
 
632
633
  class StreamMessage {
633
- constructor(fragment) {
634
- this.fragment = importStreamElements(fragment);
635
- }
636
634
  static wrap(message) {
637
635
  if (typeof message == "string") {
638
636
  return new this(createDocumentFragment(message));
@@ -640,6 +638,9 @@ class StreamMessage {
640
638
  return message;
641
639
  }
642
640
  }
641
+ constructor(fragment) {
642
+ this.fragment = importStreamElements(fragment);
643
+ }
643
644
  }
644
645
 
645
646
  StreamMessage.contentType = "text/vnd.turbo-stream.html";
@@ -688,6 +689,9 @@ function formEnctypeFromString(encoding) {
688
689
  }
689
690
 
690
691
  class FormSubmission {
692
+ static confirmMethod(message, _element, _submitter) {
693
+ return Promise.resolve(confirm(message));
694
+ }
691
695
  constructor(delegate, formElement, submitter, mustRedirect = false) {
692
696
  this.state = FormSubmissionState.initialized;
693
697
  this.delegate = delegate;
@@ -701,9 +705,6 @@ class FormSubmission {
701
705
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
702
706
  this.mustRedirect = mustRedirect;
703
707
  }
704
- static confirmMethod(message, _element, _submitter) {
705
- return Promise.resolve(confirm(message));
706
- }
707
708
  get method() {
708
709
  var _a;
709
710
  const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
@@ -729,8 +730,8 @@ class FormSubmission {
729
730
  var _a;
730
731
  return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
731
732
  }
732
- get isIdempotent() {
733
- return this.fetchRequest.isIdempotent;
733
+ get isSafe() {
734
+ return this.fetchRequest.isSafe;
734
735
  }
735
736
  get stringFormData() {
736
737
  return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
@@ -757,11 +758,11 @@ class FormSubmission {
757
758
  return true;
758
759
  }
759
760
  }
760
- prepareHeadersForRequest(headers, request) {
761
- if (!request.isIdempotent) {
761
+ prepareRequest(request) {
762
+ if (!request.isSafe) {
762
763
  const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
763
764
  if (token) {
764
- headers["X-CSRF-Token"] = token;
765
+ request.headers["X-CSRF-Token"] = token;
765
766
  }
766
767
  }
767
768
  if (this.requestAcceptsTurboStreamResponse(request)) {
@@ -772,6 +773,7 @@ class FormSubmission {
772
773
  var _a;
773
774
  this.state = FormSubmissionState.waiting;
774
775
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
776
+ this.setSubmitsWith();
775
777
  dispatch("turbo:submit-start", {
776
778
  target: this.formElement,
777
779
  detail: {
@@ -819,6 +821,7 @@ class FormSubmission {
819
821
  var _a;
820
822
  this.state = FormSubmissionState.stopped;
821
823
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
824
+ this.resetSubmitterText();
822
825
  dispatch("turbo:submit-end", {
823
826
  target: this.formElement,
824
827
  detail: Object.assign({
@@ -827,11 +830,35 @@ class FormSubmission {
827
830
  });
828
831
  this.delegate.formSubmissionFinished(this);
829
832
  }
833
+ setSubmitsWith() {
834
+ if (!this.submitter || !this.submitsWith) return;
835
+ if (this.submitter.matches("button")) {
836
+ this.originalSubmitText = this.submitter.innerHTML;
837
+ this.submitter.innerHTML = this.submitsWith;
838
+ } else if (this.submitter.matches("input")) {
839
+ const input = this.submitter;
840
+ this.originalSubmitText = input.value;
841
+ input.value = this.submitsWith;
842
+ }
843
+ }
844
+ resetSubmitterText() {
845
+ if (!this.submitter || !this.originalSubmitText) return;
846
+ if (this.submitter.matches("button")) {
847
+ this.submitter.innerHTML = this.originalSubmitText;
848
+ } else if (this.submitter.matches("input")) {
849
+ const input = this.submitter;
850
+ input.value = this.originalSubmitText;
851
+ }
852
+ }
830
853
  requestMustRedirect(request) {
831
- return !request.isIdempotent && this.mustRedirect;
854
+ return !request.isSafe && this.mustRedirect;
832
855
  }
833
856
  requestAcceptsTurboStreamResponse(request) {
834
- return !request.isIdempotent || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
857
+ return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
858
+ }
859
+ get submitsWith() {
860
+ var _a;
861
+ return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with");
835
862
  }
836
863
  }
837
864
 
@@ -936,6 +963,7 @@ class FormSubmitObserver {
936
963
  const submitter = event.submitter || undefined;
937
964
  if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
938
965
  event.preventDefault();
966
+ event.stopImmediatePropagation();
939
967
  this.delegate.formSubmitted(form, submitter);
940
968
  }
941
969
  }
@@ -963,11 +991,15 @@ function submissionDoesNotDismissDialog(form, submitter) {
963
991
  }
964
992
 
965
993
  function submissionDoesNotTargetIFrame(form, submitter) {
966
- const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
967
- for (const element of document.getElementsByName(target)) {
968
- if (element instanceof HTMLIFrameElement) return false;
994
+ if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
995
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
996
+ for (const element of document.getElementsByName(target)) {
997
+ if (element instanceof HTMLIFrameElement) return false;
998
+ }
999
+ return true;
1000
+ } else {
1001
+ return true;
969
1002
  }
970
- return true;
971
1003
  }
972
1004
 
973
1005
  class View {
@@ -1068,14 +1100,55 @@ class View {
1068
1100
  }
1069
1101
 
1070
1102
  class FrameView extends View {
1071
- invalidate() {
1072
- this.element.innerHTML = "";
1103
+ missing() {
1104
+ this.element.innerHTML = `<strong class="turbo-frame-error">Content missing</strong>`;
1073
1105
  }
1074
1106
  get snapshot() {
1075
1107
  return new Snapshot(this.element);
1076
1108
  }
1077
1109
  }
1078
1110
 
1111
+ class LinkInterceptor {
1112
+ constructor(delegate, element) {
1113
+ this.clickBubbled = event => {
1114
+ if (this.respondsToEventTarget(event.target)) {
1115
+ this.clickEvent = event;
1116
+ } else {
1117
+ delete this.clickEvent;
1118
+ }
1119
+ };
1120
+ this.linkClicked = event => {
1121
+ if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
1122
+ if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
1123
+ this.clickEvent.preventDefault();
1124
+ event.preventDefault();
1125
+ this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
1126
+ }
1127
+ }
1128
+ delete this.clickEvent;
1129
+ };
1130
+ this.willVisit = _event => {
1131
+ delete this.clickEvent;
1132
+ };
1133
+ this.delegate = delegate;
1134
+ this.element = element;
1135
+ }
1136
+ start() {
1137
+ this.element.addEventListener("click", this.clickBubbled);
1138
+ document.addEventListener("turbo:click", this.linkClicked);
1139
+ document.addEventListener("turbo:before-visit", this.willVisit);
1140
+ }
1141
+ stop() {
1142
+ this.element.removeEventListener("click", this.clickBubbled);
1143
+ document.removeEventListener("turbo:click", this.linkClicked);
1144
+ document.removeEventListener("turbo:before-visit", this.willVisit);
1145
+ }
1146
+ respondsToEventTarget(target) {
1147
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
1148
+ return element && element.closest("turbo-frame, html") == this.element;
1149
+ }
1150
+ }
1151
+
1079
1152
  class LinkClickObserver {
1080
1153
  constructor(delegate, eventTarget) {
1081
1154
  this.started = false;
@@ -1115,9 +1188,7 @@ class LinkClickObserver {
1115
1188
  return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
1116
1189
  }
1117
1190
  findLinkFromClickTarget(target) {
1118
- if (target instanceof Element) {
1119
- return target.closest("a[href]:not([target^=_]):not([download])");
1120
- }
1191
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
1121
1192
  }
1122
1193
  getLocationForLink(link) {
1123
1194
  return expandURL(link.getAttribute("href") || "");
@@ -1125,37 +1196,51 @@ class LinkClickObserver {
1125
1196
  }
1126
1197
 
1127
1198
  function doesNotTargetIFrame(anchor) {
1128
- for (const element of document.getElementsByName(anchor.target)) {
1129
- if (element instanceof HTMLIFrameElement) return false;
1199
+ if (anchor.hasAttribute("target")) {
1200
+ for (const element of document.getElementsByName(anchor.target)) {
1201
+ if (element instanceof HTMLIFrameElement) return false;
1202
+ }
1203
+ return true;
1204
+ } else {
1205
+ return true;
1130
1206
  }
1131
- return true;
1132
1207
  }
1133
1208
 
1134
1209
  class FormLinkClickObserver {
1135
1210
  constructor(delegate, element) {
1136
1211
  this.delegate = delegate;
1137
- this.linkClickObserver = new LinkClickObserver(this, element);
1212
+ this.linkInterceptor = new LinkClickObserver(this, element);
1138
1213
  }
1139
1214
  start() {
1140
- this.linkClickObserver.start();
1215
+ this.linkInterceptor.start();
1141
1216
  }
1142
1217
  stop() {
1143
- this.linkClickObserver.stop();
1218
+ this.linkInterceptor.stop();
1144
1219
  }
1145
1220
  willFollowLinkToLocation(link, location, originalEvent) {
1146
1221
  return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
1147
1222
  }
1148
1223
  followedLinkToLocation(link, location) {
1149
- const action = location.href;
1150
1224
  const form = document.createElement("form");
1225
+ const type = "hidden";
1226
+ for (const [name, value] of location.searchParams) {
1227
+ form.append(Object.assign(document.createElement("input"), {
1228
+ type: type,
1229
+ name: name,
1230
+ value: value
1231
+ }));
1232
+ }
1233
+ const action = Object.assign(location, {
1234
+ search: ""
1235
+ });
1151
1236
  form.setAttribute("data-turbo", "true");
1152
- form.setAttribute("action", action);
1237
+ form.setAttribute("action", action.href);
1153
1238
  form.setAttribute("hidden", "");
1154
1239
  const method = link.getAttribute("data-turbo-method");
1155
1240
  if (method) form.setAttribute("method", method);
1156
1241
  const turboFrame = link.getAttribute("data-turbo-frame");
1157
1242
  if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
1158
- const turboAction = link.getAttribute("data-turbo-action");
1243
+ const turboAction = getVisitAction(link);
1159
1244
  if (turboAction) form.setAttribute("data-turbo-action", turboAction);
1160
1245
  const turboConfirm = link.getAttribute("data-turbo-confirm");
1161
1246
  if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
@@ -1171,16 +1256,16 @@ class FormLinkClickObserver {
1171
1256
  }
1172
1257
 
1173
1258
  class Bardo {
1174
- constructor(delegate, permanentElementMap) {
1175
- this.delegate = delegate;
1176
- this.permanentElementMap = permanentElementMap;
1177
- }
1178
- static preservingPermanentElements(delegate, permanentElementMap, callback) {
1259
+ static async preservingPermanentElements(delegate, permanentElementMap, callback) {
1179
1260
  const bardo = new this(delegate, permanentElementMap);
1180
1261
  bardo.enter();
1181
- callback();
1262
+ await callback();
1182
1263
  bardo.leave();
1183
1264
  }
1265
+ constructor(delegate, permanentElementMap) {
1266
+ this.delegate = delegate;
1267
+ this.permanentElementMap = permanentElementMap;
1268
+ }
1184
1269
  enter() {
1185
1270
  for (const id in this.permanentElementMap) {
1186
1271
  const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
@@ -1251,8 +1336,8 @@ class Renderer {
1251
1336
  delete this.resolvingFunctions;
1252
1337
  }
1253
1338
  }
1254
- preservingPermanentElements(callback) {
1255
- Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1339
+ async preservingPermanentElements(callback) {
1340
+ await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1256
1341
  }
1257
1342
  focusFirstAutofocusableElement() {
1258
1343
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1291,10 +1376,6 @@ function elementIsFocusable(element) {
1291
1376
  }
1292
1377
 
1293
1378
  class FrameRenderer extends Renderer {
1294
- constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1295
- super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1296
- this.delegate = delegate;
1297
- }
1298
1379
  static renderElement(currentElement, newElement) {
1299
1380
  var _a;
1300
1381
  const destinationRange = document.createRange();
@@ -1307,6 +1388,10 @@ class FrameRenderer extends Renderer {
1307
1388
  currentElement.appendChild(sourceRange.extractContents());
1308
1389
  }
1309
1390
  }
1391
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1392
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1393
+ this.delegate = delegate;
1394
+ }
1310
1395
  get shouldRender() {
1311
1396
  return true;
1312
1397
  }
@@ -1368,18 +1453,6 @@ function readScrollBehavior(value, defaultValue) {
1368
1453
  }
1369
1454
 
1370
1455
  class ProgressBar {
1371
- constructor() {
1372
- this.hiding = false;
1373
- this.value = 0;
1374
- this.visible = false;
1375
- this.trickle = () => {
1376
- this.setValue(this.value + Math.random() / 100);
1377
- };
1378
- this.stylesheetElement = this.createStylesheetElement();
1379
- this.progressElement = this.createProgressElement();
1380
- this.installStylesheetElement();
1381
- this.setValue(0);
1382
- }
1383
1456
  static get defaultCSS() {
1384
1457
  return unindent`
1385
1458
  .turbo-progress-bar {
@@ -1397,6 +1470,18 @@ class ProgressBar {
1397
1470
  }
1398
1471
  `;
1399
1472
  }
1473
+ constructor() {
1474
+ this.hiding = false;
1475
+ this.value = 0;
1476
+ this.visible = false;
1477
+ this.trickle = () => {
1478
+ this.setValue(this.value + Math.random() / 100);
1479
+ };
1480
+ this.stylesheetElement = this.createStylesheetElement();
1481
+ this.progressElement = this.createProgressElement();
1482
+ this.installStylesheetElement();
1483
+ this.setValue(0);
1484
+ }
1400
1485
  show() {
1401
1486
  if (!this.visible) {
1402
1487
  this.visible = true;
@@ -1565,10 +1650,6 @@ function elementWithoutNonce(element) {
1565
1650
  }
1566
1651
 
1567
1652
  class PageSnapshot extends Snapshot {
1568
- constructor(element, headSnapshot) {
1569
- super(element);
1570
- this.headSnapshot = headSnapshot;
1571
- }
1572
1653
  static fromHTMLString(html = "") {
1573
1654
  return this.fromDocument(parseHTMLDocument(html));
1574
1655
  }
@@ -1578,6 +1659,10 @@ class PageSnapshot extends Snapshot {
1578
1659
  static fromDocument({head: head, body: body}) {
1579
1660
  return new this(body, new HeadSnapshot(head));
1580
1661
  }
1662
+ constructor(element, headSnapshot) {
1663
+ super(element);
1664
+ this.headSnapshot = headSnapshot;
1665
+ }
1581
1666
  clone() {
1582
1667
  const clonedElement = this.element.cloneNode(true);
1583
1668
  const selectElements = this.element.querySelectorAll("select");
@@ -1668,10 +1753,11 @@ class Visit {
1668
1753
  this.delegate = delegate;
1669
1754
  this.location = location;
1670
1755
  this.restorationIdentifier = restorationIdentifier || uuid();
1671
- const {action: action, historyChanged: historyChanged, referrer: referrer, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = Object.assign(Object.assign({}, defaultOptions), options);
1756
+ const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = Object.assign(Object.assign({}, defaultOptions), options);
1672
1757
  this.action = action;
1673
1758
  this.historyChanged = historyChanged;
1674
1759
  this.referrer = referrer;
1760
+ this.snapshot = snapshot;
1675
1761
  this.snapshotHTML = snapshotHTML;
1676
1762
  this.response = response;
1677
1763
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
@@ -1834,7 +1920,9 @@ class Visit {
1834
1920
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1835
1921
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1836
1922
  action: "replace",
1837
- response: this.response
1923
+ response: this.response,
1924
+ shouldCacheSnapshot: false,
1925
+ willRender: false
1838
1926
  });
1839
1927
  this.followedRedirect = true;
1840
1928
  }
@@ -1844,11 +1932,12 @@ class Visit {
1844
1932
  this.render((async () => {
1845
1933
  this.cacheSnapshot();
1846
1934
  this.performScroll();
1935
+ this.changeHistory();
1847
1936
  this.adapter.visitRendered(this);
1848
1937
  }));
1849
1938
  }
1850
1939
  }
1851
- prepareHeadersForRequest(headers, request) {
1940
+ prepareRequest(request) {
1852
1941
  if (this.acceptsStreamResponse) {
1853
1942
  request.acceptResponseType(StreamMessage.contentType);
1854
1943
  }
@@ -1956,7 +2045,7 @@ class Visit {
1956
2045
  }
1957
2046
  cacheSnapshot() {
1958
2047
  if (!this.snapshotCached) {
1959
- this.view.cacheSnapshot().then((snapshot => snapshot && this.visitCachedSnapshot(snapshot)));
2048
+ this.view.cacheSnapshot(this.snapshot).then((snapshot => snapshot && this.visitCachedSnapshot(snapshot)));
1960
2049
  this.snapshotCached = true;
1961
2050
  }
1962
2051
  }
@@ -2065,11 +2154,11 @@ class BrowserAdapter {
2065
2154
  }
2066
2155
  }
2067
2156
  reload(reason) {
2157
+ var _a;
2068
2158
  dispatch("turbo:reload", {
2069
2159
  detail: reason
2070
2160
  });
2071
- if (!this.location) return;
2072
- window.location.href = this.location.toString();
2161
+ window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;
2073
2162
  }
2074
2163
  get navigator() {
2075
2164
  return this.session.navigator;
@@ -2078,10 +2167,11 @@ class BrowserAdapter {
2078
2167
 
2079
2168
  class CacheObserver {
2080
2169
  constructor() {
2170
+ this.selector = "[data-turbo-temporary]";
2171
+ this.deprecatedSelector = "[data-turbo-cache=false]";
2081
2172
  this.started = false;
2082
- this.removeStaleElements = _event => {
2083
- const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
2084
- for (const element of staleElements) {
2173
+ this.removeTemporaryElements = _event => {
2174
+ for (const element of this.temporaryElements) {
2085
2175
  element.remove();
2086
2176
  }
2087
2177
  };
@@ -2089,14 +2179,24 @@ class CacheObserver {
2089
2179
  start() {
2090
2180
  if (!this.started) {
2091
2181
  this.started = true;
2092
- addEventListener("turbo:before-cache", this.removeStaleElements, false);
2182
+ addEventListener("turbo:before-cache", this.removeTemporaryElements, false);
2093
2183
  }
2094
2184
  }
2095
2185
  stop() {
2096
2186
  if (this.started) {
2097
2187
  this.started = false;
2098
- removeEventListener("turbo:before-cache", this.removeStaleElements, false);
2188
+ removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
2189
+ }
2190
+ }
2191
+ get temporaryElements() {
2192
+ return [ ...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation ];
2193
+ }
2194
+ get temporaryElementsWithDeprecation() {
2195
+ const elements = document.querySelectorAll(this.deprecatedSelector);
2196
+ if (elements.length) {
2197
+ console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);
2099
2198
  }
2199
+ return [ ...elements ];
2100
2200
  }
2101
2201
  }
2102
2202
 
@@ -2104,24 +2204,24 @@ class FrameRedirector {
2104
2204
  constructor(session, element) {
2105
2205
  this.session = session;
2106
2206
  this.element = element;
2107
- this.linkClickObserver = new LinkClickObserver(this, element);
2207
+ this.linkInterceptor = new LinkInterceptor(this, element);
2108
2208
  this.formSubmitObserver = new FormSubmitObserver(this, element);
2109
2209
  }
2110
2210
  start() {
2111
- this.linkClickObserver.start();
2211
+ this.linkInterceptor.start();
2112
2212
  this.formSubmitObserver.start();
2113
2213
  }
2114
2214
  stop() {
2115
- this.linkClickObserver.stop();
2215
+ this.linkInterceptor.stop();
2116
2216
  this.formSubmitObserver.stop();
2117
2217
  }
2118
- willFollowLinkToLocation(element, location, event) {
2119
- return this.shouldRedirect(element) && this.frameAllowsVisitingLocation(element, location, event);
2218
+ shouldInterceptLinkClick(element, _location, _event) {
2219
+ return this.shouldRedirect(element);
2120
2220
  }
2121
- followedLinkToLocation(element, url) {
2221
+ linkClickIntercepted(element, url, event) {
2122
2222
  const frame = this.findFrameElement(element);
2123
2223
  if (frame) {
2124
- frame.delegate.followedLinkToLocation(element, url);
2224
+ frame.delegate.linkClickIntercepted(element, url, event);
2125
2225
  }
2126
2226
  }
2127
2227
  willSubmitForm(element, submitter) {
@@ -2133,17 +2233,6 @@ class FrameRedirector {
2133
2233
  frame.delegate.formSubmitted(element, submitter);
2134
2234
  }
2135
2235
  }
2136
- frameAllowsVisitingLocation(target, {href: url}, originalEvent) {
2137
- const event = dispatch("turbo:click", {
2138
- target: target,
2139
- detail: {
2140
- url: url,
2141
- originalEvent: originalEvent
2142
- },
2143
- cancelable: true
2144
- });
2145
- return !event.defaultPrevented;
2146
- }
2147
2236
  shouldSubmit(form, submitter) {
2148
2237
  var _a;
2149
2238
  const action = getAction(form, submitter);
@@ -2268,7 +2357,6 @@ class Navigator {
2268
2357
  }
2269
2358
  }
2270
2359
  startVisit(locatable, restorationIdentifier, options = {}) {
2271
- this.lastVisit = this.currentVisit;
2272
2360
  this.stop();
2273
2361
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
2274
2362
  referrer: this.location
@@ -2308,7 +2396,7 @@ class Navigator {
2308
2396
  if (formSubmission == this.formSubmission) {
2309
2397
  const responseHTML = await fetchResponse.responseHTML;
2310
2398
  if (responseHTML) {
2311
- const shouldCacheSnapshot = formSubmission.method == FetchMethod.get;
2399
+ const shouldCacheSnapshot = formSubmission.isSafe;
2312
2400
  if (!shouldCacheSnapshot) {
2313
2401
  this.view.clearSnapshotCache();
2314
2402
  }
@@ -2355,12 +2443,10 @@ class Navigator {
2355
2443
  this.delegate.visitCompleted(visit);
2356
2444
  }
2357
2445
  locationWithActionIsSamePage(location, action) {
2358
- var _a;
2359
2446
  const anchor = getAnchor(location);
2360
- const lastLocation = ((_a = this.lastVisit) === null || _a === void 0 ? void 0 : _a.location) || this.view.lastRenderedLocation;
2361
- const currentAnchor = getAnchor(lastLocation);
2447
+ const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2362
2448
  const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2363
- return action !== "replace" && getRequestURL(location) === getRequestURL(lastLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
2449
+ return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
2364
2450
  }
2365
2451
  visitScrolledToSamePageLocation(oldURL, newURL) {
2366
2452
  this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
@@ -2371,10 +2457,8 @@ class Navigator {
2371
2457
  get restorationIdentifier() {
2372
2458
  return this.history.restorationIdentifier;
2373
2459
  }
2374
- getActionForFormSubmission(formSubmission) {
2375
- const {formElement: formElement, submitter: submitter} = formSubmission;
2376
- const action = getAttribute("data-turbo-action", submitter, formElement);
2377
- return isAction(action) ? action : "advance";
2460
+ getActionForFormSubmission({submitter: submitter, formElement: formElement}) {
2461
+ return getVisitAction(submitter, formElement) || "advance";
2378
2462
  }
2379
2463
  }
2380
2464
 
@@ -2622,7 +2706,7 @@ class PageRenderer extends Renderer {
2622
2706
  }
2623
2707
  async render() {
2624
2708
  if (this.willRender) {
2625
- this.replaceBody();
2709
+ await this.replaceBody();
2626
2710
  }
2627
2711
  }
2628
2712
  finishRendering() {
@@ -2641,16 +2725,16 @@ class PageRenderer extends Renderer {
2641
2725
  return this.newSnapshot.element;
2642
2726
  }
2643
2727
  async mergeHead() {
2728
+ const mergedHeadElements = this.mergeProvisionalElements();
2644
2729
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
2645
2730
  this.copyNewHeadScriptElements();
2646
- this.removeCurrentHeadProvisionalElements();
2647
- this.copyNewHeadProvisionalElements();
2731
+ await mergedHeadElements;
2648
2732
  await newStylesheetElements;
2649
2733
  }
2650
- replaceBody() {
2651
- this.preservingPermanentElements((() => {
2734
+ async replaceBody() {
2735
+ await this.preservingPermanentElements((async () => {
2652
2736
  this.activateNewBody();
2653
- this.assignNewBody();
2737
+ await this.assignNewBody();
2654
2738
  }));
2655
2739
  }
2656
2740
  get trackedElementsAreIdentical() {
@@ -2669,6 +2753,35 @@ class PageRenderer extends Renderer {
2669
2753
  document.head.appendChild(activateScriptElement(element));
2670
2754
  }
2671
2755
  }
2756
+ async mergeProvisionalElements() {
2757
+ const newHeadElements = [ ...this.newHeadProvisionalElements ];
2758
+ for (const element of this.currentHeadProvisionalElements) {
2759
+ if (!this.isCurrentElementInElementList(element, newHeadElements)) {
2760
+ document.head.removeChild(element);
2761
+ }
2762
+ }
2763
+ for (const element of newHeadElements) {
2764
+ document.head.appendChild(element);
2765
+ }
2766
+ }
2767
+ isCurrentElementInElementList(element, elementList) {
2768
+ for (const [index, newElement] of elementList.entries()) {
2769
+ if (element.tagName == "TITLE") {
2770
+ if (newElement.tagName != "TITLE") {
2771
+ continue;
2772
+ }
2773
+ if (element.innerHTML == newElement.innerHTML) {
2774
+ elementList.splice(index, 1);
2775
+ return true;
2776
+ }
2777
+ }
2778
+ if (newElement.isEqualNode(element)) {
2779
+ elementList.splice(index, 1);
2780
+ return true;
2781
+ }
2782
+ }
2783
+ return false;
2784
+ }
2672
2785
  removeCurrentHeadProvisionalElements() {
2673
2786
  for (const element of this.currentHeadProvisionalElements) {
2674
2787
  document.head.removeChild(element);
@@ -2689,8 +2802,8 @@ class PageRenderer extends Renderer {
2689
2802
  inertScriptElement.replaceWith(activatedScriptElement);
2690
2803
  }
2691
2804
  }
2692
- assignNewBody() {
2693
- this.renderElement(this.currentElement, this.newElement);
2805
+ async assignNewBody() {
2806
+ await this.renderElement(this.currentElement, this.newElement);
2694
2807
  }
2695
2808
  get newHeadStylesheetElements() {
2696
2809
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -2777,10 +2890,10 @@ class PageView extends View {
2777
2890
  clearSnapshotCache() {
2778
2891
  this.snapshotCache.clear();
2779
2892
  }
2780
- async cacheSnapshot() {
2781
- if (this.shouldCacheSnapshot) {
2893
+ async cacheSnapshot(snapshot = this.snapshot) {
2894
+ if (snapshot.isCacheable) {
2782
2895
  this.delegate.viewWillCacheSnapshot();
2783
- const {snapshot: snapshot, lastRenderedLocation: location} = this;
2896
+ const {lastRenderedLocation: location} = this;
2784
2897
  await nextEventLoopTick();
2785
2898
  const cachedSnapshot = snapshot.clone();
2786
2899
  this.snapshotCache.put(location, cachedSnapshot);
@@ -2793,9 +2906,6 @@ class PageView extends View {
2793
2906
  get snapshot() {
2794
2907
  return PageSnapshot.fromElement(this.element);
2795
2908
  }
2796
- get shouldCacheSnapshot() {
2797
- return this.snapshot.isCacheable;
2798
- }
2799
2909
  }
2800
2910
 
2801
2911
  class Preloader {
@@ -3127,8 +3237,8 @@ class Session {
3127
3237
  }
3128
3238
  }
3129
3239
  elementIsNavigatable(element) {
3130
- const container = element.closest("[data-turbo]");
3131
- const withinFrame = element.closest("turbo-frame");
3240
+ const container = findClosestRecursively(element, "[data-turbo]");
3241
+ const withinFrame = findClosestRecursively(element, "turbo-frame");
3132
3242
  if (this.drive || withinFrame) {
3133
3243
  if (container) {
3134
3244
  return container.getAttribute("data-turbo") != "false";
@@ -3144,8 +3254,7 @@ class Session {
3144
3254
  }
3145
3255
  }
3146
3256
  getActionForLink(link) {
3147
- const action = link.getAttribute("data-turbo-action");
3148
- return isAction(action) ? action : "advance";
3257
+ return getVisitAction(link) || "advance";
3149
3258
  }
3150
3259
  get snapshot() {
3151
3260
  return this.view.snapshot;
@@ -3213,7 +3322,10 @@ const StreamActions = {
3213
3322
  this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3214
3323
  },
3215
3324
  update() {
3216
- this.targetElements.forEach((e => e.replaceChildren(this.templateContent)));
3325
+ this.targetElements.forEach((targetElement => {
3326
+ targetElement.innerHTML = "";
3327
+ targetElement.append(this.templateContent);
3328
+ }));
3217
3329
  }
3218
3330
  };
3219
3331
 
@@ -3285,6 +3397,8 @@ var Turbo = Object.freeze({
3285
3397
  StreamActions: StreamActions
3286
3398
  });
3287
3399
 
3400
+ class TurboFrameMissingError extends Error {}
3401
+
3288
3402
  class FrameController {
3289
3403
  constructor(element) {
3290
3404
  this.fetchResponseLoaded = _fetchResponse => {};
@@ -3305,7 +3419,7 @@ class FrameController {
3305
3419
  this.view = new FrameView(this, this.element);
3306
3420
  this.appearanceObserver = new AppearanceObserver(this, this.element);
3307
3421
  this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);
3308
- this.linkClickObserver = new LinkClickObserver(this, this.element);
3422
+ this.linkInterceptor = new LinkInterceptor(this, this.element);
3309
3423
  this.restorationIdentifier = uuid();
3310
3424
  this.formSubmitObserver = new FormSubmitObserver(this, this.element);
3311
3425
  }
@@ -3318,7 +3432,7 @@ class FrameController {
3318
3432
  this.loadSourceURL();
3319
3433
  }
3320
3434
  this.formLinkClickObserver.start();
3321
- this.linkClickObserver.start();
3435
+ this.linkInterceptor.start();
3322
3436
  this.formSubmitObserver.start();
3323
3437
  }
3324
3438
  }
@@ -3327,7 +3441,7 @@ class FrameController {
3327
3441
  this.connected = false;
3328
3442
  this.appearanceObserver.stop();
3329
3443
  this.formLinkClickObserver.stop();
3330
- this.linkClickObserver.stop();
3444
+ this.linkInterceptor.stop();
3331
3445
  this.formSubmitObserver.stop();
3332
3446
  }
3333
3447
  }
@@ -3345,6 +3459,15 @@ class FrameController {
3345
3459
  this.loadSourceURL();
3346
3460
  }
3347
3461
  }
3462
+ sourceURLReloaded() {
3463
+ const {src: src} = this.element;
3464
+ this.ignoringChangesToAttribute("complete", (() => {
3465
+ this.element.removeAttribute("complete");
3466
+ }));
3467
+ this.element.src = null;
3468
+ this.element.src = src;
3469
+ return this.element.loaded;
3470
+ }
3348
3471
  completeChanged() {
3349
3472
  if (this.isIgnoringChangesTo("complete")) return;
3350
3473
  this.loadSourceURL();
@@ -3372,45 +3495,34 @@ class FrameController {
3372
3495
  try {
3373
3496
  const html = await fetchResponse.responseHTML;
3374
3497
  if (html) {
3375
- const {body: body} = parseHTMLDocument(html);
3376
- const newFrameElement = await this.extractForeignFrameElement(body);
3377
- if (newFrameElement) {
3378
- const snapshot = new Snapshot(newFrameElement);
3379
- const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
3380
- if (this.view.renderPromise) await this.view.renderPromise;
3381
- this.changeHistory();
3382
- await this.view.render(renderer);
3383
- this.complete = true;
3384
- session.frameRendered(fetchResponse, this.element);
3385
- session.frameLoaded(this.element);
3386
- this.fetchResponseLoaded(fetchResponse);
3387
- } else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3388
- console.warn(`A matching frame for #${this.element.id} was missing from the response, transforming into full-page Visit.`);
3389
- this.visitResponse(fetchResponse.response);
3498
+ const document = parseHTMLDocument(html);
3499
+ const pageSnapshot = PageSnapshot.fromDocument(document);
3500
+ if (pageSnapshot.isVisitable) {
3501
+ await this.loadFrameResponse(fetchResponse, document);
3502
+ } else {
3503
+ await this.handleUnvisitableFrameResponse(fetchResponse);
3390
3504
  }
3391
3505
  }
3392
- } catch (error) {
3393
- console.error(error);
3394
- this.view.invalidate();
3395
3506
  } finally {
3396
3507
  this.fetchResponseLoaded = () => {};
3397
3508
  }
3398
3509
  }
3399
- elementAppearedInViewport(_element) {
3510
+ elementAppearedInViewport(element) {
3511
+ this.proposeVisitIfNavigatedWithAction(element, element);
3400
3512
  this.loadSourceURL();
3401
3513
  }
3402
3514
  willSubmitFormLinkToLocation(link) {
3403
- return link.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(link);
3515
+ return this.shouldInterceptNavigation(link);
3404
3516
  }
3405
3517
  submittedFormLinkToLocation(link, _location, form) {
3406
3518
  const frame = this.findFrameElement(link);
3407
3519
  if (frame) form.setAttribute("data-turbo-frame", frame.id);
3408
3520
  }
3409
- willFollowLinkToLocation(element, location, event) {
3410
- return this.shouldInterceptNavigation(element) && this.frameAllowsVisitingLocation(element, location, event);
3521
+ shouldInterceptLinkClick(element, _location, _event) {
3522
+ return this.shouldInterceptNavigation(element);
3411
3523
  }
3412
- followedLinkToLocation(element, location) {
3413
- this.navigateFrame(element, location.href);
3524
+ linkClickIntercepted(element, location) {
3525
+ this.navigateFrame(element, location);
3414
3526
  }
3415
3527
  willSubmitForm(element, submitter) {
3416
3528
  return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
@@ -3421,12 +3533,12 @@ class FrameController {
3421
3533
  }
3422
3534
  this.formSubmission = new FormSubmission(this, element, submitter);
3423
3535
  const {fetchRequest: fetchRequest} = this.formSubmission;
3424
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3536
+ this.prepareRequest(fetchRequest);
3425
3537
  this.formSubmission.start();
3426
3538
  }
3427
- prepareHeadersForRequest(headers, request) {
3539
+ prepareRequest(request) {
3428
3540
  var _a;
3429
- headers["Turbo-Frame"] = this.id;
3541
+ request.headers["Turbo-Frame"] = this.id;
3430
3542
  if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3431
3543
  request.acceptResponseType(StreamMessage.contentType);
3432
3544
  }
@@ -3442,7 +3554,6 @@ class FrameController {
3442
3554
  this.resolveVisitPromise();
3443
3555
  }
3444
3556
  async requestFailedWithResponse(request, response) {
3445
- console.error(response);
3446
3557
  await this.loadResponse(response);
3447
3558
  this.resolveVisitPromise();
3448
3559
  }
@@ -3458,11 +3569,15 @@ class FrameController {
3458
3569
  }
3459
3570
  formSubmissionSucceededWithResponse(formSubmission, response) {
3460
3571
  const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
3461
- this.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
3572
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
3462
3573
  frame.delegate.loadResponse(response);
3574
+ if (!formSubmission.isSafe) {
3575
+ session.clearCache();
3576
+ }
3463
3577
  }
3464
3578
  formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
3465
3579
  this.element.delegate.loadResponse(fetchResponse);
3580
+ session.clearCache();
3466
3581
  }
3467
3582
  formSubmissionErrored(formSubmission, error) {
3468
3583
  console.error(error);
@@ -3492,6 +3607,22 @@ class FrameController {
3492
3607
  willRenderFrame(currentElement, _newElement) {
3493
3608
  this.previousFrameElement = currentElement.cloneNode(true);
3494
3609
  }
3610
+ async loadFrameResponse(fetchResponse, document) {
3611
+ const newFrameElement = await this.extractForeignFrameElement(document.body);
3612
+ if (newFrameElement) {
3613
+ const snapshot = new Snapshot(newFrameElement);
3614
+ const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
3615
+ if (this.view.renderPromise) await this.view.renderPromise;
3616
+ this.changeHistory();
3617
+ await this.view.render(renderer);
3618
+ this.complete = true;
3619
+ session.frameRendered(fetchResponse, this.element);
3620
+ session.frameLoaded(this.element);
3621
+ this.fetchResponseLoaded(fetchResponse);
3622
+ } else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3623
+ this.handleFrameMissingFromResponse(fetchResponse);
3624
+ }
3625
+ }
3495
3626
  async visit(url) {
3496
3627
  var _a;
3497
3628
  const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
@@ -3508,15 +3639,15 @@ class FrameController {
3508
3639
  }
3509
3640
  navigateFrame(element, url, submitter) {
3510
3641
  const frame = this.findFrameElement(element, submitter);
3511
- this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3642
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3512
3643
  this.withCurrentNavigationElement(element, (() => {
3513
3644
  frame.src = url;
3514
3645
  }));
3515
3646
  }
3516
3647
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3517
3648
  this.action = getVisitAction(submitter, element, frame);
3518
- this.frame = frame;
3519
- if (isAction(this.action)) {
3649
+ if (this.action) {
3650
+ const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3520
3651
  const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
3521
3652
  frame.delegate.fetchResponseLoaded = fetchResponse => {
3522
3653
  if (frame.src) {
@@ -3532,7 +3663,8 @@ class FrameController {
3532
3663
  visitCachedSnapshot: visitCachedSnapshot,
3533
3664
  willRender: false,
3534
3665
  updateHistory: false,
3535
- restorationIdentifier: this.restorationIdentifier
3666
+ restorationIdentifier: this.restorationIdentifier,
3667
+ snapshot: pageSnapshot
3536
3668
  };
3537
3669
  if (this.action) options.action = this.action;
3538
3670
  session.visit(frame.src, options);
@@ -3541,11 +3673,15 @@ class FrameController {
3541
3673
  }
3542
3674
  }
3543
3675
  changeHistory() {
3544
- if (this.action && this.frame) {
3676
+ if (this.action) {
3545
3677
  const method = getHistoryMethodForAction(this.action);
3546
- session.history.update(method, expandURL(this.frame.src || ""), this.restorationIdentifier);
3678
+ session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
3547
3679
  }
3548
3680
  }
3681
+ async handleUnvisitableFrameResponse(fetchResponse) {
3682
+ console.warn(`The response (${fetchResponse.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.`);
3683
+ await this.visitResponse(fetchResponse.response);
3684
+ }
3549
3685
  willHandleFrameMissingFromResponse(fetchResponse) {
3550
3686
  this.element.setAttribute("complete", "");
3551
3687
  const response = fetchResponse.response;
@@ -3566,6 +3702,14 @@ class FrameController {
3566
3702
  });
3567
3703
  return !event.defaultPrevented;
3568
3704
  }
3705
+ handleFrameMissingFromResponse(fetchResponse) {
3706
+ this.view.missing();
3707
+ this.throwFrameMissingError(fetchResponse);
3708
+ }
3709
+ throwFrameMissingError(fetchResponse) {
3710
+ 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.`;
3711
+ throw new TurboFrameMissingError(message);
3712
+ }
3569
3713
  async visitResponse(response) {
3570
3714
  const wrapped = new FetchResponse(response);
3571
3715
  const responseHTML = await wrapped.responseHTML;
@@ -3671,17 +3815,6 @@ class FrameController {
3671
3815
  const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3672
3816
  return expandURL(root);
3673
3817
  }
3674
- frameAllowsVisitingLocation(target, {href: url}, originalEvent) {
3675
- const event = dispatch("turbo:click", {
3676
- target: target,
3677
- detail: {
3678
- url: url,
3679
- originalEvent: originalEvent
3680
- },
3681
- cancelable: true
3682
- });
3683
- return !event.defaultPrevented;
3684
- }
3685
3818
  isIgnoringChangesTo(attributeName) {
3686
3819
  return this.ignoredAttributes.has(attributeName);
3687
3820
  }
@@ -3971,7 +4104,9 @@ class TurboCableStreamSourceElement extends HTMLElement {
3971
4104
  async connectedCallback() {
3972
4105
  connectStreamSource(this);
3973
4106
  this.subscription = await subscribeTo(this.channel, {
3974
- received: this.dispatchMessageEvent.bind(this)
4107
+ received: this.dispatchMessageEvent.bind(this),
4108
+ connected: this.subscriptionConnected.bind(this),
4109
+ disconnected: this.subscriptionDisconnected.bind(this)
3975
4110
  });
3976
4111
  }
3977
4112
  disconnectedCallback() {
@@ -3984,6 +4119,12 @@ class TurboCableStreamSourceElement extends HTMLElement {
3984
4119
  });
3985
4120
  return this.dispatchEvent(event);
3986
4121
  }
4122
+ subscriptionConnected() {
4123
+ this.setAttribute("connected", "");
4124
+ }
4125
+ subscriptionDisconnected() {
4126
+ this.removeAttribute("connected");
4127
+ }
3987
4128
  get channel() {
3988
4129
  const channel = this.getAttribute("channel");
3989
4130
  const signed_stream_name = this.getAttribute("signed-stream-name");
@@ -3997,18 +4138,21 @@ class TurboCableStreamSourceElement extends HTMLElement {
3997
4138
  }
3998
4139
  }
3999
4140
 
4000
- customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement);
4141
+ if (customElements.get("turbo-cable-stream-source") === undefined) {
4142
+ customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement);
4143
+ }
4001
4144
 
4002
4145
  function encodeMethodIntoRequestBody(event) {
4003
4146
  if (event.target instanceof HTMLFormElement) {
4004
4147
  const {target: form, detail: {fetchOptions: fetchOptions}} = event;
4005
4148
  form.addEventListener("turbo:submit-start", (({detail: {formSubmission: {submitter: submitter}}}) => {
4006
- const method = submitter && submitter.formMethod || fetchOptions.body && fetchOptions.body.get("_method") || form.getAttribute("method");
4149
+ const body = isBodyInit(fetchOptions.body) ? fetchOptions.body : new URLSearchParams;
4150
+ const method = determineFetchMethod(submitter, body, form);
4007
4151
  if (!/get/i.test(method)) {
4008
4152
  if (/post/i.test(method)) {
4009
- fetchOptions.body.delete("_method");
4153
+ body.delete("_method");
4010
4154
  } else {
4011
- fetchOptions.body.set("_method", method);
4155
+ body.set("_method", method);
4012
4156
  }
4013
4157
  fetchOptions.method = "post";
4014
4158
  }
@@ -4018,6 +4162,37 @@ function encodeMethodIntoRequestBody(event) {
4018
4162
  }
4019
4163
  }
4020
4164
 
4165
+ function determineFetchMethod(submitter, body, form) {
4166
+ const formMethod = determineFormMethod(submitter);
4167
+ const overrideMethod = body.get("_method");
4168
+ const method = form.getAttribute("method") || "get";
4169
+ if (typeof formMethod == "string") {
4170
+ return formMethod;
4171
+ } else if (typeof overrideMethod == "string") {
4172
+ return overrideMethod;
4173
+ } else {
4174
+ return method;
4175
+ }
4176
+ }
4177
+
4178
+ function determineFormMethod(submitter) {
4179
+ if (submitter instanceof HTMLButtonElement || submitter instanceof HTMLInputElement) {
4180
+ if (submitter.name === "_method") {
4181
+ return submitter.value;
4182
+ } else if (submitter.hasAttribute("formmethod")) {
4183
+ return submitter.formMethod;
4184
+ } else {
4185
+ return null;
4186
+ }
4187
+ } else {
4188
+ return null;
4189
+ }
4190
+ }
4191
+
4192
+ function isBodyInit(body) {
4193
+ return body instanceof FormData || body instanceof URLSearchParams;
4194
+ }
4195
+
4021
4196
  addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
4022
4197
 
4023
4198
  var adapters = {