turbo-rails 1.3.0 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 = {