turbo-rails 2.0.0.pre.beta.1 → 2.0.0.pre.beta.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d64d1046010fe176342eb0dc1269ce42575b3875e8fbea14eebd282455eb28ee
4
- data.tar.gz: 0da2bd6fdcf894118945c41d3f297c5e3638d1ff732a58b89980f033cc950ead
3
+ metadata.gz: d87101284e042e02ec6ba74cdf085367415619ee0c0f1c8b4013ad18022c00c7
4
+ data.tar.gz: 1d514a6ece035de00be6c8af254662476343db15827ca02b27fbcd7caab14d5f
5
5
  SHA512:
6
- metadata.gz: 02ce010eaa7cf96ee30e931499da76ff7beb95525c7cd1a2edd775f6aa354e8524cdbcd7bae2c8806416c6566bb7876ab3d1550fb0f29c18fe007daf2f8b0003
7
- data.tar.gz: f31df5f93132755af3045511f998e38886240656eca467de4de278682ffd1e7c101402aab704988ab6294309d79dc8e767d72ea4d4283c26eb1ac974b7bfd2a6
6
+ metadata.gz: 1b81a8c738ec5f9ff1c3584a4598e116eb5e54557295717e8eaa6ae582d66408c0554477a1bbf7a623bc8d409e45db1e984892c7a784aea2047163c719fb97a7
7
+ data.tar.gz: 1f86ba10f8bd0b76a722eac75490bc309e292e5dd04c7a488b153792ae9f00c9aef4cedf6dc28534569366c7089667abeaa8ac7ad9458ed50a82be58fec13f42
@@ -1,5 +1,5 @@
1
1
  /*!
2
- Turbo 8.0.0-beta.1
2
+ Turbo 8.0.0-beta.2
3
3
  Copyright © 2023 37signals LLC
4
4
  */
