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 +4 -4
- data/README.md +4 -9
- data/app/assets/javascripts/turbo.js +96 -34
- data/app/models/concerns/turbo/broadcastable.rb +3 -1
- data/lib/turbo/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27a0cac6803a0e1e410fb4fce4aaa60a0998aee4dec7d1b4b4cbb674533ce5cf
|
4
|
+
data.tar.gz: a4a67e8cff6989445d669cd6aa3b64b39dfc7de4ca7afdfddfd41c1e9334558e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
45
|
+
The `Turbo` instance is automatically assigned to `window.Turbo` upon import:
|
46
46
|
|
47
47
|
```js
|
48
|
-
import
|
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
|
-
|
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
|
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.
|
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
|
-
|
2794
|
-
|
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
|
-
|
2845
|
+
this.targetElements.forEach((e => e.append(this.templateContent)));
|
2800
2846
|
},
|
2801
2847
|
before() {
|
2802
|
-
|
2803
|
-
|
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
|
-
|
2855
|
+
this.targetElements.forEach((e => e.prepend(this.templateContent)));
|
2809
2856
|
},
|
2810
2857
|
remove() {
|
2811
|
-
|
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
|
-
|
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
|
-
|
2820
|
-
|
2821
|
-
|
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((
|
2852
|
-
targetChild.remove();
|
2853
|
-
}));
|
2896
|
+
this.duplicateChildren.forEach((c => c.remove()));
|
2854
2897
|
}
|
2855
2898
|
get duplicateChildren() {
|
2856
2899
|
var _a;
|
2857
|
-
|
2858
|
-
|
2859
|
-
|
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
|
2876
|
-
var _a;
|
2914
|
+
get targetElements() {
|
2877
2915
|
if (this.target) {
|
2878
|
-
return
|
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
|
-
|
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
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.
|
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-
|
13
|
+
date: 2021-06-30 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rails
|