@hotwired/turbo 8.0.13 → 8.0.14

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # Changelog
2
+
3
+ Please see [our GitHub "Releases" page](https://github.com/hotwired/turbo/releases).
@@ -555,7 +555,13 @@ function doesNotTargetIFrame(name) {
555
555
  }
556
556
 
557
557
  function findLinkFromClickTarget(target) {
558
- return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])")
558
+ const link = findClosestRecursively(target, "a[href], a[xlink\\:href]");
559
+
560
+ if (!link) return null
561
+ if (link.hasAttribute("download")) return null
562
+ if (link.hasAttribute("target") && link.target !== "_self") return null
563
+
564
+ return link
559
565
  }
560
566
 
561
567
  function getLocationForLink(link) {
@@ -642,8 +648,8 @@ function getExtension(url) {
642
648
  }
643
649
 
644
650
  function isPrefixedBy(baseURL, url) {
645
- const prefix = getPrefix(url);
646
- return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix)
651
+ const prefix = addTrailingSlash(url.origin + url.pathname);
652
+ return addTrailingSlash(baseURL.href) === prefix || baseURL.href.startsWith(prefix)
647
653
  }
648
654
 
649
655
  function locationIsVisitable(location, rootLocation) {
@@ -671,10 +677,6 @@ function getLastPathComponent(url) {
671
677
  return getPathComponents(url).slice(-1)[0]
672
678
  }
673
679
 
674
- function getPrefix(url) {
675
- return addTrailingSlash(url.origin + url.pathname)
676
- }
677
-
678
680
  function addTrailingSlash(value) {
679
681
  return value.endsWith("/") ? value : value + "/"
680
682
  }
@@ -755,15 +757,13 @@ class LimitedSet extends Set {
755
757
 
756
758
  const recentRequests = new LimitedSet(20);
757
759
 
758
- const nativeFetch = window.fetch;
759
-
760
760
  function fetchWithTurboHeaders(url, options = {}) {
761
761
  const modifiedHeaders = new Headers(options.headers || {});
762
762
  const requestUID = uuid();
763
763
  recentRequests.add(requestUID);
764
764
  modifiedHeaders.append("X-Turbo-Request-Id", requestUID);
765
765
 
766
- return nativeFetch(url, {
766
+ return window.fetch(url, {
767
767
  ...options,
768
768
  headers: modifiedHeaders
769
769
  })
@@ -1499,8 +1499,8 @@ class View {
1499
1499
  scrollToAnchor(anchor) {
1500
1500
  const element = this.snapshot.getElementForAnchor(anchor);
1501
1501
  if (element) {
1502
- this.scrollToElement(element);
1503
1502
  this.focusElement(element);
1503
+ this.scrollToElement(element);
1504
1504
  } else {
1505
1505
  this.scrollToPosition({ x: 0, y: 0 });
1506
1506
  }
@@ -3332,6 +3332,14 @@ var Idiomorph = (function () {
3332
3332
  };
3333
3333
  })();
3334
3334
 
3335
+ /**
3336
+ * Morph the state of the currentElement based on the attributes and contents of
3337
+ * the newElement. Morphing may dispatch turbo:before-morph-element,
3338
+ * turbo:before-morph-attribute, and turbo:morph-element events.
3339
+ *
3340
+ * @param currentElement Element destination of morphing changes
3341
+ * @param newElement Element source of morphing changes
3342
+ */
3335
3343
  function morphElements(currentElement, newElement, { callbacks, ...options } = {}) {
3336
3344
  Idiomorph.morph(currentElement, newElement, {
3337
3345
  ...options,
@@ -3339,12 +3347,37 @@ function morphElements(currentElement, newElement, { callbacks, ...options } = {
3339
3347
  });
3340
3348
  }
3341
3349
 
3342
- function morphChildren(currentElement, newElement) {
3350
+ /**
3351
+ * Morph the child elements of the currentElement based on the child elements of
3352
+ * the newElement. Morphing children may dispatch turbo:before-morph-element,
3353
+ * turbo:before-morph-attribute, and turbo:morph-element events.
3354
+ *
3355
+ * @param currentElement Element destination of morphing children changes
3356
+ * @param newElement Element source of morphing children changes
3357
+ */
3358
+ function morphChildren(currentElement, newElement, options = {}) {
3343
3359
  morphElements(currentElement, newElement.childNodes, {
3360
+ ...options,
3344
3361
  morphStyle: "innerHTML"
3345
3362
  });
3346
3363
  }
3347
3364
 
3365
+ function shouldRefreshFrameWithMorphing(currentFrame, newFrame) {
3366
+ return currentFrame instanceof FrameElement &&
3367
+ // newFrame cannot yet be an instance of FrameElement because custom
3368
+ // elements don't get initialized until they're attached to the DOM, so
3369
+ // test its Element#nodeName instead
3370
+ newFrame instanceof Element && newFrame.nodeName === "TURBO-FRAME" &&
3371
+ currentFrame.shouldReloadWithMorph &&
3372
+ currentFrame.id === newFrame.id &&
3373
+ (!newFrame.getAttribute("src") || urlsAreEqual(currentFrame.src, newFrame.getAttribute("src"))) &&
3374
+ !currentFrame.closest("[data-turbo-permanent]")
3375
+ }
3376
+
3377
+ function closestFrameReloadableWithMorphing(node) {
3378
+ return node.parentElement.closest("turbo-frame[src][refresh=morph]")
3379
+ }
3380
+
3348
3381
  class DefaultIdiomorphCallbacks {
3349
3382
  #beforeNodeMorphed
3350
3383
 
@@ -3403,7 +3436,20 @@ class MorphingFrameRenderer extends FrameRenderer {
3403
3436
  detail: { currentElement, newElement }
3404
3437
  });
3405
3438
 
3406
- morphChildren(currentElement, newElement);
3439
+ morphChildren(currentElement, newElement, {
3440
+ callbacks: {
3441
+ beforeNodeMorphed: (node, newNode) => {
3442
+ if (
3443
+ shouldRefreshFrameWithMorphing(node, newNode) &&
3444
+ closestFrameReloadableWithMorphing(node) === currentElement
3445
+ ) {
3446
+ node.reload();
3447
+ return false
3448
+ }
3449
+ return true
3450
+ }
3451
+ }
3452
+ });
3407
3453
  }
3408
3454
 
3409
3455
  async preservingPermanentElements(callback) {
@@ -3712,7 +3758,8 @@ class PageSnapshot extends Snapshot {
3712
3758
  }
3713
3759
 
3714
3760
  get prefersViewTransitions() {
3715
- return this.headSnapshot.getMetaValue("view-transition") === "same-origin"
3761
+ const viewTransitionEnabled = this.getSetting("view-transition") === "true" || this.headSnapshot.getMetaValue("view-transition") === "same-origin";
3762
+ return viewTransitionEnabled && !window.matchMedia("(prefers-reduced-motion: reduce)").matches
3716
3763
  }
3717
3764
 
3718
3765
  get shouldMorphPage() {
@@ -4200,6 +4247,8 @@ class BrowserAdapter {
4200
4247
 
4201
4248
  visitStarted(visit) {
4202
4249
  this.location = visit.location;
4250
+ this.redirectedToLocation = null;
4251
+
4203
4252
  visit.loadCachedSnapshot();
4204
4253
  visit.issueRequest();
4205
4254
  visit.goToSamePageAnchor();
@@ -4216,6 +4265,10 @@ class BrowserAdapter {
4216
4265
 
4217
4266
  visitRequestCompleted(visit) {
4218
4267
  visit.loadResponse();
4268
+
4269
+ if (visit.response.redirected) {
4270
+ this.redirectedToLocation = visit.redirectedToLocation;
4271
+ }
4219
4272
  }
4220
4273
 
4221
4274
  visitRequestFailedWithStatusCode(visit, statusCode) {
@@ -4305,7 +4358,7 @@ class BrowserAdapter {
4305
4358
  reload(reason) {
4306
4359
  dispatch("turbo:reload", { detail: reason });
4307
4360
 
4308
- window.location.href = this.location?.toString() || window.location.href;
4361
+ window.location.href = (this.redirectedToLocation || this.location)?.toString() || window.location.href;
4309
4362
  }
4310
4363
 
4311
4364
  get navigator() {
@@ -4618,6 +4671,8 @@ class LinkPrefetchObserver {
4618
4671
  target
4619
4672
  );
4620
4673
 
4674
+ fetchRequest.fetchOptions.priority = "low";
4675
+
4621
4676
  prefetchCache.setLater(location.toString(), fetchRequest, this.#cacheTtl);
4622
4677
  }
4623
4678
  }
@@ -5427,14 +5482,19 @@ class MorphingPageRenderer extends PageRenderer {
5427
5482
  static renderElement(currentElement, newElement) {
5428
5483
  morphElements(currentElement, newElement, {
5429
5484
  callbacks: {
5430
- beforeNodeMorphed: element => !canRefreshFrame(element)
5485
+ beforeNodeMorphed: (node, newNode) => {
5486
+ if (
5487
+ shouldRefreshFrameWithMorphing(node, newNode) &&
5488
+ !closestFrameReloadableWithMorphing(node)
5489
+ ) {
5490
+ node.reload();
5491
+ return false
5492
+ }
5493
+ return true
5494
+ }
5431
5495
  }
5432
5496
  });
5433
5497
 
5434
- for (const frame of currentElement.querySelectorAll("turbo-frame")) {
5435
- if (canRefreshFrame(frame)) frame.reload();
5436
- }
5437
-
5438
5498
  dispatch("turbo:morph", { detail: { currentElement, newElement } });
5439
5499
  }
5440
5500
 
@@ -5451,13 +5511,6 @@ class MorphingPageRenderer extends PageRenderer {
5451
5511
  }
5452
5512
  }
5453
5513
 
5454
- function canRefreshFrame(frame) {
5455
- return frame instanceof FrameElement &&
5456
- frame.src &&
5457
- frame.refresh === "morph" &&
5458
- !frame.closest("[data-turbo-permanent]")
5459
- }
5460
-
5461
5514
  class SnapshotCache {
5462
5515
  keys = []
5463
5516
  snapshots = {}
@@ -6281,6 +6334,32 @@ function setFormMode(mode) {
6281
6334
  config.forms.mode = mode;
6282
6335
  }
6283
6336
 
6337
+ /**
6338
+ * Morph the state of the currentBody based on the attributes and contents of
6339
+ * the newBody. Morphing body elements may dispatch turbo:morph,
6340
+ * turbo:before-morph-element, turbo:before-morph-attribute, and
6341
+ * turbo:morph-element events.
6342
+ *
6343
+ * @param currentBody HTMLBodyElement destination of morphing changes
6344
+ * @param newBody HTMLBodyElement source of morphing changes
6345
+ */
6346
+ function morphBodyElements(currentBody, newBody) {
6347
+ MorphingPageRenderer.renderElement(currentBody, newBody);
6348
+ }
6349
+
6350
+ /**
6351
+ * Morph the child elements of the currentFrame based on the child elements of
6352
+ * the newFrame. Morphing turbo-frame elements may dispatch turbo:before-frame-morph,
6353
+ * turbo:before-morph-element, turbo:before-morph-attribute, and
6354
+ * turbo:morph-element events.
6355
+ *
6356
+ * @param currentFrame FrameElement destination of morphing children changes
6357
+ * @param newFrame FrameElement source of morphing children changes
6358
+ */
6359
+ function morphTurboFrameElements(currentFrame, newFrame) {
6360
+ MorphingFrameRenderer.renderElement(currentFrame, newFrame);
6361
+ }
6362
+
6284
6363
  var Turbo = /*#__PURE__*/Object.freeze({
6285
6364
  __proto__: null,
6286
6365
  navigator: navigator$1,
@@ -6300,7 +6379,11 @@ var Turbo = /*#__PURE__*/Object.freeze({
6300
6379
  clearCache: clearCache,
6301
6380
  setProgressBarDelay: setProgressBarDelay,
6302
6381
  setConfirmMethod: setConfirmMethod,
6303
- setFormMode: setFormMode
6382
+ setFormMode: setFormMode,
6383
+ morphBodyElements: morphBodyElements,
6384
+ morphTurboFrameElements: morphTurboFrameElements,
6385
+ morphChildren: morphChildren,
6386
+ morphElements: morphElements
6304
6387
  });
6305
6388
 
6306
6389
  class TurboFrameMissingError extends Error {}
@@ -7175,4 +7258,4 @@ if (customElements.get("turbo-stream-source") === undefined) {
7175
7258
  window.Turbo = { ...Turbo, StreamActions };
7176
7259
  start();
7177
7260
 
7178
- export { FetchEnctype, FetchMethod, FetchRequest, FetchResponse, FrameElement, FrameLoadingStyle, FrameRenderer, PageRenderer, PageSnapshot, StreamActions, StreamElement, StreamSourceElement, cache, clearCache, config, connectStreamSource, disconnectStreamSource, fetchWithTurboHeaders as fetch, fetchEnctypeFromString, fetchMethodFromString, isSafe, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setFormMode, setProgressBarDelay, start, visit };
7261
+ export { FetchEnctype, FetchMethod, FetchRequest, FetchResponse, FrameElement, FrameLoadingStyle, FrameRenderer, PageRenderer, PageSnapshot, StreamActions, StreamElement, StreamSourceElement, cache, clearCache, config, connectStreamSource, disconnectStreamSource, fetchWithTurboHeaders as fetch, fetchEnctypeFromString, fetchMethodFromString, isSafe, morphBodyElements, morphChildren, morphElements, morphTurboFrameElements, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setFormMode, setProgressBarDelay, start, visit };
@@ -561,7 +561,13 @@ Copyright © 2025 37signals LLC
561
561
  }
562
562
 
563
563
  function findLinkFromClickTarget(target) {
564
- return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])")
564
+ const link = findClosestRecursively(target, "a[href], a[xlink\\:href]");
565
+
566
+ if (!link) return null
567
+ if (link.hasAttribute("download")) return null
568
+ if (link.hasAttribute("target") && link.target !== "_self") return null
569
+
570
+ return link
565
571
  }
566
572
 
567
573
  function getLocationForLink(link) {
@@ -648,8 +654,8 @@ Copyright © 2025 37signals LLC
648
654
  }
649
655
 
650
656
  function isPrefixedBy(baseURL, url) {
651
- const prefix = getPrefix(url);
652
- return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix)
657
+ const prefix = addTrailingSlash(url.origin + url.pathname);
658
+ return addTrailingSlash(baseURL.href) === prefix || baseURL.href.startsWith(prefix)
653
659
  }
654
660
 
655
661
  function locationIsVisitable(location, rootLocation) {
@@ -677,10 +683,6 @@ Copyright © 2025 37signals LLC
677
683
  return getPathComponents(url).slice(-1)[0]
678
684
  }
679
685
 
680
- function getPrefix(url) {
681
- return addTrailingSlash(url.origin + url.pathname)
682
- }
683
-
684
686
  function addTrailingSlash(value) {
685
687
  return value.endsWith("/") ? value : value + "/"
686
688
  }
@@ -761,15 +763,13 @@ Copyright © 2025 37signals LLC
761
763
 
762
764
  const recentRequests = new LimitedSet(20);
763
765
 
764
- const nativeFetch = window.fetch;
765
-
766
766
  function fetchWithTurboHeaders(url, options = {}) {
767
767
  const modifiedHeaders = new Headers(options.headers || {});
768
768
  const requestUID = uuid();
769
769
  recentRequests.add(requestUID);
770
770
  modifiedHeaders.append("X-Turbo-Request-Id", requestUID);
771
771
 
772
- return nativeFetch(url, {
772
+ return window.fetch(url, {
773
773
  ...options,
774
774
  headers: modifiedHeaders
775
775
  })
@@ -1505,8 +1505,8 @@ Copyright © 2025 37signals LLC
1505
1505
  scrollToAnchor(anchor) {
1506
1506
  const element = this.snapshot.getElementForAnchor(anchor);
1507
1507
  if (element) {
1508
- this.scrollToElement(element);
1509
1508
  this.focusElement(element);
1509
+ this.scrollToElement(element);
1510
1510
  } else {
1511
1511
  this.scrollToPosition({ x: 0, y: 0 });
1512
1512
  }
@@ -3338,6 +3338,14 @@ Copyright © 2025 37signals LLC
3338
3338
  };
3339
3339
  })();
3340
3340
 
3341
+ /**
3342
+ * Morph the state of the currentElement based on the attributes and contents of
3343
+ * the newElement. Morphing may dispatch turbo:before-morph-element,
3344
+ * turbo:before-morph-attribute, and turbo:morph-element events.
3345
+ *
3346
+ * @param currentElement Element destination of morphing changes
3347
+ * @param newElement Element source of morphing changes
3348
+ */
3341
3349
  function morphElements(currentElement, newElement, { callbacks, ...options } = {}) {
3342
3350
  Idiomorph.morph(currentElement, newElement, {
3343
3351
  ...options,
@@ -3345,12 +3353,37 @@ Copyright © 2025 37signals LLC
3345
3353
  });
3346
3354
  }
3347
3355
 
3348
- function morphChildren(currentElement, newElement) {
3356
+ /**
3357
+ * Morph the child elements of the currentElement based on the child elements of
3358
+ * the newElement. Morphing children may dispatch turbo:before-morph-element,
3359
+ * turbo:before-morph-attribute, and turbo:morph-element events.
3360
+ *
3361
+ * @param currentElement Element destination of morphing children changes
3362
+ * @param newElement Element source of morphing children changes
3363
+ */
3364
+ function morphChildren(currentElement, newElement, options = {}) {
3349
3365
  morphElements(currentElement, newElement.childNodes, {
3366
+ ...options,
3350
3367
  morphStyle: "innerHTML"
3351
3368
  });
3352
3369
  }
3353
3370
 
3371
+ function shouldRefreshFrameWithMorphing(currentFrame, newFrame) {
3372
+ return currentFrame instanceof FrameElement &&
3373
+ // newFrame cannot yet be an instance of FrameElement because custom
3374
+ // elements don't get initialized until they're attached to the DOM, so
3375
+ // test its Element#nodeName instead
3376
+ newFrame instanceof Element && newFrame.nodeName === "TURBO-FRAME" &&
3377
+ currentFrame.shouldReloadWithMorph &&
3378
+ currentFrame.id === newFrame.id &&
3379
+ (!newFrame.getAttribute("src") || urlsAreEqual(currentFrame.src, newFrame.getAttribute("src"))) &&
3380
+ !currentFrame.closest("[data-turbo-permanent]")
3381
+ }
3382
+
3383
+ function closestFrameReloadableWithMorphing(node) {
3384
+ return node.parentElement.closest("turbo-frame[src][refresh=morph]")
3385
+ }
3386
+
3354
3387
  class DefaultIdiomorphCallbacks {
3355
3388
  #beforeNodeMorphed
3356
3389
 
@@ -3409,7 +3442,20 @@ Copyright © 2025 37signals LLC
3409
3442
  detail: { currentElement, newElement }
3410
3443
  });
3411
3444
 
3412
- morphChildren(currentElement, newElement);
3445
+ morphChildren(currentElement, newElement, {
3446
+ callbacks: {
3447
+ beforeNodeMorphed: (node, newNode) => {
3448
+ if (
3449
+ shouldRefreshFrameWithMorphing(node, newNode) &&
3450
+ closestFrameReloadableWithMorphing(node) === currentElement
3451
+ ) {
3452
+ node.reload();
3453
+ return false
3454
+ }
3455
+ return true
3456
+ }
3457
+ }
3458
+ });
3413
3459
  }
3414
3460
 
3415
3461
  async preservingPermanentElements(callback) {
@@ -3718,7 +3764,8 @@ Copyright © 2025 37signals LLC
3718
3764
  }
3719
3765
 
3720
3766
  get prefersViewTransitions() {
3721
- return this.headSnapshot.getMetaValue("view-transition") === "same-origin"
3767
+ const viewTransitionEnabled = this.getSetting("view-transition") === "true" || this.headSnapshot.getMetaValue("view-transition") === "same-origin";
3768
+ return viewTransitionEnabled && !window.matchMedia("(prefers-reduced-motion: reduce)").matches
3722
3769
  }
3723
3770
 
3724
3771
  get shouldMorphPage() {
@@ -4206,6 +4253,8 @@ Copyright © 2025 37signals LLC
4206
4253
 
4207
4254
  visitStarted(visit) {
4208
4255
  this.location = visit.location;
4256
+ this.redirectedToLocation = null;
4257
+
4209
4258
  visit.loadCachedSnapshot();
4210
4259
  visit.issueRequest();
4211
4260
  visit.goToSamePageAnchor();
@@ -4222,6 +4271,10 @@ Copyright © 2025 37signals LLC
4222
4271
 
4223
4272
  visitRequestCompleted(visit) {
4224
4273
  visit.loadResponse();
4274
+
4275
+ if (visit.response.redirected) {
4276
+ this.redirectedToLocation = visit.redirectedToLocation;
4277
+ }
4225
4278
  }
4226
4279
 
4227
4280
  visitRequestFailedWithStatusCode(visit, statusCode) {
@@ -4311,7 +4364,7 @@ Copyright © 2025 37signals LLC
4311
4364
  reload(reason) {
4312
4365
  dispatch("turbo:reload", { detail: reason });
4313
4366
 
4314
- window.location.href = this.location?.toString() || window.location.href;
4367
+ window.location.href = (this.redirectedToLocation || this.location)?.toString() || window.location.href;
4315
4368
  }
4316
4369
 
4317
4370
  get navigator() {
@@ -4624,6 +4677,8 @@ Copyright © 2025 37signals LLC
4624
4677
  target
4625
4678
  );
4626
4679
 
4680
+ fetchRequest.fetchOptions.priority = "low";
4681
+
4627
4682
  prefetchCache.setLater(location.toString(), fetchRequest, this.#cacheTtl);
4628
4683
  }
4629
4684
  }
@@ -5433,14 +5488,19 @@ Copyright © 2025 37signals LLC
5433
5488
  static renderElement(currentElement, newElement) {
5434
5489
  morphElements(currentElement, newElement, {
5435
5490
  callbacks: {
5436
- beforeNodeMorphed: element => !canRefreshFrame(element)
5491
+ beforeNodeMorphed: (node, newNode) => {
5492
+ if (
5493
+ shouldRefreshFrameWithMorphing(node, newNode) &&
5494
+ !closestFrameReloadableWithMorphing(node)
5495
+ ) {
5496
+ node.reload();
5497
+ return false
5498
+ }
5499
+ return true
5500
+ }
5437
5501
  }
5438
5502
  });
5439
5503
 
5440
- for (const frame of currentElement.querySelectorAll("turbo-frame")) {
5441
- if (canRefreshFrame(frame)) frame.reload();
5442
- }
5443
-
5444
5504
  dispatch("turbo:morph", { detail: { currentElement, newElement } });
5445
5505
  }
5446
5506
 
@@ -5457,13 +5517,6 @@ Copyright © 2025 37signals LLC
5457
5517
  }
5458
5518
  }
5459
5519
 
5460
- function canRefreshFrame(frame) {
5461
- return frame instanceof FrameElement &&
5462
- frame.src &&
5463
- frame.refresh === "morph" &&
5464
- !frame.closest("[data-turbo-permanent]")
5465
- }
5466
-
5467
5520
  class SnapshotCache {
5468
5521
  keys = []
5469
5522
  snapshots = {}
@@ -6287,6 +6340,32 @@ Copyright © 2025 37signals LLC
6287
6340
  config.forms.mode = mode;
6288
6341
  }
6289
6342
 
6343
+ /**
6344
+ * Morph the state of the currentBody based on the attributes and contents of
6345
+ * the newBody. Morphing body elements may dispatch turbo:morph,
6346
+ * turbo:before-morph-element, turbo:before-morph-attribute, and
6347
+ * turbo:morph-element events.
6348
+ *
6349
+ * @param currentBody HTMLBodyElement destination of morphing changes
6350
+ * @param newBody HTMLBodyElement source of morphing changes
6351
+ */
6352
+ function morphBodyElements(currentBody, newBody) {
6353
+ MorphingPageRenderer.renderElement(currentBody, newBody);
6354
+ }
6355
+
6356
+ /**
6357
+ * Morph the child elements of the currentFrame based on the child elements of
6358
+ * the newFrame. Morphing turbo-frame elements may dispatch turbo:before-frame-morph,
6359
+ * turbo:before-morph-element, turbo:before-morph-attribute, and
6360
+ * turbo:morph-element events.
6361
+ *
6362
+ * @param currentFrame FrameElement destination of morphing children changes
6363
+ * @param newFrame FrameElement source of morphing children changes
6364
+ */
6365
+ function morphTurboFrameElements(currentFrame, newFrame) {
6366
+ MorphingFrameRenderer.renderElement(currentFrame, newFrame);
6367
+ }
6368
+
6290
6369
  var Turbo = /*#__PURE__*/Object.freeze({
6291
6370
  __proto__: null,
6292
6371
  navigator: navigator$1,
@@ -6306,7 +6385,11 @@ Copyright © 2025 37signals LLC
6306
6385
  clearCache: clearCache,
6307
6386
  setProgressBarDelay: setProgressBarDelay,
6308
6387
  setConfirmMethod: setConfirmMethod,
6309
- setFormMode: setFormMode
6388
+ setFormMode: setFormMode,
6389
+ morphBodyElements: morphBodyElements,
6390
+ morphTurboFrameElements: morphTurboFrameElements,
6391
+ morphChildren: morphChildren,
6392
+ morphElements: morphElements
6310
6393
  });
6311
6394
 
6312
6395
  class TurboFrameMissingError extends Error {}
@@ -7202,6 +7285,10 @@ Copyright © 2025 37signals LLC
7202
7285
  exports.fetchEnctypeFromString = fetchEnctypeFromString;
7203
7286
  exports.fetchMethodFromString = fetchMethodFromString;
7204
7287
  exports.isSafe = isSafe;
7288
+ exports.morphBodyElements = morphBodyElements;
7289
+ exports.morphChildren = morphChildren;
7290
+ exports.morphElements = morphElements;
7291
+ exports.morphTurboFrameElements = morphTurboFrameElements;
7205
7292
  exports.navigator = navigator$1;
7206
7293
  exports.registerAdapter = registerAdapter;
7207
7294
  exports.renderStreamMessage = renderStreamMessage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotwired/turbo",
3
- "version": "8.0.13",
3
+ "version": "8.0.14",
4
4
  "description": "The speed of a single-page web application without having to write any JavaScript",
5
5
  "module": "dist/turbo.es2017-esm.js",
6
6
  "main": "dist/turbo.es2017-umd.js",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "devDependencies": {
36
36
  "@open-wc/testing": "^3.1.7",
37
- "@playwright/test": "~1.30.0",
37
+ "@playwright/test": "~1.51.1",
38
38
  "@rollup/plugin-node-resolve": "13.1.3",
39
39
  "@web/dev-server-esbuild": "^0.3.3",
40
40
  "@web/test-runner": "^0.15.0",
@@ -59,10 +59,10 @@
59
59
  "test:browser": "playwright test",
60
60
  "test:unit": "NODE_OPTIONS=--inspect web-test-runner",
61
61
  "test:unit:win": "SET NODE_OPTIONS=--inspect & web-test-runner",
62
- "release": "yarn build && npm publish",
62
+ "release": "yarn build && yarn publish",
63
63
  "lint": "eslint . --ext .js"
64
64
  },
65
65
  "engines": {
66
- "node": ">= 14"
66
+ "node": ">= 18"
67
67
  }
68
68
  }