turbo-rails 2.0.4 → 2.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +43 -5
- data/app/assets/javascripts/turbo.js +158 -139
- 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) {
|
@@ -72,7 +72,7 @@ class FrameElement extends HTMLElement {
|
|
72
72
|
static delegateConstructor=undefined;
|
73
73
|
loaded=Promise.resolve();
|
74
74
|
static get observedAttributes() {
|
75
|
-
return [ "disabled", "
|
75
|
+
return [ "disabled", "loading", "src" ];
|
76
76
|
}
|
77
77
|
constructor() {
|
78
78
|
super();
|
@@ -90,11 +90,9 @@ class FrameElement extends HTMLElement {
|
|
90
90
|
attributeChangedCallback(name) {
|
91
91
|
if (name == "loading") {
|
92
92
|
this.delegate.loadingStyleChanged();
|
93
|
-
} else if (name == "complete") {
|
94
|
-
this.delegate.completeChanged();
|
95
93
|
} else if (name == "src") {
|
96
94
|
this.delegate.sourceURLChanged();
|
97
|
-
} else {
|
95
|
+
} else if (name == "disabled") {
|
98
96
|
this.delegate.disabledChanged();
|
99
97
|
}
|
100
98
|
}
|
@@ -485,13 +483,17 @@ async function around(callback, reader) {
|
|
485
483
|
return [ before, after ];
|
486
484
|
}
|
487
485
|
|
488
|
-
function doesNotTargetIFrame(
|
489
|
-
if (
|
490
|
-
|
486
|
+
function doesNotTargetIFrame(name) {
|
487
|
+
if (name === "_blank") {
|
488
|
+
return false;
|
489
|
+
} else if (name) {
|
490
|
+
for (const element of document.getElementsByName(name)) {
|
491
491
|
if (element instanceof HTMLIFrameElement) return false;
|
492
492
|
}
|
493
|
+
return true;
|
494
|
+
} else {
|
495
|
+
return true;
|
493
496
|
}
|
494
|
-
return true;
|
495
497
|
}
|
496
498
|
|
497
499
|
function findLinkFromClickTarget(target) {
|
@@ -598,7 +600,7 @@ class FetchRequest {
|
|
598
600
|
this.fetchOptions = {
|
599
601
|
credentials: "same-origin",
|
600
602
|
redirect: "follow",
|
601
|
-
method: method,
|
603
|
+
method: method.toUpperCase(),
|
602
604
|
headers: {
|
603
605
|
...this.defaultHeaders
|
604
606
|
},
|
@@ -618,7 +620,7 @@ class FetchRequest {
|
|
618
620
|
const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype);
|
619
621
|
this.url = url;
|
620
622
|
this.fetchOptions.body = body;
|
621
|
-
this.fetchOptions.method = fetchMethod;
|
623
|
+
this.fetchOptions.method = fetchMethod.toUpperCase();
|
622
624
|
}
|
623
625
|
get headers() {
|
624
626
|
return this.fetchOptions.headers;
|
@@ -1168,15 +1170,8 @@ function submissionDoesNotDismissDialog(form, submitter) {
|
|
1168
1170
|
}
|
1169
1171
|
|
1170
1172
|
function submissionDoesNotTargetIFrame(form, submitter) {
|
1171
|
-
|
1172
|
-
|
1173
|
-
for (const element of document.getElementsByName(target)) {
|
1174
|
-
if (element instanceof HTMLIFrameElement) return false;
|
1175
|
-
}
|
1176
|
-
return true;
|
1177
|
-
} else {
|
1178
|
-
return true;
|
1179
|
-
}
|
1173
|
+
const target = submitter?.getAttribute("formtarget") || form.getAttribute("target");
|
1174
|
+
return doesNotTargetIFrame(target);
|
1180
1175
|
}
|
1181
1176
|
|
1182
1177
|
class View {
|
@@ -1309,14 +1304,14 @@ class LinkInterceptor {
|
|
1309
1304
|
document.removeEventListener("turbo:before-visit", this.willVisit);
|
1310
1305
|
}
|
1311
1306
|
clickBubbled=event => {
|
1312
|
-
if (this.
|
1307
|
+
if (this.clickEventIsSignificant(event)) {
|
1313
1308
|
this.clickEvent = event;
|
1314
1309
|
} else {
|
1315
1310
|
delete this.clickEvent;
|
1316
1311
|
}
|
1317
1312
|
};
|
1318
1313
|
linkClicked=event => {
|
1319
|
-
if (this.clickEvent && this.
|
1314
|
+
if (this.clickEvent && this.clickEventIsSignificant(event)) {
|
1320
1315
|
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
1321
1316
|
this.clickEvent.preventDefault();
|
1322
1317
|
event.preventDefault();
|
@@ -1328,9 +1323,10 @@ class LinkInterceptor {
|
|
1328
1323
|
willVisit=_event => {
|
1329
1324
|
delete this.clickEvent;
|
1330
1325
|
};
|
1331
|
-
|
1332
|
-
const
|
1333
|
-
|
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;
|
1334
1330
|
}
|
1335
1331
|
}
|
1336
1332
|
|
@@ -1360,7 +1356,7 @@ class LinkClickObserver {
|
|
1360
1356
|
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
|
1361
1357
|
const target = event.composedPath && event.composedPath()[0] || event.target;
|
1362
1358
|
const link = findLinkFromClickTarget(target);
|
1363
|
-
if (link && doesNotTargetIFrame(link)) {
|
1359
|
+
if (link && doesNotTargetIFrame(link.target)) {
|
1364
1360
|
const location = getLocationForLink(link);
|
1365
1361
|
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
1366
1362
|
event.preventDefault();
|
@@ -1498,6 +1494,9 @@ class Renderer {
|
|
1498
1494
|
get shouldRender() {
|
1499
1495
|
return true;
|
1500
1496
|
}
|
1497
|
+
get shouldAutofocus() {
|
1498
|
+
return true;
|
1499
|
+
}
|
1501
1500
|
get reloadReason() {
|
1502
1501
|
return;
|
1503
1502
|
}
|
@@ -1515,9 +1514,11 @@ class Renderer {
|
|
1515
1514
|
await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
|
1516
1515
|
}
|
1517
1516
|
focusFirstAutofocusableElement() {
|
1518
|
-
|
1519
|
-
|
1520
|
-
element
|
1517
|
+
if (this.shouldAutofocus) {
|
1518
|
+
const element = this.connectedSnapshot.firstAutofocusableElement;
|
1519
|
+
if (element) {
|
1520
|
+
element.focus();
|
1521
|
+
}
|
1521
1522
|
}
|
1522
1523
|
}
|
1523
1524
|
enteringBardo(currentPermanentElement) {
|
@@ -2631,7 +2632,7 @@ class LinkPrefetchObserver {
|
|
2631
2632
|
this.#prefetchedLink = null;
|
2632
2633
|
};
|
2633
2634
|
#tryToUsePrefetchedRequest=event => {
|
2634
|
-
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "
|
2635
|
+
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
|
2635
2636
|
const cached = prefetchCache.get(event.detail.url.toString());
|
2636
2637
|
if (cached) {
|
2637
2638
|
event.detail.fetchRequest = cached;
|
@@ -2800,6 +2801,7 @@ class Navigator {
|
|
2800
2801
|
}
|
2801
2802
|
visitCompleted(visit) {
|
2802
2803
|
this.delegate.visitCompleted(visit);
|
2804
|
+
delete this.currentVisit;
|
2803
2805
|
}
|
2804
2806
|
locationWithActionIsSamePage(location, action) {
|
2805
2807
|
const anchor = getAnchor(location);
|
@@ -3630,6 +3632,80 @@ var Idiomorph = function() {
|
|
3630
3632
|
};
|
3631
3633
|
}();
|
3632
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
|
+
|
3633
3709
|
class PageRenderer extends Renderer {
|
3634
3710
|
static renderElement(currentElement, newElement) {
|
3635
3711
|
if (document.body && newElement instanceof HTMLBodyElement) {
|
@@ -3798,106 +3874,45 @@ class PageRenderer extends Renderer {
|
|
3798
3874
|
}
|
3799
3875
|
}
|
3800
3876
|
|
3801
|
-
class
|
3802
|
-
|
3803
|
-
|
3804
|
-
}
|
3805
|
-
get renderMethod() {
|
3806
|
-
return "morph";
|
3807
|
-
}
|
3808
|
-
async #morphBody() {
|
3809
|
-
this.#morphElements(this.currentElement, this.newElement);
|
3810
|
-
this.#reloadRemoteFrames();
|
3811
|
-
dispatch("turbo:morph", {
|
3812
|
-
detail: {
|
3813
|
-
currentElement: this.currentElement,
|
3814
|
-
newElement: this.newElement
|
3815
|
-
}
|
3816
|
-
});
|
3817
|
-
}
|
3818
|
-
#morphElements(currentElement, newElement, morphStyle = "outerHTML") {
|
3819
|
-
this.isMorphingTurboFrame = this.#isFrameReloadedWithMorph(currentElement);
|
3820
|
-
Idiomorph.morph(currentElement, newElement, {
|
3821
|
-
morphStyle: morphStyle,
|
3877
|
+
class MorphingPageRenderer extends PageRenderer {
|
3878
|
+
static renderElement(currentElement, newElement) {
|
3879
|
+
morphElements(currentElement, newElement, {
|
3822
3880
|
callbacks: {
|
3823
|
-
|
3824
|
-
beforeNodeMorphed: this.#shouldMorphElement,
|
3825
|
-
beforeAttributeUpdated: this.#shouldUpdateAttribute,
|
3826
|
-
beforeNodeRemoved: this.#shouldRemoveElement,
|
3827
|
-
afterNodeMorphed: this.#didMorphElement
|
3828
|
-
}
|
3829
|
-
});
|
3830
|
-
}
|
3831
|
-
#shouldAddElement=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
|
3832
|
-
#shouldMorphElement=(oldNode, newNode) => {
|
3833
|
-
if (oldNode instanceof HTMLElement) {
|
3834
|
-
if (!oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode))) {
|
3835
|
-
const event = dispatch("turbo:before-morph-element", {
|
3836
|
-
cancelable: true,
|
3837
|
-
target: oldNode,
|
3838
|
-
detail: {
|
3839
|
-
newElement: newNode
|
3840
|
-
}
|
3841
|
-
});
|
3842
|
-
return !event.defaultPrevented;
|
3843
|
-
} else {
|
3844
|
-
return false;
|
3845
|
-
}
|
3846
|
-
}
|
3847
|
-
};
|
3848
|
-
#shouldUpdateAttribute=(attributeName, target, mutationType) => {
|
3849
|
-
const event = dispatch("turbo:before-morph-attribute", {
|
3850
|
-
cancelable: true,
|
3851
|
-
target: target,
|
3852
|
-
detail: {
|
3853
|
-
attributeName: attributeName,
|
3854
|
-
mutationType: mutationType
|
3881
|
+
beforeNodeMorphed: element => !canRefreshFrame(element)
|
3855
3882
|
}
|
3856
3883
|
});
|
3857
|
-
|
3858
|
-
|
3859
|
-
#didMorphElement=(oldNode, newNode) => {
|
3860
|
-
if (newNode instanceof HTMLElement) {
|
3861
|
-
dispatch("turbo:morph-element", {
|
3862
|
-
target: oldNode,
|
3863
|
-
detail: {
|
3864
|
-
newElement: newNode
|
3865
|
-
}
|
3866
|
-
});
|
3884
|
+
for (const frame of currentElement.querySelectorAll("turbo-frame")) {
|
3885
|
+
if (canRefreshFrame(frame)) refreshFrame(frame);
|
3867
3886
|
}
|
3868
|
-
|
3869
|
-
#shouldRemoveElement=node => this.#shouldMorphElement(node);
|
3870
|
-
#reloadRemoteFrames() {
|
3871
|
-
this.#remoteFrames().forEach((frame => {
|
3872
|
-
if (this.#isFrameReloadedWithMorph(frame)) {
|
3873
|
-
this.#renderFrameWithMorph(frame);
|
3874
|
-
frame.reload();
|
3875
|
-
}
|
3876
|
-
}));
|
3877
|
-
}
|
3878
|
-
#renderFrameWithMorph(frame) {
|
3879
|
-
frame.addEventListener("turbo:before-frame-render", (event => {
|
3880
|
-
event.detail.render = this.#morphFrameUpdate;
|
3881
|
-
}), {
|
3882
|
-
once: true
|
3883
|
-
});
|
3884
|
-
}
|
3885
|
-
#morphFrameUpdate=(currentElement, newElement) => {
|
3886
|
-
dispatch("turbo:before-frame-morph", {
|
3887
|
-
target: currentElement,
|
3887
|
+
dispatch("turbo:morph", {
|
3888
3888
|
detail: {
|
3889
3889
|
currentElement: currentElement,
|
3890
3890
|
newElement: newElement
|
3891
3891
|
}
|
3892
3892
|
});
|
3893
|
-
this.#morphElements(currentElement, newElement.children, "innerHTML");
|
3894
|
-
};
|
3895
|
-
#isFrameReloadedWithMorph(element) {
|
3896
|
-
return element.src && element.refresh === "morph";
|
3897
3893
|
}
|
3898
|
-
|
3899
|
-
return
|
3894
|
+
async preservingPermanentElements(callback) {
|
3895
|
+
return await callback();
|
3896
|
+
}
|
3897
|
+
get renderMethod() {
|
3898
|
+
return "morph";
|
3900
3899
|
}
|
3900
|
+
get shouldAutofocus() {
|
3901
|
+
return false;
|
3902
|
+
}
|
3903
|
+
}
|
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();
|
3901
3916
|
}
|
3902
3917
|
|
3903
3918
|
class SnapshotCache {
|
@@ -3953,8 +3968,8 @@ class PageView extends View {
|
|
3953
3968
|
}
|
3954
3969
|
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
3955
3970
|
const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
|
3956
|
-
const rendererClass = shouldMorphPage ?
|
3957
|
-
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);
|
3958
3973
|
if (!renderer.shouldRender) {
|
3959
3974
|
this.forceReloaded = true;
|
3960
3975
|
} else {
|
@@ -4145,7 +4160,7 @@ class Session {
|
|
4145
4160
|
}
|
4146
4161
|
refresh(url, requestId) {
|
4147
4162
|
const isRecentRequest = requestId && this.recentRequests.has(requestId);
|
4148
|
-
if (!isRecentRequest) {
|
4163
|
+
if (!isRecentRequest && !this.navigator.currentVisit) {
|
4149
4164
|
this.visit(url, {
|
4150
4165
|
action: "replace",
|
4151
4166
|
shouldCacheSnapshot: false
|
@@ -4561,17 +4576,11 @@ class FrameController {
|
|
4561
4576
|
}
|
4562
4577
|
sourceURLReloaded() {
|
4563
4578
|
const {src: src} = this.element;
|
4564
|
-
this
|
4565
|
-
this.element.removeAttribute("complete");
|
4566
|
-
}));
|
4579
|
+
this.element.removeAttribute("complete");
|
4567
4580
|
this.element.src = null;
|
4568
4581
|
this.element.src = src;
|
4569
4582
|
return this.element.loaded;
|
4570
4583
|
}
|
4571
|
-
completeChanged() {
|
4572
|
-
if (this.#isIgnoringChangesTo("complete")) return;
|
4573
|
-
this.#loadSourceURL();
|
4574
|
-
}
|
4575
4584
|
loadingStyleChanged() {
|
4576
4585
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
4577
4586
|
this.appearanceObserver.start();
|
@@ -4903,13 +4912,11 @@ class FrameController {
|
|
4903
4912
|
return this.element.hasAttribute("complete");
|
4904
4913
|
}
|
4905
4914
|
set complete(value) {
|
4906
|
-
|
4907
|
-
|
4908
|
-
|
4909
|
-
|
4910
|
-
|
4911
|
-
}
|
4912
|
-
}));
|
4915
|
+
if (value) {
|
4916
|
+
this.element.setAttribute("complete", "");
|
4917
|
+
} else {
|
4918
|
+
this.element.removeAttribute("complete");
|
4919
|
+
}
|
4913
4920
|
}
|
4914
4921
|
get isActive() {
|
4915
4922
|
return this.element.isActive && this.#connected;
|
@@ -4979,12 +4986,24 @@ const StreamActions = {
|
|
4979
4986
|
this.targetElements.forEach((e => e.remove()));
|
4980
4987
|
},
|
4981
4988
|
replace() {
|
4982
|
-
|
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
|
+
}));
|
4983
4997
|
},
|
4984
4998
|
update() {
|
4999
|
+
const method = this.getAttribute("method");
|
4985
5000
|
this.targetElements.forEach((targetElement => {
|
4986
|
-
|
4987
|
-
|
5001
|
+
if (method === "morph") {
|
5002
|
+
morphChildren(targetElement, this.templateContent);
|
5003
|
+
} else {
|
5004
|
+
targetElement.innerHTML = "";
|
5005
|
+
targetElement.append(this.templateContent);
|
5006
|
+
}
|
4988
5007
|
}));
|
4989
5008
|
},
|
4990
5009
|
refresh() {
|