turbo-rails 1.3.0 → 1.3.3

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.
@@ -97,11 +97,7 @@ class FrameElement extends HTMLElement {
97
97
  this.delegate.disconnect();
98
98
  }
99
99
  reload() {
100
- const {src: src} = this;
101
- this.removeAttribute("complete");
102
- this.src = null;
103
- this.src = src;
104
- return this.loaded;
100
+ return this.delegate.sourceURLReloaded();
105
101
  }
106
102
  attributeChangedCallback(name) {
107
103
  if (name == "loading") {
@@ -286,10 +282,6 @@ class FetchResponse {
286
282
  }
287
283
  }
288
284
 
289
- function isAction(action) {
290
- return action == "advance" || action == "replace" || action == "restore";
291
- }
292
-
293
285
  function activateScriptElement(element) {
294
286
  if (element.getAttribute("data-turbo-eval") == "false") {
295
287
  return element;
@@ -322,6 +314,7 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
322
314
  const event = new CustomEvent(eventName, {
323
315
  cancelable: cancelable,
324
316
  bubbles: true,
317
+ composed: true,
325
318
  detail: detail
326
319
  });
327
320
  if (target && target.isConnected) {
@@ -435,6 +428,10 @@ function getHistoryMethodForAction(action) {
435
428
  }
436
429
  }
437
430
 
431
+ function isAction(action) {
432
+ return action == "advance" || action == "replace" || action == "restore";
433
+ }
434
+
438
435
  function getVisitAction(...elements) {
439
436
  const action = getAttribute("data-turbo-action", ...elements);
440
437
  return isAction(action) ? action : null;
@@ -460,6 +457,13 @@ function setMetaContent(name, content) {
460
457
  return element;
461
458
  }
462
459
 
460
+ function findClosestRecursively(element, selector) {
461
+ var _a;
462
+ if (element instanceof Element) {
463
+ return element.closest(selector) || findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector);
464
+ }
465
+ }
466
+
463
467
  var FetchMethod;
464
468
 
465
469
  (function(FetchMethod) {
@@ -513,9 +517,8 @@ class FetchRequest {
513
517
  this.abortController.abort();
514
518
  }
515
519
  async perform() {
516
- var _a, _b;
517
520
  const {fetchOptions: fetchOptions} = this;
518
- (_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
521
+ this.delegate.prepareRequest(this);
519
522
  await this.allowRequestToBeIntercepted(fetchOptions);
520
523
  try {
521
524
  this.delegate.requestStarted(this);
@@ -757,11 +760,11 @@ class FormSubmission {
757
760
  return true;
758
761
  }
759
762
  }
760
- prepareHeadersForRequest(headers, request) {
763
+ prepareRequest(request) {
761
764
  if (!request.isIdempotent) {
762
765
  const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
763
766
  if (token) {
764
- headers["X-CSRF-Token"] = token;
767
+ request.headers["X-CSRF-Token"] = token;
765
768
  }
766
769
  }
767
770
  if (this.requestAcceptsTurboStreamResponse(request)) {
@@ -936,6 +939,7 @@ class FormSubmitObserver {
936
939
  const submitter = event.submitter || undefined;
937
940
  if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
938
941
  event.preventDefault();
942
+ event.stopImmediatePropagation();
939
943
  this.delegate.formSubmitted(form, submitter);
940
944
  }
941
945
  }
@@ -963,11 +967,15 @@ function submissionDoesNotDismissDialog(form, submitter) {
963
967
  }
964
968
 
965
969
  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;
970
+ if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
971
+ const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
972
+ for (const element of document.getElementsByName(target)) {
973
+ if (element instanceof HTMLIFrameElement) return false;
974
+ }
975
+ return true;
976
+ } else {
977
+ return true;
969
978
  }
970
- return true;
971
979
  }
972
980
 
973
981
  class View {
@@ -1076,6 +1084,47 @@ class FrameView extends View {
1076
1084
  }
1077
1085
  }
1078
1086
 
1087
+ class LinkInterceptor {
1088
+ constructor(delegate, element) {
1089
+ this.clickBubbled = event => {
1090
+ if (this.respondsToEventTarget(event.target)) {
1091
+ this.clickEvent = event;
1092
+ } else {
1093
+ delete this.clickEvent;
1094
+ }
1095
+ };
1096
+ this.linkClicked = event => {
1097
+ if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
1098
+ if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
1099
+ this.clickEvent.preventDefault();
1100
+ event.preventDefault();
1101
+ this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
1102
+ }
1103
+ }
1104
+ delete this.clickEvent;
1105
+ };
1106
+ this.willVisit = _event => {
1107
+ delete this.clickEvent;
1108
+ };
1109
+ this.delegate = delegate;
1110
+ this.element = element;
1111
+ }
1112
+ start() {
1113
+ this.element.addEventListener("click", this.clickBubbled);
1114
+ document.addEventListener("turbo:click", this.linkClicked);
1115
+ document.addEventListener("turbo:before-visit", this.willVisit);
1116
+ }
1117
+ stop() {
1118
+ this.element.removeEventListener("click", this.clickBubbled);
1119
+ document.removeEventListener("turbo:click", this.linkClicked);
1120
+ document.removeEventListener("turbo:before-visit", this.willVisit);
1121
+ }
1122
+ respondsToEventTarget(target) {
1123
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
1124
+ return element && element.closest("turbo-frame, html") == this.element;
1125
+ }
1126
+ }
1127
+
1079
1128
  class LinkClickObserver {
1080
1129
  constructor(delegate, eventTarget) {
1081
1130
  this.started = false;
@@ -1115,9 +1164,7 @@ class LinkClickObserver {
1115
1164
  return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
1116
1165
  }
1117
1166
  findLinkFromClickTarget(target) {
1118
- if (target instanceof Element) {
1119
- return target.closest("a[href]:not([target^=_]):not([download])");
1120
- }
1167
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
1121
1168
  }
1122
1169
  getLocationForLink(link) {
1123
1170
  return expandURL(link.getAttribute("href") || "");
@@ -1125,37 +1172,51 @@ class LinkClickObserver {
1125
1172
  }
1126
1173
 
1127
1174
  function doesNotTargetIFrame(anchor) {
1128
- for (const element of document.getElementsByName(anchor.target)) {
1129
- if (element instanceof HTMLIFrameElement) return false;
1175
+ if (anchor.hasAttribute("target")) {
1176
+ for (const element of document.getElementsByName(anchor.target)) {
1177
+ if (element instanceof HTMLIFrameElement) return false;
1178
+ }
1179
+ return true;
1180
+ } else {
1181
+ return true;
1130
1182
  }
1131
- return true;
1132
1183
  }
1133
1184
 
1134
1185
  class FormLinkClickObserver {
1135
1186
  constructor(delegate, element) {
1136
1187
  this.delegate = delegate;
1137
- this.linkClickObserver = new LinkClickObserver(this, element);
1188
+ this.linkInterceptor = new LinkClickObserver(this, element);
1138
1189
  }
1139
1190
  start() {
1140
- this.linkClickObserver.start();
1191
+ this.linkInterceptor.start();
1141
1192
  }
1142
1193
  stop() {
1143
- this.linkClickObserver.stop();
1194
+ this.linkInterceptor.stop();
1144
1195
  }
1145
1196
  willFollowLinkToLocation(link, location, originalEvent) {
1146
1197
  return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
1147
1198
  }
1148
1199
  followedLinkToLocation(link, location) {
1149
- const action = location.href;
1150
1200
  const form = document.createElement("form");
1201
+ const type = "hidden";
1202
+ for (const [name, value] of location.searchParams) {
1203
+ form.append(Object.assign(document.createElement("input"), {
1204
+ type: type,
1205
+ name: name,
1206
+ value: value
1207
+ }));
1208
+ }
1209
+ const action = Object.assign(location, {
1210
+ search: ""
1211
+ });
1151
1212
  form.setAttribute("data-turbo", "true");
1152
- form.setAttribute("action", action);
1213
+ form.setAttribute("action", action.href);
1153
1214
  form.setAttribute("hidden", "");
1154
1215
  const method = link.getAttribute("data-turbo-method");
1155
1216
  if (method) form.setAttribute("method", method);
1156
1217
  const turboFrame = link.getAttribute("data-turbo-frame");
1157
1218
  if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
1158
- const turboAction = link.getAttribute("data-turbo-action");
1219
+ const turboAction = getVisitAction(link);
1159
1220
  if (turboAction) form.setAttribute("data-turbo-action", turboAction);
1160
1221
  const turboConfirm = link.getAttribute("data-turbo-confirm");
1161
1222
  if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
@@ -1175,10 +1236,10 @@ class Bardo {
1175
1236
  this.delegate = delegate;
1176
1237
  this.permanentElementMap = permanentElementMap;
1177
1238
  }
1178
- static preservingPermanentElements(delegate, permanentElementMap, callback) {
1239
+ static async preservingPermanentElements(delegate, permanentElementMap, callback) {
1179
1240
  const bardo = new this(delegate, permanentElementMap);
1180
1241
  bardo.enter();
1181
- callback();
1242
+ await callback();
1182
1243
  bardo.leave();
1183
1244
  }
1184
1245
  enter() {
@@ -1251,8 +1312,8 @@ class Renderer {
1251
1312
  delete this.resolvingFunctions;
1252
1313
  }
1253
1314
  }
1254
- preservingPermanentElements(callback) {
1255
- Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1315
+ async preservingPermanentElements(callback) {
1316
+ await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
1256
1317
  }
1257
1318
  focusFirstAutofocusableElement() {
1258
1319
  const element = this.connectedSnapshot.firstAutofocusableElement;
@@ -1668,10 +1729,11 @@ class Visit {
1668
1729
  this.delegate = delegate;
1669
1730
  this.location = location;
1670
1731
  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);
1732
+ 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
1733
  this.action = action;
1673
1734
  this.historyChanged = historyChanged;
1674
1735
  this.referrer = referrer;
1736
+ this.snapshot = snapshot;
1675
1737
  this.snapshotHTML = snapshotHTML;
1676
1738
  this.response = response;
1677
1739
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
@@ -1834,7 +1896,9 @@ class Visit {
1834
1896
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1835
1897
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1836
1898
  action: "replace",
1837
- response: this.response
1899
+ response: this.response,
1900
+ shouldCacheSnapshot: false,
1901
+ willRender: false
1838
1902
  });
1839
1903
  this.followedRedirect = true;
1840
1904
  }
@@ -1844,11 +1908,12 @@ class Visit {
1844
1908
  this.render((async () => {
1845
1909
  this.cacheSnapshot();
1846
1910
  this.performScroll();
1911
+ this.changeHistory();
1847
1912
  this.adapter.visitRendered(this);
1848
1913
  }));
1849
1914
  }
1850
1915
  }
1851
- prepareHeadersForRequest(headers, request) {
1916
+ prepareRequest(request) {
1852
1917
  if (this.acceptsStreamResponse) {
1853
1918
  request.acceptResponseType(StreamMessage.contentType);
1854
1919
  }
@@ -1956,7 +2021,7 @@ class Visit {
1956
2021
  }
1957
2022
  cacheSnapshot() {
1958
2023
  if (!this.snapshotCached) {
1959
- this.view.cacheSnapshot().then((snapshot => snapshot && this.visitCachedSnapshot(snapshot)));
2024
+ this.view.cacheSnapshot(this.snapshot).then((snapshot => snapshot && this.visitCachedSnapshot(snapshot)));
1960
2025
  this.snapshotCached = true;
1961
2026
  }
1962
2027
  }
@@ -2065,11 +2130,11 @@ class BrowserAdapter {
2065
2130
  }
2066
2131
  }
2067
2132
  reload(reason) {
2133
+ var _a;
2068
2134
  dispatch("turbo:reload", {
2069
2135
  detail: reason
2070
2136
  });
2071
- if (!this.location) return;
2072
- window.location.href = this.location.toString();
2137
+ window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;
2073
2138
  }
2074
2139
  get navigator() {
2075
2140
  return this.session.navigator;
@@ -2104,24 +2169,24 @@ class FrameRedirector {
2104
2169
  constructor(session, element) {
2105
2170
  this.session = session;
2106
2171
  this.element = element;
2107
- this.linkClickObserver = new LinkClickObserver(this, element);
2172
+ this.linkInterceptor = new LinkInterceptor(this, element);
2108
2173
  this.formSubmitObserver = new FormSubmitObserver(this, element);
2109
2174
  }
2110
2175
  start() {
2111
- this.linkClickObserver.start();
2176
+ this.linkInterceptor.start();
2112
2177
  this.formSubmitObserver.start();
2113
2178
  }
2114
2179
  stop() {
2115
- this.linkClickObserver.stop();
2180
+ this.linkInterceptor.stop();
2116
2181
  this.formSubmitObserver.stop();
2117
2182
  }
2118
- willFollowLinkToLocation(element, location, event) {
2119
- return this.shouldRedirect(element) && this.frameAllowsVisitingLocation(element, location, event);
2183
+ shouldInterceptLinkClick(element, _location, _event) {
2184
+ return this.shouldRedirect(element);
2120
2185
  }
2121
- followedLinkToLocation(element, url) {
2186
+ linkClickIntercepted(element, url, event) {
2122
2187
  const frame = this.findFrameElement(element);
2123
2188
  if (frame) {
2124
- frame.delegate.followedLinkToLocation(element, url);
2189
+ frame.delegate.linkClickIntercepted(element, url, event);
2125
2190
  }
2126
2191
  }
2127
2192
  willSubmitForm(element, submitter) {
@@ -2133,17 +2198,6 @@ class FrameRedirector {
2133
2198
  frame.delegate.formSubmitted(element, submitter);
2134
2199
  }
2135
2200
  }
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
2201
  shouldSubmit(form, submitter) {
2148
2202
  var _a;
2149
2203
  const action = getAction(form, submitter);
@@ -2268,7 +2322,6 @@ class Navigator {
2268
2322
  }
2269
2323
  }
2270
2324
  startVisit(locatable, restorationIdentifier, options = {}) {
2271
- this.lastVisit = this.currentVisit;
2272
2325
  this.stop();
2273
2326
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
2274
2327
  referrer: this.location
@@ -2355,12 +2408,10 @@ class Navigator {
2355
2408
  this.delegate.visitCompleted(visit);
2356
2409
  }
2357
2410
  locationWithActionIsSamePage(location, action) {
2358
- var _a;
2359
2411
  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);
2412
+ const currentAnchor = getAnchor(this.view.lastRenderedLocation);
2362
2413
  const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
2363
- return action !== "replace" && getRequestURL(location) === getRequestURL(lastLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
2414
+ return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
2364
2415
  }
2365
2416
  visitScrolledToSamePageLocation(oldURL, newURL) {
2366
2417
  this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
@@ -2371,10 +2422,8 @@ class Navigator {
2371
2422
  get restorationIdentifier() {
2372
2423
  return this.history.restorationIdentifier;
2373
2424
  }
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";
2425
+ getActionForFormSubmission({submitter: submitter, formElement: formElement}) {
2426
+ return getVisitAction(submitter, formElement) || "advance";
2378
2427
  }
2379
2428
  }
2380
2429
 
@@ -2622,7 +2671,7 @@ class PageRenderer extends Renderer {
2622
2671
  }
2623
2672
  async render() {
2624
2673
  if (this.willRender) {
2625
- this.replaceBody();
2674
+ await this.replaceBody();
2626
2675
  }
2627
2676
  }
2628
2677
  finishRendering() {
@@ -2641,16 +2690,16 @@ class PageRenderer extends Renderer {
2641
2690
  return this.newSnapshot.element;
2642
2691
  }
2643
2692
  async mergeHead() {
2693
+ const mergedHeadElements = this.mergeProvisionalElements();
2644
2694
  const newStylesheetElements = this.copyNewHeadStylesheetElements();
2645
2695
  this.copyNewHeadScriptElements();
2646
- this.removeCurrentHeadProvisionalElements();
2647
- this.copyNewHeadProvisionalElements();
2696
+ await mergedHeadElements;
2648
2697
  await newStylesheetElements;
2649
2698
  }
2650
- replaceBody() {
2651
- this.preservingPermanentElements((() => {
2699
+ async replaceBody() {
2700
+ await this.preservingPermanentElements((async () => {
2652
2701
  this.activateNewBody();
2653
- this.assignNewBody();
2702
+ await this.assignNewBody();
2654
2703
  }));
2655
2704
  }
2656
2705
  get trackedElementsAreIdentical() {
@@ -2669,6 +2718,35 @@ class PageRenderer extends Renderer {
2669
2718
  document.head.appendChild(activateScriptElement(element));
2670
2719
  }
2671
2720
  }
2721
+ async mergeProvisionalElements() {
2722
+ const newHeadElements = [ ...this.newHeadProvisionalElements ];
2723
+ for (const element of this.currentHeadProvisionalElements) {
2724
+ if (!this.isCurrentElementInElementList(element, newHeadElements)) {
2725
+ document.head.removeChild(element);
2726
+ }
2727
+ }
2728
+ for (const element of newHeadElements) {
2729
+ document.head.appendChild(element);
2730
+ }
2731
+ }
2732
+ isCurrentElementInElementList(element, elementList) {
2733
+ for (const [index, newElement] of elementList.entries()) {
2734
+ if (element.tagName == "TITLE") {
2735
+ if (newElement.tagName != "TITLE") {
2736
+ continue;
2737
+ }
2738
+ if (element.innerHTML == newElement.innerHTML) {
2739
+ elementList.splice(index, 1);
2740
+ return true;
2741
+ }
2742
+ }
2743
+ if (newElement.isEqualNode(element)) {
2744
+ elementList.splice(index, 1);
2745
+ return true;
2746
+ }
2747
+ }
2748
+ return false;
2749
+ }
2672
2750
  removeCurrentHeadProvisionalElements() {
2673
2751
  for (const element of this.currentHeadProvisionalElements) {
2674
2752
  document.head.removeChild(element);
@@ -2689,8 +2767,8 @@ class PageRenderer extends Renderer {
2689
2767
  inertScriptElement.replaceWith(activatedScriptElement);
2690
2768
  }
2691
2769
  }
2692
- assignNewBody() {
2693
- this.renderElement(this.currentElement, this.newElement);
2770
+ async assignNewBody() {
2771
+ await this.renderElement(this.currentElement, this.newElement);
2694
2772
  }
2695
2773
  get newHeadStylesheetElements() {
2696
2774
  return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
@@ -2777,10 +2855,10 @@ class PageView extends View {
2777
2855
  clearSnapshotCache() {
2778
2856
  this.snapshotCache.clear();
2779
2857
  }
2780
- async cacheSnapshot() {
2781
- if (this.shouldCacheSnapshot) {
2858
+ async cacheSnapshot(snapshot = this.snapshot) {
2859
+ if (snapshot.isCacheable) {
2782
2860
  this.delegate.viewWillCacheSnapshot();
2783
- const {snapshot: snapshot, lastRenderedLocation: location} = this;
2861
+ const {lastRenderedLocation: location} = this;
2784
2862
  await nextEventLoopTick();
2785
2863
  const cachedSnapshot = snapshot.clone();
2786
2864
  this.snapshotCache.put(location, cachedSnapshot);
@@ -2793,9 +2871,6 @@ class PageView extends View {
2793
2871
  get snapshot() {
2794
2872
  return PageSnapshot.fromElement(this.element);
2795
2873
  }
2796
- get shouldCacheSnapshot() {
2797
- return this.snapshot.isCacheable;
2798
- }
2799
2874
  }
2800
2875
 
2801
2876
  class Preloader {
@@ -3127,8 +3202,8 @@ class Session {
3127
3202
  }
3128
3203
  }
3129
3204
  elementIsNavigatable(element) {
3130
- const container = element.closest("[data-turbo]");
3131
- const withinFrame = element.closest("turbo-frame");
3205
+ const container = findClosestRecursively(element, "[data-turbo]");
3206
+ const withinFrame = findClosestRecursively(element, "turbo-frame");
3132
3207
  if (this.drive || withinFrame) {
3133
3208
  if (container) {
3134
3209
  return container.getAttribute("data-turbo") != "false";
@@ -3144,8 +3219,7 @@ class Session {
3144
3219
  }
3145
3220
  }
3146
3221
  getActionForLink(link) {
3147
- const action = link.getAttribute("data-turbo-action");
3148
- return isAction(action) ? action : "advance";
3222
+ return getVisitAction(link) || "advance";
3149
3223
  }
3150
3224
  get snapshot() {
3151
3225
  return this.view.snapshot;
@@ -3213,7 +3287,10 @@ const StreamActions = {
3213
3287
  this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
3214
3288
  },
3215
3289
  update() {
3216
- this.targetElements.forEach((e => e.replaceChildren(this.templateContent)));
3290
+ this.targetElements.forEach((targetElement => {
3291
+ targetElement.innerHTML = "";
3292
+ targetElement.append(this.templateContent);
3293
+ }));
3217
3294
  }
3218
3295
  };
3219
3296
 
@@ -3305,7 +3382,7 @@ class FrameController {
3305
3382
  this.view = new FrameView(this, this.element);
3306
3383
  this.appearanceObserver = new AppearanceObserver(this, this.element);
3307
3384
  this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);
3308
- this.linkClickObserver = new LinkClickObserver(this, this.element);
3385
+ this.linkInterceptor = new LinkInterceptor(this, this.element);
3309
3386
  this.restorationIdentifier = uuid();
3310
3387
  this.formSubmitObserver = new FormSubmitObserver(this, this.element);
3311
3388
  }
@@ -3318,7 +3395,7 @@ class FrameController {
3318
3395
  this.loadSourceURL();
3319
3396
  }
3320
3397
  this.formLinkClickObserver.start();
3321
- this.linkClickObserver.start();
3398
+ this.linkInterceptor.start();
3322
3399
  this.formSubmitObserver.start();
3323
3400
  }
3324
3401
  }
@@ -3327,7 +3404,7 @@ class FrameController {
3327
3404
  this.connected = false;
3328
3405
  this.appearanceObserver.stop();
3329
3406
  this.formLinkClickObserver.stop();
3330
- this.linkClickObserver.stop();
3407
+ this.linkInterceptor.stop();
3331
3408
  this.formSubmitObserver.stop();
3332
3409
  }
3333
3410
  }
@@ -3345,6 +3422,15 @@ class FrameController {
3345
3422
  this.loadSourceURL();
3346
3423
  }
3347
3424
  }
3425
+ sourceURLReloaded() {
3426
+ const {src: src} = this.element;
3427
+ this.ignoringChangesToAttribute("complete", (() => {
3428
+ this.element.removeAttribute("complete");
3429
+ }));
3430
+ this.element.src = null;
3431
+ this.element.src = src;
3432
+ return this.element.loaded;
3433
+ }
3348
3434
  completeChanged() {
3349
3435
  if (this.isIgnoringChangesTo("complete")) return;
3350
3436
  this.loadSourceURL();
@@ -3396,21 +3482,22 @@ class FrameController {
3396
3482
  this.fetchResponseLoaded = () => {};
3397
3483
  }
3398
3484
  }
3399
- elementAppearedInViewport(_element) {
3485
+ elementAppearedInViewport(element) {
3486
+ this.proposeVisitIfNavigatedWithAction(element, element);
3400
3487
  this.loadSourceURL();
3401
3488
  }
3402
3489
  willSubmitFormLinkToLocation(link) {
3403
- return link.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(link);
3490
+ return this.shouldInterceptNavigation(link);
3404
3491
  }
3405
3492
  submittedFormLinkToLocation(link, _location, form) {
3406
3493
  const frame = this.findFrameElement(link);
3407
3494
  if (frame) form.setAttribute("data-turbo-frame", frame.id);
3408
3495
  }
3409
- willFollowLinkToLocation(element, location, event) {
3410
- return this.shouldInterceptNavigation(element) && this.frameAllowsVisitingLocation(element, location, event);
3496
+ shouldInterceptLinkClick(element, _location, _event) {
3497
+ return this.shouldInterceptNavigation(element);
3411
3498
  }
3412
- followedLinkToLocation(element, location) {
3413
- this.navigateFrame(element, location.href);
3499
+ linkClickIntercepted(element, location) {
3500
+ this.navigateFrame(element, location);
3414
3501
  }
3415
3502
  willSubmitForm(element, submitter) {
3416
3503
  return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
@@ -3421,12 +3508,12 @@ class FrameController {
3421
3508
  }
3422
3509
  this.formSubmission = new FormSubmission(this, element, submitter);
3423
3510
  const {fetchRequest: fetchRequest} = this.formSubmission;
3424
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
3511
+ this.prepareRequest(fetchRequest);
3425
3512
  this.formSubmission.start();
3426
3513
  }
3427
- prepareHeadersForRequest(headers, request) {
3514
+ prepareRequest(request) {
3428
3515
  var _a;
3429
- headers["Turbo-Frame"] = this.id;
3516
+ request.headers["Turbo-Frame"] = this.id;
3430
3517
  if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
3431
3518
  request.acceptResponseType(StreamMessage.contentType);
3432
3519
  }
@@ -3458,7 +3545,7 @@ class FrameController {
3458
3545
  }
3459
3546
  formSubmissionSucceededWithResponse(formSubmission, response) {
3460
3547
  const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
3461
- this.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
3548
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
3462
3549
  frame.delegate.loadResponse(response);
3463
3550
  }
3464
3551
  formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
@@ -3508,15 +3595,15 @@ class FrameController {
3508
3595
  }
3509
3596
  navigateFrame(element, url, submitter) {
3510
3597
  const frame = this.findFrameElement(element, submitter);
3511
- this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3598
+ frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
3512
3599
  this.withCurrentNavigationElement(element, (() => {
3513
3600
  frame.src = url;
3514
3601
  }));
3515
3602
  }
3516
3603
  proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3517
3604
  this.action = getVisitAction(submitter, element, frame);
3518
- this.frame = frame;
3519
- if (isAction(this.action)) {
3605
+ if (this.action) {
3606
+ const pageSnapshot = PageSnapshot.fromElement(frame).clone();
3520
3607
  const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
3521
3608
  frame.delegate.fetchResponseLoaded = fetchResponse => {
3522
3609
  if (frame.src) {
@@ -3532,7 +3619,8 @@ class FrameController {
3532
3619
  visitCachedSnapshot: visitCachedSnapshot,
3533
3620
  willRender: false,
3534
3621
  updateHistory: false,
3535
- restorationIdentifier: this.restorationIdentifier
3622
+ restorationIdentifier: this.restorationIdentifier,
3623
+ snapshot: pageSnapshot
3536
3624
  };
3537
3625
  if (this.action) options.action = this.action;
3538
3626
  session.visit(frame.src, options);
@@ -3541,9 +3629,9 @@ class FrameController {
3541
3629
  }
3542
3630
  }
3543
3631
  changeHistory() {
3544
- if (this.action && this.frame) {
3632
+ if (this.action) {
3545
3633
  const method = getHistoryMethodForAction(this.action);
3546
- session.history.update(method, expandURL(this.frame.src || ""), this.restorationIdentifier);
3634
+ session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
3547
3635
  }
3548
3636
  }
3549
3637
  willHandleFrameMissingFromResponse(fetchResponse) {
@@ -3671,17 +3759,6 @@ class FrameController {
3671
3759
  const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3672
3760
  return expandURL(root);
3673
3761
  }
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
3762
  isIgnoringChangesTo(attributeName) {
3686
3763
  return this.ignoredAttributes.has(attributeName);
3687
3764
  }
@@ -3997,18 +4074,21 @@ class TurboCableStreamSourceElement extends HTMLElement {
3997
4074
  }
3998
4075
  }
3999
4076
 
4000
- customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement);
4077
+ if (customElements.get("turbo-cable-stream-source") === undefined) {
4078
+ customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement);
4079
+ }
4001
4080
 
4002
4081
  function encodeMethodIntoRequestBody(event) {
4003
4082
  if (event.target instanceof HTMLFormElement) {
4004
4083
  const {target: form, detail: {fetchOptions: fetchOptions}} = event;
4005
4084
  form.addEventListener("turbo:submit-start", (({detail: {formSubmission: {submitter: submitter}}}) => {
4006
- const method = submitter && submitter.formMethod || fetchOptions.body && fetchOptions.body.get("_method") || form.getAttribute("method");
4085
+ const body = isBodyInit(fetchOptions.body) ? fetchOptions.body : new URLSearchParams;
4086
+ const method = determineFetchMethod(submitter, body, form);
4007
4087
  if (!/get/i.test(method)) {
4008
4088
  if (/post/i.test(method)) {
4009
- fetchOptions.body.delete("_method");
4089
+ body.delete("_method");
4010
4090
  } else {
4011
- fetchOptions.body.set("_method", method);
4091
+ body.set("_method", method);
4012
4092
  }
4013
4093
  fetchOptions.method = "post";
4014
4094
  }
@@ -4018,6 +4098,35 @@ function encodeMethodIntoRequestBody(event) {
4018
4098
  }
4019
4099
  }
4020
4100
 
4101
+ function determineFetchMethod(submitter, body, form) {
4102
+ const formMethod = determineFormMethod(submitter);
4103
+ const overrideMethod = body.get("_method");
4104
+ const method = form.getAttribute("method") || "get";
4105
+ if (typeof formMethod == "string") {
4106
+ return formMethod;
4107
+ } else if (typeof overrideMethod == "string") {
4108
+ return overrideMethod;
4109
+ } else {
4110
+ return method;
4111
+ }
4112
+ }
4113
+
4114
+ function determineFormMethod(submitter) {
4115
+ if (submitter instanceof HTMLButtonElement || submitter instanceof HTMLInputElement) {
4116
+ if (submitter.hasAttribute("formmethod")) {
4117
+ return submitter.formMethod;
4118
+ } else {
4119
+ return null;
4120
+ }
4121
+ } else {
4122
+ return null;
4123
+ }
4124
+ }
4125
+
4126
+ function isBodyInit(body) {
4127
+ return body instanceof FormData || body instanceof URLSearchParams;
4128
+ }
4129
+
4021
4130
  addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
4022
4131
 
4023
4132
  var adapters = {