turbo-rails 1.3.2 → 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
  }
@@ -282,10 +280,6 @@ class FetchResponse {
282
280
  }
283
281
  }
284
282
 
285
- function isAction(action) {
286
- return action == "advance" || action == "replace" || action == "restore";
287
- }
288
-
289
283
  function activateScriptElement(element) {
290
284
  if (element.getAttribute("data-turbo-eval") == "false") {
291
285
  return element;
@@ -318,6 +312,7 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
318
312
  const event = new CustomEvent(eventName, {
319
313
  cancelable: cancelable,
320
314
  bubbles: true,
315
+ composed: true,
321
316
  detail: detail
322
317
  });
323
318
  if (target && target.isConnected) {
@@ -431,6 +426,10 @@ function getHistoryMethodForAction(action) {
431
426
  }
432
427
  }
433
428
 
429
+ function isAction(action) {
430
+ return action == "advance" || action == "replace" || action == "restore";
431
+ }
432
+
434
433
  function getVisitAction(...elements) {
435
434
  const action = getAttribute("data-turbo-action", ...elements);
436
435
  return isAction(action) ? action : null;
@@ -456,6 +455,13 @@ function setMetaContent(name, content) {
456
455
  return element;
457
456
  }
458
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
+
459
465
  var FetchMethod;
460
466
 
461
467
  (function(FetchMethod) {
@@ -509,9 +515,8 @@ class FetchRequest {
509
515
  this.abortController.abort();
510
516
  }
511
517
  async perform() {
512
- var _a, _b;
513
518
  const {fetchOptions: fetchOptions} = this;
514
- (_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
519
+ this.delegate.prepareRequest(this);
515
520
  await this.allowRequestToBeIntercepted(fetchOptions);
516
521
  try {
517
522
  this.delegate.requestStarted(this);
@@ -553,7 +558,7 @@ class FetchRequest {
553
558
  credentials: "same-origin",
554
559
  headers: this.headers,
555
560
  redirect: "follow",
556
- body: this.isIdempotent ? null : this.body,
561
+ body: this.isSafe ? null : this.body,
557
562
  signal: this.abortSignal,
558
563
  referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
559
564
  };
@@ -563,8 +568,8 @@ class FetchRequest {
563
568
  Accept: "text/html, application/xhtml+xml"
564
569
  };
565
570
  }
566
- get isIdempotent() {
567
- return this.method == FetchMethod.get;
571
+ get isSafe() {
572
+ return this.method === FetchMethod.get;
568
573
  }
569
574
  get abortSignal() {
570
575
  return this.abortController.signal;
@@ -626,9 +631,6 @@ class AppearanceObserver {
626
631
  }
627
632
 
628
633
  class StreamMessage {
629
- constructor(fragment) {
630
- this.fragment = importStreamElements(fragment);
631
- }
632
634
  static wrap(message) {
633
635
  if (typeof message == "string") {
634
636
  return new this(createDocumentFragment(message));
@@ -636,6 +638,9 @@ class StreamMessage {
636
638
  return message;
637
639
  }
638
640
  }
641
+ constructor(fragment) {
642
+ this.fragment = importStreamElements(fragment);
643
+ }
639
644
  }
640
645
 
641
646
  StreamMessage.contentType = "text/vnd.turbo-stream.html";
@@ -684,6 +689,9 @@ function formEnctypeFromString(encoding) {
684
689
  }
685
690
 
686
691
  class FormSubmission {
692
+ static confirmMethod(message, _element, _submitter) {
693
+ return Promise.resolve(confirm(message));
694
+ }
687
695
  constructor(delegate, formElement, submitter, mustRedirect = false) {
688
696
  this.state = FormSubmissionState.initialized;
689
697
  this.delegate = delegate;
@@ -697,9 +705,6 @@ class FormSubmission {
697
705
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
698
706
  this.mustRedirect = mustRedirect;
699
707
  }
700
- static confirmMethod(message, _element, _submitter) {
701
- return Promise.resolve(confirm(message));
702
- }
703
708
  get method() {
704
709
  var _a;
705
710
  const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
@@ -725,8 +730,8 @@ class FormSubmission {
725
730
  var _a;
726
731
  return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
727
732
  }
728
- get isIdempotent() {
729
- return this.fetchRequest.isIdempotent;
733
+ get isSafe() {
734
+ return this.fetchRequest.isSafe;
730
735
  }
731
736
  get stringFormData() {
732
737
  return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
@@ -753,11 +758,11 @@ class FormSubmission {
753
758
  return true;
754
759
  }
755
760
  }
756
- prepareHeadersForRequest(headers, request) {
757
- if (!request.isIdempotent) {
761
+ prepareRequest(request) {
762
+ if (!request.isSafe) {
758
763
  const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
759
764
  if (token) {
760
- headers["X-CSRF-Token"] = token;
765
+ request.headers["X-CSRF-Token"] = token;
761
766
  }
762
767
  }
763
768
  if (this.requestAcceptsTurboStreamResponse(request)) {
@@ -768,6 +773,7 @@ class FormSubmission {
768
773
  var _a;
769
774
  this.state = FormSubmissionState.waiting;
770
775
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
776
+ this.setSubmitsWith();
771
777
  dispatch("turbo:submit-start", {
772
778
  target: this.formElement,
773
779
  detail: {
@@ -815,6 +821,7 @@ class FormSubmission {
815
821
  var _a;
816
822
  this.state = FormSubmissionState.stopped;
817
823
  (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
824
+ this.resetSubmitterText();
818
825
  dispatch("turbo:submit-end", {
819
826
  target: this.formElement,
820
827
  detail: Object.assign({
@@ -823,11 +830,35 @@ class FormSubmission {
823
830
  });
824
831
  this.delegate.formSubmissionFinished(this);
825
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
+ }
826
853
  requestMustRedirect(request) {
827
- return !request.isIdempotent && this.mustRedirect;
854
+ return !request.isSafe && this.mustRedirect;
828
855
  }
829
856
  requestAcceptsTurboStreamResponse(request) {
830
- 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");
831
862
  }
832
863
  }
833
864
 
@@ -960,11 +991,15 @@ function submissionDoesNotDismissDialog(form, submitter) {
960
991
  }
961
992
 
962
993
  function submissionDoesNotTargetIFrame(form, submitter) {
963
- const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
964
- for (const element of document.getElementsByName(target)) {
965
- 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;
966
1002
  }
967
- return true;
968
1003
  }
969
1004
 
970
1005
  class View {
@@ -1065,8 +1100,8 @@ class View {
1065
1100
  }
1066
1101
 
1067
1102
  class FrameView extends View {
1068
- invalidate() {
1069
- this.element.innerHTML = "";
1103
+ missing() {
1104
+ this.element.innerHTML = `<strong class="turbo-frame-error">Content missing</strong>`;
1070
1105
  }
1071
1106
  get snapshot() {
1072
1107
  return new Snapshot(this.element);
@@ -1153,9 +1188,7 @@ class LinkClickObserver {
1153
1188
  return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
1154
1189
  }
1155
1190
  findLinkFromClickTarget(target) {
1156
- if (target instanceof Element) {
1157
- return target.closest("a[href]:not([target^=_]):not([download])");
1158
- }
1191
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
1159
1192
  }
1160
1193
  getLocationForLink(link) {
1161
1194
  return expandURL(link.getAttribute("href") || "");
@@ -1163,10 +1196,14 @@ class LinkClickObserver {
1163
1196
  }
1164
1197
 
1165
1198
  function doesNotTargetIFrame(anchor) {
1166
- for (const element of document.getElementsByName(anchor.target)) {
1167
- 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;
1168
1206
  }
1169
- return true;
1170
1207
  }
1171
1208
 
1172
1209
  class FormLinkClickObserver {
@@ -1184,16 +1221,26 @@ class FormLinkClickObserver {
1184
1221
  return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
1185
1222
  }
1186
1223
  followedLinkToLocation(link, location) {
1187
- const action = location.href;
1188
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
+ });
1189
1236
  form.setAttribute("data-turbo", "true");
1190
- form.setAttribute("action", action);
1237
+ form.setAttribute("action", action.href);
1191
1238
  form.setAttribute("hidden", "");
1192
1239
  const method = link.getAttribute("data-turbo-method");
1193
1240
  if (method) form.setAttribute("method", method);
1194
1241
  const turboFrame = link.getAttribute("data-turbo-frame");
1195
1242
  if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
1196
- const turboAction = link.getAttribute("data-turbo-action");
1243
+ const turboAction = getVisitAction(link);
1197
1244
  if (turboAction) form.setAttribute("data-turbo-action", turboAction);
1198
1245
  const turboConfirm = link.getAttribute("data-turbo-confirm");
1199
1246
  if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
@@ -1209,16 +1256,16 @@ class FormLinkClickObserver {
1209
1256
  }
1210
1257
 
1211
1258
  class Bardo {
1212
- constructor(delegate, permanentElementMap) {
1213
- this.delegate = delegate;
1214
- this.permanentElementMap = permanentElementMap;
1215
- }
1216
- static preservingPermanentElements(delegate, permanentElementMap, callback) {
1259
+ static async preservingPermanentElements(delegate, permanentElementMap, callback) {
1217
1260
  const bardo = new this(delegate, permanentElementMap);
1218
1261
  bardo.enter();
1219
- callback();
1262
+ await callback();
1220
1263
  bardo.leave();
1221
1264
  }
1265
+ constructor(delegate, permanentElementMap) {
1266
+ this.delegate = delegate;
1267
+ this.permanentElementMap = permanentElementMap;
1268
+ }
1222
1269
  enter() {
1223
1270
  for (const id in this.permanentElementMap) {
1224
1271
  const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
@@ -1289,8 +1336,8 @@ class Renderer {
1289
1336
  delete this.resolvingFunctions;
1290
1337
  }
1291
1338
  }
1292
- preservingPermanentElements(callback) {
1293
- Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1339
+ async preservingPermanentElements(callback) {
1340
+ await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1294
1341
  }
1295
1342
  focusFirstAutofocusableElement() {
1296
1343
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1329,10 +1376,6 @@ function elementIsFocusable(element) {
1329
1376
  }
1330
1377
 
1331
1378
  class FrameRenderer extends Renderer {
1332
- constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1333
- super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1334
- this.delegate = delegate;
1335
- }
1336
1379
  static renderElement(currentElement, newElement) {
1337
1380
  var _a;
1338
1381
  const destinationRange = document.createRange();
@@ -1345,6 +1388,10 @@ class FrameRenderer extends Renderer {
1345
1388
  currentElement.appendChild(sourceRange.extractContents());
1346
1389
  }
1347
1390
  }
1391
+ constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
1392
+ super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
1393
+ this.delegate = delegate;
1394
+ }
1348
1395
  get shouldRender() {
1349
1396
  return true;
1350
1397
  }
@@ -1406,18 +1453,6 @@ function readScrollBehavior(value, defaultValue) {
1406
1453
  }
1407
1454
 
1408
1455
  class ProgressBar {
1409
- constructor() {
1410
- this.hiding = false;
1411
- this.value = 0;
1412
- this.visible = false;
1413
- this.trickle = () => {
1414
- this.setValue(this.value + Math.random() / 100);
1415
- };
1416
- this.stylesheetElement = this.createStylesheetElement();
1417
- this.progressElement = this.createProgressElement();
1418
- this.installStylesheetElement();
1419
- this.setValue(0);
1420
- }
1421
1456
  static get defaultCSS() {
1422
1457
  return unindent`
1423
1458
  .turbo-progress-bar {
@@ -1435,6 +1470,18 @@ class ProgressBar {
1435
1470
  }
1436
1471
  `;
1437
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
+ }
1438
1485
  show() {
1439
1486
  if (!this.visible) {
1440
1487
  this.visible = true;
@@ -1603,10 +1650,6 @@ function elementWithoutNonce(element) {
1603
1650
  }
1604
1651
 
1605
1652
  class PageSnapshot extends Snapshot {
1606
- constructor(element, headSnapshot) {
1607
- super(element);
1608
- this.headSnapshot = headSnapshot;
1609
- }
1610
1653
  static fromHTMLString(html = "") {
1611
1654
  return this.fromDocument(parseHTMLDocument(html));
1612
1655
  }
@@ -1616,6 +1659,10 @@ class PageSnapshot extends Snapshot {
1616
1659
  static fromDocument({head: head, body: body}) {
1617
1660
  return new this(body, new HeadSnapshot(head));
1618
1661
  }
1662
+ constructor(element, headSnapshot) {
1663
+ super(element);
1664
+ this.headSnapshot = headSnapshot;
1665
+ }
1619
1666
  clone() {
1620
1667
  const clonedElement = this.element.cloneNode(true);
1621
1668
  const selectElements = this.element.querySelectorAll("select");
@@ -1873,7 +1920,9 @@ class Visit {
1873
1920
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1874
1921
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1875
1922
  action: "replace",
1876
- response: this.response
1923
+ response: this.response,
1924
+ shouldCacheSnapshot: false,
1925
+ willRender: false
1877
1926
  });
1878
1927
  this.followedRedirect = true;
1879
1928
  }
@@ -1888,7 +1937,7 @@ class Visit {
1888
1937
  }));
1889
1938
  }
1890
1939
  }
1891
- prepareHeadersForRequest(headers, request) {
1940
+ prepareRequest(request) {
1892
1941
  if (this.acceptsStreamResponse) {
1893
1942
  request.acceptResponseType(StreamMessage.contentType);
1894
1943
  }
@@ -2118,10 +2167,11 @@ class BrowserAdapter {
2118
2167
 
2119
2168
  class CacheObserver {
2120
2169
  constructor() {
2170
+ this.selector = "[data-turbo-temporary]";
2171
+ this.deprecatedSelector = "[data-turbo-cache=false]";
2121
2172
  this.started = false;
2122
- this.removeStaleElements = _event => {
2123
- const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
2124
- for (const element of staleElements) {
2173
+ this.removeTemporaryElements = _event => {
2174
+ for (const element of this.temporaryElements) {
2125
2175
  element.remove();
2126
2176
  }
2127
2177
  };
@@ -2129,14 +2179,24 @@ class CacheObserver {
2129
2179
  start() {
2130
2180
  if (!this.started) {
2131
2181
  this.started = true;
2132
- addEventListener("turbo:before-cache", this.removeStaleElements, false);
2182
+ addEventListener("turbo:before-cache", this.removeTemporaryElements, false);
2133
2183
  }
2134
2184
  }
2135
2185
  stop() {
2136
2186
  if (this.started) {
2137
2187
  this.started = false;
2138
- 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.`);
2139
2198
  }
2199
+ return [ ...elements ];
2140
2200
  }
2141
2201
  }
2142
2202
 
@@ -2336,7 +2396,7 @@ class Navigator {
2336
2396
  if (formSubmission == this.formSubmission) {
2337
2397
  const responseHTML = await fetchResponse.responseHTML;
2338
2398
  if (responseHTML) {
2339
- const shouldCacheSnapshot = formSubmission.method == FetchMethod.get;
2399
+ const shouldCacheSnapshot = formSubmission.isSafe;
2340
2400
  if (!shouldCacheSnapshot) {
2341
2401
  this.view.clearSnapshotCache();
2342
2402
  }
@@ -2397,10 +2457,8 @@ class Navigator {
2397
2457
  get restorationIdentifier() {
2398
2458
  return this.history.restorationIdentifier;
2399
2459
  }
2400
- getActionForFormSubmission(formSubmission) {
2401
- const {formElement: formElement, submitter: submitter} = formSubmission;
2402
- const action = getAttribute("data-turbo-action", submitter, formElement);
2403
- return isAction(action) ? action : "advance";
2460
+ getActionForFormSubmission({submitter: submitter, formElement: formElement}) {
2461
+ return getVisitAction(submitter, formElement) || "advance";
2404
2462
  }
2405
2463
  }
2406
2464
 
@@ -2648,7 +2706,7 @@ class PageRenderer extends Renderer {
2648
2706
  }
2649
2707
  async render() {
2650
2708
  if (this.willRender) {
2651
- this.replaceBody();
2709
+ await this.replaceBody();
2652
2710
  }
2653
2711
  }
2654
2712
  finishRendering() {
@@ -2667,16 +2725,16 @@ class PageRenderer extends Renderer {
2667
2725
  return this.newSnapshot.element;
2668
2726
  }
2669
2727
  async mergeHead() {
2728
+ const mergedHeadElements = this.mergeProvisionalElements();
2670
2729
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
2671
2730
  this.copyNewHeadScriptElements();
2672
- this.removeCurrentHeadProvisionalElements();
2673
- this.copyNewHeadProvisionalElements();
2731
+ await mergedHeadElements;
2674
2732
  await newStylesheetElements;
2675
2733
  }
2676
- replaceBody() {
2677
- this.preservingPermanentElements((() => {
2734
+ async replaceBody() {
2735
+ await this.preservingPermanentElements((async () => {
2678
2736
  this.activateNewBody();
2679
- this.assignNewBody();
2737
+ await this.assignNewBody();
2680
2738
  }));
2681
2739
  }
2682
2740
  get trackedElementsAreIdentical() {
@@ -2695,6 +2753,35 @@ class PageRenderer extends Renderer {
2695
2753
  document.head.appendChild(activateScriptElement(element));
2696
2754
  }
2697
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
+ }
2698
2785
  removeCurrentHeadProvisionalElements() {
2699
2786
  for (const element of this.currentHeadProvisionalElements) {
2700
2787
  document.head.removeChild(element);
@@ -2715,8 +2802,8 @@ class PageRenderer extends Renderer {
2715
2802
  inertScriptElement.replaceWith(activatedScriptElement);
2716
2803
  }
2717
2804
  }
2718
- assignNewBody() {
2719
- this.renderElement(this.currentElement, this.newElement);
2805
+ async assignNewBody() {
2806
+ await this.renderElement(this.currentElement, this.newElement);
2720
2807
  }
2721
2808
  get newHeadStylesheetElements() {
2722
2809
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -3150,8 +3237,8 @@ class Session {
3150
3237
  }
3151
3238
  }
3152
3239
  elementIsNavigatable(element) {
3153
- const container = element.closest("[data-turbo]");
3154
- const withinFrame = element.closest("turbo-frame");
3240
+ const container = findClosestRecursively(element, "[data-turbo]");
3241
+ const withinFrame = findClosestRecursively(element, "turbo-frame");
3155
3242
  if (this.drive || withinFrame) {
3156
3243
  if (container) {
3157
3244
  return container.getAttribute("data-turbo") != "false";
@@ -3167,8 +3254,7 @@ class Session {
3167
3254
  }
3168
3255
  }
3169
3256
  getActionForLink(link) {
3170
- const action = link.getAttribute("data-turbo-action");
3171
- return isAction(action) ? action : "advance";
3257
+ return getVisitAction(link) || "advance";
3172
3258
  }
3173
3259
  get snapshot() {
3174
3260
  return this.view.snapshot;
@@ -3236,7 +3322,10 @@ const StreamActions = {
3236
3322
  this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3237
3323
  },
3238
3324
  update() {
3239
- this.targetElements.forEach((e => e.replaceChildren(this.templateContent)));
3325
+ this.targetElements.forEach((targetElement => {
3326
+ targetElement.innerHTML = "";
3327
+ targetElement.append(this.templateContent);
3328
+ }));
3240
3329
  }
3241
3330
  };
3242
3331
 
@@ -3308,6 +3397,8 @@ var Turbo = Object.freeze({
3308
3397
  StreamActions: StreamActions
3309
3398
  });
3310
3399
 
3400
+ class TurboFrameMissingError extends Error {}
3401
+
3311
3402
  class FrameController {
3312
3403
  constructor(element) {
3313
3404
  this.fetchResponseLoaded = _fetchResponse => {};
@@ -3404,31 +3495,20 @@ class FrameController {
3404
3495
  try {
3405
3496
  const html = await fetchResponse.responseHTML;
3406
3497
  if (html) {
3407
- const {body: body} = parseHTMLDocument(html);
3408
- const newFrameElement = await this.extractForeignFrameElement(body);
3409
- if (newFrameElement) {
3410
- const snapshot = new Snapshot(newFrameElement);
3411
- const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
3412
- if (this.view.renderPromise) await this.view.renderPromise;
3413
- this.changeHistory();
3414
- await this.view.render(renderer);
3415
- this.complete = true;
3416
- session.frameRendered(fetchResponse, this.element);
3417
- session.frameLoaded(this.element);
3418
- this.fetchResponseLoaded(fetchResponse);
3419
- } else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3420
- console.warn(`A matching frame for #${this.element.id} was missing from the response, transforming into full-page Visit.`);
3421
- 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);
3422
3504
  }
3423
3505
  }
3424
- } catch (error) {
3425
- console.error(error);
3426
- this.view.invalidate();
3427
3506
  } finally {
3428
3507
  this.fetchResponseLoaded = () => {};
3429
3508
  }
3430
3509
  }
3431
- elementAppearedInViewport(_element) {
3510
+ elementAppearedInViewport(element) {
3511
+ this.proposeVisitIfNavigatedWithAction(element, element);
3432
3512
  this.loadSourceURL();
3433
3513
  }
3434
3514
  willSubmitFormLinkToLocation(link) {
@@ -3453,12 +3533,12 @@ class FrameController {
3453
3533
  }
3454
3534
  this.formSubmission = new FormSubmission(this, element, submitter);
3455
3535
  const {fetchRequest: fetchRequest} = this.formSubmission;
3456
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3536
+ this.prepareRequest(fetchRequest);
3457
3537
  this.formSubmission.start();
3458
3538
  }
3459
- prepareHeadersForRequest(headers, request) {
3539
+ prepareRequest(request) {
3460
3540
  var _a;
3461
- headers["Turbo-Frame"] = this.id;
3541
+ request.headers["Turbo-Frame"] = this.id;
3462
3542
  if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3463
3543
  request.acceptResponseType(StreamMessage.contentType);
3464
3544
  }
@@ -3474,7 +3554,6 @@ class FrameController {
3474
3554
  this.resolveVisitPromise();
3475
3555
  }
3476
3556
  async requestFailedWithResponse(request, response) {
3477
- console.error(response);
3478
3557
  await this.loadResponse(response);
3479
3558
  this.resolveVisitPromise();
3480
3559
  }
@@ -3492,9 +3571,13 @@ class FrameController {
3492
3571
  const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
3493
3572
  frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
3494
3573
  frame.delegate.loadResponse(response);
3574
+ if (!formSubmission.isSafe) {
3575
+ session.clearCache();
3576
+ }
3495
3577
  }
3496
3578
  formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
3497
3579
  this.element.delegate.loadResponse(fetchResponse);
3580
+ session.clearCache();
3498
3581
  }
3499
3582
  formSubmissionErrored(formSubmission, error) {
3500
3583
  console.error(error);
@@ -3524,6 +3607,22 @@ class FrameController {
3524
3607
  willRenderFrame(currentElement, _newElement) {
3525
3608
  this.previousFrameElement = currentElement.cloneNode(true);
3526
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
+ }
3527
3626
  async visit(url) {
3528
3627
  var _a;
3529
3628
  const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
@@ -3540,7 +3639,6 @@ class FrameController {
3540
3639
  }
3541
3640
  navigateFrame(element, url, submitter) {
3542
3641
  const frame = this.findFrameElement(element, submitter);
3543
- this.pageSnapshot = PageSnapshot.fromElement(frame).clone();
3544
3642
  frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3545
3643
  this.withCurrentNavigationElement(element, (() => {
3546
3644
  frame.src = url;
@@ -3548,7 +3646,8 @@ class FrameController {
3548
3646
  }
3549
3647
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3550
3648
  this.action = getVisitAction(submitter, element, frame);
3551
- if (isAction(this.action)) {
3649
+ if (this.action) {
3650
+ const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3552
3651
  const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
3553
3652
  frame.delegate.fetchResponseLoaded = fetchResponse => {
3554
3653
  if (frame.src) {
@@ -3565,7 +3664,7 @@ class FrameController {
3565
3664
  willRender: false,
3566
3665
  updateHistory: false,
3567
3666
  restorationIdentifier: this.restorationIdentifier,
3568
- snapshot: this.pageSnapshot
3667
+ snapshot: pageSnapshot
3569
3668
  };
3570
3669
  if (this.action) options.action = this.action;
3571
3670
  session.visit(frame.src, options);
@@ -3579,6 +3678,10 @@ class FrameController {
3579
3678
  session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
3580
3679
  }
3581
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
+ }
3582
3685
  willHandleFrameMissingFromResponse(fetchResponse) {
3583
3686
  this.element.setAttribute("complete", "");
3584
3687
  const response = fetchResponse.response;
@@ -3599,6 +3702,14 @@ class FrameController {
3599
3702
  });
3600
3703
  return !event.defaultPrevented;
3601
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
+ }
3602
3713
  async visitResponse(response) {
3603
3714
  const wrapped = new FetchResponse(response);
3604
3715
  const responseHTML = await wrapped.responseHTML;
@@ -3993,7 +4104,9 @@ class TurboCableStreamSourceElement extends HTMLElement {
3993
4104
  async connectedCallback() {
3994
4105
  connectStreamSource(this);
3995
4106
  this.subscription = await subscribeTo(this.channel, {
3996
- received: this.dispatchMessageEvent.bind(this)
4107
+ received: this.dispatchMessageEvent.bind(this),
4108
+ connected: this.subscriptionConnected.bind(this),
4109
+ disconnected: this.subscriptionDisconnected.bind(this)
3997
4110
  });
3998
4111
  }
3999
4112
  disconnectedCallback() {
@@ -4006,6 +4119,12 @@ class TurboCableStreamSourceElement extends HTMLElement {
4006
4119
  });
4007
4120
  return this.dispatchEvent(event);
4008
4121
  }
4122
+ subscriptionConnected() {
4123
+ this.setAttribute("connected", "");
4124
+ }
4125
+ subscriptionDisconnected() {
4126
+ this.removeAttribute("connected");
4127
+ }
4009
4128
  get channel() {
4010
4129
  const channel = this.getAttribute("channel");
4011
4130
  const signed_stream_name = this.getAttribute("signed-stream-name");
@@ -4019,18 +4138,21 @@ class TurboCableStreamSourceElement extends HTMLElement {
4019
4138
  }
4020
4139
  }
4021
4140
 
4022
- 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
+ }
4023
4144
 
4024
4145
  function encodeMethodIntoRequestBody(event) {
4025
4146
  if (event.target instanceof HTMLFormElement) {
4026
4147
  const {target: form, detail: {fetchOptions: fetchOptions}} = event;
4027
4148
  form.addEventListener("turbo:submit-start", (({detail: {formSubmission: {submitter: submitter}}}) => {
4028
- 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);
4029
4151
  if (!/get/i.test(method)) {
4030
4152
  if (/post/i.test(method)) {
4031
- fetchOptions.body.delete("_method");
4153
+ body.delete("_method");
4032
4154
  } else {
4033
- fetchOptions.body.set("_method", method);
4155
+ body.set("_method", method);
4034
4156
  }
4035
4157
  fetchOptions.method = "post";
4036
4158
  }
@@ -4040,6 +4162,37 @@ function encodeMethodIntoRequestBody(event) {
4040
4162
  }
4041
4163
  }
4042
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
+
4043
4196
  addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
4044
4197
 
4045
4198
  var adapters = {