@hotwired/turbo 8.0.0-beta.4 → 8.0.0-rc.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 +143 -108
- package/dist/turbo.es2017-umd.js +143 -108
- package/package.json +2 -2
package/dist/turbo.es2017-esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
Turbo 8.0.0-
|
|
2
|
+
Turbo 8.0.0-rc.2
|
|
3
3
|
Copyright © 2024 37signals LLC
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
@@ -1469,7 +1469,12 @@ class View {
|
|
|
1469
1469
|
// Rendering
|
|
1470
1470
|
|
|
1471
1471
|
async render(renderer) {
|
|
1472
|
-
const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
|
|
1472
|
+
const { isPreview, shouldRender, willRender, newSnapshot: snapshot } = renderer;
|
|
1473
|
+
|
|
1474
|
+
// A workaround to ignore tracked element mismatch reloads when performing
|
|
1475
|
+
// a promoted Visit from a frame navigation
|
|
1476
|
+
const shouldInvalidate = willRender;
|
|
1477
|
+
|
|
1473
1478
|
if (shouldRender) {
|
|
1474
1479
|
try {
|
|
1475
1480
|
this.renderPromise = new Promise((resolve) => (this.#resolveRenderPromise = resolve));
|
|
@@ -1477,7 +1482,7 @@ class View {
|
|
|
1477
1482
|
await this.prepareToRenderSnapshot(renderer);
|
|
1478
1483
|
|
|
1479
1484
|
const renderInterception = new Promise((resolve) => (this.#resolveInterceptionPromise = resolve));
|
|
1480
|
-
const options = { resume: this.#resolveInterceptionPromise, render: this.renderer.renderElement };
|
|
1485
|
+
const options = { resume: this.#resolveInterceptionPromise, render: this.renderer.renderElement, renderMethod: this.renderer.renderMethod };
|
|
1481
1486
|
const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
|
|
1482
1487
|
if (!immediateRender) await renderInterception;
|
|
1483
1488
|
|
|
@@ -1490,7 +1495,7 @@ class View {
|
|
|
1490
1495
|
this.#resolveRenderPromise(undefined);
|
|
1491
1496
|
delete this.renderPromise;
|
|
1492
1497
|
}
|
|
1493
|
-
} else {
|
|
1498
|
+
} else if (shouldInvalidate) {
|
|
1494
1499
|
this.invalidate(renderer.reloadReason);
|
|
1495
1500
|
}
|
|
1496
1501
|
}
|
|
@@ -2360,6 +2365,7 @@ class Visit {
|
|
|
2360
2365
|
this.snapshotHTML = snapshotHTML;
|
|
2361
2366
|
this.response = response;
|
|
2362
2367
|
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
|
2368
|
+
this.isPageRefresh = this.view.isPageRefresh(this);
|
|
2363
2369
|
this.visitCachedSnapshot = visitCachedSnapshot;
|
|
2364
2370
|
this.willRender = willRender;
|
|
2365
2371
|
this.updateHistory = updateHistory;
|
|
@@ -2525,7 +2531,7 @@ class Visit {
|
|
|
2525
2531
|
const isPreview = this.shouldIssueRequest();
|
|
2526
2532
|
this.render(async () => {
|
|
2527
2533
|
this.cacheSnapshot();
|
|
2528
|
-
if (this.isSamePage) {
|
|
2534
|
+
if (this.isSamePage || this.isPageRefresh) {
|
|
2529
2535
|
this.adapter.visitRendered(this);
|
|
2530
2536
|
} else {
|
|
2531
2537
|
if (this.view.renderPromise) await this.view.renderPromise;
|
|
@@ -3167,7 +3173,7 @@ class LinkPrefetchObserver {
|
|
|
3167
3173
|
prepareRequest(request) {
|
|
3168
3174
|
const link = request.target;
|
|
3169
3175
|
|
|
3170
|
-
request.headers["Sec-Purpose"] = "prefetch";
|
|
3176
|
+
request.headers["X-Sec-Purpose"] = "prefetch";
|
|
3171
3177
|
|
|
3172
3178
|
const turboFrame = link.closest("turbo-frame");
|
|
3173
3179
|
const turboFrameTarget = link.getAttribute("data-turbo-frame") || turboFrame?.getAttribute("target") || turboFrame?.id;
|
|
@@ -3177,7 +3183,7 @@ class LinkPrefetchObserver {
|
|
|
3177
3183
|
}
|
|
3178
3184
|
|
|
3179
3185
|
if (link.hasAttribute("data-turbo-stream")) {
|
|
3180
|
-
request.acceptResponseType(
|
|
3186
|
+
request.acceptResponseType(StreamMessage.contentType);
|
|
3181
3187
|
}
|
|
3182
3188
|
}
|
|
3183
3189
|
|
|
@@ -3202,7 +3208,7 @@ class LinkPrefetchObserver {
|
|
|
3202
3208
|
#isPrefetchable(link) {
|
|
3203
3209
|
const href = link.getAttribute("href");
|
|
3204
3210
|
|
|
3205
|
-
if (!href || href === "#" || link.
|
|
3211
|
+
if (!href || href === "#" || link.getAttribute("data-turbo") === "false" || link.getAttribute("data-turbo-prefetch") === "false") {
|
|
3206
3212
|
return false
|
|
3207
3213
|
}
|
|
3208
3214
|
|
|
@@ -3218,7 +3224,8 @@ class LinkPrefetchObserver {
|
|
|
3218
3224
|
return false
|
|
3219
3225
|
}
|
|
3220
3226
|
|
|
3221
|
-
|
|
3227
|
+
const turboMethod = link.getAttribute("data-turbo-method");
|
|
3228
|
+
if (turboMethod && turboMethod !== "get") {
|
|
3222
3229
|
return false
|
|
3223
3230
|
}
|
|
3224
3231
|
|
|
@@ -3226,13 +3233,9 @@ class LinkPrefetchObserver {
|
|
|
3226
3233
|
return false
|
|
3227
3234
|
}
|
|
3228
3235
|
|
|
3229
|
-
if (link.pathname + link.search === document.location.pathname + document.location.search) {
|
|
3230
|
-
return false
|
|
3231
|
-
}
|
|
3232
|
-
|
|
3233
3236
|
const turboPrefetchParent = findClosestRecursively(link, "[data-turbo-prefetch]");
|
|
3234
3237
|
|
|
3235
|
-
if (turboPrefetchParent && turboPrefetchParent.
|
|
3238
|
+
if (turboPrefetchParent && turboPrefetchParent.getAttribute("data-turbo-prefetch") === "false") {
|
|
3236
3239
|
return false
|
|
3237
3240
|
}
|
|
3238
3241
|
|
|
@@ -3825,7 +3828,7 @@ var Idiomorph = (function () {
|
|
|
3825
3828
|
* @returns {boolean}
|
|
3826
3829
|
*/
|
|
3827
3830
|
function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
|
|
3828
|
-
return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement;
|
|
3831
|
+
return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
|
|
3829
3832
|
}
|
|
3830
3833
|
|
|
3831
3834
|
/**
|
|
@@ -4547,92 +4550,6 @@ var Idiomorph = (function () {
|
|
|
4547
4550
|
}
|
|
4548
4551
|
})();
|
|
4549
4552
|
|
|
4550
|
-
class MorphRenderer extends Renderer {
|
|
4551
|
-
async render() {
|
|
4552
|
-
if (this.willRender) await this.#morphBody();
|
|
4553
|
-
}
|
|
4554
|
-
|
|
4555
|
-
get renderMethod() {
|
|
4556
|
-
return "morph"
|
|
4557
|
-
}
|
|
4558
|
-
|
|
4559
|
-
// Private
|
|
4560
|
-
|
|
4561
|
-
async #morphBody() {
|
|
4562
|
-
this.#morphElements(this.currentElement, this.newElement);
|
|
4563
|
-
this.#reloadRemoteFrames();
|
|
4564
|
-
|
|
4565
|
-
dispatch("turbo:morph", {
|
|
4566
|
-
detail: {
|
|
4567
|
-
currentElement: this.currentElement,
|
|
4568
|
-
newElement: this.newElement
|
|
4569
|
-
}
|
|
4570
|
-
});
|
|
4571
|
-
}
|
|
4572
|
-
|
|
4573
|
-
#morphElements(currentElement, newElement, morphStyle = "outerHTML") {
|
|
4574
|
-
this.isMorphingTurboFrame = this.#isFrameReloadedWithMorph(currentElement);
|
|
4575
|
-
|
|
4576
|
-
Idiomorph.morph(currentElement, newElement, {
|
|
4577
|
-
morphStyle: morphStyle,
|
|
4578
|
-
callbacks: {
|
|
4579
|
-
beforeNodeAdded: this.#shouldAddElement,
|
|
4580
|
-
beforeNodeMorphed: this.#shouldMorphElement,
|
|
4581
|
-
beforeNodeRemoved: this.#shouldRemoveElement
|
|
4582
|
-
}
|
|
4583
|
-
});
|
|
4584
|
-
}
|
|
4585
|
-
|
|
4586
|
-
#shouldAddElement = (node) => {
|
|
4587
|
-
return !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id))
|
|
4588
|
-
}
|
|
4589
|
-
|
|
4590
|
-
#shouldMorphElement = (oldNode, newNode) => {
|
|
4591
|
-
if (oldNode instanceof HTMLElement) {
|
|
4592
|
-
return !oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode))
|
|
4593
|
-
} else {
|
|
4594
|
-
return true
|
|
4595
|
-
}
|
|
4596
|
-
}
|
|
4597
|
-
|
|
4598
|
-
#shouldRemoveElement = (node) => {
|
|
4599
|
-
return this.#shouldMorphElement(node)
|
|
4600
|
-
}
|
|
4601
|
-
|
|
4602
|
-
#reloadRemoteFrames() {
|
|
4603
|
-
this.#remoteFrames().forEach((frame) => {
|
|
4604
|
-
if (this.#isFrameReloadedWithMorph(frame)) {
|
|
4605
|
-
this.#renderFrameWithMorph(frame);
|
|
4606
|
-
frame.reload();
|
|
4607
|
-
}
|
|
4608
|
-
});
|
|
4609
|
-
}
|
|
4610
|
-
|
|
4611
|
-
#renderFrameWithMorph(frame) {
|
|
4612
|
-
frame.addEventListener("turbo:before-frame-render", (event) => {
|
|
4613
|
-
event.detail.render = this.#morphFrameUpdate;
|
|
4614
|
-
}, { once: true });
|
|
4615
|
-
}
|
|
4616
|
-
|
|
4617
|
-
#morphFrameUpdate = (currentElement, newElement) => {
|
|
4618
|
-
dispatch("turbo:before-frame-morph", {
|
|
4619
|
-
target: currentElement,
|
|
4620
|
-
detail: { currentElement, newElement }
|
|
4621
|
-
});
|
|
4622
|
-
this.#morphElements(currentElement, newElement.children, "innerHTML");
|
|
4623
|
-
}
|
|
4624
|
-
|
|
4625
|
-
#isFrameReloadedWithMorph(element) {
|
|
4626
|
-
return element.src && element.refresh === "morph"
|
|
4627
|
-
}
|
|
4628
|
-
|
|
4629
|
-
#remoteFrames() {
|
|
4630
|
-
return Array.from(document.querySelectorAll('turbo-frame[src]')).filter(frame => {
|
|
4631
|
-
return !frame.closest('[data-turbo-permanent]')
|
|
4632
|
-
})
|
|
4633
|
-
}
|
|
4634
|
-
}
|
|
4635
|
-
|
|
4636
4553
|
class PageRenderer extends Renderer {
|
|
4637
4554
|
static renderElement(currentElement, newElement) {
|
|
4638
4555
|
if (document.body && newElement instanceof HTMLBodyElement) {
|
|
@@ -4845,6 +4762,122 @@ class PageRenderer extends Renderer {
|
|
|
4845
4762
|
}
|
|
4846
4763
|
}
|
|
4847
4764
|
|
|
4765
|
+
class MorphRenderer extends PageRenderer {
|
|
4766
|
+
async render() {
|
|
4767
|
+
if (this.willRender) await this.#morphBody();
|
|
4768
|
+
}
|
|
4769
|
+
|
|
4770
|
+
get renderMethod() {
|
|
4771
|
+
return "morph"
|
|
4772
|
+
}
|
|
4773
|
+
|
|
4774
|
+
// Private
|
|
4775
|
+
|
|
4776
|
+
async #morphBody() {
|
|
4777
|
+
this.#morphElements(this.currentElement, this.newElement);
|
|
4778
|
+
this.#reloadRemoteFrames();
|
|
4779
|
+
|
|
4780
|
+
dispatch("turbo:morph", {
|
|
4781
|
+
detail: {
|
|
4782
|
+
currentElement: this.currentElement,
|
|
4783
|
+
newElement: this.newElement
|
|
4784
|
+
}
|
|
4785
|
+
});
|
|
4786
|
+
}
|
|
4787
|
+
|
|
4788
|
+
#morphElements(currentElement, newElement, morphStyle = "outerHTML") {
|
|
4789
|
+
this.isMorphingTurboFrame = this.#isFrameReloadedWithMorph(currentElement);
|
|
4790
|
+
|
|
4791
|
+
Idiomorph.morph(currentElement, newElement, {
|
|
4792
|
+
ignoreActiveValue: true,
|
|
4793
|
+
morphStyle: morphStyle,
|
|
4794
|
+
callbacks: {
|
|
4795
|
+
beforeNodeAdded: this.#shouldAddElement,
|
|
4796
|
+
beforeNodeMorphed: this.#shouldMorphElement,
|
|
4797
|
+
beforeAttributeUpdated: this.#shouldUpdateAttribute,
|
|
4798
|
+
beforeNodeRemoved: this.#shouldRemoveElement,
|
|
4799
|
+
afterNodeMorphed: this.#didMorphElement
|
|
4800
|
+
}
|
|
4801
|
+
});
|
|
4802
|
+
}
|
|
4803
|
+
|
|
4804
|
+
#shouldAddElement = (node) => {
|
|
4805
|
+
return !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id))
|
|
4806
|
+
}
|
|
4807
|
+
|
|
4808
|
+
#shouldMorphElement = (oldNode, newNode) => {
|
|
4809
|
+
if (oldNode instanceof HTMLElement) {
|
|
4810
|
+
if (!oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode))) {
|
|
4811
|
+
const event = dispatch("turbo:before-morph-element", {
|
|
4812
|
+
cancelable: true,
|
|
4813
|
+
target: oldNode,
|
|
4814
|
+
detail: {
|
|
4815
|
+
newElement: newNode
|
|
4816
|
+
}
|
|
4817
|
+
});
|
|
4818
|
+
|
|
4819
|
+
return !event.defaultPrevented
|
|
4820
|
+
} else {
|
|
4821
|
+
return false
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
|
|
4826
|
+
#shouldUpdateAttribute = (attributeName, target, mutationType) => {
|
|
4827
|
+
const event = dispatch("turbo:before-morph-attribute", { cancelable: true, target, detail: { attributeName, mutationType } });
|
|
4828
|
+
|
|
4829
|
+
return !event.defaultPrevented
|
|
4830
|
+
}
|
|
4831
|
+
|
|
4832
|
+
#didMorphElement = (oldNode, newNode) => {
|
|
4833
|
+
if (newNode instanceof HTMLElement) {
|
|
4834
|
+
dispatch("turbo:morph-element", {
|
|
4835
|
+
target: oldNode,
|
|
4836
|
+
detail: {
|
|
4837
|
+
newElement: newNode
|
|
4838
|
+
}
|
|
4839
|
+
});
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
|
|
4843
|
+
#shouldRemoveElement = (node) => {
|
|
4844
|
+
return this.#shouldMorphElement(node)
|
|
4845
|
+
}
|
|
4846
|
+
|
|
4847
|
+
#reloadRemoteFrames() {
|
|
4848
|
+
this.#remoteFrames().forEach((frame) => {
|
|
4849
|
+
if (this.#isFrameReloadedWithMorph(frame)) {
|
|
4850
|
+
this.#renderFrameWithMorph(frame);
|
|
4851
|
+
frame.reload();
|
|
4852
|
+
}
|
|
4853
|
+
});
|
|
4854
|
+
}
|
|
4855
|
+
|
|
4856
|
+
#renderFrameWithMorph(frame) {
|
|
4857
|
+
frame.addEventListener("turbo:before-frame-render", (event) => {
|
|
4858
|
+
event.detail.render = this.#morphFrameUpdate;
|
|
4859
|
+
}, { once: true });
|
|
4860
|
+
}
|
|
4861
|
+
|
|
4862
|
+
#morphFrameUpdate = (currentElement, newElement) => {
|
|
4863
|
+
dispatch("turbo:before-frame-morph", {
|
|
4864
|
+
target: currentElement,
|
|
4865
|
+
detail: { currentElement, newElement }
|
|
4866
|
+
});
|
|
4867
|
+
this.#morphElements(currentElement, newElement.children, "innerHTML");
|
|
4868
|
+
}
|
|
4869
|
+
|
|
4870
|
+
#isFrameReloadedWithMorph(element) {
|
|
4871
|
+
return element.src && element.refresh === "morph"
|
|
4872
|
+
}
|
|
4873
|
+
|
|
4874
|
+
#remoteFrames() {
|
|
4875
|
+
return Array.from(document.querySelectorAll('turbo-frame[src]')).filter(frame => {
|
|
4876
|
+
return !frame.closest('[data-turbo-permanent]')
|
|
4877
|
+
})
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4880
|
+
|
|
4848
4881
|
class SnapshotCache {
|
|
4849
4882
|
keys = []
|
|
4850
4883
|
snapshots = {}
|
|
@@ -5004,7 +5037,7 @@ class Preloader {
|
|
|
5004
5037
|
// Fetch request delegate
|
|
5005
5038
|
|
|
5006
5039
|
prepareRequest(fetchRequest) {
|
|
5007
|
-
fetchRequest.headers["Sec-Purpose"] = "prefetch";
|
|
5040
|
+
fetchRequest.headers["X-Sec-Purpose"] = "prefetch";
|
|
5008
5041
|
}
|
|
5009
5042
|
|
|
5010
5043
|
async requestSucceededWithResponse(fetchRequest, fetchResponse) {
|
|
@@ -5138,8 +5171,10 @@ class Session {
|
|
|
5138
5171
|
const frameElement = options.frame ? document.getElementById(options.frame) : null;
|
|
5139
5172
|
|
|
5140
5173
|
if (frameElement instanceof FrameElement) {
|
|
5174
|
+
const action = options.action || getVisitAction(frameElement);
|
|
5175
|
+
|
|
5176
|
+
frameElement.delegate.proposeVisitIfNavigatedWithAction(frameElement, action);
|
|
5141
5177
|
frameElement.src = location.toString();
|
|
5142
|
-
frameElement.loaded;
|
|
5143
5178
|
} else {
|
|
5144
5179
|
this.navigator.proposeVisit(expandURL(location), options);
|
|
5145
5180
|
}
|
|
@@ -5780,7 +5815,7 @@ class FrameController {
|
|
|
5780
5815
|
// Appearance observer delegate
|
|
5781
5816
|
|
|
5782
5817
|
elementAppearedInViewport(element) {
|
|
5783
|
-
this.proposeVisitIfNavigatedWithAction(element, element);
|
|
5818
|
+
this.proposeVisitIfNavigatedWithAction(element, getVisitAction(element));
|
|
5784
5819
|
this.#loadSourceURL();
|
|
5785
5820
|
}
|
|
5786
5821
|
|
|
@@ -5868,7 +5903,7 @@ class FrameController {
|
|
|
5868
5903
|
formSubmissionSucceededWithResponse(formSubmission, response) {
|
|
5869
5904
|
const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter);
|
|
5870
5905
|
|
|
5871
|
-
frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.
|
|
5906
|
+
frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame));
|
|
5872
5907
|
frame.delegate.loadResponse(response);
|
|
5873
5908
|
|
|
5874
5909
|
if (!formSubmission.isSafe) {
|
|
@@ -5973,15 +6008,15 @@ class FrameController {
|
|
|
5973
6008
|
#navigateFrame(element, url, submitter) {
|
|
5974
6009
|
const frame = this.#findFrameElement(element, submitter);
|
|
5975
6010
|
|
|
5976
|
-
frame.delegate.proposeVisitIfNavigatedWithAction(frame, element,
|
|
6011
|
+
frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(submitter, element, frame));
|
|
5977
6012
|
|
|
5978
6013
|
this.#withCurrentNavigationElement(element, () => {
|
|
5979
6014
|
frame.src = url;
|
|
5980
6015
|
});
|
|
5981
6016
|
}
|
|
5982
6017
|
|
|
5983
|
-
proposeVisitIfNavigatedWithAction(frame,
|
|
5984
|
-
this.action =
|
|
6018
|
+
proposeVisitIfNavigatedWithAction(frame, action = null) {
|
|
6019
|
+
this.action = action;
|
|
5985
6020
|
|
|
5986
6021
|
if (this.action) {
|
|
5987
6022
|
const pageSnapshot = PageSnapshot.fromElement(frame).clone();
|
package/dist/turbo.es2017-umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
Turbo 8.0.0-
|
|
2
|
+
Turbo 8.0.0-rc.2
|
|
3
3
|
Copyright © 2024 37signals LLC
|
|
4
4
|
*/
|
|
5
5
|
(function (global, factory) {
|
|
@@ -1475,7 +1475,12 @@ Copyright © 2024 37signals LLC
|
|
|
1475
1475
|
// Rendering
|
|
1476
1476
|
|
|
1477
1477
|
async render(renderer) {
|
|
1478
|
-
const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
|
|
1478
|
+
const { isPreview, shouldRender, willRender, newSnapshot: snapshot } = renderer;
|
|
1479
|
+
|
|
1480
|
+
// A workaround to ignore tracked element mismatch reloads when performing
|
|
1481
|
+
// a promoted Visit from a frame navigation
|
|
1482
|
+
const shouldInvalidate = willRender;
|
|
1483
|
+
|
|
1479
1484
|
if (shouldRender) {
|
|
1480
1485
|
try {
|
|
1481
1486
|
this.renderPromise = new Promise((resolve) => (this.#resolveRenderPromise = resolve));
|
|
@@ -1483,7 +1488,7 @@ Copyright © 2024 37signals LLC
|
|
|
1483
1488
|
await this.prepareToRenderSnapshot(renderer);
|
|
1484
1489
|
|
|
1485
1490
|
const renderInterception = new Promise((resolve) => (this.#resolveInterceptionPromise = resolve));
|
|
1486
|
-
const options = { resume: this.#resolveInterceptionPromise, render: this.renderer.renderElement };
|
|
1491
|
+
const options = { resume: this.#resolveInterceptionPromise, render: this.renderer.renderElement, renderMethod: this.renderer.renderMethod };
|
|
1487
1492
|
const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
|
|
1488
1493
|
if (!immediateRender) await renderInterception;
|
|
1489
1494
|
|
|
@@ -1496,7 +1501,7 @@ Copyright © 2024 37signals LLC
|
|
|
1496
1501
|
this.#resolveRenderPromise(undefined);
|
|
1497
1502
|
delete this.renderPromise;
|
|
1498
1503
|
}
|
|
1499
|
-
} else {
|
|
1504
|
+
} else if (shouldInvalidate) {
|
|
1500
1505
|
this.invalidate(renderer.reloadReason);
|
|
1501
1506
|
}
|
|
1502
1507
|
}
|
|
@@ -2366,6 +2371,7 @@ Copyright © 2024 37signals LLC
|
|
|
2366
2371
|
this.snapshotHTML = snapshotHTML;
|
|
2367
2372
|
this.response = response;
|
|
2368
2373
|
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
|
2374
|
+
this.isPageRefresh = this.view.isPageRefresh(this);
|
|
2369
2375
|
this.visitCachedSnapshot = visitCachedSnapshot;
|
|
2370
2376
|
this.willRender = willRender;
|
|
2371
2377
|
this.updateHistory = updateHistory;
|
|
@@ -2531,7 +2537,7 @@ Copyright © 2024 37signals LLC
|
|
|
2531
2537
|
const isPreview = this.shouldIssueRequest();
|
|
2532
2538
|
this.render(async () => {
|
|
2533
2539
|
this.cacheSnapshot();
|
|
2534
|
-
if (this.isSamePage) {
|
|
2540
|
+
if (this.isSamePage || this.isPageRefresh) {
|
|
2535
2541
|
this.adapter.visitRendered(this);
|
|
2536
2542
|
} else {
|
|
2537
2543
|
if (this.view.renderPromise) await this.view.renderPromise;
|
|
@@ -3173,7 +3179,7 @@ Copyright © 2024 37signals LLC
|
|
|
3173
3179
|
prepareRequest(request) {
|
|
3174
3180
|
const link = request.target;
|
|
3175
3181
|
|
|
3176
|
-
request.headers["Sec-Purpose"] = "prefetch";
|
|
3182
|
+
request.headers["X-Sec-Purpose"] = "prefetch";
|
|
3177
3183
|
|
|
3178
3184
|
const turboFrame = link.closest("turbo-frame");
|
|
3179
3185
|
const turboFrameTarget = link.getAttribute("data-turbo-frame") || turboFrame?.getAttribute("target") || turboFrame?.id;
|
|
@@ -3183,7 +3189,7 @@ Copyright © 2024 37signals LLC
|
|
|
3183
3189
|
}
|
|
3184
3190
|
|
|
3185
3191
|
if (link.hasAttribute("data-turbo-stream")) {
|
|
3186
|
-
request.acceptResponseType(
|
|
3192
|
+
request.acceptResponseType(StreamMessage.contentType);
|
|
3187
3193
|
}
|
|
3188
3194
|
}
|
|
3189
3195
|
|
|
@@ -3208,7 +3214,7 @@ Copyright © 2024 37signals LLC
|
|
|
3208
3214
|
#isPrefetchable(link) {
|
|
3209
3215
|
const href = link.getAttribute("href");
|
|
3210
3216
|
|
|
3211
|
-
if (!href || href === "#" || link.
|
|
3217
|
+
if (!href || href === "#" || link.getAttribute("data-turbo") === "false" || link.getAttribute("data-turbo-prefetch") === "false") {
|
|
3212
3218
|
return false
|
|
3213
3219
|
}
|
|
3214
3220
|
|
|
@@ -3224,7 +3230,8 @@ Copyright © 2024 37signals LLC
|
|
|
3224
3230
|
return false
|
|
3225
3231
|
}
|
|
3226
3232
|
|
|
3227
|
-
|
|
3233
|
+
const turboMethod = link.getAttribute("data-turbo-method");
|
|
3234
|
+
if (turboMethod && turboMethod !== "get") {
|
|
3228
3235
|
return false
|
|
3229
3236
|
}
|
|
3230
3237
|
|
|
@@ -3232,13 +3239,9 @@ Copyright © 2024 37signals LLC
|
|
|
3232
3239
|
return false
|
|
3233
3240
|
}
|
|
3234
3241
|
|
|
3235
|
-
if (link.pathname + link.search === document.location.pathname + document.location.search) {
|
|
3236
|
-
return false
|
|
3237
|
-
}
|
|
3238
|
-
|
|
3239
3242
|
const turboPrefetchParent = findClosestRecursively(link, "[data-turbo-prefetch]");
|
|
3240
3243
|
|
|
3241
|
-
if (turboPrefetchParent && turboPrefetchParent.
|
|
3244
|
+
if (turboPrefetchParent && turboPrefetchParent.getAttribute("data-turbo-prefetch") === "false") {
|
|
3242
3245
|
return false
|
|
3243
3246
|
}
|
|
3244
3247
|
|
|
@@ -3831,7 +3834,7 @@ Copyright © 2024 37signals LLC
|
|
|
3831
3834
|
* @returns {boolean}
|
|
3832
3835
|
*/
|
|
3833
3836
|
function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
|
|
3834
|
-
return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement;
|
|
3837
|
+
return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
|
|
3835
3838
|
}
|
|
3836
3839
|
|
|
3837
3840
|
/**
|
|
@@ -4553,92 +4556,6 @@ Copyright © 2024 37signals LLC
|
|
|
4553
4556
|
}
|
|
4554
4557
|
})();
|
|
4555
4558
|
|
|
4556
|
-
class MorphRenderer extends Renderer {
|
|
4557
|
-
async render() {
|
|
4558
|
-
if (this.willRender) await this.#morphBody();
|
|
4559
|
-
}
|
|
4560
|
-
|
|
4561
|
-
get renderMethod() {
|
|
4562
|
-
return "morph"
|
|
4563
|
-
}
|
|
4564
|
-
|
|
4565
|
-
// Private
|
|
4566
|
-
|
|
4567
|
-
async #morphBody() {
|
|
4568
|
-
this.#morphElements(this.currentElement, this.newElement);
|
|
4569
|
-
this.#reloadRemoteFrames();
|
|
4570
|
-
|
|
4571
|
-
dispatch("turbo:morph", {
|
|
4572
|
-
detail: {
|
|
4573
|
-
currentElement: this.currentElement,
|
|
4574
|
-
newElement: this.newElement
|
|
4575
|
-
}
|
|
4576
|
-
});
|
|
4577
|
-
}
|
|
4578
|
-
|
|
4579
|
-
#morphElements(currentElement, newElement, morphStyle = "outerHTML") {
|
|
4580
|
-
this.isMorphingTurboFrame = this.#isFrameReloadedWithMorph(currentElement);
|
|
4581
|
-
|
|
4582
|
-
Idiomorph.morph(currentElement, newElement, {
|
|
4583
|
-
morphStyle: morphStyle,
|
|
4584
|
-
callbacks: {
|
|
4585
|
-
beforeNodeAdded: this.#shouldAddElement,
|
|
4586
|
-
beforeNodeMorphed: this.#shouldMorphElement,
|
|
4587
|
-
beforeNodeRemoved: this.#shouldRemoveElement
|
|
4588
|
-
}
|
|
4589
|
-
});
|
|
4590
|
-
}
|
|
4591
|
-
|
|
4592
|
-
#shouldAddElement = (node) => {
|
|
4593
|
-
return !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id))
|
|
4594
|
-
}
|
|
4595
|
-
|
|
4596
|
-
#shouldMorphElement = (oldNode, newNode) => {
|
|
4597
|
-
if (oldNode instanceof HTMLElement) {
|
|
4598
|
-
return !oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode))
|
|
4599
|
-
} else {
|
|
4600
|
-
return true
|
|
4601
|
-
}
|
|
4602
|
-
}
|
|
4603
|
-
|
|
4604
|
-
#shouldRemoveElement = (node) => {
|
|
4605
|
-
return this.#shouldMorphElement(node)
|
|
4606
|
-
}
|
|
4607
|
-
|
|
4608
|
-
#reloadRemoteFrames() {
|
|
4609
|
-
this.#remoteFrames().forEach((frame) => {
|
|
4610
|
-
if (this.#isFrameReloadedWithMorph(frame)) {
|
|
4611
|
-
this.#renderFrameWithMorph(frame);
|
|
4612
|
-
frame.reload();
|
|
4613
|
-
}
|
|
4614
|
-
});
|
|
4615
|
-
}
|
|
4616
|
-
|
|
4617
|
-
#renderFrameWithMorph(frame) {
|
|
4618
|
-
frame.addEventListener("turbo:before-frame-render", (event) => {
|
|
4619
|
-
event.detail.render = this.#morphFrameUpdate;
|
|
4620
|
-
}, { once: true });
|
|
4621
|
-
}
|
|
4622
|
-
|
|
4623
|
-
#morphFrameUpdate = (currentElement, newElement) => {
|
|
4624
|
-
dispatch("turbo:before-frame-morph", {
|
|
4625
|
-
target: currentElement,
|
|
4626
|
-
detail: { currentElement, newElement }
|
|
4627
|
-
});
|
|
4628
|
-
this.#morphElements(currentElement, newElement.children, "innerHTML");
|
|
4629
|
-
}
|
|
4630
|
-
|
|
4631
|
-
#isFrameReloadedWithMorph(element) {
|
|
4632
|
-
return element.src && element.refresh === "morph"
|
|
4633
|
-
}
|
|
4634
|
-
|
|
4635
|
-
#remoteFrames() {
|
|
4636
|
-
return Array.from(document.querySelectorAll('turbo-frame[src]')).filter(frame => {
|
|
4637
|
-
return !frame.closest('[data-turbo-permanent]')
|
|
4638
|
-
})
|
|
4639
|
-
}
|
|
4640
|
-
}
|
|
4641
|
-
|
|
4642
4559
|
class PageRenderer extends Renderer {
|
|
4643
4560
|
static renderElement(currentElement, newElement) {
|
|
4644
4561
|
if (document.body && newElement instanceof HTMLBodyElement) {
|
|
@@ -4851,6 +4768,122 @@ Copyright © 2024 37signals LLC
|
|
|
4851
4768
|
}
|
|
4852
4769
|
}
|
|
4853
4770
|
|
|
4771
|
+
class MorphRenderer extends PageRenderer {
|
|
4772
|
+
async render() {
|
|
4773
|
+
if (this.willRender) await this.#morphBody();
|
|
4774
|
+
}
|
|
4775
|
+
|
|
4776
|
+
get renderMethod() {
|
|
4777
|
+
return "morph"
|
|
4778
|
+
}
|
|
4779
|
+
|
|
4780
|
+
// Private
|
|
4781
|
+
|
|
4782
|
+
async #morphBody() {
|
|
4783
|
+
this.#morphElements(this.currentElement, this.newElement);
|
|
4784
|
+
this.#reloadRemoteFrames();
|
|
4785
|
+
|
|
4786
|
+
dispatch("turbo:morph", {
|
|
4787
|
+
detail: {
|
|
4788
|
+
currentElement: this.currentElement,
|
|
4789
|
+
newElement: this.newElement
|
|
4790
|
+
}
|
|
4791
|
+
});
|
|
4792
|
+
}
|
|
4793
|
+
|
|
4794
|
+
#morphElements(currentElement, newElement, morphStyle = "outerHTML") {
|
|
4795
|
+
this.isMorphingTurboFrame = this.#isFrameReloadedWithMorph(currentElement);
|
|
4796
|
+
|
|
4797
|
+
Idiomorph.morph(currentElement, newElement, {
|
|
4798
|
+
ignoreActiveValue: true,
|
|
4799
|
+
morphStyle: morphStyle,
|
|
4800
|
+
callbacks: {
|
|
4801
|
+
beforeNodeAdded: this.#shouldAddElement,
|
|
4802
|
+
beforeNodeMorphed: this.#shouldMorphElement,
|
|
4803
|
+
beforeAttributeUpdated: this.#shouldUpdateAttribute,
|
|
4804
|
+
beforeNodeRemoved: this.#shouldRemoveElement,
|
|
4805
|
+
afterNodeMorphed: this.#didMorphElement
|
|
4806
|
+
}
|
|
4807
|
+
});
|
|
4808
|
+
}
|
|
4809
|
+
|
|
4810
|
+
#shouldAddElement = (node) => {
|
|
4811
|
+
return !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id))
|
|
4812
|
+
}
|
|
4813
|
+
|
|
4814
|
+
#shouldMorphElement = (oldNode, newNode) => {
|
|
4815
|
+
if (oldNode instanceof HTMLElement) {
|
|
4816
|
+
if (!oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode))) {
|
|
4817
|
+
const event = dispatch("turbo:before-morph-element", {
|
|
4818
|
+
cancelable: true,
|
|
4819
|
+
target: oldNode,
|
|
4820
|
+
detail: {
|
|
4821
|
+
newElement: newNode
|
|
4822
|
+
}
|
|
4823
|
+
});
|
|
4824
|
+
|
|
4825
|
+
return !event.defaultPrevented
|
|
4826
|
+
} else {
|
|
4827
|
+
return false
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
|
|
4832
|
+
#shouldUpdateAttribute = (attributeName, target, mutationType) => {
|
|
4833
|
+
const event = dispatch("turbo:before-morph-attribute", { cancelable: true, target, detail: { attributeName, mutationType } });
|
|
4834
|
+
|
|
4835
|
+
return !event.defaultPrevented
|
|
4836
|
+
}
|
|
4837
|
+
|
|
4838
|
+
#didMorphElement = (oldNode, newNode) => {
|
|
4839
|
+
if (newNode instanceof HTMLElement) {
|
|
4840
|
+
dispatch("turbo:morph-element", {
|
|
4841
|
+
target: oldNode,
|
|
4842
|
+
detail: {
|
|
4843
|
+
newElement: newNode
|
|
4844
|
+
}
|
|
4845
|
+
});
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
|
|
4849
|
+
#shouldRemoveElement = (node) => {
|
|
4850
|
+
return this.#shouldMorphElement(node)
|
|
4851
|
+
}
|
|
4852
|
+
|
|
4853
|
+
#reloadRemoteFrames() {
|
|
4854
|
+
this.#remoteFrames().forEach((frame) => {
|
|
4855
|
+
if (this.#isFrameReloadedWithMorph(frame)) {
|
|
4856
|
+
this.#renderFrameWithMorph(frame);
|
|
4857
|
+
frame.reload();
|
|
4858
|
+
}
|
|
4859
|
+
});
|
|
4860
|
+
}
|
|
4861
|
+
|
|
4862
|
+
#renderFrameWithMorph(frame) {
|
|
4863
|
+
frame.addEventListener("turbo:before-frame-render", (event) => {
|
|
4864
|
+
event.detail.render = this.#morphFrameUpdate;
|
|
4865
|
+
}, { once: true });
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4868
|
+
#morphFrameUpdate = (currentElement, newElement) => {
|
|
4869
|
+
dispatch("turbo:before-frame-morph", {
|
|
4870
|
+
target: currentElement,
|
|
4871
|
+
detail: { currentElement, newElement }
|
|
4872
|
+
});
|
|
4873
|
+
this.#morphElements(currentElement, newElement.children, "innerHTML");
|
|
4874
|
+
}
|
|
4875
|
+
|
|
4876
|
+
#isFrameReloadedWithMorph(element) {
|
|
4877
|
+
return element.src && element.refresh === "morph"
|
|
4878
|
+
}
|
|
4879
|
+
|
|
4880
|
+
#remoteFrames() {
|
|
4881
|
+
return Array.from(document.querySelectorAll('turbo-frame[src]')).filter(frame => {
|
|
4882
|
+
return !frame.closest('[data-turbo-permanent]')
|
|
4883
|
+
})
|
|
4884
|
+
}
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4854
4887
|
class SnapshotCache {
|
|
4855
4888
|
keys = []
|
|
4856
4889
|
snapshots = {}
|
|
@@ -5010,7 +5043,7 @@ Copyright © 2024 37signals LLC
|
|
|
5010
5043
|
// Fetch request delegate
|
|
5011
5044
|
|
|
5012
5045
|
prepareRequest(fetchRequest) {
|
|
5013
|
-
fetchRequest.headers["Sec-Purpose"] = "prefetch";
|
|
5046
|
+
fetchRequest.headers["X-Sec-Purpose"] = "prefetch";
|
|
5014
5047
|
}
|
|
5015
5048
|
|
|
5016
5049
|
async requestSucceededWithResponse(fetchRequest, fetchResponse) {
|
|
@@ -5144,8 +5177,10 @@ Copyright © 2024 37signals LLC
|
|
|
5144
5177
|
const frameElement = options.frame ? document.getElementById(options.frame) : null;
|
|
5145
5178
|
|
|
5146
5179
|
if (frameElement instanceof FrameElement) {
|
|
5180
|
+
const action = options.action || getVisitAction(frameElement);
|
|
5181
|
+
|
|
5182
|
+
frameElement.delegate.proposeVisitIfNavigatedWithAction(frameElement, action);
|
|
5147
5183
|
frameElement.src = location.toString();
|
|
5148
|
-
frameElement.loaded;
|
|
5149
5184
|
} else {
|
|
5150
5185
|
this.navigator.proposeVisit(expandURL(location), options);
|
|
5151
5186
|
}
|
|
@@ -5786,7 +5821,7 @@ Copyright © 2024 37signals LLC
|
|
|
5786
5821
|
// Appearance observer delegate
|
|
5787
5822
|
|
|
5788
5823
|
elementAppearedInViewport(element) {
|
|
5789
|
-
this.proposeVisitIfNavigatedWithAction(element, element);
|
|
5824
|
+
this.proposeVisitIfNavigatedWithAction(element, getVisitAction(element));
|
|
5790
5825
|
this.#loadSourceURL();
|
|
5791
5826
|
}
|
|
5792
5827
|
|
|
@@ -5874,7 +5909,7 @@ Copyright © 2024 37signals LLC
|
|
|
5874
5909
|
formSubmissionSucceededWithResponse(formSubmission, response) {
|
|
5875
5910
|
const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter);
|
|
5876
5911
|
|
|
5877
|
-
frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.
|
|
5912
|
+
frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame));
|
|
5878
5913
|
frame.delegate.loadResponse(response);
|
|
5879
5914
|
|
|
5880
5915
|
if (!formSubmission.isSafe) {
|
|
@@ -5979,15 +6014,15 @@ Copyright © 2024 37signals LLC
|
|
|
5979
6014
|
#navigateFrame(element, url, submitter) {
|
|
5980
6015
|
const frame = this.#findFrameElement(element, submitter);
|
|
5981
6016
|
|
|
5982
|
-
frame.delegate.proposeVisitIfNavigatedWithAction(frame, element,
|
|
6017
|
+
frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(submitter, element, frame));
|
|
5983
6018
|
|
|
5984
6019
|
this.#withCurrentNavigationElement(element, () => {
|
|
5985
6020
|
frame.src = url;
|
|
5986
6021
|
});
|
|
5987
6022
|
}
|
|
5988
6023
|
|
|
5989
|
-
proposeVisitIfNavigatedWithAction(frame,
|
|
5990
|
-
this.action =
|
|
6024
|
+
proposeVisitIfNavigatedWithAction(frame, action = null) {
|
|
6025
|
+
this.action = action;
|
|
5991
6026
|
|
|
5992
6027
|
if (this.action) {
|
|
5993
6028
|
const pageSnapshot = PageSnapshot.fromElement(frame).clone();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotwired/turbo",
|
|
3
|
-
"version": "8.0.0-
|
|
3
|
+
"version": "8.0.0-rc.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",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"chai": "~4.3.4",
|
|
45
45
|
"eslint": "^8.13.0",
|
|
46
46
|
"express": "^4.18.2",
|
|
47
|
-
"idiomorph": "
|
|
47
|
+
"idiomorph": "https://github.com/bigskysoftware/idiomorph.git",
|
|
48
48
|
"multer": "^1.4.2",
|
|
49
49
|
"rollup": "^2.35.1"
|
|
50
50
|
},
|