@hotwired/turbo 7.2.4 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/turbo.es2017-esm.js +238 -128
  2. package/dist/turbo.es2017-umd.js +238 -128
  3. package/dist/types/core/bardo.d.ts +1 -1
  4. package/dist/types/core/drive/form_submission.d.ts +10 -6
  5. package/dist/types/core/drive/head_snapshot.d.ts +3 -3
  6. package/dist/types/core/drive/history.d.ts +3 -3
  7. package/dist/types/core/drive/navigator.d.ts +2 -2
  8. package/dist/types/core/drive/page_renderer.d.ts +4 -2
  9. package/dist/types/core/drive/page_view.d.ts +2 -2
  10. package/dist/types/core/drive/visit.d.ts +5 -5
  11. package/dist/types/core/errors.d.ts +2 -0
  12. package/dist/types/core/frames/frame_controller.d.ts +11 -9
  13. package/dist/types/core/frames/frame_view.d.ts +2 -2
  14. package/dist/types/core/index.d.ts +5 -4
  15. package/dist/types/core/native/browser_adapter.d.ts +1 -1
  16. package/dist/types/core/renderer.d.ts +2 -2
  17. package/dist/types/core/session.d.ts +12 -12
  18. package/dist/types/core/snapshot.d.ts +1 -1
  19. package/dist/types/core/streams/stream_actions.d.ts +2 -2
  20. package/dist/types/core/types.d.ts +3 -4
  21. package/dist/types/core/url.d.ts +1 -1
  22. package/dist/types/core/view.d.ts +1 -1
  23. package/dist/types/elements/frame_element.d.ts +1 -1
  24. package/dist/types/elements/stream_element.d.ts +2 -2
  25. package/dist/types/http/fetch_request.d.ts +8 -8
  26. package/dist/types/http/index.d.ts +1 -1
  27. package/dist/types/observers/appearance_observer.d.ts +6 -6
  28. package/dist/types/observers/cache_observer.d.ts +5 -1
  29. package/dist/types/observers/form_link_click_observer.d.ts +1 -1
  30. package/dist/types/observers/link_click_observer.d.ts +1 -1
  31. package/dist/types/polyfills/custom-elements-native-shim.d.ts +1 -0
  32. package/dist/types/tests/functional/frame_tests.d.ts +7 -0
  33. package/dist/types/tests/helpers/dom_test_case.d.ts +1 -2
  34. package/dist/types/tests/helpers/page.d.ts +15 -8
  35. package/dist/types/tests/unit/deprecated_adapter_support_tests.d.ts +1 -0
  36. package/dist/types/tests/unit/export_tests.d.ts +1 -5
  37. package/dist/types/tests/unit/stream_element_tests.d.ts +0 -10
  38. package/dist/types/util.d.ts +3 -1
  39. package/package.json +17 -11
  40. package/dist/types/tests/helpers/intern_test_case.d.ts +0 -20
  41. package/dist/types/tests/unit/deprecated_adapter_support_test.d.ts +0 -24
  42. package/dist/types/tests/unit/index.d.ts +0 -3
  43. /package/dist/types/tests/{functional → integration}/ujs_tests.d.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  /*
2
- Turbo 7.2.4
3
- Copyright © 2022 37signals LLC
2
+ Turbo 7.3.0
3
+ Copyright © 2023 37signals LLC
4
4
  */
