turbo-rails 0.5.11 → 0.5.12

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: 349e14ec6f35af3029178e7ba854d16047aaf5e00ab700b1f2fb751170347518
4
- data.tar.gz: b5177b878833a5abda62f208d787de1931d4db4cf3ba373a67458bee8c9d49a3
3
+ metadata.gz: 27a0cac6803a0e1e410fb4fce4aaa60a0998aee4dec7d1b4b4cbb674533ce5cf
4
+ data.tar.gz: a4a67e8cff6989445d669cd6aa3b64b39dfc7de4ca7afdfddfd41c1e9334558e
5
5
  SHA512:
6
- metadata.gz: 6e0e11bc2c95419d394c758ff04e03dd734f85a45e61001e623c25bffdfc83530977637a89dbe5f173d81fd6dc4a86d81a1ff99ffedb4f23b00215e76c16ecad
7
- data.tar.gz: 48dfec6f22f6eee01950f77c79c54f4d6a8fde629454ac2845b2d6bff6f8b464d18bcf7ede203a16a83dcc7421cbe33f92c74bc3a76124048ebbc897641ef388
6
+ metadata.gz: a2de3cde68a13beffda32366c8b209666b1bae8a8dc3423ed9f5645dcae00fe88ba9f8ff08582eb60c9212a209d24683adf14f220a670923eac6806268198583
7
+ data.tar.gz: be9f2251ca7bf7684d71ecfba9cbaaefbc64423d1b7f86428fc733facc9e1867ff6b54df1c756cc96ce0367ffe2cbb090af2bbf3319d432d88fbae005a3edd7b
data/README.md CHANGED
@@ -40,13 +40,12 @@ The JavaScript for Turbo can either be run through the asset pipeline, which is
40
40
 
41
41
  Running `turbo:install` will install through NPM if Webpacker is installed in the application. Otherwise the asset pipeline version is used.
42
42
 
43
- If you're using Webpack and need to use either the cable consumer or the Turbo instance, you can import [`Turbo`](https://turbo.hotwire.dev/reference/drive) and/or [`cable`](https://github.com/hotwired/turbo-rails/blob/main/app/javascript/turbo/cable.js) (`import { Turbo, 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)).
43
+ If you're using Webpack 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)).
44
44
 
45
- If you're using a [native adapter](https://turbo.hotwire.dev/handbook/native), you'll need to assign `window.Turbo`, even if it's not used for anything else:
45
+ The `Turbo` instance is automatically assigned to `window.Turbo` upon import:
46
46
 
47
47
  ```js
48
- import { Turbo } from "@hotwired/turbo-rails"
49
- window.Turbo = Turbo
48
+ import "@hotwired/turbo-rails"
50
49
  ```
51
50
 
52
51
  ## Usage
