turbo-rails 2.0.5 → 2.0.6
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 +4 -4
- data/README.md +43 -5
- data/app/assets/javascripts/turbo.js +150 -121
- data/app/assets/javascripts/turbo.min.js +6 -6
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams/broadcasts.rb +15 -3
- data/app/channels/turbo/streams_channel.rb +15 -15
- data/app/controllers/turbo/frames/frame_request.rb +2 -2
- data/app/helpers/turbo/drive_helper.rb +2 -2
- data/app/helpers/turbo/streams/action_helper.rb +3 -2
- data/app/models/concerns/turbo/broadcastable.rb +19 -11
- data/app/models/turbo/streams/tag_builder.rb +26 -0
- data/lib/turbo/engine.rb +21 -6
- data/lib/turbo/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7bc53746ef39d3412872a00dade91b735622b4edca9b42d321c4f2114da6dd4b
|
4
|
+
data.tar.gz: 53974cb2408b7a98cf7cf6a33b9fc8c9ebf948c1e196597f33f499437ea47a47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16fdd0089b68c5f6ed5777324af45fb54a2101439319b70637d60eebaa29965e8f9b3ae9dc2b825505d6b9155a2e74b319c47231f80300aac622b32ed2d0c908
|
7
|
+
data.tar.gz: 8d8753af6214007c64250931c9bbba39b341a91decb60a71b6d5a1bf62a08803076127e81a9db3d87ca209a2753558f688cc2319df78a7def4c9a7ca878c0e55
|
data/README.md
CHANGED
@@ -56,7 +56,7 @@ When the user clicks on the `Edit this todo` link, as a direct response to this
|
|
56
56
|
|
57
57
|
### A note on custom layouts
|
58
58
|
|
59
|
-
In order to render turbo frame requests without the application layout, Turbo registers a custom [layout method](https://api.rubyonrails.org/classes/ActionView/Layouts/ClassMethods.html#method-i-layout).
|
59
|
+
In order to render turbo frame requests without the application layout, Turbo registers a custom [layout method](https://api.rubyonrails.org/classes/ActionView/Layouts/ClassMethods.html#method-i-layout).
|
60
60
|
If your application uses custom layout resolution, you have to make sure to return `"turbo_rails/frame"` (or `false` for TurboRails < 1.4.0) for turbo frame requests:
|
61
61
|
|
62
62
|
```ruby
|
@@ -64,7 +64,7 @@ layout :custom_layout
|
|
64
64
|
|
65
65
|
def custom_layout
|
66
66
|
return "turbo_rails/frame" if turbo_frame_request?
|
67
|
-
|
67
|
+
|
68
68
|
# ... your custom layout logic
|
69
69
|
```
|
70
70
|
|
@@ -74,14 +74,14 @@ If you are using a custom, but "static" layout,
|
|
74
74
|
layout "some_static_layout"
|
75
75
|
```
|
76
76
|
|
77
|
-
you **have** to change it to a layout method in order to conditionally return `
|
77
|
+
you **have** to change it to a layout method in order to conditionally return `"turbo_rails/frame"` for turbo frame requests:
|
78
78
|
|
79
79
|
```ruby
|
80
80
|
layout :custom_layout
|
81
81
|
|
82
82
|
def custom_layout
|
83
83
|
return "turbo_rails/frame" if turbo_frame_request?
|
84
|
-
|
84
|
+
|
85
85
|
"some_static_layout"
|
86
86
|
```
|
87
87
|
|
@@ -111,7 +111,7 @@ This gem is automatically configured for applications made with Rails 7+ (unless
|
|
111
111
|
3. Run `./bin/rails turbo:install`
|
112
112
|
4. Run `./bin/rails turbo:install:redis` to change the development Action Cable adapter from Async (the default one) to Redis. The Async adapter does not support Turbo Stream broadcasting.
|
113
113
|
|
114
|
-
Running `turbo:install` will install through NPM if
|
114
|
+
Running `turbo:install` will install through NPM or Bun if a JavaScript runtime is used in the application. Otherwise the asset pipeline version is used. To use the asset pipeline version, you must have `importmap-rails` installed first and listed higher in the Gemfile.
|
115
115
|
|
116
116
|
If you're using node and need to use the cable consumer, you can import [`cable`](https://github.com/hotwired/turbo-rails/blob/main/app/javascript/turbo/cable.js) (`import { cable } from "@hotwired/turbo-rails"`), but ensure that your application actually *uses* the members it `import`s when using this style (see [turbo-rails#48](https://github.com/hotwired/turbo-rails/issues/48)).
|
117
117
|
|
@@ -152,10 +152,48 @@ The [`Turbo::TestAssertions::IntegrationTestAssertions`](./lib/turbo/test_assert
|
|
152
152
|
|
153
153
|
The [`Turbo::Broadcastable::TestHelper`](./lib/turbo/broadcastable/test_helper.rb) concern provides Action Cable-aware test helpers that assert that `<turbo-stream>` elements were or were not broadcast over Action Cable. `Turbo::Broadcastable::TestHelper` is automatically included in [`ActiveSupport::TestCase`](https://edgeapi.rubyonrails.org/classes/ActiveSupport/TestCase.html).
|
154
154
|
|
155
|
+
### Rendering Outside of a Request
|
156
|
+
|
157
|
+
Turbo utilizes [ActionController::Renderer][] to render templates and partials
|
158
|
+
outside the context of the request-response cycle. If you need to render a
|
159
|
+
Turbo-aware template, partial, or component, use [ActionController::Renderer][]:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
ApplicationController.renderer.render template: "posts/show", assigns: { post: Post.first } # => "<html>…"
|
163
|
+
PostsController.renderer.render :show, assigns: { post: Post.first } # => "<html>…"
|
164
|
+
```
|
165
|
+
|
166
|
+
As a shortcut, you can also call render directly on the controller class itself:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
ApplicationController.render template: "posts/show", assigns: { post: Post.first } # => "<html>…"
|
170
|
+
PostsController.render :show, assigns: { post: Post.first } # => "<html>…"
|
171
|
+
```
|
172
|
+
|
173
|
+
[ActionController::Renderer]: https://api.rubyonrails.org/classes/ActionController/Renderer.html
|
174
|
+
|
155
175
|
## Development
|
156
176
|
|
157
177
|
Run the tests with `./bin/test`.
|
158
178
|
|
179
|
+
### Using local Turbo version
|
180
|
+
|
181
|
+
Often you might want to test changes made locally to [Turbo lib](https://github.com/hotwired/turbo) itself. To package your local development version of Turbo you can use [yarn link](https://classic.yarnpkg.com/lang/en/docs/cli/link/) feature:
|
182
|
+
|
183
|
+
```sh
|
184
|
+
cd <local-turbo-dir>
|
185
|
+
yarn link
|
186
|
+
|
187
|
+
cd <local-turbo-rails-dir>
|
188
|
+
yarn link @hotwired/turbo
|
189
|
+
|
190
|
+
# Build the JS distribution files...
|
191
|
+
yarn build
|
192
|
+
# ...and commit the changes
|
193
|
+
```
|
194
|
+
|
195
|
+
Now you can reference your version of turbo-rails in your Rails projects packaged with your local version of Turbo.
|
196
|
+
|
159
197
|
## License
|
160
198
|
|
161
199
|
Turbo is released under the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/*!
|
2
|
-
Turbo 8.0.
|
2
|
+
Turbo 8.0.5
|
3
3
|
Copyright © 2024 37signals LLC
|
4
4
|
*/
|
5
5
|
(function(prototype) {
|
@@ -483,13 +483,17 @@ async function around(callback, reader) {
|
|
483
483
|
return [ before, after ];
|
484
484
|
}
|
485
485
|
|
486
|
-
function doesNotTargetIFrame(
|
487
|
-
if (
|
488
|
-
|
486
|
+
function doesNotTargetIFrame(name) {
|
487
|
+
if (name === "_blank") {
|
488
|
+
return false;
|
489
|
+
} else if (name) {
|
490
|
+
for (const element of document.getElementsByName(name)) {
|
489
491
|
if (element instanceof HTMLIFrameElement) return false;
|
490
492
|
}
|
493
|
+
return true;
|
494
|
+
} else {
|
495
|
+
return true;
|
491
496
|
}
|
492
|
-
return true;
|
493
497
|
}
|
494
498
|
|
495
499
|
function findLinkFromClickTarget(target) {
|
@@ -596,7 +600,7 @@ class FetchRequest {
|
|
596
600
|
this.fetchOptions = {
|
597
601
|
credentials: "same-origin",
|
598
602
|
redirect: "follow",
|
599
|
-
method: method,
|
603
|
+
method: method.toUpperCase(),
|
600
604
|
headers: {
|
601
605
|
...this.defaultHeaders
|
602
606
|
},
|
@@ -616,7 +620,7 @@ class FetchRequest {
|
|
616
620
|
const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype);
|
617
621
|
this.url = url;
|
618
622
|
this.fetchOptions.body = body;
|
619
|
-
this.fetchOptions.method = fetchMethod;
|
623
|
+
this.fetchOptions.method = fetchMethod.toUpperCase();
|
620
624
|
}
|
621
625
|
get headers() {
|
622
626
|
return this.fetchOptions.headers;
|
@@ -1166,15 +1170,8 @@ function submissionDoesNotDismissDialog(form, submitter) {
|
|
1166
1170
|
}
|
1167
1171
|
|
1168
1172
|
function submissionDoesNotTargetIFrame(form, submitter) {
|
1169
|
-
|
1170
|
-
|
1171
|
-
for (const element of document.getElementsByName(target)) {
|
1172
|
-
if (element instanceof HTMLIFrameElement) return false;
|
1173
|
-
}
|
1174
|
-
return true;
|
1175
|
-
} else {
|
1176
|
-
return true;
|
1177
|
-
}
|
1173
|
+
const target = submitter?.getAttribute("formtarget") || form.getAttribute("target");
|
1174
|
+
return doesNotTargetIFrame(target);
|
1178
1175
|
}
|
1179
1176
|
|
1180
1177
|
class View {
|
@@ -1307,14 +1304,14 @@ class LinkInterceptor {
|
|
1307
1304
|
document.removeEventListener("turbo:before-visit", this.willVisit);
|
1308
1305
|
}
|
1309
1306
|
clickBubbled=event => {
|
1310
|
-
if (this.
|
1307
|
+
if (this.clickEventIsSignificant(event)) {
|
1311
1308
|
this.clickEvent = event;
|
1312
1309
|
} else {
|
1313
1310
|
delete this.clickEvent;
|
1314
1311
|
}
|
1315
1312
|
};
|
1316
1313
|
linkClicked=event => {
|
1317
|
-
if (this.clickEvent && this.
|
1314
|
+
if (this.clickEvent && this.clickEventIsSignificant(event)) {
|
1318
1315
|
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
1319
1316
|
this.clickEvent.preventDefault();
|
1320
1317
|
event.preventDefault();
|
@@ -1326,9 +1323,10 @@ class LinkInterceptor {
|
|
1326
1323
|
willVisit=_event => {
|
1327
1324
|
delete this.clickEvent;
|
1328
1325
|
};
|
1329
|
-
|
1330
|
-
const
|
1331
|
-
|
1326
|
+
clickEventIsSignificant(event) {
|
1327
|
+
const target = event.composed ? event.target?.parentElement : event.target;
|
1328
|
+
const element = findLinkFromClickTarget(target) || target;
|
1329
|
+
return element instanceof Element && element.closest("turbo-frame, html") == this.element;
|
1332
1330
|
}
|
1333
1331
|
}
|
1334
1332
|
|
@@ -1358,7 +1356,7 @@ class LinkClickObserver {
|
|
1358
1356
|
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
|
1359
1357
|
const target = event.composedPath && event.composedPath()[0] || event.target;
|
1360
1358
|
const link = findLinkFromClickTarget(target);
|
1361
|
-
if (link && doesNotTargetIFrame(link)) {
|
1359
|
+
if (link && doesNotTargetIFrame(link.target)) {
|
1362
1360
|
const location = getLocationForLink(link);
|
1363
1361
|
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
1364
1362
|
event.preventDefault();
|
@@ -1496,6 +1494,9 @@ class Renderer {
|
|
1496
1494
|
get shouldRender() {
|
1497
1495
|
return true;
|
1498
1496
|
}
|
1497
|
+
get shouldAutofocus() {
|
1498
|
+
return true;
|
1499
|
+
}
|
1499
1500
|
get reloadReason() {
|
1500
1501
|
return;
|
1501
1502
|
}
|
@@ -1513,9 +1514,11 @@ class Renderer {
|
|
1513
1514
|
await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
|
1514
1515
|
}
|
1515
1516
|
focusFirstAutofocusableElement() {
|
1516
|
-
|
1517
|
-
|
1518
|
-
element
|
1517
|
+
if (this.shouldAutofocus) {
|
1518
|
+
const element = this.connectedSnapshot.firstAutofocusableElement;
|
1519
|
+
if (element) {
|
1520
|
+
element.focus();
|
1521
|
+
}
|
1519
1522
|
}
|
1520
1523
|
}
|
1521
1524
|
enteringBardo(currentPermanentElement) {
|
@@ -2629,7 +2632,7 @@ class LinkPrefetchObserver {
|
|
2629
2632
|
this.#prefetchedLink = null;
|
2630
2633
|
};
|
2631
2634
|
#tryToUsePrefetchedRequest=event => {
|
2632
|
-
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "
|
2635
|
+
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
|
2633
2636
|
const cached = prefetchCache.get(event.detail.url.toString());
|
2634
2637
|
if (cached) {
|
2635
2638
|
event.detail.fetchRequest = cached;
|
@@ -2798,6 +2801,7 @@ class Navigator {
|
|
2798
2801
|
}
|
2799
2802
|
visitCompleted(visit) {
|
2800
2803
|
this.delegate.visitCompleted(visit);
|
2804
|
+
delete this.currentVisit;
|
2801
2805
|
}
|
2802
2806
|
locationWithActionIsSamePage(location, action) {
|
2803
2807
|
const anchor = getAnchor(location);
|
@@ -3628,6 +3632,80 @@ var Idiomorph = function() {
|
|
3628
3632
|
};
|
3629
3633
|
}();
|
3630
3634
|
|
3635
|
+
function morphElements(currentElement, newElement, {callbacks: callbacks, ...options} = {}) {
|
3636
|
+
Idiomorph.morph(currentElement, newElement, {
|
3637
|
+
...options,
|
3638
|
+
callbacks: new DefaultIdiomorphCallbacks(callbacks)
|
3639
|
+
});
|
3640
|
+
}
|
3641
|
+
|
3642
|
+
function morphChildren(currentElement, newElement) {
|
3643
|
+
morphElements(currentElement, newElement.children, {
|
3644
|
+
morphStyle: "innerHTML"
|
3645
|
+
});
|
3646
|
+
}
|
3647
|
+
|
3648
|
+
class DefaultIdiomorphCallbacks {
|
3649
|
+
#beforeNodeMorphed;
|
3650
|
+
constructor({beforeNodeMorphed: beforeNodeMorphed} = {}) {
|
3651
|
+
this.#beforeNodeMorphed = beforeNodeMorphed || (() => true);
|
3652
|
+
}
|
3653
|
+
beforeNodeAdded=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
|
3654
|
+
beforeNodeMorphed=(currentElement, newElement) => {
|
3655
|
+
if (currentElement instanceof Element) {
|
3656
|
+
if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) {
|
3657
|
+
const event = dispatch("turbo:before-morph-element", {
|
3658
|
+
cancelable: true,
|
3659
|
+
target: currentElement,
|
3660
|
+
detail: {
|
3661
|
+
currentElement: currentElement,
|
3662
|
+
newElement: newElement
|
3663
|
+
}
|
3664
|
+
});
|
3665
|
+
return !event.defaultPrevented;
|
3666
|
+
} else {
|
3667
|
+
return false;
|
3668
|
+
}
|
3669
|
+
}
|
3670
|
+
};
|
3671
|
+
beforeAttributeUpdated=(attributeName, target, mutationType) => {
|
3672
|
+
const event = dispatch("turbo:before-morph-attribute", {
|
3673
|
+
cancelable: true,
|
3674
|
+
target: target,
|
3675
|
+
detail: {
|
3676
|
+
attributeName: attributeName,
|
3677
|
+
mutationType: mutationType
|
3678
|
+
}
|
3679
|
+
});
|
3680
|
+
return !event.defaultPrevented;
|
3681
|
+
};
|
3682
|
+
beforeNodeRemoved=node => this.beforeNodeMorphed(node);
|
3683
|
+
afterNodeMorphed=(currentElement, newElement) => {
|
3684
|
+
if (currentElement instanceof Element) {
|
3685
|
+
dispatch("turbo:morph-element", {
|
3686
|
+
target: currentElement,
|
3687
|
+
detail: {
|
3688
|
+
currentElement: currentElement,
|
3689
|
+
newElement: newElement
|
3690
|
+
}
|
3691
|
+
});
|
3692
|
+
}
|
3693
|
+
};
|
3694
|
+
}
|
3695
|
+
|
3696
|
+
class MorphingFrameRenderer extends FrameRenderer {
|
3697
|
+
static renderElement(currentElement, newElement) {
|
3698
|
+
dispatch("turbo:before-frame-morph", {
|
3699
|
+
target: currentElement,
|
3700
|
+
detail: {
|
3701
|
+
currentElement: currentElement,
|
3702
|
+
newElement: newElement
|
3703
|
+
}
|
3704
|
+
});
|
3705
|
+
morphChildren(currentElement, newElement);
|
3706
|
+
}
|
3707
|
+
}
|
3708
|
+
|
3631
3709
|
class PageRenderer extends Renderer {
|
3632
3710
|
static renderElement(currentElement, newElement) {
|
3633
3711
|
if (document.body && newElement instanceof HTMLBodyElement) {
|
@@ -3796,108 +3874,47 @@ class PageRenderer extends Renderer {
|
|
3796
3874
|
}
|
3797
3875
|
}
|
3798
3876
|
|
3799
|
-
class
|
3800
|
-
|
3801
|
-
|
3802
|
-
}
|
3803
|
-
get renderMethod() {
|
3804
|
-
return "morph";
|
3805
|
-
}
|
3806
|
-
async #morphBody() {
|
3807
|
-
this.#morphElements(this.currentElement, this.newElement);
|
3808
|
-
this.#reloadRemoteFrames();
|
3809
|
-
dispatch("turbo:morph", {
|
3810
|
-
detail: {
|
3811
|
-
currentElement: this.currentElement,
|
3812
|
-
newElement: this.newElement
|
3813
|
-
}
|
3814
|
-
});
|
3815
|
-
}
|
3816
|
-
#morphElements(currentElement, newElement, morphStyle = "outerHTML") {
|
3817
|
-
this.isMorphingTurboFrame = this.#isFrameReloadedWithMorph(currentElement);
|
3818
|
-
Idiomorph.morph(currentElement, newElement, {
|
3819
|
-
morphStyle: morphStyle,
|
3877
|
+
class MorphingPageRenderer extends PageRenderer {
|
3878
|
+
static renderElement(currentElement, newElement) {
|
3879
|
+
morphElements(currentElement, newElement, {
|
3820
3880
|
callbacks: {
|
3821
|
-
|
3822
|
-
beforeNodeMorphed: this.#shouldMorphElement,
|
3823
|
-
beforeAttributeUpdated: this.#shouldUpdateAttribute,
|
3824
|
-
beforeNodeRemoved: this.#shouldRemoveElement,
|
3825
|
-
afterNodeMorphed: this.#didMorphElement
|
3881
|
+
beforeNodeMorphed: element => !canRefreshFrame(element)
|
3826
3882
|
}
|
3827
3883
|
});
|
3828
|
-
|
3829
|
-
|
3830
|
-
#shouldMorphElement=(oldNode, newNode) => {
|
3831
|
-
if (oldNode instanceof HTMLElement) {
|
3832
|
-
if (!oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode))) {
|
3833
|
-
const event = dispatch("turbo:before-morph-element", {
|
3834
|
-
cancelable: true,
|
3835
|
-
target: oldNode,
|
3836
|
-
detail: {
|
3837
|
-
newElement: newNode
|
3838
|
-
}
|
3839
|
-
});
|
3840
|
-
return !event.defaultPrevented;
|
3841
|
-
} else {
|
3842
|
-
return false;
|
3843
|
-
}
|
3884
|
+
for (const frame of currentElement.querySelectorAll("turbo-frame")) {
|
3885
|
+
if (canRefreshFrame(frame)) refreshFrame(frame);
|
3844
3886
|
}
|
3845
|
-
|
3846
|
-
#shouldUpdateAttribute=(attributeName, target, mutationType) => {
|
3847
|
-
const event = dispatch("turbo:before-morph-attribute", {
|
3848
|
-
cancelable: true,
|
3849
|
-
target: target,
|
3850
|
-
detail: {
|
3851
|
-
attributeName: attributeName,
|
3852
|
-
mutationType: mutationType
|
3853
|
-
}
|
3854
|
-
});
|
3855
|
-
return !event.defaultPrevented;
|
3856
|
-
};
|
3857
|
-
#didMorphElement=(oldNode, newNode) => {
|
3858
|
-
if (newNode instanceof HTMLElement) {
|
3859
|
-
dispatch("turbo:morph-element", {
|
3860
|
-
target: oldNode,
|
3861
|
-
detail: {
|
3862
|
-
newElement: newNode
|
3863
|
-
}
|
3864
|
-
});
|
3865
|
-
}
|
3866
|
-
};
|
3867
|
-
#shouldRemoveElement=node => this.#shouldMorphElement(node);
|
3868
|
-
#reloadRemoteFrames() {
|
3869
|
-
this.#remoteFrames().forEach((frame => {
|
3870
|
-
if (this.#isFrameReloadedWithMorph(frame)) {
|
3871
|
-
this.#renderFrameWithMorph(frame);
|
3872
|
-
frame.reload();
|
3873
|
-
}
|
3874
|
-
}));
|
3875
|
-
}
|
3876
|
-
#renderFrameWithMorph(frame) {
|
3877
|
-
frame.addEventListener("turbo:before-frame-render", (event => {
|
3878
|
-
event.detail.render = this.#morphFrameUpdate;
|
3879
|
-
}), {
|
3880
|
-
once: true
|
3881
|
-
});
|
3882
|
-
}
|
3883
|
-
#morphFrameUpdate=(currentElement, newElement) => {
|
3884
|
-
dispatch("turbo:before-frame-morph", {
|
3885
|
-
target: currentElement,
|
3887
|
+
dispatch("turbo:morph", {
|
3886
3888
|
detail: {
|
3887
3889
|
currentElement: currentElement,
|
3888
3890
|
newElement: newElement
|
3889
3891
|
}
|
3890
3892
|
});
|
3891
|
-
this.#morphElements(currentElement, newElement.children, "innerHTML");
|
3892
|
-
};
|
3893
|
-
#isFrameReloadedWithMorph(element) {
|
3894
|
-
return element.src && element.refresh === "morph";
|
3895
3893
|
}
|
3896
|
-
|
3897
|
-
return
|
3894
|
+
async preservingPermanentElements(callback) {
|
3895
|
+
return await callback();
|
3896
|
+
}
|
3897
|
+
get renderMethod() {
|
3898
|
+
return "morph";
|
3899
|
+
}
|
3900
|
+
get shouldAutofocus() {
|
3901
|
+
return false;
|
3898
3902
|
}
|
3899
3903
|
}
|
3900
3904
|
|
3905
|
+
function canRefreshFrame(frame) {
|
3906
|
+
return frame instanceof FrameElement && frame.src && frame.refresh === "morph" && !frame.closest("[data-turbo-permanent]");
|
3907
|
+
}
|
3908
|
+
|
3909
|
+
function refreshFrame(frame) {
|
3910
|
+
frame.addEventListener("turbo:before-frame-render", (({detail: detail}) => {
|
3911
|
+
detail.render = MorphingFrameRenderer.renderElement;
|
3912
|
+
}), {
|
3913
|
+
once: true
|
3914
|
+
});
|
3915
|
+
frame.reload();
|
3916
|
+
}
|
3917
|
+
|
3901
3918
|
class SnapshotCache {
|
3902
3919
|
keys=[];
|
3903
3920
|
snapshots={};
|
@@ -3951,8 +3968,8 @@ class PageView extends View {
|
|
3951
3968
|
}
|
3952
3969
|
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
3953
3970
|
const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
|
3954
|
-
const rendererClass = shouldMorphPage ?
|
3955
|
-
const renderer = new rendererClass(this.snapshot, snapshot,
|
3971
|
+
const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;
|
3972
|
+
const renderer = new rendererClass(this.snapshot, snapshot, rendererClass.renderElement, isPreview, willRender);
|
3956
3973
|
if (!renderer.shouldRender) {
|
3957
3974
|
this.forceReloaded = true;
|
3958
3975
|
} else {
|
@@ -4143,7 +4160,7 @@ class Session {
|
|
4143
4160
|
}
|
4144
4161
|
refresh(url, requestId) {
|
4145
4162
|
const isRecentRequest = requestId && this.recentRequests.has(requestId);
|
4146
|
-
if (!isRecentRequest) {
|
4163
|
+
if (!isRecentRequest && !this.navigator.currentVisit) {
|
4147
4164
|
this.visit(url, {
|
4148
4165
|
action: "replace",
|
4149
4166
|
shouldCacheSnapshot: false
|
@@ -4969,12 +4986,24 @@ const StreamActions = {
|
|
4969
4986
|
this.targetElements.forEach((e => e.remove()));
|
4970
4987
|
},
|
4971
4988
|
replace() {
|
4972
|
-
|
4989
|
+
const method = this.getAttribute("method");
|
4990
|
+
this.targetElements.forEach((targetElement => {
|
4991
|
+
if (method === "morph") {
|
4992
|
+
morphElements(targetElement, this.templateContent);
|
4993
|
+
} else {
|
4994
|
+
targetElement.replaceWith(this.templateContent);
|
4995
|
+
}
|
4996
|
+
}));
|
4973
4997
|
},
|
4974
4998
|
update() {
|
4999
|
+
const method = this.getAttribute("method");
|
4975
5000
|
this.targetElements.forEach((targetElement => {
|
4976
|
-
|
4977
|
-
|
5001
|
+
if (method === "morph") {
|
5002
|
+
morphChildren(targetElement, this.templateContent);
|
5003
|
+
} else {
|
5004
|
+
targetElement.innerHTML = "";
|
5005
|
+
targetElement.append(this.templateContent);
|
5006
|
+
}
|
4978
5007
|
}));
|
4979
5008
|
},
|
4980
5009
|
refresh() {
|