5
5
  (function (global, factory) {
6
6
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
@@ -93,16 +93,13 @@ Copyright © 2022 37signals LLC
93
93
  (function () {
94
94
  if ("submitter" in Event.prototype)
95
95
  return;
96
- let prototype;
96
+ let prototype = window.Event.prototype;
97
97
  if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
98
98
  prototype = window.SubmitEvent.prototype;
99
99
  }
100
100
  else if ("SubmitEvent" in window) {
101
101
  return;
102
102
  }
103
- else {
104
- prototype = window.Event.prototype;
105
- }
106
103
  addEventListener("click", clickCaptured, true);
107
104
  Object.defineProperty(prototype, "submitter", {
108
105
  get() {
@@ -119,14 +116,14 @@ Copyright © 2022 37signals LLC
119
116
  FrameLoadingStyle["lazy"] = "lazy";
120
117
  })(exports.FrameLoadingStyle || (exports.FrameLoadingStyle = {}));
121
118
  class FrameElement extends HTMLElement {
119
+ static get observedAttributes() {
120
+ return ["disabled", "complete", "loading", "src"];
121
+ }
122
122
  constructor() {
123
123
  super();
124
124
  this.loaded = Promise.resolve();
125
125
  this.delegate = new FrameElement.delegateConstructor(this);
126
126
  }
127
- static get observedAttributes() {
128
- return ["disabled", "complete", "loading", "src"];
129
- }
130
127
  connectedCallback() {
131
128
  this.delegate.connect();
132
129
  }
@@ -313,10 +310,6 @@ Copyright © 2022 37signals LLC
313
310
  }
314
311
  }
315
312
 
316
- function isAction(action) {
317
- return action == "advance" || action == "replace" || action == "restore";
318
- }
319
-
320
313
  function activateScriptElement(element) {
321
314
  if (element.getAttribute("data-turbo-eval") == "false") {
322
315
  return element;
@@ -347,6 +340,7 @@ Copyright © 2022 37signals LLC
347
340
  const event = new CustomEvent(eventName, {
348
341
  cancelable,
349
342
  bubbles: true,
343
+ composed: true,
350
344
  detail,
351
345
  });
352
346
  if (target && target.isConnected) {
@@ -446,6 +440,9 @@ Copyright © 2022 37signals LLC
446
440
  return history.pushState;
447
441
  }
448
442
  }
443
+ function isAction(action) {
444
+ return action == "advance" || action == "replace" || action == "restore";
445
+ }
449
446
  function getVisitAction(...elements) {
450
447
  const action = getAttribute("data-turbo-action", ...elements);
451
448
  return isAction(action) ? action : null;
@@ -467,6 +464,13 @@ Copyright © 2022 37signals LLC
467
464
  element.setAttribute("content", content);
468
465
  return element;
469
466
  }
467
+ function findClosestRecursively(element, selector) {
468
+ var _a;
469
+ if (element instanceof Element) {
470
+ return (element.closest(selector) ||
471
+ findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector));
472
+ }
473
+ }
470
474
 
471
475
  var FetchMethod;
472
476
  (function (FetchMethod) {
@@ -514,9 +518,8 @@ Copyright © 2022 37signals LLC
514
518
  this.abortController.abort();
515
519
  }
516
520
  async perform() {
517
- var _a, _b;
518
521
  const { fetchOptions } = this;
519
- (_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
522
+ this.delegate.prepareRequest(this);
520
523
  await this.allowRequestToBeIntercepted(fetchOptions);
521
524
  try {
522
525
  this.delegate.requestStarted(this);
@@ -560,7 +563,7 @@ Copyright © 2022 37signals LLC
560
563
  credentials: "same-origin",
561
564
  headers: this.headers,
562
565
  redirect: "follow",
563
- body: this.isIdempotent ? null : this.body,
566
+ body: this.isSafe ? null : this.body,
564
567
  signal: this.abortSignal,
565
568
  referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href,
566
569
  };
@@ -570,8 +573,8 @@ Copyright © 2022 37signals LLC
570
573
  Accept: "text/html, application/xhtml+xml",
571
574
  };
572
575
  }
573
- get isIdempotent() {
574
- return this.method == FetchMethod.get;
576
+ get isSafe() {
577
+ return this.method === FetchMethod.get;
575
578
  }
576
579
  get abortSignal() {
577
580
  return this.abortController.signal;
@@ -631,9 +634,6 @@ Copyright © 2022 37signals LLC
631
634
  }
632
635
 
633
636
  class StreamMessage {
634
- constructor(fragment) {
635
- this.fragment = importStreamElements(fragment);
636
- }
637
637
  static wrap(message) {
638
638
  if (typeof message == "string") {
639
639
  return new this(createDocumentFragment(message));
@@ -642,6 +642,9 @@ Copyright © 2022 37signals LLC
642
642
  return message;
643
643
  }
644
644
  }
645
+ constructor(fragment) {
646
+ this.fragment = importStreamElements(fragment);
647
+ }
645
648
  }
646
649
  StreamMessage.contentType = "text/vnd.turbo-stream.html";
647
650
  function importStreamElements(fragment) {
@@ -681,6 +684,9 @@ Copyright © 2022 37signals LLC
681
684
  }
682
685
  }
683
686
  class FormSubmission {
687
+ static confirmMethod(message, _element, _submitter) {
688
+ return Promise.resolve(confirm(message));
689
+ }
684
690
  constructor(delegate, formElement, submitter, mustRedirect = false) {
685
691
  this.state = FormSubmissionState.initialized;
686
692
  this.delegate = delegate;
@@ -694,9 +700,6 @@ Copyright © 2022 37signals LLC
694
700
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
695
701
  this.mustRedirect = mustRedirect;
696
702
  }
697
- static confirmMethod(message, _element, _submitter) {
698
- return Promise.resolve(confirm(message));
699
- }
700
703
  get method() {
701
704
  var _a;
702
705
  const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
@@ -724,8 +727,8 @@ Copyright © 2022 37signals LLC
724
727
  var _a;
725
728
  return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
726
729
  }
727
- get isIdempotent() {
728
- return this.fetchRequest.isIdempotent;
730
+ get isSafe() {
731
+ return this.fetchRequest.isSafe;
729
732
  }
730
733
  get stringFormData() {
731
734
  return [...this.formData].reduce((entries, [name, value]) => {
@@ -754,11 +757,11 @@ Copyright © 2022 37signals LLC
754
757
  return true;
755
758
  }
756
759
  }
757
- prepareHeadersForRequest(headers, request) {
758
- if (!request.isIdempotent) {
760
+ prepareRequest(request) {
761
+ if (!request.isSafe) {
759
762
  const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
760
763
  if (token) {
761
- headers["X-CSRF-Token"] = token;
764
+ request.headers["X-CSRF-Token"] = token;
762
765
  }
763
766
  }
764
767
  if (this.requestAcceptsTurboStreamResponse(request)) {
@@ -769,6 +772,7 @@ Copyright © 2022 37signals LLC
769
772
  var _a;
770
773
  this.state = FormSubmissionState.waiting;
771
774
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
775
+ this.setSubmitsWith();
772
776
  dispatch("turbo:submit-start", {
773
777
  target: this.formElement,
774
778
  detail: { formSubmission: this },
@@ -804,17 +808,46 @@ Copyright © 2022 37signals LLC
804
808
  var _a;
805
809
  this.state = FormSubmissionState.stopped;
806
810
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
811
+ this.resetSubmitterText();
807
812
  dispatch("turbo:submit-end", {
808
813
  target: this.formElement,
809
814
  detail: Object.assign({ formSubmission: this }, this.result),
810
815
  });
811
816
  this.delegate.formSubmissionFinished(this);
812
817
  }
818
+ setSubmitsWith() {
819
+ if (!this.submitter || !this.submitsWith)
820
+ return;
821
+ if (this.submitter.matches("button")) {
822
+ this.originalSubmitText = this.submitter.innerHTML;
823
+ this.submitter.innerHTML = this.submitsWith;
824
+ }
825
+ else if (this.submitter.matches("input")) {
826
+ const input = this.submitter;
827
+ this.originalSubmitText = input.value;
828
+ input.value = this.submitsWith;
829
+ }
830
+ }
831
+ resetSubmitterText() {
832
+ if (!this.submitter || !this.originalSubmitText)
833
+ return;
834
+ if (this.submitter.matches("button")) {
835
+ this.submitter.innerHTML = this.originalSubmitText;
836
+ }
837
+ else if (this.submitter.matches("input")) {
838
+ const input = this.submitter;
839
+ input.value = this.originalSubmitText;
840
+ }
841
+ }
813
842
  requestMustRedirect(request) {
814
- return !request.isIdempotent && this.mustRedirect;
843
+ return !request.isSafe && this.mustRedirect;
815
844
  }
816
845
  requestAcceptsTurboStreamResponse(request) {
817
- return !request.isIdempotent || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
846
+ return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
847
+ }
848
+ get submitsWith() {
849
+ var _a;
850
+ return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with");
818
851
  }
819
852
  }
820
853
  function buildFormData(formElement, submitter) {
@@ -946,12 +979,17 @@ Copyright © 2022 37signals LLC
946
979
  return method != "dialog";
947
980
  }
948
981
  function submissionDoesNotTargetIFrame(form, submitter) {
949
- const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
950
- for (const element of document.getElementsByName(target)) {
951
- if (element instanceof HTMLIFrameElement)
952
- return false;
982
+ if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
983
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
984
+ for (const element of document.getElementsByName(target)) {
985
+ if (element instanceof HTMLIFrameElement)
986
+ return false;
987
+ }
988
+ return true;
989
+ }
990
+ else {
991
+ return true;
953
992
  }
954
- return true;
955
993
  }
956
994
 
957
995
  class View {
@@ -1049,8 +1087,8 @@ Copyright © 2022 37signals LLC
1049
1087
  }
1050
1088
 
1051
1089
  class FrameView extends View {
1052
- invalidate() {
1053
- this.element.innerHTML = "";
1090
+ missing() {
1091
+ this.element.innerHTML = `<strong class="turbo-frame-error">Content missing</strong>`;
1054
1092
  }
1055
1093
  get snapshot() {
1056
1094
  return new Snapshot(this.element);
@@ -1144,20 +1182,23 @@ Copyright © 2022 37signals LLC
1144
1182
  event.shiftKey);
1145
1183
  }
1146
1184
  findLinkFromClickTarget(target) {
1147
- if (target instanceof Element) {
1148
- return target.closest("a[href]:not([target^=_]):not([download])");
1149
- }
1185
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
1150
1186
  }
1151
1187
  getLocationForLink(link) {
1152
1188
  return expandURL(link.getAttribute("href") || "");
1153
1189
  }
1154
1190
  }
1155
1191
  function doesNotTargetIFrame(anchor) {
1156
- for (const element of document.getElementsByName(anchor.target)) {
1157
- if (element instanceof HTMLIFrameElement)
1158
- return false;
1192
+ if (anchor.hasAttribute("target")) {
1193
+ for (const element of document.getElementsByName(anchor.target)) {
1194
+ if (element instanceof HTMLIFrameElement)
1195
+ return false;
1196
+ }
1197
+ return true;
1198
+ }
1199
+ else {
1200
+ return true;
1159
1201
  }
1160
- return true;
1161
1202
  }
1162
1203
 
1163
1204
  class FormLinkClickObserver {
@@ -1176,10 +1217,14 @@ Copyright © 2022 37signals LLC
1176
1217
  link.hasAttribute("data-turbo-method"));
1177
1218
  }
1178
1219
  followedLinkToLocation(link, location) {
1179
- const action = location.href;
1180
1220
  const form = document.createElement("form");
1221
+ const type = "hidden";
1222
+ for (const [name, value] of location.searchParams) {
1223
+ form.append(Object.assign(document.createElement("input"), { type, name, value }));
1224
+ }
1225
+ const action = Object.assign(location, { search: "" });
1181
1226
  form.setAttribute("data-turbo", "true");
1182
- form.setAttribute("action", action);
1227
+ form.setAttribute("action", action.href);
1183
1228
  form.setAttribute("hidden", "");
1184
1229
  const method = link.getAttribute("data-turbo-method");
1185
1230
  if (method)
@@ -1187,7 +1232,7 @@ Copyright © 2022 37signals LLC
1187
1232
  const turboFrame = link.getAttribute("data-turbo-frame");
1188
1233
  if (turboFrame)
1189
1234
  form.setAttribute("data-turbo-frame", turboFrame);
1190
- const turboAction = link.getAttribute("data-turbo-action");
1235
+ const turboAction = getVisitAction(link);
1191
1236
  if (turboAction)
1192
1237
  form.setAttribute("data-turbo-action", turboAction);
1193
1238
  const turboConfirm = link.getAttribute("data-turbo-confirm");
@@ -1204,16 +1249,16 @@ Copyright © 2022 37signals LLC
1204
1249
  }
1205
1250
 
1206
1251
  class Bardo {
1207
- constructor(delegate, permanentElementMap) {
1208
- this.delegate = delegate;
1209
- this.permanentElementMap = permanentElementMap;
1210
- }
1211
- static preservingPermanentElements(delegate, permanentElementMap, callback) {
1252
+ static async preservingPermanentElements(delegate, permanentElementMap, callback) {
1212
1253
  const bardo = new this(delegate, permanentElementMap);
1213
1254
  bardo.enter();
1214
- callback();
1255
+ await callback();
1215
1256
  bardo.leave();
1216
1257
  }
1258
+ constructor(delegate, permanentElementMap) {
1259
+ this.delegate = delegate;
1260
+ this.permanentElementMap = permanentElementMap;
1261
+ }
1217
1262
  enter() {
1218
1263
  for (const id in this.permanentElementMap) {
1219
1264
  const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
@@ -1280,8 +1325,8 @@ Copyright © 2022 37signals LLC
1280
1325
  delete this.resolvingFunctions;
1281
1326
  }
1282
1327
  }
1283
- preservingPermanentElements(callback) {
1284
- Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1328
+ async preservingPermanentElements(callback) {
1329
+ await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1285
1330
  }
1286
1331
  focusFirstAutofocusableElement() {
1287
1332
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1320,10 +1365,6 @@ Copyright © 2022 37signals LLC
1320
1365
  }
1321
1366
 
1322
1367
  class FrameRenderer extends Renderer {
1323
- constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1324
- super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1325
- this.delegate = delegate;
1326
- }
1327
1368
  static renderElement(currentElement, newElement) {
1328
1369
  var _a;
1329
1370
  const destinationRange = document.createRange();
@@ -1336,6 +1377,10 @@ Copyright © 2022 37signals LLC
1336
1377
  currentElement.appendChild(sourceRange.extractContents());
1337
1378
  }
1338
1379
  }
1380
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1381
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1382
+ this.delegate = delegate;
1383
+ }
1339
1384
  get shouldRender() {
1340
1385
  return true;
1341
1386
  }
@@ -1394,18 +1439,6 @@ Copyright © 2022 37signals LLC
1394
1439
  }
1395
1440
 
1396
1441
  class ProgressBar {
1397
- constructor() {
1398
- this.hiding = false;
1399
- this.value = 0;
1400
- this.visible = false;
1401
- this.trickle = () => {
1402
- this.setValue(this.value + Math.random() / 100);
1403
- };
1404
- this.stylesheetElement = this.createStylesheetElement();
1405
- this.progressElement = this.createProgressElement();
1406
- this.installStylesheetElement();
1407
- this.setValue(0);
1408
- }
1409
1442
  static get defaultCSS() {
1410
1443
  return unindent `
1411
1444
  .turbo-progress-bar {
@@ -1423,6 +1456,18 @@ Copyright © 2022 37signals LLC
1423
1456
  }
1424
1457
  `;
1425
1458
  }
1459
+ constructor() {
1460
+ this.hiding = false;
1461
+ this.value = 0;
1462
+ this.visible = false;
1463
+ this.trickle = () => {
1464
+ this.setValue(this.value + Math.random() / 100);
1465
+ };
1466
+ this.stylesheetElement = this.createStylesheetElement();
1467
+ this.progressElement = this.createProgressElement();
1468
+ this.installStylesheetElement();
1469
+ this.setValue(0);
1470
+ }
1426
1471
  show() {
1427
1472
  if (!this.visible) {
1428
1473
  this.visible = true;
@@ -1593,10 +1638,6 @@ Copyright © 2022 37signals LLC
1593
1638
  }
1594
1639
 
1595
1640
  class PageSnapshot extends Snapshot {
1596
- constructor(element, headSnapshot) {
1597
- super(element);
1598
- this.headSnapshot = headSnapshot;
1599
- }
1600
1641
  static fromHTMLString(html = "") {
1601
1642
  return this.fromDocument(parseHTMLDocument(html));
1602
1643
  }
@@ -1606,6 +1647,10 @@ Copyright © 2022 37signals LLC
1606
1647
  static fromDocument({ head, body }) {
1607
1648
  return new this(body, new HeadSnapshot(head));
1608
1649
  }
1650
+ constructor(element, headSnapshot) {
1651
+ super(element);
1652
+ this.headSnapshot = headSnapshot;
1653
+ }
1609
1654
  clone() {
1610
1655
  const clonedElement = this.element.cloneNode(true);
1611
1656
  const selectElements = this.element.querySelectorAll("select");
@@ -1866,6 +1911,8 @@ Copyright © 2022 37signals LLC
1866
1911
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1867
1912
  action: "replace",
1868
1913
  response: this.response,
1914
+ shouldCacheSnapshot: false,
1915
+ willRender: false,
1869
1916
  });
1870
1917
  this.followedRedirect = true;
1871
1918
  }
@@ -1880,7 +1927,7 @@ Copyright © 2022 37signals LLC
1880
1927
  });
1881
1928
  }
1882
1929
  }
1883
- prepareHeadersForRequest(headers, request) {
1930
+ prepareRequest(request) {
1884
1931
  if (this.acceptsStreamResponse) {
1885
1932
  request.acceptResponseType(StreamMessage.contentType);
1886
1933
  }
@@ -2103,10 +2150,11 @@ Copyright © 2022 37signals LLC
2103
2150
 
2104
2151
  class CacheObserver {
2105
2152
  constructor() {
2153
+ this.selector = "[data-turbo-temporary]";
2154
+ this.deprecatedSelector = "[data-turbo-cache=false]";
2106
2155
  this.started = false;
2107
- this.removeStaleElements = ((_event) => {
2108
- const staleElements = [...document.querySelectorAll('[data-turbo-cache="false"]')];
2109
- for (const element of staleElements) {
2156
+ this.removeTemporaryElements = ((_event) => {
2157
+ for (const element of this.temporaryElements) {
2110
2158
  element.remove();
2111
2159
  }
2112
2160
  });
@@ -2114,15 +2162,25 @@ Copyright © 2022 37signals LLC
2114
2162
  start() {
2115
2163
  if (!this.started) {
2116
2164
  this.started = true;
2117
- addEventListener("turbo:before-cache", this.removeStaleElements, false);
2165
+ addEventListener("turbo:before-cache", this.removeTemporaryElements, false);
2118
2166
  }
2119
2167
  }
2120
2168
  stop() {
2121
2169
  if (this.started) {
2122
2170
  this.started = false;
2123
- removeEventListener("turbo:before-cache", this.removeStaleElements, false);
2171
+ removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
2124
2172
  }
2125
2173
  }
2174
+ get temporaryElements() {
2175
+ return [...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation];
2176
+ }
2177
+ get temporaryElementsWithDeprecation() {
2178
+ const elements = document.querySelectorAll(this.deprecatedSelector);
2179
+ if (elements.length) {
2180
+ console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);
2181
+ }
2182
+ return [...elements];
2183
+ }
2126
2184
  }
2127
2185
 
2128
2186
  class FrameRedirector {
@@ -2321,7 +2379,7 @@ Copyright © 2022 37signals LLC
2321
2379
  if (formSubmission == this.formSubmission) {
2322
2380
  const responseHTML = await fetchResponse.responseHTML;
2323
2381
  if (responseHTML) {
2324
- const shouldCacheSnapshot = formSubmission.method == FetchMethod.get;
2382
+ const shouldCacheSnapshot = formSubmission.isSafe;
2325
2383
  if (!shouldCacheSnapshot) {
2326
2384
  this.view.clearSnapshotCache();
2327
2385
  }
@@ -2381,10 +2439,8 @@ Copyright © 2022 37signals LLC
2381
2439
  get restorationIdentifier() {
2382
2440
  return this.history.restorationIdentifier;
2383
2441
  }
2384
- getActionForFormSubmission(formSubmission) {
2385
- const { formElement, submitter } = formSubmission;
2386
- const action = getAttribute("data-turbo-action", submitter, formElement);
2387
- return isAction(action) ? action : "advance";
2442
+ getActionForFormSubmission({ submitter, formElement }) {
2443
+ return getVisitAction(submitter, formElement) || "advance";
2388
2444
  }
2389
2445
  }
2390
2446
 
@@ -2626,7 +2682,7 @@ Copyright © 2022 37signals LLC
2626
2682
  }
2627
2683
  async render() {
2628
2684
  if (this.willRender) {
2629
- this.replaceBody();
2685
+ await this.replaceBody();
2630
2686
  }
2631
2687
  }
2632
2688
  finishRendering() {
@@ -2645,16 +2701,16 @@ Copyright © 2022 37signals LLC
2645
2701
  return this.newSnapshot.element;
2646
2702
  }
2647
2703
  async mergeHead() {
2704
+ const mergedHeadElements = this.mergeProvisionalElements();
2648
2705
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
2649
2706
  this.copyNewHeadScriptElements();
2650
- this.removeCurrentHeadProvisionalElements();
2651
- this.copyNewHeadProvisionalElements();
2707
+ await mergedHeadElements;
2652
2708
  await newStylesheetElements;
2653
2709
  }
2654
- replaceBody() {
2655
- this.preservingPermanentElements(() => {
2710
+ async replaceBody() {
2711
+ await this.preservingPermanentElements(async () => {
2656
2712
  this.activateNewBody();
2657
- this.assignNewBody();
2713
+ await this.assignNewBody();
2658
2714
  });
2659
2715
  }
2660
2716
  get trackedElementsAreIdentical() {
@@ -2673,6 +2729,35 @@ Copyright © 2022 37signals LLC
2673
2729
  document.head.appendChild(activateScriptElement(element));
2674
2730
  }
2675
2731
  }
2732
+ async mergeProvisionalElements() {
2733
+ const newHeadElements = [...this.newHeadProvisionalElements];
2734
+ for (const element of this.currentHeadProvisionalElements) {
2735
+ if (!this.isCurrentElementInElementList(element, newHeadElements)) {
2736
+ document.head.removeChild(element);
2737
+ }
2738
+ }
2739
+ for (const element of newHeadElements) {
2740
+ document.head.appendChild(element);
2741
+ }
2742
+ }
2743
+ isCurrentElementInElementList(element, elementList) {
2744
+ for (const [index, newElement] of elementList.entries()) {
2745
+ if (element.tagName == "TITLE") {
2746
+ if (newElement.tagName != "TITLE") {
2747
+ continue;
2748
+ }
2749
+ if (element.innerHTML == newElement.innerHTML) {
2750
+ elementList.splice(index, 1);
2751
+ return true;
2752
+ }
2753
+ }
2754
+ if (newElement.isEqualNode(element)) {
2755
+ elementList.splice(index, 1);
2756
+ return true;
2757
+ }
2758
+ }
2759
+ return false;
2760
+ }
2676
2761
  removeCurrentHeadProvisionalElements() {
2677
2762
  for (const element of this.currentHeadProvisionalElements) {
2678
2763
  document.head.removeChild(element);
@@ -2693,8 +2778,8 @@ Copyright © 2022 37signals LLC
2693
2778
  inertScriptElement.replaceWith(activatedScriptElement);
2694
2779
  }
2695
2780
  }
2696
- assignNewBody() {
2697
- this.renderElement(this.currentElement, this.newElement);
2781
+ async assignNewBody() {
2782
+ await this.renderElement(this.currentElement, this.newElement);
2698
2783
  }
2699
2784
  get newHeadStylesheetElements() {
2700
2785
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -3111,8 +3196,8 @@ Copyright © 2022 37signals LLC
3111
3196
  }
3112
3197
  }
3113
3198
  elementIsNavigatable(element) {
3114
- const container = element.closest("[data-turbo]");
3115
- const withinFrame = element.closest("turbo-frame");
3199
+ const container = findClosestRecursively(element, "[data-turbo]");
3200
+ const withinFrame = findClosestRecursively(element, "turbo-frame");
3116
3201
  if (this.drive || withinFrame) {
3117
3202
  if (container) {
3118
3203
  return container.getAttribute("data-turbo") != "false";
@@ -3131,8 +3216,7 @@ Copyright © 2022 37signals LLC
3131
3216
  }
3132
3217
  }
3133
3218
  getActionForLink(link) {
3134
- const action = link.getAttribute("data-turbo-action");
3135
- return isAction(action) ? action : "advance";
3219
+ return getVisitAction(link) || "advance";
3136
3220
  }
3137
3221
  get snapshot() {
3138
3222
  return this.view.snapshot;
@@ -3192,7 +3276,10 @@ Copyright © 2022 37signals LLC
3192
3276
  this.targetElements.forEach((e) => e.replaceWith(this.templateContent));
3193
3277
  },
3194
3278
  update() {
3195
- this.targetElements.forEach((e) => e.replaceChildren(this.templateContent));
3279
+ this.targetElements.forEach((targetElement) => {
3280
+ targetElement.innerHTML = "";
3281
+ targetElement.append(this.templateContent);
3282
+ });
3196
3283
  },
3197
3284
  };
3198
3285
 
@@ -3252,6 +3339,9 @@ Copyright © 2022 37signals LLC
3252
3339
  StreamActions: StreamActions
3253
3340
  });
3254
3341
 
3342
+ class TurboFrameMissingError extends Error {
3343
+ }
3344
+
3255
3345
  class FrameController {
3256
3346
  constructor(element) {
3257
3347
  this.fetchResponseLoaded = (_fetchResponse) => { };
@@ -3352,35 +3442,22 @@ Copyright © 2022 37signals LLC
3352
3442
  try {
3353
3443
  const html = await fetchResponse.responseHTML;
3354
3444
  if (html) {
3355
- const { body } = parseHTMLDocument(html);
3356
- const newFrameElement = await this.extractForeignFrameElement(body);
3357
- if (newFrameElement) {
3358
- const snapshot = new Snapshot(newFrameElement);
3359
- const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
3360
- if (this.view.renderPromise)
3361
- await this.view.renderPromise;
3362
- this.changeHistory();
3363
- await this.view.render(renderer);
3364
- this.complete = true;
3365
- session.frameRendered(fetchResponse, this.element);
3366
- session.frameLoaded(this.element);
3367
- this.fetchResponseLoaded(fetchResponse);
3445
+ const document = parseHTMLDocument(html);
3446
+ const pageSnapshot = PageSnapshot.fromDocument(document);
3447
+ if (pageSnapshot.isVisitable) {
3448
+ await this.loadFrameResponse(fetchResponse, document);
3368
3449
  }
3369
- else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3370
- console.warn(`A matching frame for #${this.element.id} was missing from the response, transforming into full-page Visit.`);
3371
- this.visitResponse(fetchResponse.response);
3450
+ else {
3451
+ await this.handleUnvisitableFrameResponse(fetchResponse);
3372
3452
  }
3373
3453
  }
3374
3454
  }
3375
- catch (error) {
3376
- console.error(error);
3377
- this.view.invalidate();
3378
- }
3379
3455
  finally {
3380
3456
  this.fetchResponseLoaded = () => { };
3381
3457
  }
3382
3458
  }
3383
- elementAppearedInViewport(_element) {
3459
+ elementAppearedInViewport(element) {
3460
+ this.proposeVisitIfNavigatedWithAction(element, element);
3384
3461
  this.loadSourceURL();
3385
3462
  }
3386
3463
  willSubmitFormLinkToLocation(link) {
@@ -3406,12 +3483,12 @@ Copyright © 2022 37signals LLC
3406
3483
  }
3407
3484
  this.formSubmission = new FormSubmission(this, element, submitter);
3408
3485
  const { fetchRequest } = this.formSubmission;
3409
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3486
+ this.prepareRequest(fetchRequest);
3410
3487
  this.formSubmission.start();
3411
3488
  }
3412
- prepareHeadersForRequest(headers, request) {
3489
+ prepareRequest(request) {
3413
3490
  var _a;
3414
- headers["Turbo-Frame"] = this.id;
3491
+ request.headers["Turbo-Frame"] = this.id;
3415
3492
  if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3416
3493
  request.acceptResponseType(StreamMessage.contentType);
3417
3494
  }
@@ -3427,7 +3504,6 @@ Copyright © 2022 37signals LLC
3427
3504
  this.resolveVisitPromise();
3428
3505
  }
3429
3506
  async requestFailedWithResponse(request, response) {
3430
- console.error(response);
3431
3507
  await this.loadResponse(response);
3432
3508
  this.resolveVisitPromise();
3433
3509
  }
@@ -3445,9 +3521,13 @@ Copyright © 2022 37signals LLC
3445
3521
  const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
3446
3522
  frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
3447
3523
  frame.delegate.loadResponse(response);
3524
+ if (!formSubmission.isSafe) {
3525
+ session.clearCache();
3526
+ }
3448
3527
  }
3449
3528
  formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
3450
3529
  this.element.delegate.loadResponse(fetchResponse);
3530
+ session.clearCache();
3451
3531
  }
3452
3532
  formSubmissionErrored(formSubmission, error) {
3453
3533
  console.error(error);
@@ -3475,6 +3555,24 @@ Copyright © 2022 37signals LLC
3475
3555
  willRenderFrame(currentElement, _newElement) {
3476
3556
  this.previousFrameElement = currentElement.cloneNode(true);
3477
3557
  }
3558
+ async loadFrameResponse(fetchResponse, document) {
3559
+ const newFrameElement = await this.extractForeignFrameElement(document.body);
3560
+ if (newFrameElement) {
3561
+ const snapshot = new Snapshot(newFrameElement);
3562
+ const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
3563
+ if (this.view.renderPromise)
3564
+ await this.view.renderPromise;
3565
+ this.changeHistory();
3566
+ await this.view.render(renderer);
3567
+ this.complete = true;
3568
+ session.frameRendered(fetchResponse, this.element);
3569
+ session.frameLoaded(this.element);
3570
+ this.fetchResponseLoaded(fetchResponse);
3571
+ }
3572
+ else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3573
+ this.handleFrameMissingFromResponse(fetchResponse);
3574
+ }
3575
+ }
3478
3576
  async visit(url) {
3479
3577
  var _a;
3480
3578
  const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);
@@ -3491,7 +3589,6 @@ Copyright © 2022 37signals LLC
3491
3589
  }
3492
3590
  navigateFrame(element, url, submitter) {
3493
3591
  const frame = this.findFrameElement(element, submitter);
3494
- this.pageSnapshot = PageSnapshot.fromElement(frame).clone();
3495
3592
  frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3496
3593
  this.withCurrentNavigationElement(element, () => {
3497
3594
  frame.src = url;
@@ -3499,7 +3596,8 @@ Copyright © 2022 37signals LLC
3499
3596
  }
3500
3597
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3501
3598
  this.action = getVisitAction(submitter, element, frame);
3502
- if (isAction(this.action)) {
3599
+ if (this.action) {
3600
+ const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3503
3601
  const { visitCachedSnapshot } = frame.delegate;
3504
3602
  frame.delegate.fetchResponseLoaded = (fetchResponse) => {
3505
3603
  if (frame.src) {
@@ -3512,7 +3610,7 @@ Copyright © 2022 37signals LLC
3512
3610
  willRender: false,
3513
3611
  updateHistory: false,
3514
3612
  restorationIdentifier: this.restorationIdentifier,
3515
- snapshot: this.pageSnapshot,
3613
+ snapshot: pageSnapshot,
3516
3614
  };
3517
3615
  if (this.action)
3518
3616
  options.action = this.action;
@@ -3527,6 +3625,10 @@ Copyright © 2022 37signals LLC
3527
3625
  session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
3528
3626
  }
3529
3627
  }
3628
+ async handleUnvisitableFrameResponse(fetchResponse) {
3629
+ console.warn(`The response (${fetchResponse.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.`);
3630
+ await this.visitResponse(fetchResponse.response);
3631
+ }
3530
3632
  willHandleFrameMissingFromResponse(fetchResponse) {
3531
3633
  this.element.setAttribute("complete", "");
3532
3634
  const response = fetchResponse.response;
@@ -3545,6 +3647,14 @@ Copyright © 2022 37signals LLC
3545
3647
  });
3546
3648
  return !event.defaultPrevented;
3547
3649
  }
3650
+ handleFrameMissingFromResponse(fetchResponse) {
3651
+ this.view.missing();
3652
+ this.throwFrameMissingError(fetchResponse);
3653
+ }
3654
+ throwFrameMissingError(fetchResponse) {
3655
+ const message = `The response (${fetchResponse.statusCode}) did not contain the expected <turbo-frame id="${this.element.id}"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;
3656
+ throw new TurboFrameMissingError(message);
3657
+ }
3548
3658
  async visitResponse(response) {
3549
3659
  const wrapped = new FetchResponse(response);
3550
3660
  const responseHTML = await wrapped.responseHTML;