@hotwired/turbo 8.0.0-beta.1 → 8.0.0-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.
- package/dist/turbo.es2017-esm.js +133 -52
- package/dist/turbo.es2017-umd.js +133 -52
- package/package.json +2 -4
package/dist/turbo.es2017-esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
Turbo 8.0.0-beta.
|
|
2
|
+
Turbo 8.0.0-beta.2
|
|
3
3
|
Copyright © 2023 37signals LLC
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
@@ -633,13 +633,33 @@ async function around(callback, reader) {
|
|
|
633
633
|
return [before, after]
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
-
|
|
636
|
+
class LimitedSet extends Set {
|
|
637
|
+
constructor(maxSize) {
|
|
638
|
+
super();
|
|
639
|
+
this.maxSize = maxSize;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
add(value) {
|
|
643
|
+
if (this.size >= this.maxSize) {
|
|
644
|
+
const iterator = this.values();
|
|
645
|
+
const oldestValue = iterator.next().value;
|
|
646
|
+
this.delete(oldestValue);
|
|
647
|
+
}
|
|
648
|
+
super.add(value);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const recentRequests = new LimitedSet(20);
|
|
653
|
+
|
|
654
|
+
const nativeFetch = window.fetch;
|
|
655
|
+
|
|
656
|
+
function fetchWithTurboHeaders(url, options = {}) {
|
|
637
657
|
const modifiedHeaders = new Headers(options.headers || {});
|
|
638
658
|
const requestUID = uuid();
|
|
639
|
-
|
|
659
|
+
recentRequests.add(requestUID);
|
|
640
660
|
modifiedHeaders.append("X-Turbo-Request-Id", requestUID);
|
|
641
661
|
|
|
642
|
-
return
|
|
662
|
+
return nativeFetch(url, {
|
|
643
663
|
...options,
|
|
644
664
|
headers: modifiedHeaders
|
|
645
665
|
})
|
|
@@ -766,7 +786,7 @@ class FetchRequest {
|
|
|
766
786
|
await this.#allowRequestToBeIntercepted(fetchOptions);
|
|
767
787
|
try {
|
|
768
788
|
this.delegate.requestStarted(this);
|
|
769
|
-
const response = await
|
|
789
|
+
const response = await fetchWithTurboHeaders(this.url.href, fetchOptions);
|
|
770
790
|
return await this.receive(response)
|
|
771
791
|
} catch (error) {
|
|
772
792
|
if (error.name !== "AbortError") {
|
|
@@ -1046,6 +1066,7 @@ class FormSubmission {
|
|
|
1046
1066
|
this.state = FormSubmissionState.waiting;
|
|
1047
1067
|
this.submitter?.setAttribute("disabled", "");
|
|
1048
1068
|
this.setSubmitsWith();
|
|
1069
|
+
markAsBusy(this.formElement);
|
|
1049
1070
|
dispatch("turbo:submit-start", {
|
|
1050
1071
|
target: this.formElement,
|
|
1051
1072
|
detail: { formSubmission: this }
|
|
@@ -1084,6 +1105,7 @@ class FormSubmission {
|
|
|
1084
1105
|
this.state = FormSubmissionState.stopped;
|
|
1085
1106
|
this.submitter?.removeAttribute("disabled");
|
|
1086
1107
|
this.resetSubmitterText();
|
|
1108
|
+
clearBusyState(this.formElement);
|
|
1087
1109
|
dispatch("turbo:submit-end", {
|
|
1088
1110
|
target: this.formElement,
|
|
1089
1111
|
detail: { formSubmission: this, ...this.result }
|
|
@@ -1411,6 +1433,14 @@ class View {
|
|
|
1411
1433
|
}
|
|
1412
1434
|
}
|
|
1413
1435
|
|
|
1436
|
+
markVisitDirection(direction) {
|
|
1437
|
+
this.element.setAttribute("data-turbo-visit-direction", direction);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
unmarkVisitDirection() {
|
|
1441
|
+
this.element.removeAttribute("data-turbo-visit-direction");
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1414
1444
|
async renderSnapshot(renderer) {
|
|
1415
1445
|
await renderer.render();
|
|
1416
1446
|
}
|
|
@@ -1780,14 +1810,14 @@ class FrameRenderer extends Renderer {
|
|
|
1780
1810
|
}
|
|
1781
1811
|
|
|
1782
1812
|
async render() {
|
|
1783
|
-
await
|
|
1813
|
+
await nextRepaint();
|
|
1784
1814
|
this.preservingPermanentElements(() => {
|
|
1785
1815
|
this.loadFrameElement();
|
|
1786
1816
|
});
|
|
1787
1817
|
this.scrollFrameIntoView();
|
|
1788
|
-
await
|
|
1818
|
+
await nextRepaint();
|
|
1789
1819
|
this.focusFirstAutofocusableElement();
|
|
1790
|
-
await
|
|
1820
|
+
await nextRepaint();
|
|
1791
1821
|
this.activateScriptElements();
|
|
1792
1822
|
}
|
|
1793
1823
|
|
|
@@ -2213,6 +2243,12 @@ const SystemStatusCode = {
|
|
|
2213
2243
|
contentTypeMismatch: -2
|
|
2214
2244
|
};
|
|
2215
2245
|
|
|
2246
|
+
const Direction = {
|
|
2247
|
+
advance: "forward",
|
|
2248
|
+
restore: "back",
|
|
2249
|
+
replace: "none"
|
|
2250
|
+
};
|
|
2251
|
+
|
|
2216
2252
|
class Visit {
|
|
2217
2253
|
identifier = uuid() // Required by turbo-ios
|
|
2218
2254
|
timingMetrics = {}
|
|
@@ -2242,7 +2278,8 @@ class Visit {
|
|
|
2242
2278
|
willRender,
|
|
2243
2279
|
updateHistory,
|
|
2244
2280
|
shouldCacheSnapshot,
|
|
2245
|
-
acceptsStreamResponse
|
|
2281
|
+
acceptsStreamResponse,
|
|
2282
|
+
direction
|
|
2246
2283
|
} = {
|
|
2247
2284
|
...defaultOptions,
|
|
2248
2285
|
...options
|
|
@@ -2260,6 +2297,7 @@ class Visit {
|
|
|
2260
2297
|
this.scrolled = !willRender;
|
|
2261
2298
|
this.shouldCacheSnapshot = shouldCacheSnapshot;
|
|
2262
2299
|
this.acceptsStreamResponse = acceptsStreamResponse;
|
|
2300
|
+
this.direction = direction || Direction[action];
|
|
2263
2301
|
}
|
|
2264
2302
|
|
|
2265
2303
|
get adapter() {
|
|
@@ -2512,7 +2550,7 @@ class Visit {
|
|
|
2512
2550
|
// Scrolling
|
|
2513
2551
|
|
|
2514
2552
|
performScroll() {
|
|
2515
|
-
if (!this.scrolled && !this.view.forceReloaded && !this.view.
|
|
2553
|
+
if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) {
|
|
2516
2554
|
if (this.action == "restore") {
|
|
2517
2555
|
this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
|
|
2518
2556
|
} else {
|
|
@@ -2587,9 +2625,7 @@ class Visit {
|
|
|
2587
2625
|
|
|
2588
2626
|
async render(callback) {
|
|
2589
2627
|
this.cancelRender();
|
|
2590
|
-
await
|
|
2591
|
-
this.frame = requestAnimationFrame(() => resolve());
|
|
2592
|
-
});
|
|
2628
|
+
this.frame = await nextRepaint();
|
|
2593
2629
|
await callback();
|
|
2594
2630
|
delete this.frame;
|
|
2595
2631
|
}
|
|
@@ -2867,6 +2903,7 @@ class History {
|
|
|
2867
2903
|
restorationData = {}
|
|
2868
2904
|
started = false
|
|
2869
2905
|
pageLoaded = false
|
|
2906
|
+
currentIndex = 0
|
|
2870
2907
|
|
|
2871
2908
|
constructor(delegate) {
|
|
2872
2909
|
this.delegate = delegate;
|
|
@@ -2876,6 +2913,7 @@ class History {
|
|
|
2876
2913
|
if (!this.started) {
|
|
2877
2914
|
addEventListener("popstate", this.onPopState, false);
|
|
2878
2915
|
addEventListener("load", this.onPageLoad, false);
|
|
2916
|
+
this.currentIndex = history.state?.turbo?.restorationIndex || 0;
|
|
2879
2917
|
this.started = true;
|
|
2880
2918
|
this.replace(new URL(window.location.href));
|
|
2881
2919
|
}
|
|
@@ -2898,7 +2936,9 @@ class History {
|
|
|
2898
2936
|
}
|
|
2899
2937
|
|
|
2900
2938
|
update(method, location, restorationIdentifier = uuid()) {
|
|
2901
|
-
|
|
2939
|
+
if (method === history.pushState) ++this.currentIndex;
|
|
2940
|
+
|
|
2941
|
+
const state = { turbo: { restorationIdentifier, restorationIndex: this.currentIndex } };
|
|
2902
2942
|
method.call(history, state, "", location.href);
|
|
2903
2943
|
this.location = location;
|
|
2904
2944
|
this.restorationIdentifier = restorationIdentifier;
|
|
@@ -2942,9 +2982,11 @@ class History {
|
|
|
2942
2982
|
const { turbo } = event.state || {};
|
|
2943
2983
|
if (turbo) {
|
|
2944
2984
|
this.location = new URL(window.location.href);
|
|
2945
|
-
const { restorationIdentifier } = turbo;
|
|
2985
|
+
const { restorationIdentifier, restorationIndex } = turbo;
|
|
2946
2986
|
this.restorationIdentifier = restorationIdentifier;
|
|
2947
|
-
this.
|
|
2987
|
+
const direction = restorationIndex > this.currentIndex ? "forward" : "back";
|
|
2988
|
+
this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
|
|
2989
|
+
this.currentIndex = restorationIndex;
|
|
2948
2990
|
}
|
|
2949
2991
|
}
|
|
2950
2992
|
}
|
|
@@ -3281,7 +3323,7 @@ async function withAutofocusFromFragment(fragment, callback) {
|
|
|
3281
3323
|
}
|
|
3282
3324
|
|
|
3283
3325
|
callback();
|
|
3284
|
-
await
|
|
3326
|
+
await nextRepaint();
|
|
3285
3327
|
|
|
3286
3328
|
const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
|
|
3287
3329
|
|
|
@@ -4535,7 +4577,11 @@ class PageView extends View {
|
|
|
4535
4577
|
}
|
|
4536
4578
|
|
|
4537
4579
|
isPageRefresh(visit) {
|
|
4538
|
-
return !visit || (this.lastRenderedLocation.
|
|
4580
|
+
return !visit || (this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === "replace")
|
|
4581
|
+
}
|
|
4582
|
+
|
|
4583
|
+
shouldPreserveScrollPosition(visit) {
|
|
4584
|
+
return this.isPageRefresh(visit) && this.snapshot.shouldPreserveScrollPosition
|
|
4539
4585
|
}
|
|
4540
4586
|
|
|
4541
4587
|
get snapshot() {
|
|
@@ -4546,27 +4592,28 @@ class PageView extends View {
|
|
|
4546
4592
|
class Preloader {
|
|
4547
4593
|
selector = "a[data-turbo-preload]"
|
|
4548
4594
|
|
|
4549
|
-
constructor(delegate) {
|
|
4595
|
+
constructor(delegate, snapshotCache) {
|
|
4550
4596
|
this.delegate = delegate;
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
get snapshotCache() {
|
|
4554
|
-
return this.delegate.navigator.view.snapshotCache
|
|
4597
|
+
this.snapshotCache = snapshotCache;
|
|
4555
4598
|
}
|
|
4556
4599
|
|
|
4557
4600
|
start() {
|
|
4558
4601
|
if (document.readyState === "loading") {
|
|
4559
|
-
|
|
4560
|
-
this.preloadOnLoadLinksForView(document.body);
|
|
4561
|
-
})
|
|
4602
|
+
document.addEventListener("DOMContentLoaded", this.#preloadAll);
|
|
4562
4603
|
} else {
|
|
4563
4604
|
this.preloadOnLoadLinksForView(document.body);
|
|
4564
4605
|
}
|
|
4565
4606
|
}
|
|
4566
4607
|
|
|
4608
|
+
stop() {
|
|
4609
|
+
document.removeEventListener("DOMContentLoaded", this.#preloadAll);
|
|
4610
|
+
}
|
|
4611
|
+
|
|
4567
4612
|
preloadOnLoadLinksForView(element) {
|
|
4568
4613
|
for (const link of element.querySelectorAll(this.selector)) {
|
|
4569
|
-
this.
|
|
4614
|
+
if (this.delegate.shouldPreloadLink(link)) {
|
|
4615
|
+
this.preloadURL(link);
|
|
4616
|
+
}
|
|
4570
4617
|
}
|
|
4571
4618
|
}
|
|
4572
4619
|
|
|
@@ -4577,31 +4624,39 @@ class Preloader {
|
|
|
4577
4624
|
return
|
|
4578
4625
|
}
|
|
4579
4626
|
|
|
4627
|
+
const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams(), link);
|
|
4628
|
+
await fetchRequest.perform();
|
|
4629
|
+
}
|
|
4630
|
+
|
|
4631
|
+
// Fetch request delegate
|
|
4632
|
+
|
|
4633
|
+
prepareRequest(fetchRequest) {
|
|
4634
|
+
fetchRequest.headers["Sec-Purpose"] = "prefetch";
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4637
|
+
async requestSucceededWithResponse(fetchRequest, fetchResponse) {
|
|
4580
4638
|
try {
|
|
4581
|
-
const
|
|
4582
|
-
const
|
|
4583
|
-
const snapshot = PageSnapshot.fromHTMLString(responseText);
|
|
4639
|
+
const responseHTML = await fetchResponse.responseHTML;
|
|
4640
|
+
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
|
4584
4641
|
|
|
4585
|
-
this.snapshotCache.put(
|
|
4642
|
+
this.snapshotCache.put(fetchRequest.url, snapshot);
|
|
4586
4643
|
} catch (_) {
|
|
4587
4644
|
// If we cannot preload that is ok!
|
|
4588
4645
|
}
|
|
4589
4646
|
}
|
|
4590
|
-
}
|
|
4591
4647
|
|
|
4592
|
-
|
|
4593
|
-
constructor(maxSize) {
|
|
4594
|
-
super();
|
|
4595
|
-
this.maxSize = maxSize;
|
|
4596
|
-
}
|
|
4648
|
+
requestStarted(fetchRequest) {}
|
|
4597
4649
|
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4650
|
+
requestErrored(fetchRequest) {}
|
|
4651
|
+
|
|
4652
|
+
requestFinished(fetchRequest) {}
|
|
4653
|
+
|
|
4654
|
+
requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
|
|
4655
|
+
|
|
4656
|
+
requestFailedWithResponse(fetchRequest, fetchResponse) {}
|
|
4657
|
+
|
|
4658
|
+
#preloadAll = () => {
|
|
4659
|
+
this.preloadOnLoadLinksForView(document.body);
|
|
4605
4660
|
}
|
|
4606
4661
|
}
|
|
4607
4662
|
|
|
@@ -4634,7 +4689,6 @@ class Cache {
|
|
|
4634
4689
|
class Session {
|
|
4635
4690
|
navigator = new Navigator(this)
|
|
4636
4691
|
history = new History(this)
|
|
4637
|
-
preloader = new Preloader(this)
|
|
4638
4692
|
view = new PageView(this, document.documentElement)
|
|
4639
4693
|
adapter = new BrowserAdapter(this)
|
|
4640
4694
|
|
|
@@ -4648,7 +4702,6 @@ class Session {
|
|
|
4648
4702
|
frameRedirector = new FrameRedirector(this, document.documentElement)
|
|
4649
4703
|
streamMessageRenderer = new StreamMessageRenderer()
|
|
4650
4704
|
cache = new Cache(this)
|
|
4651
|
-
recentRequests = new LimitedSet(20)
|
|
4652
4705
|
|
|
4653
4706
|
drive = true
|
|
4654
4707
|
enabled = true
|
|
@@ -4656,6 +4709,11 @@ class Session {
|
|
|
4656
4709
|
started = false
|
|
4657
4710
|
formMode = "on"
|
|
4658
4711
|
|
|
4712
|
+
constructor(recentRequests) {
|
|
4713
|
+
this.recentRequests = recentRequests;
|
|
4714
|
+
this.preloader = new Preloader(this, this.view.snapshotCache);
|
|
4715
|
+
}
|
|
4716
|
+
|
|
4659
4717
|
start() {
|
|
4660
4718
|
if (!this.started) {
|
|
4661
4719
|
this.pageObserver.start();
|
|
@@ -4688,6 +4746,7 @@ class Session {
|
|
|
4688
4746
|
this.streamObserver.stop();
|
|
4689
4747
|
this.frameRedirector.stop();
|
|
4690
4748
|
this.history.stop();
|
|
4749
|
+
this.preloader.stop();
|
|
4691
4750
|
this.started = false;
|
|
4692
4751
|
}
|
|
4693
4752
|
}
|
|
@@ -4747,13 +4806,33 @@ class Session {
|
|
|
4747
4806
|
return this.history.restorationIdentifier
|
|
4748
4807
|
}
|
|
4749
4808
|
|
|
4809
|
+
// Preloader delegate
|
|
4810
|
+
|
|
4811
|
+
shouldPreloadLink(element) {
|
|
4812
|
+
const isUnsafe = element.hasAttribute("data-turbo-method");
|
|
4813
|
+
const isStream = element.hasAttribute("data-turbo-stream");
|
|
4814
|
+
const frameTarget = element.getAttribute("data-turbo-frame");
|
|
4815
|
+
const frame = frameTarget == "_top" ?
|
|
4816
|
+
null :
|
|
4817
|
+
document.getElementById(frameTarget) || findClosestRecursively(element, "turbo-frame:not([disabled])");
|
|
4818
|
+
|
|
4819
|
+
if (isUnsafe || isStream || frame instanceof FrameElement) {
|
|
4820
|
+
return false
|
|
4821
|
+
} else {
|
|
4822
|
+
const location = new URL(element.href);
|
|
4823
|
+
|
|
4824
|
+
return this.elementIsNavigatable(element) && locationIsVisitable(location, this.snapshot.rootLocation)
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
|
|
4750
4828
|
// History delegate
|
|
4751
4829
|
|
|
4752
|
-
|
|
4830
|
+
historyPoppedToLocationWithRestorationIdentifierAndDirection(location, restorationIdentifier, direction) {
|
|
4753
4831
|
if (this.enabled) {
|
|
4754
4832
|
this.navigator.startVisit(location, restorationIdentifier, {
|
|
4755
4833
|
action: "restore",
|
|
4756
|
-
historyChanged: true
|
|
4834
|
+
historyChanged: true,
|
|
4835
|
+
direction
|
|
4757
4836
|
});
|
|
4758
4837
|
} else {
|
|
4759
4838
|
this.adapter.pageInvalidated({
|
|
@@ -4809,6 +4888,7 @@ class Session {
|
|
|
4809
4888
|
visitStarted(visit) {
|
|
4810
4889
|
if (!visit.acceptsStreamResponse) {
|
|
4811
4890
|
markAsBusy(document.documentElement);
|
|
4891
|
+
this.view.markVisitDirection(visit.direction);
|
|
4812
4892
|
}
|
|
4813
4893
|
extendURLWithDeprecatedProperties(visit.location);
|
|
4814
4894
|
if (!visit.silent) {
|
|
@@ -4817,6 +4897,7 @@ class Session {
|
|
|
4817
4897
|
}
|
|
4818
4898
|
|
|
4819
4899
|
visitCompleted(visit) {
|
|
4900
|
+
this.view.unmarkVisitDirection();
|
|
4820
4901
|
clearBusyState(document.documentElement);
|
|
4821
4902
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
|
4822
4903
|
}
|
|
@@ -5055,7 +5136,7 @@ const deprecatedLocationPropertyDescriptors = {
|
|
|
5055
5136
|
}
|
|
5056
5137
|
};
|
|
5057
5138
|
|
|
5058
|
-
const session = new Session();
|
|
5139
|
+
const session = new Session(recentRequests);
|
|
5059
5140
|
const { cache, navigator: navigator$1 } = session;
|
|
5060
5141
|
|
|
5061
5142
|
/**
|
|
@@ -5165,7 +5246,7 @@ var Turbo = /*#__PURE__*/Object.freeze({
|
|
|
5165
5246
|
PageRenderer: PageRenderer,
|
|
5166
5247
|
PageSnapshot: PageSnapshot,
|
|
5167
5248
|
FrameRenderer: FrameRenderer,
|
|
5168
|
-
fetch:
|
|
5249
|
+
fetch: fetchWithTurboHeaders,
|
|
5169
5250
|
start: start,
|
|
5170
5251
|
registerAdapter: registerAdapter,
|
|
5171
5252
|
visit: visit,
|
|
@@ -6034,7 +6115,7 @@ if (customElements.get("turbo-stream-source") === undefined) {
|
|
|
6034
6115
|
}
|
|
6035
6116
|
})();
|
|
6036
6117
|
|
|
6037
|
-
window.Turbo = Turbo;
|
|
6118
|
+
window.Turbo = { ...Turbo, StreamActions };
|
|
6038
6119
|
start();
|
|
6039
6120
|
|
|
6040
|
-
export { FetchEnctype, FetchMethod, FetchRequest, FetchResponse, FrameElement, FrameLoadingStyle, FrameRenderer, PageRenderer, PageSnapshot, StreamActions, StreamElement, StreamSourceElement, cache, clearCache, connectStreamSource, disconnectStreamSource, fetch, fetchEnctypeFromString, fetchMethodFromString, isSafe, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setFormMode, setProgressBarDelay, start, visit };
|
|
6121
|
+
export { FetchEnctype, FetchMethod, FetchRequest, FetchResponse, FrameElement, FrameLoadingStyle, FrameRenderer, PageRenderer, PageSnapshot, StreamActions, StreamElement, StreamSourceElement, cache, clearCache, connectStreamSource, disconnectStreamSource, fetchWithTurboHeaders as fetch, fetchEnctypeFromString, fetchMethodFromString, isSafe, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setFormMode, setProgressBarDelay, start, visit };
|
package/dist/turbo.es2017-umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
Turbo 8.0.0-beta.
|
|
2
|
+
Turbo 8.0.0-beta.2
|
|
3
3
|
Copyright © 2023 37signals LLC
|
|
4
4
|
*/
|
|
5
5
|
(function (global, factory) {
|
|
@@ -639,13 +639,33 @@ Copyright © 2023 37signals LLC
|
|
|
639
639
|
return [before, after]
|
|
640
640
|
}
|
|
641
641
|
|
|
642
|
-
|
|
642
|
+
class LimitedSet extends Set {
|
|
643
|
+
constructor(maxSize) {
|
|
644
|
+
super();
|
|
645
|
+
this.maxSize = maxSize;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
add(value) {
|
|
649
|
+
if (this.size >= this.maxSize) {
|
|
650
|
+
const iterator = this.values();
|
|
651
|
+
const oldestValue = iterator.next().value;
|
|
652
|
+
this.delete(oldestValue);
|
|
653
|
+
}
|
|
654
|
+
super.add(value);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const recentRequests = new LimitedSet(20);
|
|
659
|
+
|
|
660
|
+
const nativeFetch = window.fetch;
|
|
661
|
+
|
|
662
|
+
function fetchWithTurboHeaders(url, options = {}) {
|
|
643
663
|
const modifiedHeaders = new Headers(options.headers || {});
|
|
644
664
|
const requestUID = uuid();
|
|
645
|
-
|
|
665
|
+
recentRequests.add(requestUID);
|
|
646
666
|
modifiedHeaders.append("X-Turbo-Request-Id", requestUID);
|
|
647
667
|
|
|
648
|
-
return
|
|
668
|
+
return nativeFetch(url, {
|
|
649
669
|
...options,
|
|
650
670
|
headers: modifiedHeaders
|
|
651
671
|
})
|
|
@@ -772,7 +792,7 @@ Copyright © 2023 37signals LLC
|
|
|
772
792
|
await this.#allowRequestToBeIntercepted(fetchOptions);
|
|
773
793
|
try {
|
|
774
794
|
this.delegate.requestStarted(this);
|
|
775
|
-
const response = await
|
|
795
|
+
const response = await fetchWithTurboHeaders(this.url.href, fetchOptions);
|
|
776
796
|
return await this.receive(response)
|
|
777
797
|
} catch (error) {
|
|
778
798
|
if (error.name !== "AbortError") {
|
|
@@ -1052,6 +1072,7 @@ Copyright © 2023 37signals LLC
|
|
|
1052
1072
|
this.state = FormSubmissionState.waiting;
|
|
1053
1073
|
this.submitter?.setAttribute("disabled", "");
|
|
1054
1074
|
this.setSubmitsWith();
|
|
1075
|
+
markAsBusy(this.formElement);
|
|
1055
1076
|
dispatch("turbo:submit-start", {
|
|
1056
1077
|
target: this.formElement,
|
|
1057
1078
|
detail: { formSubmission: this }
|
|
@@ -1090,6 +1111,7 @@ Copyright © 2023 37signals LLC
|
|
|
1090
1111
|
this.state = FormSubmissionState.stopped;
|
|
1091
1112
|
this.submitter?.removeAttribute("disabled");
|
|
1092
1113
|
this.resetSubmitterText();
|
|
1114
|
+
clearBusyState(this.formElement);
|
|
1093
1115
|
dispatch("turbo:submit-end", {
|
|
1094
1116
|
target: this.formElement,
|
|
1095
1117
|
detail: { formSubmission: this, ...this.result }
|
|
@@ -1417,6 +1439,14 @@ Copyright © 2023 37signals LLC
|
|
|
1417
1439
|
}
|
|
1418
1440
|
}
|
|
1419
1441
|
|
|
1442
|
+
markVisitDirection(direction) {
|
|
1443
|
+
this.element.setAttribute("data-turbo-visit-direction", direction);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
unmarkVisitDirection() {
|
|
1447
|
+
this.element.removeAttribute("data-turbo-visit-direction");
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1420
1450
|
async renderSnapshot(renderer) {
|
|
1421
1451
|
await renderer.render();
|
|
1422
1452
|
}
|
|
@@ -1786,14 +1816,14 @@ Copyright © 2023 37signals LLC
|
|
|
1786
1816
|
}
|
|
1787
1817
|
|
|
1788
1818
|
async render() {
|
|
1789
|
-
await
|
|
1819
|
+
await nextRepaint();
|
|
1790
1820
|
this.preservingPermanentElements(() => {
|
|
1791
1821
|
this.loadFrameElement();
|
|
1792
1822
|
});
|
|
1793
1823
|
this.scrollFrameIntoView();
|
|
1794
|
-
await
|
|
1824
|
+
await nextRepaint();
|
|
1795
1825
|
this.focusFirstAutofocusableElement();
|
|
1796
|
-
await
|
|
1826
|
+
await nextRepaint();
|
|
1797
1827
|
this.activateScriptElements();
|
|
1798
1828
|
}
|
|
1799
1829
|
|
|
@@ -2219,6 +2249,12 @@ Copyright © 2023 37signals LLC
|
|
|
2219
2249
|
contentTypeMismatch: -2
|
|
2220
2250
|
};
|
|
2221
2251
|
|
|
2252
|
+
const Direction = {
|
|
2253
|
+
advance: "forward",
|
|
2254
|
+
restore: "back",
|
|
2255
|
+
replace: "none"
|
|
2256
|
+
};
|
|
2257
|
+
|
|
2222
2258
|
class Visit {
|
|
2223
2259
|
identifier = uuid() // Required by turbo-ios
|
|
2224
2260
|
timingMetrics = {}
|
|
@@ -2248,7 +2284,8 @@ Copyright © 2023 37signals LLC
|
|
|
2248
2284
|
willRender,
|
|
2249
2285
|
updateHistory,
|
|
2250
2286
|
shouldCacheSnapshot,
|
|
2251
|
-
acceptsStreamResponse
|
|
2287
|
+
acceptsStreamResponse,
|
|
2288
|
+
direction
|
|
2252
2289
|
} = {
|
|
2253
2290
|
...defaultOptions,
|
|
2254
2291
|
...options
|
|
@@ -2266,6 +2303,7 @@ Copyright © 2023 37signals LLC
|
|
|
2266
2303
|
this.scrolled = !willRender;
|
|
2267
2304
|
this.shouldCacheSnapshot = shouldCacheSnapshot;
|
|
2268
2305
|
this.acceptsStreamResponse = acceptsStreamResponse;
|
|
2306
|
+
this.direction = direction || Direction[action];
|
|
2269
2307
|
}
|
|
2270
2308
|
|
|
2271
2309
|
get adapter() {
|
|
@@ -2518,7 +2556,7 @@ Copyright © 2023 37signals LLC
|
|
|
2518
2556
|
// Scrolling
|
|
2519
2557
|
|
|
2520
2558
|
performScroll() {
|
|
2521
|
-
if (!this.scrolled && !this.view.forceReloaded && !this.view.
|
|
2559
|
+
if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) {
|
|
2522
2560
|
if (this.action == "restore") {
|
|
2523
2561
|
this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
|
|
2524
2562
|
} else {
|
|
@@ -2593,9 +2631,7 @@ Copyright © 2023 37signals LLC
|
|
|
2593
2631
|
|
|
2594
2632
|
async render(callback) {
|
|
2595
2633
|
this.cancelRender();
|
|
2596
|
-
await
|
|
2597
|
-
this.frame = requestAnimationFrame(() => resolve());
|
|
2598
|
-
});
|
|
2634
|
+
this.frame = await nextRepaint();
|
|
2599
2635
|
await callback();
|
|
2600
2636
|
delete this.frame;
|
|
2601
2637
|
}
|
|
@@ -2873,6 +2909,7 @@ Copyright © 2023 37signals LLC
|
|
|
2873
2909
|
restorationData = {}
|
|
2874
2910
|
started = false
|
|
2875
2911
|
pageLoaded = false
|
|
2912
|
+
currentIndex = 0
|
|
2876
2913
|
|
|
2877
2914
|
constructor(delegate) {
|
|
2878
2915
|
this.delegate = delegate;
|
|
@@ -2882,6 +2919,7 @@ Copyright © 2023 37signals LLC
|
|
|
2882
2919
|
if (!this.started) {
|
|
2883
2920
|
addEventListener("popstate", this.onPopState, false);
|
|
2884
2921
|
addEventListener("load", this.onPageLoad, false);
|
|
2922
|
+
this.currentIndex = history.state?.turbo?.restorationIndex || 0;
|
|
2885
2923
|
this.started = true;
|
|
2886
2924
|
this.replace(new URL(window.location.href));
|
|
2887
2925
|
}
|
|
@@ -2904,7 +2942,9 @@ Copyright © 2023 37signals LLC
|
|
|
2904
2942
|
}
|
|
2905
2943
|
|
|
2906
2944
|
update(method, location, restorationIdentifier = uuid()) {
|
|
2907
|
-
|
|
2945
|
+
if (method === history.pushState) ++this.currentIndex;
|
|
2946
|
+
|
|
2947
|
+
const state = { turbo: { restorationIdentifier, restorationIndex: this.currentIndex } };
|
|
2908
2948
|
method.call(history, state, "", location.href);
|
|
2909
2949
|
this.location = location;
|
|
2910
2950
|
this.restorationIdentifier = restorationIdentifier;
|
|
@@ -2948,9 +2988,11 @@ Copyright © 2023 37signals LLC
|
|
|
2948
2988
|
const { turbo } = event.state || {};
|
|
2949
2989
|
if (turbo) {
|
|
2950
2990
|
this.location = new URL(window.location.href);
|
|
2951
|
-
const { restorationIdentifier } = turbo;
|
|
2991
|
+
const { restorationIdentifier, restorationIndex } = turbo;
|
|
2952
2992
|
this.restorationIdentifier = restorationIdentifier;
|
|
2953
|
-
this.
|
|
2993
|
+
const direction = restorationIndex > this.currentIndex ? "forward" : "back";
|
|
2994
|
+
this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
|
|
2995
|
+
this.currentIndex = restorationIndex;
|
|
2954
2996
|
}
|
|
2955
2997
|
}
|
|
2956
2998
|
}
|
|
@@ -3287,7 +3329,7 @@ Copyright © 2023 37signals LLC
|
|
|
3287
3329
|
}
|
|
3288
3330
|
|
|
3289
3331
|
callback();
|
|
3290
|
-
await
|
|
3332
|
+
await nextRepaint();
|
|
3291
3333
|
|
|
3292
3334
|
const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
|
|
3293
3335
|
|
|
@@ -4541,7 +4583,11 @@ Copyright © 2023 37signals LLC
|
|
|
4541
4583
|
}
|
|
4542
4584
|
|
|
4543
4585
|
isPageRefresh(visit) {
|
|
4544
|
-
return !visit || (this.lastRenderedLocation.
|
|
4586
|
+
return !visit || (this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === "replace")
|
|
4587
|
+
}
|
|
4588
|
+
|
|
4589
|
+
shouldPreserveScrollPosition(visit) {
|
|
4590
|
+
return this.isPageRefresh(visit) && this.snapshot.shouldPreserveScrollPosition
|
|
4545
4591
|
}
|
|
4546
4592
|
|
|
4547
4593
|
get snapshot() {
|
|
@@ -4552,27 +4598,28 @@ Copyright © 2023 37signals LLC
|
|
|
4552
4598
|
class Preloader {
|
|
4553
4599
|
selector = "a[data-turbo-preload]"
|
|
4554
4600
|
|
|
4555
|
-
constructor(delegate) {
|
|
4601
|
+
constructor(delegate, snapshotCache) {
|
|
4556
4602
|
this.delegate = delegate;
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
get snapshotCache() {
|
|
4560
|
-
return this.delegate.navigator.view.snapshotCache
|
|
4603
|
+
this.snapshotCache = snapshotCache;
|
|
4561
4604
|
}
|
|
4562
4605
|
|
|
4563
4606
|
start() {
|
|
4564
4607
|
if (document.readyState === "loading") {
|
|
4565
|
-
|
|
4566
|
-
this.preloadOnLoadLinksForView(document.body);
|
|
4567
|
-
})
|
|
4608
|
+
document.addEventListener("DOMContentLoaded", this.#preloadAll);
|
|
4568
4609
|
} else {
|
|
4569
4610
|
this.preloadOnLoadLinksForView(document.body);
|
|
4570
4611
|
}
|
|
4571
4612
|
}
|
|
4572
4613
|
|
|
4614
|
+
stop() {
|
|
4615
|
+
document.removeEventListener("DOMContentLoaded", this.#preloadAll);
|
|
4616
|
+
}
|
|
4617
|
+
|
|
4573
4618
|
preloadOnLoadLinksForView(element) {
|
|
4574
4619
|
for (const link of element.querySelectorAll(this.selector)) {
|
|
4575
|
-
this.
|
|
4620
|
+
if (this.delegate.shouldPreloadLink(link)) {
|
|
4621
|
+
this.preloadURL(link);
|
|
4622
|
+
}
|
|
4576
4623
|
}
|
|
4577
4624
|
}
|
|
4578
4625
|
|
|
@@ -4583,31 +4630,39 @@ Copyright © 2023 37signals LLC
|
|
|
4583
4630
|
return
|
|
4584
4631
|
}
|
|
4585
4632
|
|
|
4633
|
+
const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams(), link);
|
|
4634
|
+
await fetchRequest.perform();
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4637
|
+
// Fetch request delegate
|
|
4638
|
+
|
|
4639
|
+
prepareRequest(fetchRequest) {
|
|
4640
|
+
fetchRequest.headers["Sec-Purpose"] = "prefetch";
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4643
|
+
async requestSucceededWithResponse(fetchRequest, fetchResponse) {
|
|
4586
4644
|
try {
|
|
4587
|
-
const
|
|
4588
|
-
const
|
|
4589
|
-
const snapshot = PageSnapshot.fromHTMLString(responseText);
|
|
4645
|
+
const responseHTML = await fetchResponse.responseHTML;
|
|
4646
|
+
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
|
4590
4647
|
|
|
4591
|
-
this.snapshotCache.put(
|
|
4648
|
+
this.snapshotCache.put(fetchRequest.url, snapshot);
|
|
4592
4649
|
} catch (_) {
|
|
4593
4650
|
// If we cannot preload that is ok!
|
|
4594
4651
|
}
|
|
4595
4652
|
}
|
|
4596
|
-
}
|
|
4597
4653
|
|
|
4598
|
-
|
|
4599
|
-
constructor(maxSize) {
|
|
4600
|
-
super();
|
|
4601
|
-
this.maxSize = maxSize;
|
|
4602
|
-
}
|
|
4654
|
+
requestStarted(fetchRequest) {}
|
|
4603
4655
|
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4656
|
+
requestErrored(fetchRequest) {}
|
|
4657
|
+
|
|
4658
|
+
requestFinished(fetchRequest) {}
|
|
4659
|
+
|
|
4660
|
+
requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
|
|
4661
|
+
|
|
4662
|
+
requestFailedWithResponse(fetchRequest, fetchResponse) {}
|
|
4663
|
+
|
|
4664
|
+
#preloadAll = () => {
|
|
4665
|
+
this.preloadOnLoadLinksForView(document.body);
|
|
4611
4666
|
}
|
|
4612
4667
|
}
|
|
4613
4668
|
|
|
@@ -4640,7 +4695,6 @@ Copyright © 2023 37signals LLC
|
|
|
4640
4695
|
class Session {
|
|
4641
4696
|
navigator = new Navigator(this)
|
|
4642
4697
|
history = new History(this)
|
|
4643
|
-
preloader = new Preloader(this)
|
|
4644
4698
|
view = new PageView(this, document.documentElement)
|
|
4645
4699
|
adapter = new BrowserAdapter(this)
|
|
4646
4700
|
|
|
@@ -4654,7 +4708,6 @@ Copyright © 2023 37signals LLC
|
|
|
4654
4708
|
frameRedirector = new FrameRedirector(this, document.documentElement)
|
|
4655
4709
|
streamMessageRenderer = new StreamMessageRenderer()
|
|
4656
4710
|
cache = new Cache(this)
|
|
4657
|
-
recentRequests = new LimitedSet(20)
|
|
4658
4711
|
|
|
4659
4712
|
drive = true
|
|
4660
4713
|
enabled = true
|
|
@@ -4662,6 +4715,11 @@ Copyright © 2023 37signals LLC
|
|
|
4662
4715
|
started = false
|
|
4663
4716
|
formMode = "on"
|
|
4664
4717
|
|
|
4718
|
+
constructor(recentRequests) {
|
|
4719
|
+
this.recentRequests = recentRequests;
|
|
4720
|
+
this.preloader = new Preloader(this, this.view.snapshotCache);
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4665
4723
|
start() {
|
|
4666
4724
|
if (!this.started) {
|
|
4667
4725
|
this.pageObserver.start();
|
|
@@ -4694,6 +4752,7 @@ Copyright © 2023 37signals LLC
|
|
|
4694
4752
|
this.streamObserver.stop();
|
|
4695
4753
|
this.frameRedirector.stop();
|
|
4696
4754
|
this.history.stop();
|
|
4755
|
+
this.preloader.stop();
|
|
4697
4756
|
this.started = false;
|
|
4698
4757
|
}
|
|
4699
4758
|
}
|
|
@@ -4753,13 +4812,33 @@ Copyright © 2023 37signals LLC
|
|
|
4753
4812
|
return this.history.restorationIdentifier
|
|
4754
4813
|
}
|
|
4755
4814
|
|
|
4815
|
+
// Preloader delegate
|
|
4816
|
+
|
|
4817
|
+
shouldPreloadLink(element) {
|
|
4818
|
+
const isUnsafe = element.hasAttribute("data-turbo-method");
|
|
4819
|
+
const isStream = element.hasAttribute("data-turbo-stream");
|
|
4820
|
+
const frameTarget = element.getAttribute("data-turbo-frame");
|
|
4821
|
+
const frame = frameTarget == "_top" ?
|
|
4822
|
+
null :
|
|
4823
|
+
document.getElementById(frameTarget) || findClosestRecursively(element, "turbo-frame:not([disabled])");
|
|
4824
|
+
|
|
4825
|
+
if (isUnsafe || isStream || frame instanceof FrameElement) {
|
|
4826
|
+
return false
|
|
4827
|
+
} else {
|
|
4828
|
+
const location = new URL(element.href);
|
|
4829
|
+
|
|
4830
|
+
return this.elementIsNavigatable(element) && locationIsVisitable(location, this.snapshot.rootLocation)
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4833
|
+
|
|
4756
4834
|
// History delegate
|
|
4757
4835
|
|
|
4758
|
-
|
|
4836
|
+
historyPoppedToLocationWithRestorationIdentifierAndDirection(location, restorationIdentifier, direction) {
|
|
4759
4837
|
if (this.enabled) {
|
|
4760
4838
|
this.navigator.startVisit(location, restorationIdentifier, {
|
|
4761
4839
|
action: "restore",
|
|
4762
|
-
historyChanged: true
|
|
4840
|
+
historyChanged: true,
|
|
4841
|
+
direction
|
|
4763
4842
|
});
|
|
4764
4843
|
} else {
|
|
4765
4844
|
this.adapter.pageInvalidated({
|
|
@@ -4815,6 +4894,7 @@ Copyright © 2023 37signals LLC
|
|
|
4815
4894
|
visitStarted(visit) {
|
|
4816
4895
|
if (!visit.acceptsStreamResponse) {
|
|
4817
4896
|
markAsBusy(document.documentElement);
|
|
4897
|
+
this.view.markVisitDirection(visit.direction);
|
|
4818
4898
|
}
|
|
4819
4899
|
extendURLWithDeprecatedProperties(visit.location);
|
|
4820
4900
|
if (!visit.silent) {
|
|
@@ -4823,6 +4903,7 @@ Copyright © 2023 37signals LLC
|
|
|
4823
4903
|
}
|
|
4824
4904
|
|
|
4825
4905
|
visitCompleted(visit) {
|
|
4906
|
+
this.view.unmarkVisitDirection();
|
|
4826
4907
|
clearBusyState(document.documentElement);
|
|
4827
4908
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
|
4828
4909
|
}
|
|
@@ -5061,7 +5142,7 @@ Copyright © 2023 37signals LLC
|
|
|
5061
5142
|
}
|
|
5062
5143
|
};
|
|
5063
5144
|
|
|
5064
|
-
const session = new Session();
|
|
5145
|
+
const session = new Session(recentRequests);
|
|
5065
5146
|
const { cache, navigator: navigator$1 } = session;
|
|
5066
5147
|
|
|
5067
5148
|
/**
|
|
@@ -5171,7 +5252,7 @@ Copyright © 2023 37signals LLC
|
|
|
5171
5252
|
PageRenderer: PageRenderer,
|
|
5172
5253
|
PageSnapshot: PageSnapshot,
|
|
5173
5254
|
FrameRenderer: FrameRenderer,
|
|
5174
|
-
fetch:
|
|
5255
|
+
fetch: fetchWithTurboHeaders,
|
|
5175
5256
|
start: start,
|
|
5176
5257
|
registerAdapter: registerAdapter,
|
|
5177
5258
|
visit: visit,
|
|
@@ -6040,7 +6121,7 @@ Copyright © 2023 37signals LLC
|
|
|
6040
6121
|
}
|
|
6041
6122
|
})();
|
|
6042
6123
|
|
|
6043
|
-
window.Turbo = Turbo;
|
|
6124
|
+
window.Turbo = { ...Turbo, StreamActions };
|
|
6044
6125
|
start();
|
|
6045
6126
|
|
|
6046
6127
|
exports.FetchEnctype = FetchEnctype;
|
|
@@ -6059,7 +6140,7 @@ Copyright © 2023 37signals LLC
|
|
|
6059
6140
|
exports.clearCache = clearCache;
|
|
6060
6141
|
exports.connectStreamSource = connectStreamSource;
|
|
6061
6142
|
exports.disconnectStreamSource = disconnectStreamSource;
|
|
6062
|
-
exports.fetch =
|
|
6143
|
+
exports.fetch = fetchWithTurboHeaders;
|
|
6063
6144
|
exports.fetchEnctypeFromString = fetchEnctypeFromString;
|
|
6064
6145
|
exports.fetchMethodFromString = fetchMethodFromString;
|
|
6065
6146
|
exports.isSafe = isSafe;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotwired/turbo",
|
|
3
|
-
"version": "8.0.0-beta.
|
|
3
|
+
"version": "8.0.0-beta.2",
|
|
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",
|
|
@@ -32,9 +32,6 @@
|
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
|
-
"dependencies": {
|
|
36
|
-
"idiomorph": "https://github.com/basecamp/idiomorph#rollout-build"
|
|
37
|
-
},
|
|
38
35
|
"devDependencies": {
|
|
39
36
|
"@open-wc/testing": "^3.1.7",
|
|
40
37
|
"@playwright/test": "^1.28.0",
|
|
@@ -47,6 +44,7 @@
|
|
|
47
44
|
"chai": "~4.3.4",
|
|
48
45
|
"eslint": "^8.13.0",
|
|
49
46
|
"express": "^4.18.2",
|
|
47
|
+
"idiomorph": "https://github.com/basecamp/idiomorph#rollout-build",
|
|
50
48
|
"multer": "^1.4.2",
|
|
51
49
|
"rollup": "^2.35.1"
|
|
52
50
|
},
|