@@ -56,11 +55,7 @@ You can watch [the video introduction to Hotwire](https://hotwire.dev/#screencas
56
55
 
57
56
  ## Compatibility with Rails UJS
58
57
 
59
- Rails UJS includes helpers for sending links and forms over XMLHttpRequest, so you can respond with Ajax. Turbo supersedes this functionality, so you should ensure that you're either running Rails 6.1 with the defaults that turn this off for forms, or that you add `config.action_view.form_with_generates_remote_forms = false` to your `config/application.rb`.
60
-
61
- Note that the helpers that turn `link_to` into remote invocations will _not_ currently work with Turbo. Links that have been made remote will not stick within frames nor will they allow you to respond with turbo stream actions. The recommendation is to replace these links with styled `button_to`, so you'll flow through a regular form, and you'll be better off with a11y compliance.
62
-
63
- You can still use the `data-confirm` and `data-disable-with`.
58
+ Turbo can coexist with Rails UJS, but you need to take a series of ugprade steps to make it happen. See [the upgrading guide](https://github.com/hotwired/turbo-rails/blob/main/UPGRADING.md).
64
59
 
65
60
 
66
61
  ## Development
@@ -943,6 +943,9 @@ class Renderer {
943
943
  return element;
944
944
  } else {
945
945
  const createdScriptElement = document.createElement("script");
946
+ if (this.cspNonce) {
947
+ createdScriptElement.nonce = this.cspNonce;
948
+ }
946
949
  createdScriptElement.textContent = element.textContent;
947
950
  createdScriptElement.async = false;
948
951
  copyElementAttributes(createdScriptElement, element);
@@ -970,6 +973,10 @@ class Renderer {
970
973
  get permanentElementMap() {
971
974
  return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
972
975
  }
976
+ get cspNonce() {
977
+ var _a;
978
+ return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
979
+ }
973
980
  }
974
981
 
975
982
  function copyElementAttributes(destinationElement, sourceElement) {
@@ -994,6 +1001,8 @@ class FrameRenderer extends Renderer {
994
1001
  this.scrollFrameIntoView();
995
1002
  await nextAnimationFrame();
996
1003
  this.focusFirstAutofocusableElement();
1004
+ await nextAnimationFrame();
1005
+ this.activateScriptElements();
997
1006
  }
998
1007
  loadFrameElement() {
999
1008
  var _a;
@@ -1020,6 +1029,15 @@ class FrameRenderer extends Renderer {
1020
1029
  }
1021
1030
  return false;
1022
1031
  }
1032
+ activateScriptElements() {
1033
+ for (const inertScriptElement of this.newScriptElements) {
1034
+ const activatedScriptElement = this.createScriptElement(inertScriptElement);
1035
+ inertScriptElement.replaceWith(activatedScriptElement);
1036
+ }
1037
+ }
1038
+ get newScriptElements() {
1039
+ return this.currentElement.querySelectorAll("script");
1040
+ }
1023
1041
  }
1024
1042
 
1025
1043
  function readScrollLogicalPosition(value, defaultValue) {
@@ -1627,6 +1645,30 @@ class BrowserAdapter {
1627
1645
  }
1628
1646
  }
1629
1647
 
1648
+ class CacheObserver {
1649
+ constructor() {
1650
+ this.started = false;
1651
+ }
1652
+ start() {
1653
+ if (!this.started) {
1654
+ this.started = true;
1655
+ addEventListener("turbo:before-cache", this.removeStaleElements, false);
1656
+ }
1657
+ }
1658
+ stop() {
1659
+ if (this.started) {
1660
+ this.started = false;
1661
+ removeEventListener("turbo:before-cache", this.removeStaleElements, false);
1662
+ }
1663
+ }
1664
+ removeStaleElements() {
1665
+ const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
1666
+ for (const element of staleElements) {
1667
+ element.remove();
1668
+ }
1669
+ }
1670
+ }
1671
+
1630
1672
  class FormSubmitObserver {
1631
1673
  constructor(delegate) {
1632
1674
  this.started = false;
@@ -2216,7 +2258,7 @@ class PageRenderer extends Renderer {
2216
2258
  return this.newHeadSnapshot.provisionalElements;
2217
2259
  }
2218
2260
  get newBodyScriptElements() {
2219
- return [ ...this.newElement.querySelectorAll("script") ];
2261
+ return this.newElement.querySelectorAll("script");
2220
2262
  }
2221
2263
  }
2222
2264
 
@@ -2307,6 +2349,7 @@ class Session {
2307
2349
  this.view = new PageView(this, document.documentElement);
2308
2350
  this.adapter = new BrowserAdapter(this);
2309
2351
  this.pageObserver = new PageObserver(this);
2352
+ this.cacheObserver = new CacheObserver;
2310
2353
  this.linkClickObserver = new LinkClickObserver(this);
2311
2354
  this.formSubmitObserver = new FormSubmitObserver(this);
2312
2355
  this.scrollObserver = new ScrollObserver(this);
@@ -2319,6 +2362,7 @@ class Session {
2319
2362
  start() {
2320
2363
  if (!this.started) {
2321
2364
  this.pageObserver.start();
2365
+ this.cacheObserver.start();
2322
2366
  this.linkClickObserver.start();
2323
2367
  this.formSubmitObserver.start();
2324
2368
  this.scrollObserver.start();
@@ -2335,6 +2379,7 @@ class Session {
2335
2379
  stop() {
2336
2380
  if (this.started) {
2337
2381
  this.pageObserver.stop();
2382
+ this.cacheObserver.stop();
2338
2383
  this.linkClickObserver.stop();
2339
2384
  this.formSubmitObserver.stop();
2340
2385
  this.scrollObserver.stop();
@@ -2371,9 +2416,9 @@ class Session {
2371
2416
  get restorationIdentifier() {
2372
2417
  return this.history.restorationIdentifier;
2373
2418
  }
2374
- historyPoppedToLocationWithRestorationIdentifier(location) {
2419
+ historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
2375
2420
  if (this.enabled) {
2376
- this.navigator.proposeVisit(location, {
2421
+ this.navigator.startVisit(location, restorationIdentifier, {
2377
2422
  action: "restore",
2378
2423
  historyChanged: true
2379
2424
  });
@@ -2790,36 +2835,36 @@ function activateElement(element, currentURL) {
2790
2835
 
2791
2836
  const StreamActions = {
2792
2837
  after() {
2793
- var _a, _b;
2794
- (_b = (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.insertBefore(this.templateContent, this.targetElement.nextSibling);
2838
+ this.targetElements.forEach((e => {
2839
+ var _a;
2840
+ return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
2841
+ }));
2795
2842
  },
2796
2843
  append() {
2797
- var _a;
2798
2844
  this.removeDuplicateTargetChildren();
2799
- (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.append(this.templateContent);
2845
+ this.targetElements.forEach((e => e.append(this.templateContent)));
2800
2846
  },
2801
2847
  before() {
2802
- var _a, _b;
2803
- (_b = (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.insertBefore(this.templateContent, this.targetElement);
2848
+ this.targetElements.forEach((e => {
2849
+ var _a;
2850
+ return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
2851
+ }));
2804
2852
  },
2805
2853
  prepend() {
2806
- var _a;
2807
2854
  this.removeDuplicateTargetChildren();
2808
- (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.prepend(this.templateContent);
2855
+ this.targetElements.forEach((e => e.prepend(this.templateContent)));
2809
2856
  },
2810
2857
  remove() {
2811
- var _a;
2812
- (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.remove();
2858
+ this.targetElements.forEach((e => e.remove()));
2813
2859
  },
2814
2860
  replace() {
2815
- var _a;
2816
- (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.replaceWith(this.templateContent);
2861
+ this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
2817
2862
  },
2818
2863
  update() {
2819
- if (this.targetElement) {
2820
- this.targetElement.innerHTML = "";
2821
- this.targetElement.append(this.templateContent);
2822
- }
2864
+ this.targetElements.forEach((e => {
2865
+ e.innerHTML = "";
2866
+ e.append(this.templateContent);
2867
+ }));
2823
2868
  }
2824
2869
  };
2825
2870
 
@@ -2848,19 +2893,13 @@ class StreamElement extends HTMLElement {
2848
2893
  } catch (_a) {}
2849
2894
  }
2850
2895
  removeDuplicateTargetChildren() {
2851
- this.duplicateChildren.forEach((({targetChild: targetChild}) => {
2852
- targetChild.remove();
2853
- }));
2896
+ this.duplicateChildren.forEach((c => c.remove()));
2854
2897
  }
2855
2898
  get duplicateChildren() {
2856
2899
  var _a;
2857
- return [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].map((templateChild => {
2858
- let targetChild = [ ...this.targetElement.children ].filter((c => c.id === templateChild.id))[0];
2859
- return {
2860
- targetChild: targetChild,
2861
- templateChild: templateChild
2862
- };
2863
- })).filter((({targetChild: targetChild}) => targetChild));
2900
+ const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
2901
+ const newChildrenIds = [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].filter((c => !!c.id)).map((c => c.id));
2902
+ return existingChildren.filter((c => newChildrenIds.includes(c.id)));
2864
2903
  }
2865
2904
  get performAction() {
2866
2905
  if (this.action) {
@@ -2872,15 +2911,17 @@ class StreamElement extends HTMLElement {
2872
2911
  }
2873
2912
  this.raise("action attribute is missing");
2874
2913
  }
2875
- get targetElement() {
2876
- var _a;
2914
+ get targetElements() {
2877
2915
  if (this.target) {
2878
- return (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
2916
+ return this.targetElementsById;
2917
+ } else if (this.targets) {
2918
+ return this.targetElementsByQuery;
2919
+ } else {
2920
+ this.raise("target or targets attribute is missing");
2879
2921
  }
2880
- this.raise("target attribute is missing");
2881
2922
  }
2882
2923
  get templateContent() {
2883
- return this.templateElement.content;
2924
+ return this.templateElement.content.cloneNode(true);
2884
2925
  }
2885
2926
  get templateElement() {
2886
2927
  if (this.firstElementChild instanceof HTMLTemplateElement) {
@@ -2894,6 +2935,9 @@ class StreamElement extends HTMLElement {
2894
2935
  get target() {
2895
2936
  return this.getAttribute("target");
2896
2937
  }
2938
+ get targets() {
2939
+ return this.getAttribute("targets");
2940
+ }
2897
2941
  raise(message) {
2898
2942
  throw new Error(`${this.description}: ${message}`);
2899
2943
  }
@@ -2907,6 +2951,24 @@ class StreamElement extends HTMLElement {
2907
2951
  cancelable: true
2908
2952
  });
2909
2953
  }
2954
+ get targetElementsById() {
2955
+ var _a;
2956
+ const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
2957
+ if (element !== null) {
2958
+ return [ element ];
2959
+ } else {
2960
+ return [];
2961
+ }
2962
+ }
2963
+ get targetElementsByQuery() {
2964
+ var _a;
2965
+ const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);
2966
+ if (elements.length !== 0) {
2967
+ return Array.prototype.slice.call(elements);
2968
+ } else {
2969
+ return [];
2970
+ }
2971
+ }
2910
2972
  }
2911
2973
 
2912
2974
  FrameElement.delegateConstructor = FrameController;
@@ -265,7 +265,9 @@ module Turbo::Broadcastable
265
265
 
266
266
  def broadcast_rendering_with_defaults(options)
267
267
  options.tap do |o|
268
- o[:object] ||= self
268
+ # Add the current instance into the locals with the element name (which is the un-namespaced name)
269
+ # as the key. This parallels how the ActionView::ObjectRenderer would create a local variable.
270
+ o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self)
269
271
  o[:partial] ||= to_partial_path
270
272
  end
271
273
  end
data/lib/turbo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Turbo
2
- VERSION = "0.5.11"
2
+ VERSION = "0.5.12"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turbo-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.11
4
+ version: 0.5.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Stephenson
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2021-06-14 00:00:00.000000000 Z
13
+ date: 2021-06-30 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails