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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49c33a9c7eaf917754b0deb21c776b9db3a457e09453bfb4da400ce2ea676b6f
4
- data.tar.gz: a6823b945a543045681165fa5f429cc3a7fe0928b6ae274505b61a20dee90662
3
+ metadata.gz: 7bc53746ef39d3412872a00dade91b735622b4edca9b42d321c4f2114da6dd4b
4
+ data.tar.gz: 53974cb2408b7a98cf7cf6a33b9fc8c9ebf948c1e196597f33f499437ea47a47
5
5
  SHA512:
6
- metadata.gz: 93ae8bb2c473aa705eb4b46ba0fe35fc61aa71513993755f2d33fbb6fcdec96881f19b9ae020e46d0dab98aaa8b771ba0dff26c2317370126404d7883d81a51e
7
- data.tar.gz: 303817c62ed4ce953084dae5e11025501eddab78398783d4266394b81fb4f5622486160abf30959633cea66a5b5a3aaf9c94cce82da2d0e5148cd98d60e32512
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.4
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(anchor) {
487
- if (anchor.hasAttribute("target")) {
488
- 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)) {
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
- if (submitter?.hasAttribute("formtarget") || form.hasAttribute("target")) {
1170
- const target = submitter?.getAttribute("formtarget") || form.target;
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.respondsToEventTarget(event.target)) {
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.respondsToEventTarget(event.target) && event.target instanceof Element) {
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
- respondsToEventTarget(target) {
1330
- const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
1331
- 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;
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
- const element = this.connectedSnapshot.firstAutofocusableElement;
1517
- if (element) {
1518
- element.focus();
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 === "get") {
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 MorphRenderer extends PageRenderer {
3800
- async render() {
3801
- if (this.willRender) await this.#morphBody();
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
- beforeNodeAdded: this.#shouldAddElement,
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
- #shouldAddElement=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
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
- #remoteFrames() {
3897
- 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";
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 ? MorphRenderer : PageRenderer;
3955
- 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);
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
- 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
+ }));
4973
4997
  },
4974
4998
  update() {
4999
+ const method = this.getAttribute("method");
4975
5000
  this.targetElements.forEach((targetElement => {
4976
- targetElement.innerHTML = "";
4977
- targetElement.append(this.templateContent);
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() {