5
5
  (function(prototype) {
@@ -485,12 +485,31 @@ async function around(callback, reader) {
485
485
  return [ before, after ];
486
486
  }
487
487
 
488
- function fetch(url, options = {}) {
488
+ class LimitedSet extends Set {
489
+ constructor(maxSize) {
490
+ super();
491
+ this.maxSize = maxSize;
492
+ }
493
+ add(value) {
494
+ if (this.size >= this.maxSize) {
495
+ const iterator = this.values();
496
+ const oldestValue = iterator.next().value;
497
+ this.delete(oldestValue);
498
+ }
499
+ super.add(value);
500
+ }
501
+ }
502
+
503
+ const recentRequests = new LimitedSet(20);
504
+
505
+ const nativeFetch = window.fetch;
506
+
507
+ function fetchWithTurboHeaders(url, options = {}) {
489
508
  const modifiedHeaders = new Headers(options.headers || {});
490
509
  const requestUID = uuid();
491
- window.Turbo.session.recentRequests.add(requestUID);
510
+ recentRequests.add(requestUID);
492
511
  modifiedHeaders.append("X-Turbo-Request-Id", requestUID);
493
- return window.fetch(url, {
512
+ return nativeFetch(url, {
494
513
  ...options,
495
514
  headers: modifiedHeaders
496
515
  });
@@ -609,7 +628,7 @@ class FetchRequest {
609
628
  await this.#allowRequestToBeIntercepted(fetchOptions);
610
629
  try {
611
630
  this.delegate.requestStarted(this);
612
- const response = await fetch(this.url.href, fetchOptions);
631
+ const response = await fetchWithTurboHeaders(this.url.href, fetchOptions);
613
632
  return await this.receive(response);
614
633
  } catch (error) {
615
634
  if (error.name !== "AbortError") {
@@ -848,6 +867,7 @@ class FormSubmission {
848
867
  this.state = FormSubmissionState.waiting;
849
868
  this.submitter?.setAttribute("disabled", "");
850
869
  this.setSubmitsWith();
870
+ markAsBusy(this.formElement);
851
871
  dispatch("turbo:submit-start", {
852
872
  target: this.formElement,
853
873
  detail: {
@@ -895,6 +915,7 @@ class FormSubmission {
895
915
  this.state = FormSubmissionState.stopped;
896
916
  this.submitter?.removeAttribute("disabled");
897
917
  this.resetSubmitterText();
918
+ clearBusyState(this.formElement);
898
919
  dispatch("turbo:submit-end", {
899
920
  target: this.formElement,
900
921
  detail: {
@@ -1176,6 +1197,12 @@ class View {
1176
1197
  this.element.removeAttribute("data-turbo-preview");
1177
1198
  }
1178
1199
  }
1200
+ markVisitDirection(direction) {
1201
+ this.element.setAttribute("data-turbo-visit-direction", direction);
1202
+ }
1203
+ unmarkVisitDirection() {
1204
+ this.element.removeAttribute("data-turbo-visit-direction");
1205
+ }
1179
1206
  async renderSnapshot(renderer) {
1180
1207
  await renderer.render();
1181
1208
  }
@@ -1480,14 +1507,14 @@ class FrameRenderer extends Renderer {
1480
1507
  return true;
1481
1508
  }
1482
1509
  async render() {
1483
- await nextAnimationFrame();
1510
+ await nextRepaint();
1484
1511
  this.preservingPermanentElements((() => {
1485
1512
  this.loadFrameElement();
1486
1513
  }));
1487
1514
  this.scrollFrameIntoView();
1488
- await nextAnimationFrame();
1515
+ await nextRepaint();
1489
1516
  this.focusFirstAutofocusableElement();
1490
- await nextAnimationFrame();
1517
+ await nextRepaint();
1491
1518
  this.activateScriptElements();
1492
1519
  }
1493
1520
  loadFrameElement() {
@@ -1846,6 +1873,12 @@ const SystemStatusCode = {
1846
1873
  contentTypeMismatch: -2
1847
1874
  };
1848
1875
 
1876
+ const Direction = {
1877
+ advance: "forward",
1878
+ restore: "back",
1879
+ replace: "none"
1880
+ };
1881
+
1849
1882
  class Visit {
1850
1883
  identifier=uuid();
1851
1884
  timingMetrics={};
@@ -1861,7 +1894,7 @@ class Visit {
1861
1894
  this.delegate = delegate;
1862
1895
  this.location = location;
1863
1896
  this.restorationIdentifier = restorationIdentifier || uuid();
1864
- const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = {
1897
+ 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} = {
1865
1898
  ...defaultOptions,
1866
1899
  ...options
1867
1900
  };
@@ -1878,6 +1911,7 @@ class Visit {
1878
1911
  this.scrolled = !willRender;
1879
1912
  this.shouldCacheSnapshot = shouldCacheSnapshot;
1880
1913
  this.acceptsStreamResponse = acceptsStreamResponse;
1914
+ this.direction = direction || Direction[action];
1881
1915
  }
1882
1916
  get adapter() {
1883
1917
  return this.delegate.adapter;
@@ -2098,7 +2132,7 @@ class Visit {
2098
2132
  this.finishRequest();
2099
2133
  }
2100
2134
  performScroll() {
2101
- if (!this.scrolled && !this.view.forceReloaded && !this.view.snapshot.shouldPreserveScrollPosition) {
2135
+ if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) {
2102
2136
  if (this.action == "restore") {
2103
2137
  this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
2104
2138
  } else {
@@ -2162,9 +2196,7 @@ class Visit {
2162
2196
  }
2163
2197
  async render(callback) {
2164
2198
  this.cancelRender();
2165
- await new Promise((resolve => {
2166
- this.frame = requestAnimationFrame((() => resolve()));
2167
- }));
2199
+ this.frame = await nextRepaint();
2168
2200
  await callback();
2169
2201
  delete this.frame;
2170
2202
  }
@@ -2386,6 +2418,7 @@ class History {
2386
2418
  restorationData={};
2387
2419
  started=false;
2388
2420
  pageLoaded=false;
2421
+ currentIndex=0;
2389
2422
  constructor(delegate) {
2390
2423
  this.delegate = delegate;
2391
2424
  }
@@ -2393,6 +2426,7 @@ class History {
2393
2426
  if (!this.started) {
2394
2427
  addEventListener("popstate", this.onPopState, false);
2395
2428
  addEventListener("load", this.onPageLoad, false);
2429
+ this.currentIndex = history.state?.turbo?.restorationIndex || 0;
2396
2430
  this.started = true;
2397
2431
  this.replace(new URL(window.location.href));
2398
2432
  }
@@ -2411,9 +2445,11 @@ class History {
2411
2445
  this.update(history.replaceState, location, restorationIdentifier);
2412
2446
  }
2413
2447
  update(method, location, restorationIdentifier = uuid()) {
2448
+ if (method === history.pushState) ++this.currentIndex;
2414
2449
  const state = {
2415
2450
  turbo: {
2416
- restorationIdentifier: restorationIdentifier
2451
+ restorationIdentifier: restorationIdentifier,
2452
+ restorationIndex: this.currentIndex
2417
2453
  }
2418
2454
  };
2419
2455
  method.call(history, state, "", location.href);
@@ -2448,9 +2484,11 @@ class History {
2448
2484
  const {turbo: turbo} = event.state || {};
2449
2485
  if (turbo) {
2450
2486
  this.location = new URL(window.location.href);
2451
- const {restorationIdentifier: restorationIdentifier} = turbo;
2487
+ const {restorationIdentifier: restorationIdentifier, restorationIndex: restorationIndex} = turbo;
2452
2488
  this.restorationIdentifier = restorationIdentifier;
2453
- this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
2489
+ const direction = restorationIndex > this.currentIndex ? "forward" : "back";
2490
+ this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
2491
+ this.currentIndex = restorationIndex;
2454
2492
  }
2455
2493
  }
2456
2494
  };
@@ -2725,7 +2763,7 @@ async function withAutofocusFromFragment(fragment, callback) {
2725
2763
  elementWithAutofocus.id = willAutofocusId;
2726
2764
  }
2727
2765
  callback();
2728
- await nextAnimationFrame();
2766
+ await nextRepaint();
2729
2767
  const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
2730
2768
  if (hasNoActiveElement && willAutofocusId) {
2731
2769
  const elementToAutofocus = document.getElementById(willAutofocusId);
@@ -3663,7 +3701,10 @@ class PageView extends View {
3663
3701
  return this.snapshotCache.get(location);
3664
3702
  }
3665
3703
  isPageRefresh(visit) {
3666
- return !visit || this.lastRenderedLocation.href === visit.location.href && visit.action === "replace";
3704
+ return !visit || this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === "replace";
3705
+ }
3706
+ shouldPreserveScrollPosition(visit) {
3707
+ return this.isPageRefresh(visit) && this.snapshot.shouldPreserveScrollPosition;
3667
3708
  }
3668
3709
  get snapshot() {
3669
3710
  return PageSnapshot.fromElement(this.element);
@@ -3672,24 +3713,25 @@ class PageView extends View {
3672
3713
 
3673
3714
  class Preloader {
3674
3715
  selector="a[data-turbo-preload]";
3675
- constructor(delegate) {
3716
+ constructor(delegate, snapshotCache) {
3676
3717
  this.delegate = delegate;
3677
- }
3678
- get snapshotCache() {
3679
- return this.delegate.navigator.view.snapshotCache;
3718
+ this.snapshotCache = snapshotCache;
3680
3719
  }
3681
3720
  start() {
3682
3721
  if (document.readyState === "loading") {
3683
- return document.addEventListener("DOMContentLoaded", (() => {
3684
- this.preloadOnLoadLinksForView(document.body);
3685
- }));
3722
+ document.addEventListener("DOMContentLoaded", this.#preloadAll);
3686
3723
  } else {
3687
3724
  this.preloadOnLoadLinksForView(document.body);
3688
3725
  }
3689
3726
  }
3727
+ stop() {
3728
+ document.removeEventListener("DOMContentLoaded", this.#preloadAll);
3729
+ }
3690
3730
  preloadOnLoadLinksForView(element) {
3691
3731
  for (const link of element.querySelectorAll(this.selector)) {
3692
- this.preloadURL(link);
3732
+ if (this.delegate.shouldPreloadLink(link)) {
3733
+ this.preloadURL(link);
3734
+ }
3693
3735
  }
3694
3736
  }
3695
3737
  async preloadURL(link) {
@@ -3697,33 +3739,27 @@ class Preloader {
3697
3739
  if (this.snapshotCache.has(location)) {
3698
3740
  return;
3699
3741
  }
3700
- try {
3701
- const response = await fetch(location.toString(), {
3702
- headers: {
3703
- "Sec-Purpose": "prefetch",
3704
- Accept: "text/html"
3705
- }
3706
- });
3707
- const responseText = await response.text();
3708
- const snapshot = PageSnapshot.fromHTMLString(responseText);
3709
- this.snapshotCache.put(location, snapshot);
3710
- } catch (_) {}
3742
+ const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, link);
3743
+ await fetchRequest.perform();
3711
3744
  }
3712
- }
3713
-
3714
- class LimitedSet extends Set {
3715
- constructor(maxSize) {
3716
- super();
3717
- this.maxSize = maxSize;
3745
+ prepareRequest(fetchRequest) {
3746
+ fetchRequest.headers["Sec-Purpose"] = "prefetch";
3718
3747
  }
3719
- add(value) {
3720
- if (this.size >= this.maxSize) {
3721
- const iterator = this.values();
3722
- const oldestValue = iterator.next().value;
3723
- this.delete(oldestValue);
3724
- }
3725
- super.add(value);
3748
+ async requestSucceededWithResponse(fetchRequest, fetchResponse) {
3749
+ try {
3750
+ const responseHTML = await fetchResponse.responseHTML;
3751
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
3752
+ this.snapshotCache.put(fetchRequest.url, snapshot);
3753
+ } catch (_) {}
3726
3754
  }
3755
+ requestStarted(fetchRequest) {}
3756
+ requestErrored(fetchRequest) {}
3757
+ requestFinished(fetchRequest) {}
3758
+ requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
3759
+ requestFailedWithResponse(fetchRequest, fetchResponse) {}
3760
+ #preloadAll=() => {
3761
+ this.preloadOnLoadLinksForView(document.body);
3762
+ };
3727
3763
  }
3728
3764
 
3729
3765
  class Cache {
@@ -3750,7 +3786,6 @@ class Cache {
3750
3786
  class Session {
3751
3787
  navigator=new Navigator(this);
3752
3788
  history=new History(this);
3753
- preloader=new Preloader(this);
3754
3789
  view=new PageView(this, document.documentElement);
3755
3790
  adapter=new BrowserAdapter(this);
3756
3791
  pageObserver=new PageObserver(this);
@@ -3763,12 +3798,15 @@ class Session {
3763
3798
  frameRedirector=new FrameRedirector(this, document.documentElement);
3764
3799
  streamMessageRenderer=new StreamMessageRenderer;
3765
3800
  cache=new Cache(this);
3766
- recentRequests=new LimitedSet(20);
3767
3801
  drive=true;
3768
3802
  enabled=true;
3769
3803
  progressBarDelay=500;
3770
3804
  started=false;
3771
3805
  formMode="on";
3806
+ constructor(recentRequests) {
3807
+ this.recentRequests = recentRequests;
3808
+ this.preloader = new Preloader(this, this.view.snapshotCache);
3809
+ }
3772
3810
  start() {
3773
3811
  if (!this.started) {
3774
3812
  this.pageObserver.start();
@@ -3799,6 +3837,7 @@ class Session {
3799
3837
  this.streamObserver.stop();
3800
3838
  this.frameRedirector.stop();
3801
3839
  this.history.stop();
3840
+ this.preloader.stop();
3802
3841
  this.started = false;
3803
3842
  }
3804
3843
  }
@@ -3847,11 +3886,24 @@ class Session {
3847
3886
  get restorationIdentifier() {
3848
3887
  return this.history.restorationIdentifier;
3849
3888
  }
3850
- historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
3889
+ shouldPreloadLink(element) {
3890
+ const isUnsafe = element.hasAttribute("data-turbo-method");
3891
+ const isStream = element.hasAttribute("data-turbo-stream");
3892
+ const frameTarget = element.getAttribute("data-turbo-frame");
3893
+ const frame = frameTarget == "_top" ? null : document.getElementById(frameTarget) || findClosestRecursively(element, "turbo-frame:not([disabled])");
3894
+ if (isUnsafe || isStream || frame instanceof FrameElement) {
3895
+ return false;
3896
+ } else {
3897
+ const location = new URL(element.href);
3898
+ return this.elementIsNavigatable(element) && locationIsVisitable(location, this.snapshot.rootLocation);
3899
+ }
3900
+ }
3901
+ historyPoppedToLocationWithRestorationIdentifierAndDirection(location, restorationIdentifier, direction) {
3851
3902
  if (this.enabled) {
3852
3903
  this.navigator.startVisit(location, restorationIdentifier, {
3853
3904
  action: "restore",
3854
- historyChanged: true
3905
+ historyChanged: true,
3906
+ direction: direction
3855
3907
  });
3856
3908
  } else {
3857
3909
  this.adapter.pageInvalidated({
@@ -3889,6 +3941,7 @@ class Session {
3889
3941
  visitStarted(visit) {
3890
3942
  if (!visit.acceptsStreamResponse) {
3891
3943
  markAsBusy(document.documentElement);
3944
+ this.view.markVisitDirection(visit.direction);
3892
3945
  }
3893
3946
  extendURLWithDeprecatedProperties(visit.location);
3894
3947
  if (!visit.silent) {
@@ -3896,6 +3949,7 @@ class Session {
3896
3949
  }
3897
3950
  }
3898
3951
  visitCompleted(visit) {
3952
+ this.view.unmarkVisitDirection();
3899
3953
  clearBusyState(document.documentElement);
3900
3954
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
3901
3955
  }
@@ -4086,7 +4140,7 @@ const deprecatedLocationPropertyDescriptors = {
4086
4140
  }
4087
4141
  };
4088
4142
 
4089
- const session = new Session;
4143
+ const session = new Session(recentRequests);
4090
4144
 
4091
4145
  const {cache: cache, navigator: navigator$1} = session;
4092
4146
 
@@ -4139,7 +4193,7 @@ var Turbo = Object.freeze({
4139
4193
  PageRenderer: PageRenderer,
4140
4194
  PageSnapshot: PageSnapshot,
4141
4195
  FrameRenderer: FrameRenderer,
4142
- fetch: fetch,
4196
+ fetch: fetchWithTurboHeaders,
4143
4197
  start: start,
4144
4198
  registerAdapter: registerAdapter,
4145
4199
  visit: visit,
@@ -4806,11 +4860,14 @@ if (customElements.get("turbo-stream-source") === undefined) {
4806
4860
  }
4807
4861
  })();
4808
4862
 
4809
- window.Turbo = Turbo;
4863
+ window.Turbo = {
4864
+ ...Turbo,
4865
+ StreamActions: StreamActions
4866
+ };
4810
4867
 
4811
4868
  start();
4812
4869
 
4813
- var turbo_es2017Esm = Object.freeze({
4870
+ var Turbo$1 = Object.freeze({
4814
4871
  __proto__: null,
4815
4872
  FetchEnctype: FetchEnctype,
4816
4873
  FetchMethod: FetchMethod,
@@ -4828,7 +4885,7 @@ var turbo_es2017Esm = Object.freeze({
4828
4885
  clearCache: clearCache,
4829
4886
  connectStreamSource: connectStreamSource,
4830
4887
  disconnectStreamSource: disconnectStreamSource,
4831
- fetch: fetch,
4888
+ fetch: fetchWithTurboHeaders,
4832
4889
  fetchEnctypeFromString: fetchEnctypeFromString,
4833
4890
  fetchMethodFromString: fetchMethodFromString,
4834
4891
  isSafe: isSafe,
@@ -4979,6 +5036,8 @@ function isBodyInit(body) {
4979
5036
  return body instanceof FormData || body instanceof URLSearchParams;
4980
5037
  }
4981
5038
 
5039
+ window.Turbo = Turbo$1;
5040
+
4982
5041
  addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
4983
5042
 
4984
5043
  var adapters = {
@@ -5514,4 +5573,4 @@ var index = Object.freeze({
5514
5573
  getConfig: getConfig
5515
5574
  });
5516
5575
 
5517
- export { turbo_es2017Esm as Turbo, cable };
5576
+ export { Turbo$1 as Turbo, cable };