@hotwired/turbo-rails 8.0.19 → 8.0.21

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.
@@ -1,68 +1,7 @@
1
1
  /*!
2
- Turbo 8.0.18
3
- Copyright © 2025 37signals LLC
2
+ Turbo 8.0.21
3
+ Copyright © 2026 37signals LLC
4
4
  */
5
- (function(prototype) {
6
- if (typeof prototype.requestSubmit == "function") return;
7
- prototype.requestSubmit = function(submitter) {
8
- if (submitter) {
9
- validateSubmitter(submitter, this);
10
- submitter.click();
11
- } else {
12
- submitter = document.createElement("input");
13
- submitter.type = "submit";
14
- submitter.hidden = true;
15
- this.appendChild(submitter);
16
- submitter.click();
17
- this.removeChild(submitter);
18
- }
19
- };
20
- function validateSubmitter(submitter, form) {
21
- submitter instanceof HTMLElement || raise(TypeError, "parameter 1 is not of type 'HTMLElement'");
22
- submitter.type == "submit" || raise(TypeError, "The specified element is not a submit button");
23
- submitter.form == form || raise(DOMException, "The specified element is not owned by this form element", "NotFoundError");
24
- }
25
- function raise(errorConstructor, message, name) {
26
- throw new errorConstructor("Failed to execute 'requestSubmit' on 'HTMLFormElement': " + message + ".", name);
27
- }
28
- })(HTMLFormElement.prototype);
29
-
30
- const submittersByForm = new WeakMap;
31
-
32
- function findSubmitterFromClickTarget(target) {
33
- const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
34
- const candidate = element ? element.closest("input, button") : null;
35
- return candidate?.type == "submit" ? candidate : null;
36
- }
37
-
38
- function clickCaptured(event) {
39
- const submitter = findSubmitterFromClickTarget(event.target);
40
- if (submitter && submitter.form) {
41
- submittersByForm.set(submitter.form, submitter);
42
- }
43
- }
44
-
45
- (function() {
46
- if ("submitter" in Event.prototype) return;
47
- let prototype = window.Event.prototype;
48
- if ("SubmitEvent" in window) {
49
- const prototypeOfSubmitEvent = window.SubmitEvent.prototype;
50
- if (/Apple Computer/.test(navigator.vendor) && !("submitter" in prototypeOfSubmitEvent)) {
51
- prototype = prototypeOfSubmitEvent;
52
- } else {
53
- return;
54
- }
55
- }
56
- addEventListener("click", clickCaptured, true);
57
- Object.defineProperty(prototype, "submitter", {
58
- get() {
59
- if (this.type == "submit" && this.target instanceof HTMLFormElement) {
60
- return submittersByForm.get(this.target);
61
- }
62
- }
63
- });
64
- })();
65
-
66
5
  const FrameLoadingStyle = {
67
6
  eager: "eager",
68
7
  lazy: "lazy"
@@ -240,10 +179,6 @@ function nextEventLoopTick() {
240
179
  return new Promise((resolve => setTimeout((() => resolve()), 0)));
241
180
  }
242
181
 
243
- function nextMicrotask() {
244
- return Promise.resolve();
245
- }
246
-
247
182
  function parseHTMLDocument(html = "") {
248
183
  return (new DOMParser).parseFromString(html, "text/html");
249
184
  }
@@ -273,7 +208,7 @@ function uuid() {
273
208
  } else if (i == 19) {
274
209
  return (Math.floor(Math.random() * 4) + 8).toString(16);
275
210
  } else {
276
- return Math.floor(Math.random() * 15).toString(16);
211
+ return Math.floor(Math.random() * 16).toString(16);
277
212
  }
278
213
  })).join("");
279
214
  }
@@ -411,15 +346,13 @@ function doesNotTargetIFrame(name) {
411
346
  function findLinkFromClickTarget(target) {
412
347
  const link = findClosestRecursively(target, "a[href], a[xlink\\:href]");
413
348
  if (!link) return null;
349
+ if (link.href.startsWith("#")) return null;
414
350
  if (link.hasAttribute("download")) return null;
415
- if (link.hasAttribute("target") && link.target !== "_self") return null;
351
+ const linkTarget = link.getAttribute("target");
352
+ if (linkTarget && linkTarget !== "_self") return null;
416
353
  return link;
417
354
  }
418
355
 
419
- function getLocationForLink(link) {
420
- return expandURL(link.getAttribute("href") || "");
421
- }
422
-
423
356
  function debounce(fn, delay) {
424
357
  let timeoutId = null;
425
358
  return (...args) => {
@@ -500,6 +433,10 @@ function locationIsVisitable(location, rootLocation) {
500
433
  return isPrefixedBy(location, rootLocation) && !config.drive.unvisitableExtensions.has(getExtension(location));
501
434
  }
502
435
 
436
+ function getLocationForLink(link) {
437
+ return expandURL(link.getAttribute("href") || "");
438
+ }
439
+
503
440
  function getRequestURL(url) {
504
441
  const anchor = getAnchor(url);
505
442
  return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
@@ -871,34 +808,94 @@ function importStreamElements(fragment) {
871
808
  return fragment;
872
809
  }
873
810
 
811
+ const identity = key => key;
812
+
813
+ class LRUCache {
814
+ keys=[];
815
+ entries={};
816
+ #toCacheKey;
817
+ constructor(size, toCacheKey = identity) {
818
+ this.size = size;
819
+ this.#toCacheKey = toCacheKey;
820
+ }
821
+ has(key) {
822
+ return this.#toCacheKey(key) in this.entries;
823
+ }
824
+ get(key) {
825
+ if (this.has(key)) {
826
+ const entry = this.read(key);
827
+ this.touch(key);
828
+ return entry;
829
+ }
830
+ }
831
+ put(key, entry) {
832
+ this.write(key, entry);
833
+ this.touch(key);
834
+ return entry;
835
+ }
836
+ clear() {
837
+ for (const key of Object.keys(this.entries)) {
838
+ this.evict(key);
839
+ }
840
+ }
841
+ read(key) {
842
+ return this.entries[this.#toCacheKey(key)];
843
+ }
844
+ write(key, entry) {
845
+ this.entries[this.#toCacheKey(key)] = entry;
846
+ }
847
+ touch(key) {
848
+ key = this.#toCacheKey(key);
849
+ const index = this.keys.indexOf(key);
850
+ if (index > -1) this.keys.splice(index, 1);
851
+ this.keys.unshift(key);
852
+ this.trim();
853
+ }
854
+ trim() {
855
+ for (const key of this.keys.splice(this.size)) {
856
+ this.evict(key);
857
+ }
858
+ }
859
+ evict(key) {
860
+ delete this.entries[key];
861
+ }
862
+ }
863
+
874
864
  const PREFETCH_DELAY = 100;
875
865
 
876
- class PrefetchCache {
866
+ class PrefetchCache extends LRUCache {
877
867
  #prefetchTimeout=null;
878
- #prefetched=null;
879
- get(url) {
880
- if (this.#prefetched && this.#prefetched.url === url && this.#prefetched.expire > Date.now()) {
881
- return this.#prefetched.request;
882
- }
868
+ #maxAges={};
869
+ constructor(size = 1, prefetchDelay = PREFETCH_DELAY) {
870
+ super(size, toCacheKey);
871
+ this.prefetchDelay = prefetchDelay;
883
872
  }
884
- setLater(url, request, ttl) {
885
- this.clear();
873
+ putLater(url, request, ttl) {
886
874
  this.#prefetchTimeout = setTimeout((() => {
887
875
  request.perform();
888
- this.set(url, request, ttl);
876
+ this.put(url, request, ttl);
889
877
  this.#prefetchTimeout = null;
890
- }), PREFETCH_DELAY);
878
+ }), this.prefetchDelay);
891
879
  }
892
- set(url, request, ttl) {
893
- this.#prefetched = {
894
- url: url,
895
- request: request,
896
- expire: new Date((new Date).getTime() + ttl)
897
- };
880
+ put(url, request, ttl = cacheTtl) {
881
+ super.put(url, request);
882
+ this.#maxAges[toCacheKey(url)] = new Date((new Date).getTime() + ttl);
898
883
  }
899
884
  clear() {
885
+ super.clear();
900
886
  if (this.#prefetchTimeout) clearTimeout(this.#prefetchTimeout);
901
- this.#prefetched = null;
887
+ }
888
+ evict(key) {
889
+ super.evict(key);
890
+ delete this.#maxAges[key];
891
+ }
892
+ has(key) {
893
+ if (super.has(key)) {
894
+ const maxAge = this.#maxAges[toCacheKey(key)];
895
+ return maxAge && maxAge > Date.now();
896
+ } else {
897
+ return false;
898
+ }
902
899
  }
903
900
  }
904
901
 
@@ -1723,12 +1720,8 @@ var Idiomorph = function() {
1723
1720
  }
1724
1721
  function morphOuterHTML(ctx, oldNode, newNode) {
1725
1722
  const oldParent = normalizeParent(oldNode);
1726
- let childNodes = Array.from(oldParent.childNodes);
1727
- const index = childNodes.indexOf(oldNode);
1728
- const rightMargin = childNodes.length - (index + 1);
1729
1723
  morphChildren(ctx, oldParent, newNode, oldNode, oldNode.nextSibling);
1730
- childNodes = Array.from(oldParent.childNodes);
1731
- return childNodes.slice(index, childNodes.length - rightMargin);
1724
+ return Array.from(oldParent.childNodes);
1732
1725
  }
1733
1726
  function saveAndRestoreFocus(ctx, fn) {
1734
1727
  if (!ctx.config.restoreFocus) return fn();
@@ -1738,8 +1731,8 @@ var Idiomorph = function() {
1738
1731
  }
1739
1732
  const {id: activeElementId, selectionStart: selectionStart, selectionEnd: selectionEnd} = activeElement;
1740
1733
  const results = fn();
1741
- if (activeElementId && activeElementId !== document.activeElement?.id) {
1742
- activeElement = ctx.target.querySelector(`#${activeElementId}`);
1734
+ if (activeElementId && activeElementId !== document.activeElement?.getAttribute("id")) {
1735
+ activeElement = ctx.target.querySelector(`[id="${activeElementId}"]`);
1743
1736
  activeElement?.focus();
1744
1737
  }
1745
1738
  if (activeElement && !activeElement.selectionEnd && selectionEnd) {
@@ -1766,11 +1759,14 @@ var Idiomorph = function() {
1766
1759
  continue;
1767
1760
  }
1768
1761
  }
1769
- if (newChild instanceof Element && ctx.persistentIds.has(newChild.id)) {
1770
- const movedChild = moveBeforeById(oldParent, newChild.id, insertionPoint, ctx);
1771
- morphNode(movedChild, newChild, ctx);
1772
- insertionPoint = movedChild.nextSibling;
1773
- continue;
1762
+ if (newChild instanceof Element) {
1763
+ const newChildId = newChild.getAttribute("id");
1764
+ if (ctx.persistentIds.has(newChildId)) {
1765
+ const movedChild = moveBeforeById(oldParent, newChildId, insertionPoint, ctx);
1766
+ morphNode(movedChild, newChild, ctx);
1767
+ insertionPoint = movedChild.nextSibling;
1768
+ continue;
1769
+ }
1774
1770
  }
1775
1771
  const insertedNode = createNode(oldParent, newChild, insertionPoint, ctx);
1776
1772
  if (insertedNode) {
@@ -1822,7 +1818,7 @@ var Idiomorph = function() {
1822
1818
  softMatch = undefined;
1823
1819
  }
1824
1820
  }
1825
- if (cursor.contains(document.activeElement)) break;
1821
+ if (ctx.activeElementAndParents.includes(cursor)) break;
1826
1822
  cursor = cursor.nextSibling;
1827
1823
  }
1828
1824
  return softMatch || null;
@@ -1841,7 +1837,7 @@ var Idiomorph = function() {
1841
1837
  function isSoftMatch(oldNode, newNode) {
1842
1838
  const oldElt = oldNode;
1843
1839
  const newElt = newNode;
1844
- return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && (!oldElt.id || oldElt.id === newElt.id);
1840
+ return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && (!oldElt.getAttribute?.("id") || oldElt.getAttribute?.("id") === newElt.getAttribute?.("id"));
1845
1841
  }
1846
1842
  return findBestMatch;
1847
1843
  }();
@@ -1864,13 +1860,13 @@ var Idiomorph = function() {
1864
1860
  return cursor;
1865
1861
  }
1866
1862
  function moveBeforeById(parentNode, id, after, ctx) {
1867
- const target = ctx.target.querySelector(`#${id}`) || ctx.pantry.querySelector(`#${id}`);
1863
+ const target = ctx.target.getAttribute?.("id") === id && ctx.target || ctx.target.querySelector(`[id="${id}"]`) || ctx.pantry.querySelector(`[id="${id}"]`);
1868
1864
  removeElementFromAncestorsIdMaps(target, ctx);
1869
1865
  moveBefore(parentNode, target, after);
1870
1866
  return target;
1871
1867
  }
1872
1868
  function removeElementFromAncestorsIdMaps(element, ctx) {
1873
- const id = element.id;
1869
+ const id = element.getAttribute("id");
1874
1870
  while (element = element.parentNode) {
1875
1871
  let idSet = ctx.idMap.get(element);
1876
1872
  if (idSet) {
@@ -2114,6 +2110,7 @@ var Idiomorph = function() {
2114
2110
  idMap: idMap,
2115
2111
  persistentIds: persistentIds,
2116
2112
  pantry: createPantry(),
2113
+ activeElementAndParents: createActiveElementAndParents(oldNode),
2117
2114
  callbacks: mergedConfig.callbacks,
2118
2115
  head: mergedConfig.head
2119
2116
  };
@@ -2131,16 +2128,29 @@ var Idiomorph = function() {
2131
2128
  document.body.insertAdjacentElement("afterend", pantry);
2132
2129
  return pantry;
2133
2130
  }
2131
+ function createActiveElementAndParents(oldNode) {
2132
+ let activeElementAndParents = [];
2133
+ let elt = document.activeElement;
2134
+ if (elt?.tagName !== "BODY" && oldNode.contains(elt)) {
2135
+ while (elt) {
2136
+ activeElementAndParents.push(elt);
2137
+ if (elt === oldNode) break;
2138
+ elt = elt.parentElement;
2139
+ }
2140
+ }
2141
+ return activeElementAndParents;
2142
+ }
2134
2143
  function findIdElements(root) {
2135
2144
  let elements = Array.from(root.querySelectorAll("[id]"));
2136
- if (root.id) {
2145
+ if (root.getAttribute?.("id")) {
2137
2146
  elements.push(root);
2138
2147
  }
2139
2148
  return elements;
2140
2149
  }
2141
2150
  function populateIdMapWithTree(idMap, persistentIds, root, elements) {
2142
2151
  for (const elt of elements) {
2143
- if (persistentIds.has(elt.id)) {
2152
+ const id = elt.getAttribute("id");
2153
+ if (persistentIds.has(id)) {
2144
2154
  let current = elt;
2145
2155
  while (current) {
2146
2156
  let idSet = idMap.get(current);
@@ -2148,7 +2158,7 @@ var Idiomorph = function() {
2148
2158
  idSet = new Set;
2149
2159
  idMap.set(current, idSet);
2150
2160
  }
2151
- idSet.add(elt.id);
2161
+ idSet.add(id);
2152
2162
  if (current === root) break;
2153
2163
  current = current.parentElement;
2154
2164
  }
@@ -2211,7 +2221,7 @@ var Idiomorph = function() {
2211
2221
  return newContent;
2212
2222
  } else if (newContent instanceof Node) {
2213
2223
  if (newContent.parentNode) {
2214
- return createDuckTypedParent(newContent);
2224
+ return new SlicedParentNode(newContent);
2215
2225
  } else {
2216
2226
  const dummyParent = document.createElement("div");
2217
2227
  dummyParent.append(newContent);
@@ -2225,19 +2235,43 @@ var Idiomorph = function() {
2225
2235
  return dummyParent;
2226
2236
  }
2227
2237
  }
2228
- function createDuckTypedParent(newContent) {
2229
- return {
2230
- childNodes: [ newContent ],
2231
- querySelectorAll: s => {
2232
- const elements = newContent.querySelectorAll(s);
2233
- return newContent.matches(s) ? [ newContent, ...elements ] : elements;
2234
- },
2235
- insertBefore: (n, r) => newContent.parentNode.insertBefore(n, r),
2236
- moveBefore: (n, r) => newContent.parentNode.moveBefore(n, r),
2237
- get __idiomorphRoot() {
2238
- return newContent;
2238
+ class SlicedParentNode {
2239
+ constructor(node) {
2240
+ this.originalNode = node;
2241
+ this.realParentNode = node.parentNode;
2242
+ this.previousSibling = node.previousSibling;
2243
+ this.nextSibling = node.nextSibling;
2244
+ }
2245
+ get childNodes() {
2246
+ const nodes = [];
2247
+ let cursor = this.previousSibling ? this.previousSibling.nextSibling : this.realParentNode.firstChild;
2248
+ while (cursor && cursor != this.nextSibling) {
2249
+ nodes.push(cursor);
2250
+ cursor = cursor.nextSibling;
2239
2251
  }
2240
- };
2252
+ return nodes;
2253
+ }
2254
+ querySelectorAll(selector) {
2255
+ return this.childNodes.reduce(((results, node) => {
2256
+ if (node instanceof Element) {
2257
+ if (node.matches(selector)) results.push(node);
2258
+ const nodeList = node.querySelectorAll(selector);
2259
+ for (let i = 0; i < nodeList.length; i++) {
2260
+ results.push(nodeList[i]);
2261
+ }
2262
+ }
2263
+ return results;
2264
+ }), []);
2265
+ }
2266
+ insertBefore(node, referenceNode) {
2267
+ return this.realParentNode.insertBefore(node, referenceNode);
2268
+ }
2269
+ moveBefore(node, referenceNode) {
2270
+ return this.realParentNode.moveBefore(node, referenceNode);
2271
+ }
2272
+ get __idiomorphRoot() {
2273
+ return this.originalNode;
2274
+ }
2241
2275
  }
2242
2276
  function parseContent(newContent) {
2243
2277
  let parser = new DOMParser;
@@ -2287,7 +2321,11 @@ function morphChildren(currentElement, newElement, options = {}) {
2287
2321
  }
2288
2322
 
2289
2323
  function shouldRefreshFrameWithMorphing(currentFrame, newFrame) {
2290
- return currentFrame instanceof FrameElement && newFrame instanceof Element && newFrame.nodeName === "TURBO-FRAME" && currentFrame.shouldReloadWithMorph && currentFrame.id === newFrame.id && (!newFrame.getAttribute("src") || urlsAreEqual(currentFrame.src, newFrame.getAttribute("src"))) && !currentFrame.closest("[data-turbo-permanent]");
2324
+ return currentFrame instanceof FrameElement && currentFrame.shouldReloadWithMorph && (!newFrame || areFramesCompatibleForRefreshing(currentFrame, newFrame)) && !currentFrame.closest("[data-turbo-permanent]");
2325
+ }
2326
+
2327
+ function areFramesCompatibleForRefreshing(currentFrame, newFrame) {
2328
+ return newFrame instanceof Element && newFrame.nodeName === "TURBO-FRAME" && currentFrame.id === newFrame.id && (!newFrame.getAttribute("src") || urlsAreEqual(currentFrame.src, newFrame.getAttribute("src")));
2291
2329
  }
2292
2330
 
2293
2331
  function closestFrameReloadableWithMorphing(node) {
@@ -2588,11 +2626,17 @@ class PageSnapshot extends Snapshot {
2588
2626
  for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
2589
2627
  clonedPasswordInput.value = "";
2590
2628
  }
2629
+ for (const clonedNoscriptElement of clonedElement.querySelectorAll("noscript")) {
2630
+ clonedNoscriptElement.remove();
2631
+ }
2591
2632
  return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot);
2592
2633
  }
2593
2634
  get lang() {
2594
2635
  return this.documentElement.getAttribute("lang");
2595
2636
  }
2637
+ get dir() {
2638
+ return this.documentElement.getAttribute("dir");
2639
+ }
2596
2640
  get headElement() {
2597
2641
  return this.headSnapshot.element;
2598
2642
  }
@@ -2616,11 +2660,11 @@ class PageSnapshot extends Snapshot {
2616
2660
  const viewTransitionEnabled = this.getSetting("view-transition") === "true" || this.headSnapshot.getMetaValue("view-transition") === "same-origin";
2617
2661
  return viewTransitionEnabled && !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
2618
2662
  }
2619
- get shouldMorphPage() {
2620
- return this.getSetting("refresh-method") === "morph";
2663
+ get refreshMethod() {
2664
+ return this.getSetting("refresh-method");
2621
2665
  }
2622
- get shouldPreserveScrollPosition() {
2623
- return this.getSetting("refresh-scroll") === "preserve";
2666
+ get refreshScroll() {
2667
+ return this.getSetting("refresh-scroll");
2624
2668
  }
2625
2669
  getSetting(name) {
2626
2670
  return this.headSnapshot.getMetaValue(`turbo-${name}`);
@@ -2653,7 +2697,8 @@ const defaultOptions = {
2653
2697
  willRender: true,
2654
2698
  updateHistory: true,
2655
2699
  shouldCacheSnapshot: true,
2656
- acceptsStreamResponse: false
2700
+ acceptsStreamResponse: false,
2701
+ refresh: {}
2657
2702
  };
2658
2703
 
2659
2704
  const TimingMetric = {
@@ -2698,7 +2743,7 @@ class Visit {
2698
2743
  this.delegate = delegate;
2699
2744
  this.location = location;
2700
2745
  this.restorationIdentifier = restorationIdentifier || uuid();
2701
- const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse, direction: direction} = {
2746
+ const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse, direction: direction, refresh: refresh} = {
2702
2747
  ...defaultOptions,
2703
2748
  ...options
2704
2749
  };
@@ -2708,7 +2753,6 @@ class Visit {
2708
2753
  this.snapshot = snapshot;
2709
2754
  this.snapshotHTML = snapshotHTML;
2710
2755
  this.response = response;
2711
- this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
2712
2756
  this.isPageRefresh = this.view.isPageRefresh(this);
2713
2757
  this.visitCachedSnapshot = visitCachedSnapshot;
2714
2758
  this.willRender = willRender;
@@ -2717,6 +2761,7 @@ class Visit {
2717
2761
  this.shouldCacheSnapshot = shouldCacheSnapshot;
2718
2762
  this.acceptsStreamResponse = acceptsStreamResponse;
2719
2763
  this.direction = direction || Direction[action];
2764
+ this.refresh = refresh;
2720
2765
  }
2721
2766
  get adapter() {
2722
2767
  return this.delegate.adapter;
@@ -2730,9 +2775,6 @@ class Visit {
2730
2775
  get restorationData() {
2731
2776
  return this.history.getRestorationDataForIdentifier(this.restorationIdentifier);
2732
2777
  }
2733
- get silent() {
2734
- return this.isSamePage;
2735
- }
2736
2778
  start() {
2737
2779
  if (this.state == VisitState.initialized) {
2738
2780
  this.recordTimingMetric(TimingMetric.visitStart);
@@ -2851,7 +2893,7 @@ class Visit {
2851
2893
  const isPreview = this.shouldIssueRequest();
2852
2894
  this.render((async () => {
2853
2895
  this.cacheSnapshot();
2854
- if (this.isSamePage || this.isPageRefresh) {
2896
+ if (this.isPageRefresh) {
2855
2897
  this.adapter.visitRendered(this);
2856
2898
  } else {
2857
2899
  if (this.view.renderPromise) await this.view.renderPromise;
@@ -2875,16 +2917,6 @@ class Visit {
2875
2917
  this.followedRedirect = true;
2876
2918
  }
2877
2919
  }
2878
- goToSamePageAnchor() {
2879
- if (this.isSamePage) {
2880
- this.render((async () => {
2881
- this.cacheSnapshot();
2882
- this.performScroll();
2883
- this.changeHistory();
2884
- this.adapter.visitRendered(this);
2885
- }));
2886
- }
2887
- }
2888
2920
  prepareRequest(request) {
2889
2921
  if (this.acceptsStreamResponse) {
2890
2922
  request.acceptResponseType(StreamMessage.contentType);
@@ -2943,9 +2975,6 @@ class Visit {
2943
2975
  } else {
2944
2976
  this.scrollToAnchor() || this.view.scrollToTop();
2945
2977
  }
2946
- if (this.isSamePage) {
2947
- this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);
2948
- }
2949
2978
  this.scrolled = true;
2950
2979
  }
2951
2980
  }
@@ -2975,9 +3004,7 @@ class Visit {
2975
3004
  return typeof this.response == "object";
2976
3005
  }
2977
3006
  shouldIssueRequest() {
2978
- if (this.isSamePage) {
2979
- return false;
2980
- } else if (this.action == "restore") {
3007
+ if (this.action == "restore") {
2981
3008
  return !this.hasCachedSnapshot();
2982
3009
  } else {
2983
3010
  return this.willRender;
@@ -3032,7 +3059,6 @@ class BrowserAdapter {
3032
3059
  this.redirectedToLocation = null;
3033
3060
  visit.loadCachedSnapshot();
3034
3061
  visit.issueRequest();
3035
- visit.goToSamePageAnchor();
3036
3062
  }
3037
3063
  visitRequestStarted(visit) {
3038
3064
  this.progressBar.setValue(0);
@@ -3126,7 +3152,6 @@ class BrowserAdapter {
3126
3152
 
3127
3153
  class CacheObserver {
3128
3154
  selector="[data-turbo-temporary]";
3129
- deprecatedSelector="[data-turbo-cache=false]";
3130
3155
  started=false;
3131
3156
  start() {
3132
3157
  if (!this.started) {
@@ -3146,14 +3171,7 @@ class CacheObserver {
3146
3171
  }
3147
3172
  };
3148
3173
  get temporaryElements() {
3149
- return [ ...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation ];
3150
- }
3151
- get temporaryElementsWithDeprecation() {
3152
- const elements = document.querySelectorAll(this.deprecatedSelector);
3153
- if (elements.length) {
3154
- console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);
3155
- }
3156
- return [ ...elements ];
3174
+ return [ ...document.querySelectorAll(this.selector) ];
3157
3175
  }
3158
3176
  }
3159
3177
 
@@ -3221,7 +3239,6 @@ class History {
3221
3239
  restorationIdentifier=uuid();
3222
3240
  restorationData={};
3223
3241
  started=false;
3224
- pageLoaded=false;
3225
3242
  currentIndex=0;
3226
3243
  constructor(delegate) {
3227
3244
  this.delegate = delegate;
@@ -3229,7 +3246,6 @@ class History {
3229
3246
  start() {
3230
3247
  if (!this.started) {
3231
3248
  addEventListener("popstate", this.onPopState, false);
3232
- addEventListener("load", this.onPageLoad, false);
3233
3249
  this.currentIndex = history.state?.turbo?.restorationIndex || 0;
3234
3250
  this.started = true;
3235
3251
  this.replace(new URL(window.location.href));
@@ -3238,7 +3254,6 @@ class History {
3238
3254
  stop() {
3239
3255
  if (this.started) {
3240
3256
  removeEventListener("popstate", this.onPopState, false);
3241
- removeEventListener("load", this.onPageLoad, false);
3242
3257
  this.started = false;
3243
3258
  }
3244
3259
  }
@@ -3284,28 +3299,19 @@ class History {
3284
3299
  }
3285
3300
  }
3286
3301
  onPopState=event => {
3287
- if (this.shouldHandlePopState()) {
3288
- const {turbo: turbo} = event.state || {};
3289
- if (turbo) {
3290
- this.location = new URL(window.location.href);
3291
- const {restorationIdentifier: restorationIdentifier, restorationIndex: restorationIndex} = turbo;
3292
- this.restorationIdentifier = restorationIdentifier;
3293
- const direction = restorationIndex > this.currentIndex ? "forward" : "back";
3294
- this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
3295
- this.currentIndex = restorationIndex;
3296
- }
3302
+ const {turbo: turbo} = event.state || {};
3303
+ this.location = new URL(window.location.href);
3304
+ if (turbo) {
3305
+ const {restorationIdentifier: restorationIdentifier, restorationIndex: restorationIndex} = turbo;
3306
+ this.restorationIdentifier = restorationIdentifier;
3307
+ const direction = restorationIndex > this.currentIndex ? "forward" : "back";
3308
+ this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
3309
+ this.currentIndex = restorationIndex;
3310
+ } else {
3311
+ this.currentIndex++;
3312
+ this.delegate.historyPoppedWithEmptyState(this.location);
3297
3313
  }
3298
3314
  };
3299
- onPageLoad=async _event => {
3300
- await nextMicrotask();
3301
- this.pageLoaded = true;
3302
- };
3303
- shouldHandlePopState() {
3304
- return this.pageIsLoaded();
3305
- }
3306
- pageIsLoaded() {
3307
- return this.pageLoaded || document.readyState == "complete";
3308
- }
3309
3315
  }
3310
3316
 
3311
3317
  class LinkPrefetchObserver {
@@ -3361,7 +3367,7 @@ class LinkPrefetchObserver {
3361
3367
  this.#prefetchedLink = link;
3362
3368
  const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, target);
3363
3369
  fetchRequest.fetchOptions.priority = "low";
3364
- prefetchCache.setLater(location.toString(), fetchRequest, this.#cacheTtl);
3370
+ prefetchCache.putLater(location, fetchRequest, this.#cacheTtl);
3365
3371
  }
3366
3372
  }
3367
3373
  };
@@ -3374,7 +3380,7 @@ class LinkPrefetchObserver {
3374
3380
  };
3375
3381
  #tryToUsePrefetchedRequest=event => {
3376
3382
  if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
3377
- const cached = prefetchCache.get(event.detail.url.toString());
3383
+ const cached = prefetchCache.get(event.detail.url);
3378
3384
  if (cached) {
3379
3385
  event.detail.fetchRequest = cached;
3380
3386
  }
@@ -3523,7 +3529,7 @@ class Navigator {
3523
3529
  } else {
3524
3530
  await this.view.renderPage(snapshot, false, true, this.currentVisit);
3525
3531
  }
3526
- if (!snapshot.shouldPreserveScrollPosition) {
3532
+ if (snapshot.refreshScroll !== "preserve") {
3527
3533
  this.view.scrollToTop();
3528
3534
  }
3529
3535
  this.view.clearSnapshotCache();
@@ -3551,13 +3557,7 @@ class Navigator {
3551
3557
  delete this.currentVisit;
3552
3558
  }
3553
3559
  locationWithActionIsSamePage(location, action) {
3554
- const anchor = getAnchor(location);
3555
- const currentAnchor = getAnchor(this.view.lastRenderedLocation);
3556
- const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
3557
- return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
3558
- }
3559
- visitScrolledToSamePageLocation(oldURL, newURL) {
3560
- this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
3560
+ return false;
3561
3561
  }
3562
3562
  get location() {
3563
3563
  return this.history.location;
@@ -3888,12 +3888,17 @@ class PageRenderer extends Renderer {
3888
3888
  }
3889
3889
  #setLanguage() {
3890
3890
  const {documentElement: documentElement} = this.currentSnapshot;
3891
- const {lang: lang} = this.newSnapshot;
3891
+ const {dir: dir, lang: lang} = this.newSnapshot;
3892
3892
  if (lang) {
3893
3893
  documentElement.setAttribute("lang", lang);
3894
3894
  } else {
3895
3895
  documentElement.removeAttribute("lang");
3896
3896
  }
3897
+ if (dir) {
3898
+ documentElement.setAttribute("dir", dir);
3899
+ } else {
3900
+ documentElement.removeAttribute("dir");
3901
+ }
3897
3902
  }
3898
3903
  async mergeHead() {
3899
3904
  const mergedHeadElements = this.mergeProvisionalElements();
@@ -3973,8 +3978,14 @@ class PageRenderer extends Renderer {
3973
3978
  }
3974
3979
  activateNewBody() {
3975
3980
  document.adoptNode(this.newElement);
3981
+ this.removeNoscriptElements();
3976
3982
  this.activateNewBodyScriptElements();
3977
3983
  }
3984
+ removeNoscriptElements() {
3985
+ for (const noscriptElement of this.newElement.querySelectorAll("noscript")) {
3986
+ noscriptElement.remove();
3987
+ }
3988
+ }
3978
3989
  activateNewBodyScriptElements() {
3979
3990
  for (const inertScriptElement of this.newBodyScriptElements) {
3980
3991
  const activatedScriptElement = activateScriptElement(inertScriptElement);
@@ -4038,47 +4049,12 @@ class MorphingPageRenderer extends PageRenderer {
4038
4049
  }
4039
4050
  }
4040
4051
 
4041
- class SnapshotCache {
4042
- keys=[];
4043
- snapshots={};
4052
+ class SnapshotCache extends LRUCache {
4044
4053
  constructor(size) {
4045
- this.size = size;
4046
- }
4047
- has(location) {
4048
- return toCacheKey(location) in this.snapshots;
4049
- }
4050
- get(location) {
4051
- if (this.has(location)) {
4052
- const snapshot = this.read(location);
4053
- this.touch(location);
4054
- return snapshot;
4055
- }
4056
- }
4057
- put(location, snapshot) {
4058
- this.write(location, snapshot);
4059
- this.touch(location);
4060
- return snapshot;
4061
- }
4062
- clear() {
4063
- this.snapshots = {};
4064
- }
4065
- read(location) {
4066
- return this.snapshots[toCacheKey(location)];
4054
+ super(size, toCacheKey);
4067
4055
  }
4068
- write(location, snapshot) {
4069
- this.snapshots[toCacheKey(location)] = snapshot;
4070
- }
4071
- touch(location) {
4072
- const key = toCacheKey(location);
4073
- const index = this.keys.indexOf(key);
4074
- if (index > -1) this.keys.splice(index, 1);
4075
- this.keys.unshift(key);
4076
- this.trim();
4077
- }
4078
- trim() {
4079
- for (const key of this.keys.splice(this.size)) {
4080
- delete this.snapshots[key];
4081
- }
4056
+ get snapshots() {
4057
+ return this.entries;
4082
4058
  }
4083
4059
  }
4084
4060
 
@@ -4090,7 +4066,7 @@ class PageView extends View {
4090
4066
  return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions;
4091
4067
  }
4092
4068
  renderPage(snapshot, isPreview = false, willRender = true, visit) {
4093
- const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
4069
+ const shouldMorphPage = this.isPageRefresh(visit) && (visit?.refresh?.method || this.snapshot.refreshMethod) === "morph";
4094
4070
  const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
4095
4071
  const renderer = new rendererClass(this.snapshot, snapshot, isPreview, willRender);
4096
4072
  if (!renderer.shouldRender) {
@@ -4125,7 +4101,7 @@ class PageView extends View {
4125
4101
  return !visit || this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === "replace";
4126
4102
  }
4127
4103
  shouldPreserveScrollPosition(visit) {
4128
- return this.isPageRefresh(visit) && this.snapshot.shouldPreserveScrollPosition;
4104
+ return this.isPageRefresh(visit) && (visit?.refresh?.scroll || this.snapshot.refreshScroll) === "preserve";
4129
4105
  }
4130
4106
  get snapshot() {
4131
4107
  return PageSnapshot.fromElement(this.element);
@@ -4278,13 +4254,21 @@ class Session {
4278
4254
  this.navigator.proposeVisit(expandURL(location), options);
4279
4255
  }
4280
4256
  }
4281
- refresh(url, requestId) {
4257
+ refresh(url, options = {}) {
4258
+ options = typeof options === "string" ? {
4259
+ requestId: options
4260
+ } : options;
4261
+ const {method: method, requestId: requestId, scroll: scroll} = options;
4282
4262
  const isRecentRequest = requestId && this.recentRequests.has(requestId);
4283
4263
  const isCurrentUrl = url === document.baseURI;
4284
4264
  if (!isRecentRequest && !this.navigator.currentVisit && isCurrentUrl) {
4285
4265
  this.visit(url, {
4286
4266
  action: "replace",
4287
- shouldCacheSnapshot: false
4267
+ shouldCacheSnapshot: false,
4268
+ refresh: {
4269
+ method: method,
4270
+ scroll: scroll
4271
+ }
4288
4272
  });
4289
4273
  }
4290
4274
  }
@@ -4360,6 +4344,11 @@ class Session {
4360
4344
  });
4361
4345
  }
4362
4346
  }
4347
+ historyPoppedWithEmptyState(location) {
4348
+ this.history.replace(location);
4349
+ this.view.lastRenderedLocation = location;
4350
+ this.view.cacheSnapshot();
4351
+ }
4363
4352
  scrollPositionChanged(position) {
4364
4353
  this.history.updateRestorationData({
4365
4354
  scrollPosition: position
@@ -4384,7 +4373,7 @@ class Session {
4384
4373
  });
4385
4374
  }
4386
4375
  allowsVisitingLocationWithAction(location, action) {
4387
- return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
4376
+ return this.applicationAllowsVisitingLocation(location);
4388
4377
  }
4389
4378
  visitProposedToLocation(location, options) {
4390
4379
  extendURLWithDeprecatedProperties(location);
@@ -4396,21 +4385,13 @@ class Session {
4396
4385
  this.view.markVisitDirection(visit.direction);
4397
4386
  }
4398
4387
  extendURLWithDeprecatedProperties(visit.location);
4399
- if (!visit.silent) {
4400
- this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
4401
- }
4388
+ this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
4402
4389
  }
4403
4390
  visitCompleted(visit) {
4404
4391
  this.view.unmarkVisitDirection();
4405
4392
  clearBusyState(document.documentElement);
4406
4393
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
4407
4394
  }
4408
- locationWithActionIsSamePage(location, action) {
4409
- return this.navigator.locationWithActionIsSamePage(location, action);
4410
- }
4411
- visitScrolledToSamePageLocation(oldURL, newURL) {
4412
- this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
4413
- }
4414
4395
  willSubmitForm(form, submitter) {
4415
4396
  const action = getAction$1(form, submitter);
4416
4397
  return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
@@ -4432,9 +4413,7 @@ class Session {
4432
4413
  this.renderStreamMessage(message);
4433
4414
  }
4434
4415
  viewWillCacheSnapshot() {
4435
- if (!this.navigator.currentVisit?.silent) {
4436
- this.notifyApplicationBeforeCachingSnapshot();
4437
- }
4416
+ this.notifyApplicationBeforeCachingSnapshot();
4438
4417
  }
4439
4418
  allowsImmediateRender({element: element}, options) {
4440
4419
  const event = this.notifyApplicationBeforeRender(element, options);
@@ -4521,12 +4500,6 @@ class Session {
4521
4500
  }
4522
4501
  });
4523
4502
  }
4524
- notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
4525
- dispatchEvent(new HashChangeEvent("hashchange", {
4526
- oldURL: oldURL.toString(),
4527
- newURL: newURL.toString()
4528
- }));
4529
- }
4530
4503
  notifyApplicationAfterFrameLoad(frame) {
4531
4504
  return dispatch("turbo:frame-load", {
4532
4505
  target: frame
@@ -4592,7 +4565,7 @@ const deprecatedLocationPropertyDescriptors = {
4592
4565
 
4593
4566
  const session = new Session(recentRequests);
4594
4567
 
4595
- const {cache: cache, navigator: navigator$1} = session;
4568
+ const {cache: cache, navigator: navigator} = session;
4596
4569
 
4597
4570
  function start() {
4598
4571
  session.start();
@@ -4618,11 +4591,6 @@ function renderStreamMessage(message) {
4618
4591
  session.renderStreamMessage(message);
4619
4592
  }
4620
4593
 
4621
- function clearCache() {
4622
- console.warn("Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
4623
- session.clearCache();
4624
- }
4625
-
4626
4594
  function setProgressBarDelay(delay) {
4627
4595
  console.warn("Please replace `Turbo.setProgressBarDelay(delay)` with `Turbo.config.drive.progressBarDelay = delay`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
4628
4596
  config.drive.progressBarDelay = delay;
@@ -4648,7 +4616,7 @@ function morphTurboFrameElements(currentFrame, newFrame) {
4648
4616
 
4649
4617
  var Turbo = Object.freeze({
4650
4618
  __proto__: null,
4651
- navigator: navigator$1,
4619
+ navigator: navigator,
4652
4620
  session: session,
4653
4621
  cache: cache,
4654
4622
  PageRenderer: PageRenderer,
@@ -4662,7 +4630,6 @@ var Turbo = Object.freeze({
4662
4630
  connectStreamSource: connectStreamSource,
4663
4631
  disconnectStreamSource: disconnectStreamSource,
4664
4632
  renderStreamMessage: renderStreamMessage,
4665
- clearCache: clearCache,
4666
4633
  setProgressBarDelay: setProgressBarDelay,
4667
4634
  setConfirmMethod: setConfirmMethod,
4668
4635
  setFormMode: setFormMode,
@@ -4712,15 +4679,23 @@ class FrameController {
4712
4679
  this.formLinkClickObserver.stop();
4713
4680
  this.linkInterceptor.stop();
4714
4681
  this.formSubmitObserver.stop();
4682
+ if (!this.element.hasAttribute("recurse")) {
4683
+ this.#currentFetchRequest?.cancel();
4684
+ }
4715
4685
  }
4716
4686
  }
4717
4687
  disabledChanged() {
4718
- if (this.loadingStyle == FrameLoadingStyle.eager) {
4688
+ if (this.disabled) {
4689
+ this.#currentFetchRequest?.cancel();
4690
+ } else if (this.loadingStyle == FrameLoadingStyle.eager) {
4719
4691
  this.#loadSourceURL();
4720
4692
  }
4721
4693
  }
4722
4694
  sourceURLChanged() {
4723
4695
  if (this.#isIgnoringChangesTo("src")) return;
4696
+ if (!this.sourceURL) {
4697
+ this.#currentFetchRequest?.cancel();
4698
+ }
4724
4699
  if (this.element.isConnected) {
4725
4700
  this.complete = false;
4726
4701
  }
@@ -4798,11 +4773,12 @@ class FrameController {
4798
4773
  }
4799
4774
  this.formSubmission = new FormSubmission(this, element, submitter);
4800
4775
  const {fetchRequest: fetchRequest} = this.formSubmission;
4801
- this.prepareRequest(fetchRequest);
4776
+ const frame = this.#findFrameElement(element, submitter);
4777
+ this.prepareRequest(fetchRequest, frame);
4802
4778
  this.formSubmission.start();
4803
4779
  }
4804
- prepareRequest(request) {
4805
- request.headers["Turbo-Frame"] = this.id;
4780
+ prepareRequest(request, frame = this) {
4781
+ request.headers["Turbo-Frame"] = frame.id;
4806
4782
  if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
4807
4783
  request.acceptResponseType(StreamMessage.contentType);
4808
4784
  }
@@ -4996,7 +4972,8 @@ class FrameController {
4996
4972
  }
4997
4973
  #findFrameElement(element, submitter) {
4998
4974
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
4999
- return getFrameElementById(id) ?? this.element;
4975
+ const target = this.#getFrameElementById(id);
4976
+ return target instanceof FrameElement ? target : this.element;
5000
4977
  }
5001
4978
  async extractForeignFrameElement(container) {
5002
4979
  let element;
@@ -5030,9 +5007,11 @@ class FrameController {
5030
5007
  return false;
5031
5008
  }
5032
5009
  if (id) {
5033
- const frameElement = getFrameElementById(id);
5010
+ const frameElement = this.#getFrameElementById(id);
5034
5011
  if (frameElement) {
5035
5012
  return !frameElement.disabled;
5013
+ } else if (id == "_parent") {
5014
+ return false;
5036
5015
  }
5037
5016
  }
5038
5017
  if (!session.elementIsNavigatable(element)) {
@@ -5046,8 +5025,11 @@ class FrameController {
5046
5025
  get id() {
5047
5026
  return this.element.id;
5048
5027
  }
5028
+ get disabled() {
5029
+ return this.element.disabled;
5030
+ }
5049
5031
  get enabled() {
5050
- return !this.element.disabled;
5032
+ return !this.disabled;
5051
5033
  }
5052
5034
  get sourceURL() {
5053
5035
  if (this.element.src) {
@@ -5096,13 +5078,12 @@ class FrameController {
5096
5078
  callback();
5097
5079
  delete this.currentNavigationElement;
5098
5080
  }
5099
- }
5100
-
5101
- function getFrameElementById(id) {
5102
- if (id != null) {
5103
- const element = document.getElementById(id);
5104
- if (element instanceof FrameElement) {
5105
- return element;
5081
+ #getFrameElementById(id) {
5082
+ if (id != null) {
5083
+ const element = id === "_parent" ? this.element.parentElement.closest("turbo-frame") : document.getElementById(id);
5084
+ if (element instanceof FrameElement) {
5085
+ return element;
5086
+ }
5106
5087
  }
5107
5088
  }
5108
5089
  }
@@ -5126,6 +5107,7 @@ function activateElement(element, currentURL) {
5126
5107
 
5127
5108
  const StreamActions = {
5128
5109
  after() {
5110
+ this.removeDuplicateTargetSiblings();
5129
5111
  this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e.nextSibling)));
5130
5112
  },
5131
5113
  append() {
@@ -5133,6 +5115,7 @@ const StreamActions = {
5133
5115
  this.targetElements.forEach((e => e.append(this.templateContent)));
5134
5116
  },
5135
5117
  before() {
5118
+ this.removeDuplicateTargetSiblings();
5136
5119
  this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e)));
5137
5120
  },
5138
5121
  prepend() {
@@ -5164,7 +5147,14 @@ const StreamActions = {
5164
5147
  }));
5165
5148
  },
5166
5149
  refresh() {
5167
- session.refresh(this.baseURI, this.requestId);
5150
+ const method = this.getAttribute("method");
5151
+ const requestId = this.requestId;
5152
+ const scroll = this.getAttribute("scroll");
5153
+ session.refresh(this.baseURI, {
5154
+ method: method,
5155
+ requestId: requestId,
5156
+ scroll: scroll
5157
+ });
5168
5158
  }
5169
5159
  };
5170
5160
 
@@ -5203,6 +5193,14 @@ class StreamElement extends HTMLElement {
5203
5193
  const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.getAttribute("id"))).map((c => c.getAttribute("id")));
5204
5194
  return existingChildren.filter((c => newChildrenIds.includes(c.getAttribute("id"))));
5205
5195
  }
5196
+ removeDuplicateTargetSiblings() {
5197
+ this.duplicateSiblings.forEach((c => c.remove()));
5198
+ }
5199
+ get duplicateSiblings() {
5200
+ const existingChildren = this.targetElements.flatMap((e => [ ...e.parentElement.children ])).filter((c => !!c.id));
5201
+ const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
5202
+ return existingChildren.filter((c => newChildrenIds.includes(c.id)));
5203
+ }
5206
5204
  get performAction() {
5207
5205
  if (this.action) {
5208
5206
  const actionFunction = StreamActions[this.action];
@@ -5313,10 +5311,10 @@ if (customElements.get("turbo-stream-source") === undefined) {
5313
5311
  }
5314
5312
 
5315
5313
  (() => {
5316
- let element = document.currentScript;
5317
- if (!element) return;
5318
- if (element.hasAttribute("data-turbo-suppress-warning")) return;
5319
- element = element.parentElement;
5314
+ const scriptElement = document.currentScript;
5315
+ if (!scriptElement) return;
5316
+ if (scriptElement.hasAttribute("data-turbo-suppress-warning")) return;
5317
+ let element = scriptElement.parentElement;
5320
5318
  while (element) {
5321
5319
  if (element == document.body) {
5322
5320
  return console.warn(unindent`
@@ -5328,7 +5326,7 @@ if (customElements.get("turbo-stream-source") === undefined) {
5328
5326
 
5329
5327
  ——
5330
5328
  Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
5331
- `, element.outerHTML);
5329
+ `, scriptElement.outerHTML);
5332
5330
  }
5333
5331
  element = element.parentElement;
5334
5332
  }
@@ -5356,7 +5354,6 @@ var Turbo$1 = Object.freeze({
5356
5354
  StreamElement: StreamElement,
5357
5355
  StreamSourceElement: StreamSourceElement,
5358
5356
  cache: cache,
5359
- clearCache: clearCache,
5360
5357
  config: config,
5361
5358
  connectStreamSource: connectStreamSource,
5362
5359
  disconnectStreamSource: disconnectStreamSource,
@@ -5368,7 +5365,7 @@ var Turbo$1 = Object.freeze({
5368
5365
  morphChildren: morphChildren,
5369
5366
  morphElements: morphElements,
5370
5367
  morphTurboFrameElements: morphTurboFrameElements,
5371
- navigator: navigator$1,
5368
+ navigator: navigator,
5372
5369
  registerAdapter: registerAdapter,
5373
5370
  renderStreamMessage: renderStreamMessage,
5374
5371
  session: session,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotwired/turbo-rails",
3
- "version": "8.0.19",
3
+ "version": "8.0.21",
4
4
  "description": "The speed of a single-page web application without having to write any JavaScript",
5
5
  "module": "app/javascript/turbo/index.js",
6
6
  "main": "app/assets/javascripts/turbo.js",
@@ -19,7 +19,7 @@
19
19
  "release": "yarn publish && git commit -am \"$npm_package_name v$npm_package_version\" && git push"
20
20
  },
21
21
  "dependencies": {
22
- "@hotwired/turbo": "^8.0.19",
22
+ "@hotwired/turbo": "^8.0.21",
23
23
  "@rails/actioncable": ">=7.0"
24
24
  },
25
25
  "devDependencies": {