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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce426f6fcdec338c0a3eb5c3f97ee2660ef618d23535671ddf464d2650041997
4
- data.tar.gz: eab0160dacd564b0e002ea3fe658bbc6f4770b7c912867c335758d2c3237c24b
3
+ metadata.gz: 7bc53746ef39d3412872a00dade91b735622b4edca9b42d321c4f2114da6dd4b
4
+ data.tar.gz: 53974cb2408b7a98cf7cf6a33b9fc8c9ebf948c1e196597f33f499437ea47a47
5
5
  SHA512:
6
- metadata.gz: d80adee8976dad280b8ff058d5c0615617f37ca3ba5c164db63dce81ad9263b8cfdf704b50c0aaeac9d5d675ad66298ca34902f2e31f5cfc493f8041ca32d12e
7
- data.tar.gz: 696dc8a61b6b0860ca5b2db540f4e573e7531a293a84854068bb1277997f3c73aa993527afdd4a9dc5c741fc8c83ef258cf2022d3a354c6b4d3e2fef2cca0dd1
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 `false` for turbo frame requests:
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 Node.js 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.
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.3
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", "complete", "loading", "src" ];
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(anchor) {
489
- if (anchor.hasAttribute("target")) {
490
- for (const element of document.getElementsByName(anchor.target)) {
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
- if (submitter?.hasAttribute("formtarget") || form.hasAttribute("target")) {
1172
- const target = submitter?.getAttribute("formtarget") || form.target;
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.respondsToEventTarget(event.target)) {
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.respondsToEventTarget(event.target) && event.target instanceof Element) {
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
- respondsToEventTarget(target) {
1332
- const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
1333
- return element && element.closest("turbo-frame, html") == this.element;
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
- const element = this.connectedSnapshot.firstAutofocusableElement;
1519
- if (element) {
1520
- element.focus();
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 === "get") {
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 MorphRenderer extends PageRenderer {
3802
- async render() {
3803
- if (this.willRender) await this.#morphBody();
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
- beforeNodeAdded: this.#shouldAddElement,
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
- return !event.defaultPrevented;
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
- #remoteFrames() {
3899
- return Array.from(document.querySelectorAll("turbo-frame[src]")).filter((frame => !frame.closest("[data-turbo-permanent]")));
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 ? MorphRenderer : PageRenderer;
3957
- const renderer = new rendererClass(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
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.#ignoringChangesToAttribute("complete", (() => {
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
- this.#ignoringChangesToAttribute("complete", (() => {
4907
- if (value) {
4908
- this.element.setAttribute("complete", "");
4909
- } else {
4910
- this.element.removeAttribute("complete");
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
- this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
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
- targetElement.innerHTML = "";
4987
- targetElement.append(this.templateContent);
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() {