turbo-rails 0.5.9 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -11
- data/Rakefile +4 -0
- data/app/assets/javascripts/turbo.js +1427 -1112
- data/app/channels/turbo/streams/broadcasts.rb +24 -0
- data/app/helpers/turbo/frames_helper.rb +4 -0
- data/app/helpers/turbo/streams/action_helper.rb +12 -3
- data/app/helpers/turbo/streams_helper.rb +5 -3
- data/app/javascript/turbo/cable.js +6 -3
- data/app/jobs/turbo/streams/action_broadcast_job.rb +2 -0
- data/app/jobs/turbo/streams/broadcast_job.rb +2 -0
- data/app/models/concerns/turbo/broadcastable.rb +72 -9
- data/app/models/turbo/streams/tag_builder.rb +140 -11
- data/lib/install/turbo_with_asset_pipeline.rb +10 -10
- data/lib/install/turbo_with_webpacker.rb +14 -8
- data/lib/turbo/engine.rb +5 -1
- data/lib/turbo/test_assertions.rb +2 -3
- data/lib/turbo/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42d7a658511c698c83b4e41853f96f5b2534a9ee81b89500044a22d6695a5150
|
4
|
+
data.tar.gz: 46e9abb9a01e4c9b50d2c2b704ff0e522a7231496b58d7acbc78303cdef67486
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be906021b12ba905c7ce316900cb28db1ec22cbec561c2e009758a58cb059c82984862bae1f045c2f60b1db9fd396ebb2c88ba07c08a2871d1f39556f67cd2b1
|
7
|
+
data.tar.gz: de3c17c9f07971421add4c6038f385b609d465eb939f1860900c635b35b72b65ebbbea030a2d45722bb6bda86b2b38e5d794c005e063698c3ec63478fd3dd083
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Turbo
|
2
2
|
|
3
|
-
[Turbo](https://turbo.
|
3
|
+
[Turbo](https://turbo.hotwired.dev) gives you the speed of a single-page web application without having to write any JavaScript. Turbo accelerates links and form submissions without requiring you to change your server-side generated HTML. It lets you carve up a page into independent frames, which can be lazy-loaded and operate as independent components. And finally, helps you make partial page updates using just HTML and a set of CRUD-like container tags. These three techniques reduce the amount of custom JavaScript that many web applications need to write by an order of magnitude. And for the few dynamic bits that are left, you're invited to finish the job with [Stimulus](https://github.com/hotwired/stimulus).
|
4
4
|
|
5
5
|
On top of accelerating web applications, Turbo was built from the ground-up to form the foundation of hybrid native applications. Write the navigational shell of your Android or iOS app using the standard platform tooling, then seamlessly fill in features from the web, following native navigation patterns. Not every mobile screen needs to be written in Swift or Kotlin to feel native. With Turbo, you spend less time wrangling JSON, waiting on app stores to approve updates, or reimplementing features you've already created in HTML.
|
6
6
|
|
@@ -40,32 +40,29 @@ 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
|
53
52
|
|
54
|
-
You can watch [the video introduction to Hotwire](https://
|
53
|
+
You can watch [the video introduction to Hotwire](https://hotwired.dev/#screencast), which focuses extensively on demonstration Turbo in a Rails demo. Then you should familiarize yourself with [Turbo handbook](https://turbo.hotwired.dev/handbook/introduction) to understand Drive, Frames, and Streams in-depth. Finally, dive into the code documentation by starting with [`Turbo::FramesHelper`](https://github.com/hotwired/turbo-rails/blob/main/app/helpers/turbo/frames_helper.rb), [`Turbo::StreamsHelper`](https://github.com/hotwired/turbo-rails/blob/main/app/helpers/turbo/streams_helper.rb), [`Turbo::Streams::TagBuilder`](https://github.com/hotwired/turbo-rails/blob/main/app/models/turbo/streams/tag_builder.rb), and [`Turbo::Broadcastable`](https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb).
|
55
54
|
|
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 upgrade 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
|
67
62
|
|
68
63
|
* To run the Rails tests: `bundle exec rake`.
|
64
|
+
* To install dependencies: `bundle install`
|
65
|
+
* To prepare the test database: `cd tests/dummy; RAILS_ENV=test ./bin/rails db:migrate`
|
69
66
|
* To compile the JavaScript for the asset pipeline: `yarn build`
|
70
67
|
|
71
68
|
|
data/Rakefile
CHANGED
@@ -2,6 +2,10 @@ require "bundler/setup"
|
|
2
2
|
require "bundler/gem_tasks"
|
3
3
|
require "rake/testtask"
|
4
4
|
|
5
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
6
|
+
load "rails/tasks/engine.rake"
|
7
|
+
load "rails/tasks/statistics.rake"
|
8
|
+
|
5
9
|
Rake::TestTask.new do |test|
|
6
10
|
test.libs << "test"
|
7
11
|
test.test_files = FileList["test/**/*_test.rb"]
|
@@ -55,7 +55,7 @@ class FrameElement extends HTMLElement {
|
|
55
55
|
this.delegate = new FrameElement.delegateConstructor(this);
|
56
56
|
}
|
57
57
|
static get observedAttributes() {
|
58
|
-
return [ "loading", "src" ];
|
58
|
+
return [ "disabled", "loading", "src" ];
|
59
59
|
}
|
60
60
|
connectedCallback() {
|
61
61
|
this.delegate.connect();
|
@@ -63,11 +63,18 @@ class FrameElement extends HTMLElement {
|
|
63
63
|
disconnectedCallback() {
|
64
64
|
this.delegate.disconnect();
|
65
65
|
}
|
66
|
+
reload() {
|
67
|
+
const {src: src} = this;
|
68
|
+
this.src = null;
|
69
|
+
this.src = src;
|
70
|
+
}
|
66
71
|
attributeChangedCallback(name) {
|
67
72
|
if (name == "loading") {
|
68
73
|
this.delegate.loadingStyleChanged();
|
69
74
|
} else if (name == "src") {
|
70
75
|
this.delegate.sourceURLChanged();
|
76
|
+
} else {
|
77
|
+
this.delegate.disabledChanged();
|
71
78
|
}
|
72
79
|
}
|
73
80
|
get src() {
|
@@ -133,9 +140,7 @@ function frameLoadingStyleFromString(style) {
|
|
133
140
|
}
|
134
141
|
|
135
142
|
function expandURL(locatable) {
|
136
|
-
|
137
|
-
anchor.href = locatable.toString();
|
138
|
-
return new URL(anchor.href);
|
143
|
+
return new URL(locatable.toString(), document.baseURI);
|
139
144
|
}
|
140
145
|
|
141
146
|
function getAnchor(url) {
|
@@ -144,8 +149,6 @@ function getAnchor(url) {
|
|
144
149
|
return url.hash.slice(1);
|
145
150
|
} else if (anchorMatch = url.href.match(/#(.*)$/)) {
|
146
151
|
return anchorMatch[1];
|
147
|
-
} else {
|
148
|
-
return "";
|
149
152
|
}
|
150
153
|
}
|
151
154
|
|
@@ -162,13 +165,17 @@ function isPrefixedBy(baseURL, url) {
|
|
162
165
|
return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
|
163
166
|
}
|
164
167
|
|
168
|
+
function getRequestURL(url) {
|
169
|
+
const anchor = getAnchor(url);
|
170
|
+
return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
|
171
|
+
}
|
172
|
+
|
165
173
|
function toCacheKey(url) {
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
}
|
174
|
+
return getRequestURL(url);
|
175
|
+
}
|
176
|
+
|
177
|
+
function urlsAreEqual(left, right) {
|
178
|
+
return expandURL(left).href == expandURL(right).href;
|
172
179
|
}
|
173
180
|
|
174
181
|
function getPathComponents(url) {
|
@@ -321,8 +328,10 @@ function fetchMethodFromString(method) {
|
|
321
328
|
class FetchRequest {
|
322
329
|
constructor(delegate, method, location, body = new URLSearchParams) {
|
323
330
|
this.abortController = new AbortController;
|
331
|
+
this.resolveRequestPromise = value => {};
|
324
332
|
this.delegate = delegate;
|
325
333
|
this.method = method;
|
334
|
+
this.headers = this.defaultHeaders;
|
326
335
|
if (this.isIdempotent) {
|
327
336
|
this.url = mergeFormDataEntries(location, [ ...body.entries() ]);
|
328
337
|
} else {
|
@@ -343,12 +352,10 @@ class FetchRequest {
|
|
343
352
|
this.abortController.abort();
|
344
353
|
}
|
345
354
|
async perform() {
|
355
|
+
var _a, _b;
|
346
356
|
const {fetchOptions: fetchOptions} = this;
|
347
|
-
|
348
|
-
|
349
|
-
fetchOptions: fetchOptions
|
350
|
-
}
|
351
|
-
});
|
357
|
+
(_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
|
358
|
+
await this.allowRequestToBeIntercepted(fetchOptions);
|
352
359
|
try {
|
353
360
|
this.delegate.requestStarted(this);
|
354
361
|
const response = await fetch(this.url.href, fetchOptions);
|
@@ -387,23 +394,28 @@ class FetchRequest {
|
|
387
394
|
signal: this.abortSignal
|
388
395
|
};
|
389
396
|
}
|
397
|
+
get defaultHeaders() {
|
398
|
+
return {
|
399
|
+
Accept: "text/html, application/xhtml+xml"
|
400
|
+
};
|
401
|
+
}
|
390
402
|
get isIdempotent() {
|
391
403
|
return this.method == FetchMethod.get;
|
392
404
|
}
|
393
|
-
get headers() {
|
394
|
-
const headers = Object.assign({}, this.defaultHeaders);
|
395
|
-
if (typeof this.delegate.prepareHeadersForRequest == "function") {
|
396
|
-
this.delegate.prepareHeadersForRequest(headers, this);
|
397
|
-
}
|
398
|
-
return headers;
|
399
|
-
}
|
400
405
|
get abortSignal() {
|
401
406
|
return this.abortController.signal;
|
402
407
|
}
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
408
|
+
async allowRequestToBeIntercepted(fetchOptions) {
|
409
|
+
const requestInterception = new Promise((resolve => this.resolveRequestPromise = resolve));
|
410
|
+
const event = dispatch("turbo:before-fetch-request", {
|
411
|
+
cancelable: true,
|
412
|
+
detail: {
|
413
|
+
fetchOptions: fetchOptions,
|
414
|
+
url: this.url.href,
|
415
|
+
resume: this.resolveRequestPromise
|
416
|
+
}
|
417
|
+
});
|
418
|
+
if (event.defaultPrevented) await requestInterception;
|
407
419
|
}
|
408
420
|
}
|
409
421
|
|
@@ -548,6 +560,9 @@ class FormSubmission {
|
|
548
560
|
var _a;
|
549
561
|
return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
|
550
562
|
}
|
563
|
+
get isIdempotent() {
|
564
|
+
return this.fetchRequest.isIdempotent;
|
565
|
+
}
|
551
566
|
get stringFormData() {
|
552
567
|
return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
|
553
568
|
}
|
@@ -639,8 +654,8 @@ function buildFormData(formElement, submitter) {
|
|
639
654
|
const formData = new FormData(formElement);
|
640
655
|
const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
|
641
656
|
const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
|
642
|
-
if (name && formData.get(name) != value) {
|
643
|
-
formData.append(name, value
|
657
|
+
if (name && value != null && formData.get(name) != value) {
|
658
|
+
formData.append(name, value);
|
644
659
|
}
|
645
660
|
return formData;
|
646
661
|
}
|
@@ -682,6 +697,9 @@ class Snapshot {
|
|
682
697
|
return null;
|
683
698
|
}
|
684
699
|
}
|
700
|
+
get isConnected() {
|
701
|
+
return this.element.isConnected;
|
702
|
+
}
|
685
703
|
get firstAutofocusableElement() {
|
686
704
|
return this.element.querySelector("[autofocus]");
|
687
705
|
}
|
@@ -691,8 +709,16 @@ class Snapshot {
|
|
691
709
|
getPermanentElementById(id) {
|
692
710
|
return this.element.querySelector(`#${id}[data-turbo-permanent]`);
|
693
711
|
}
|
694
|
-
|
695
|
-
|
712
|
+
getPermanentElementMapForSnapshot(snapshot) {
|
713
|
+
const permanentElementMap = {};
|
714
|
+
for (const currentPermanentElement of this.permanentElements) {
|
715
|
+
const {id: id} = currentPermanentElement;
|
716
|
+
const newPermanentElement = snapshot.getPermanentElementById(id);
|
717
|
+
if (newPermanentElement) {
|
718
|
+
permanentElementMap[id] = [ currentPermanentElement, newPermanentElement ];
|
719
|
+
}
|
720
|
+
}
|
721
|
+
return permanentElementMap;
|
696
722
|
}
|
697
723
|
}
|
698
724
|
|
@@ -722,6 +748,8 @@ class FormInterceptor {
|
|
722
748
|
|
723
749
|
class View {
|
724
750
|
constructor(delegate, element) {
|
751
|
+
this.resolveRenderPromise = value => {};
|
752
|
+
this.resolveInterceptionPromise = value => {};
|
725
753
|
this.delegate = delegate;
|
726
754
|
this.element = element;
|
727
755
|
}
|
@@ -729,6 +757,7 @@ class View {
|
|
729
757
|
const element = this.snapshot.getElementForAnchor(anchor);
|
730
758
|
if (element) {
|
731
759
|
this.scrollToElement(element);
|
760
|
+
this.focusElement(element);
|
732
761
|
} else {
|
733
762
|
this.scrollToPosition({
|
734
763
|
x: 0,
|
@@ -736,9 +765,23 @@ class View {
|
|
736
765
|
});
|
737
766
|
}
|
738
767
|
}
|
768
|
+
scrollToAnchorFromLocation(location) {
|
769
|
+
this.scrollToAnchor(getAnchor(location));
|
770
|
+
}
|
739
771
|
scrollToElement(element) {
|
740
772
|
element.scrollIntoView();
|
741
773
|
}
|
774
|
+
focusElement(element) {
|
775
|
+
if (element instanceof HTMLElement) {
|
776
|
+
if (element.hasAttribute("tabindex")) {
|
777
|
+
element.focus();
|
778
|
+
} else {
|
779
|
+
element.setAttribute("tabindex", "-1");
|
780
|
+
element.focus();
|
781
|
+
element.removeAttribute("tabindex");
|
782
|
+
}
|
783
|
+
}
|
784
|
+
}
|
742
785
|
scrollToPosition({x: x, y: y}) {
|
743
786
|
this.scrollRoot.scrollTo(x, y);
|
744
787
|
}
|
@@ -746,20 +789,22 @@ class View {
|
|
746
789
|
return window;
|
747
790
|
}
|
748
791
|
async render(renderer) {
|
749
|
-
if (this.renderer) {
|
750
|
-
throw new Error("rendering is already in progress");
|
751
|
-
}
|
752
792
|
const {isPreview: isPreview, shouldRender: shouldRender, newSnapshot: snapshot} = renderer;
|
753
793
|
if (shouldRender) {
|
754
794
|
try {
|
795
|
+
this.renderPromise = new Promise((resolve => this.resolveRenderPromise = resolve));
|
755
796
|
this.renderer = renderer;
|
756
797
|
this.prepareToRenderSnapshot(renderer);
|
757
|
-
this.
|
798
|
+
const renderInterception = new Promise((resolve => this.resolveInterceptionPromise = resolve));
|
799
|
+
const immediateRender = this.delegate.allowsImmediateRender(snapshot, this.resolveInterceptionPromise);
|
800
|
+
if (!immediateRender) await renderInterception;
|
758
801
|
await this.renderSnapshot(renderer);
|
759
802
|
this.delegate.viewRenderedSnapshot(snapshot, isPreview);
|
760
803
|
this.finishRenderingSnapshot(renderer);
|
761
804
|
} finally {
|
762
805
|
delete this.renderer;
|
806
|
+
this.resolveRenderPromise(undefined);
|
807
|
+
delete this.renderPromise;
|
763
808
|
}
|
764
809
|
} else {
|
765
810
|
this.invalidate();
|
@@ -837,6 +882,56 @@ class LinkInterceptor {
|
|
837
882
|
}
|
838
883
|
}
|
839
884
|
|
885
|
+
class Bardo {
|
886
|
+
constructor(permanentElementMap) {
|
887
|
+
this.permanentElementMap = permanentElementMap;
|
888
|
+
}
|
889
|
+
static preservingPermanentElements(permanentElementMap, callback) {
|
890
|
+
const bardo = new this(permanentElementMap);
|
891
|
+
bardo.enter();
|
892
|
+
callback();
|
893
|
+
bardo.leave();
|
894
|
+
}
|
895
|
+
enter() {
|
896
|
+
for (const id in this.permanentElementMap) {
|
897
|
+
const [, newPermanentElement] = this.permanentElementMap[id];
|
898
|
+
this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
|
899
|
+
}
|
900
|
+
}
|
901
|
+
leave() {
|
902
|
+
for (const id in this.permanentElementMap) {
|
903
|
+
const [currentPermanentElement] = this.permanentElementMap[id];
|
904
|
+
this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
|
905
|
+
this.replacePlaceholderWithPermanentElement(currentPermanentElement);
|
906
|
+
}
|
907
|
+
}
|
908
|
+
replaceNewPermanentElementWithPlaceholder(permanentElement) {
|
909
|
+
const placeholder = createPlaceholderForPermanentElement(permanentElement);
|
910
|
+
permanentElement.replaceWith(placeholder);
|
911
|
+
}
|
912
|
+
replaceCurrentPermanentElementWithClone(permanentElement) {
|
913
|
+
const clone = permanentElement.cloneNode(true);
|
914
|
+
permanentElement.replaceWith(clone);
|
915
|
+
}
|
916
|
+
replacePlaceholderWithPermanentElement(permanentElement) {
|
917
|
+
const placeholder = this.getPlaceholderById(permanentElement.id);
|
918
|
+
placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
|
919
|
+
}
|
920
|
+
getPlaceholderById(id) {
|
921
|
+
return this.placeholders.find((element => element.content == id));
|
922
|
+
}
|
923
|
+
get placeholders() {
|
924
|
+
return [ ...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]") ];
|
925
|
+
}
|
926
|
+
}
|
927
|
+
|
928
|
+
function createPlaceholderForPermanentElement(permanentElement) {
|
929
|
+
const element = document.createElement("meta");
|
930
|
+
element.setAttribute("name", "turbo-permanent-placeholder");
|
931
|
+
element.setAttribute("content", permanentElement.id);
|
932
|
+
return element;
|
933
|
+
}
|
934
|
+
|
840
935
|
class Renderer {
|
841
936
|
constructor(currentSnapshot, newSnapshot, isPreview) {
|
842
937
|
this.currentSnapshot = currentSnapshot;
|
@@ -864,6 +959,9 @@ class Renderer {
|
|
864
959
|
return element;
|
865
960
|
} else {
|
866
961
|
const createdScriptElement = document.createElement("script");
|
962
|
+
if (this.cspNonce) {
|
963
|
+
createdScriptElement.nonce = this.cspNonce;
|
964
|
+
}
|
867
965
|
createdScriptElement.textContent = element.textContent;
|
868
966
|
createdScriptElement.async = false;
|
869
967
|
copyElementAttributes(createdScriptElement, element);
|
@@ -871,28 +969,29 @@ class Renderer {
|
|
871
969
|
}
|
872
970
|
}
|
873
971
|
preservingPermanentElements(callback) {
|
874
|
-
|
875
|
-
callback();
|
876
|
-
replacePlaceholderElementsWithClonedPermanentElements(placeholders);
|
972
|
+
Bardo.preservingPermanentElements(this.permanentElementMap, callback);
|
877
973
|
}
|
878
974
|
focusFirstAutofocusableElement() {
|
879
|
-
const element = this.
|
975
|
+
const element = this.connectedSnapshot.firstAutofocusableElement;
|
880
976
|
if (elementIsFocusable(element)) {
|
881
977
|
element.focus();
|
882
978
|
}
|
883
979
|
}
|
980
|
+
get connectedSnapshot() {
|
981
|
+
return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
|
982
|
+
}
|
884
983
|
get currentElement() {
|
885
984
|
return this.currentSnapshot.element;
|
886
985
|
}
|
887
986
|
get newElement() {
|
888
987
|
return this.newSnapshot.element;
|
889
988
|
}
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
return
|
989
|
+
get permanentElementMap() {
|
990
|
+
return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
|
991
|
+
}
|
992
|
+
get cspNonce() {
|
993
|
+
var _a;
|
994
|
+
return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
|
896
995
|
}
|
897
996
|
}
|
898
997
|
|
@@ -902,37 +1001,6 @@ function copyElementAttributes(destinationElement, sourceElement) {
|
|
902
1001
|
}
|
903
1002
|
}
|
904
1003
|
|
905
|
-
function createPlaceholderForPermanentElement(permanentElement) {
|
906
|
-
const element = document.createElement("meta");
|
907
|
-
element.setAttribute("name", "turbo-permanent-placeholder");
|
908
|
-
element.setAttribute("content", permanentElement.id);
|
909
|
-
return {
|
910
|
-
element: element,
|
911
|
-
permanentElement: permanentElement
|
912
|
-
};
|
913
|
-
}
|
914
|
-
|
915
|
-
function replacePlaceholderElementsWithClonedPermanentElements(placeholders) {
|
916
|
-
for (const {element: element, permanentElement: permanentElement} of placeholders) {
|
917
|
-
const clonedElement = permanentElement.cloneNode(true);
|
918
|
-
replaceElementWithElement(element, clonedElement);
|
919
|
-
}
|
920
|
-
}
|
921
|
-
|
922
|
-
function relocatePermanentElements(currentSnapshot, newSnapshot) {
|
923
|
-
return currentSnapshot.getPermanentElementsPresentInSnapshot(newSnapshot).reduce(((placeholders, permanentElement) => {
|
924
|
-
const newElement = newSnapshot.getPermanentElementById(permanentElement.id);
|
925
|
-
if (newElement) {
|
926
|
-
const placeholder = createPlaceholderForPermanentElement(permanentElement);
|
927
|
-
replaceElementWithElement(permanentElement, placeholder.element);
|
928
|
-
replaceElementWithElement(newElement, permanentElement);
|
929
|
-
return [ ...placeholders, placeholder ];
|
930
|
-
} else {
|
931
|
-
return placeholders;
|
932
|
-
}
|
933
|
-
}), []);
|
934
|
-
}
|
935
|
-
|
936
1004
|
function elementIsFocusable(element) {
|
937
1005
|
return element && typeof element.focus == "function";
|
938
1006
|
}
|
@@ -949,6 +1017,8 @@ class FrameRenderer extends Renderer {
|
|
949
1017
|
this.scrollFrameIntoView();
|
950
1018
|
await nextAnimationFrame();
|
951
1019
|
this.focusFirstAutofocusableElement();
|
1020
|
+
await nextAnimationFrame();
|
1021
|
+
this.activateScriptElements();
|
952
1022
|
}
|
953
1023
|
loadFrameElement() {
|
954
1024
|
var _a;
|
@@ -975,6 +1045,15 @@ class FrameRenderer extends Renderer {
|
|
975
1045
|
}
|
976
1046
|
return false;
|
977
1047
|
}
|
1048
|
+
activateScriptElements() {
|
1049
|
+
for (const inertScriptElement of this.newScriptElements) {
|
1050
|
+
const activatedScriptElement = this.createScriptElement(inertScriptElement);
|
1051
|
+
inertScriptElement.replaceWith(activatedScriptElement);
|
1052
|
+
}
|
1053
|
+
}
|
1054
|
+
get newScriptElements() {
|
1055
|
+
return this.currentElement.querySelectorAll("script");
|
1056
|
+
}
|
978
1057
|
}
|
979
1058
|
|
980
1059
|
function readScrollLogicalPosition(value, defaultValue) {
|
@@ -985,446 +1064,121 @@ function readScrollLogicalPosition(value, defaultValue) {
|
|
985
1064
|
}
|
986
1065
|
}
|
987
1066
|
|
988
|
-
class
|
989
|
-
constructor(
|
990
|
-
this.
|
991
|
-
this.
|
992
|
-
this.
|
993
|
-
this.
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
}
|
1001
|
-
this.linkInterceptor.start();
|
1002
|
-
this.formInterceptor.start();
|
1003
|
-
}
|
1004
|
-
disconnect() {
|
1005
|
-
this.appearanceObserver.stop();
|
1006
|
-
this.linkInterceptor.stop();
|
1007
|
-
this.formInterceptor.stop();
|
1008
|
-
}
|
1009
|
-
sourceURLChanged() {
|
1010
|
-
if (this.loadingStyle == FrameLoadingStyle.eager) {
|
1011
|
-
this.loadSourceURL();
|
1012
|
-
}
|
1013
|
-
}
|
1014
|
-
loadingStyleChanged() {
|
1015
|
-
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
1016
|
-
this.appearanceObserver.start();
|
1017
|
-
} else {
|
1018
|
-
this.appearanceObserver.stop();
|
1019
|
-
this.loadSourceURL();
|
1020
|
-
}
|
1067
|
+
class ProgressBar {
|
1068
|
+
constructor() {
|
1069
|
+
this.hiding = false;
|
1070
|
+
this.value = 0;
|
1071
|
+
this.visible = false;
|
1072
|
+
this.trickle = () => {
|
1073
|
+
this.setValue(this.value + Math.random() / 100);
|
1074
|
+
};
|
1075
|
+
this.stylesheetElement = this.createStylesheetElement();
|
1076
|
+
this.progressElement = this.createProgressElement();
|
1077
|
+
this.installStylesheetElement();
|
1078
|
+
this.setValue(0);
|
1021
1079
|
}
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1080
|
+
static get defaultCSS() {
|
1081
|
+
return unindent`
|
1082
|
+
.turbo-progress-bar {
|
1083
|
+
position: fixed;
|
1084
|
+
display: block;
|
1085
|
+
top: 0;
|
1086
|
+
left: 0;
|
1087
|
+
height: 3px;
|
1088
|
+
background: #0076ff;
|
1089
|
+
z-index: 9999;
|
1090
|
+
transition:
|
1091
|
+
width ${ProgressBar.animationDuration}ms ease-out,
|
1092
|
+
opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
|
1093
|
+
transform: translate3d(0, 0, 0);
|
1031
1094
|
}
|
1095
|
+
`;
|
1096
|
+
}
|
1097
|
+
show() {
|
1098
|
+
if (!this.visible) {
|
1099
|
+
this.visible = true;
|
1100
|
+
this.installProgressElement();
|
1101
|
+
this.startTrickling();
|
1032
1102
|
}
|
1033
1103
|
}
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
}
|
1043
|
-
} catch (error) {
|
1044
|
-
console.error(error);
|
1045
|
-
this.view.invalidate();
|
1104
|
+
hide() {
|
1105
|
+
if (this.visible && !this.hiding) {
|
1106
|
+
this.hiding = true;
|
1107
|
+
this.fadeProgressElement((() => {
|
1108
|
+
this.uninstallProgressElement();
|
1109
|
+
this.stopTrickling();
|
1110
|
+
this.visible = false;
|
1111
|
+
this.hiding = false;
|
1112
|
+
}));
|
1046
1113
|
}
|
1047
1114
|
}
|
1048
|
-
|
1049
|
-
this.
|
1115
|
+
setValue(value) {
|
1116
|
+
this.value = value;
|
1117
|
+
this.refresh();
|
1050
1118
|
}
|
1051
|
-
|
1052
|
-
|
1119
|
+
installStylesheetElement() {
|
1120
|
+
document.head.insertBefore(this.stylesheetElement, document.head.firstChild);
|
1053
1121
|
}
|
1054
|
-
|
1055
|
-
this.
|
1122
|
+
installProgressElement() {
|
1123
|
+
this.progressElement.style.width = "0";
|
1124
|
+
this.progressElement.style.opacity = "1";
|
1125
|
+
document.documentElement.insertBefore(this.progressElement, document.body);
|
1126
|
+
this.refresh();
|
1056
1127
|
}
|
1057
|
-
|
1058
|
-
|
1128
|
+
fadeProgressElement(callback) {
|
1129
|
+
this.progressElement.style.opacity = "0";
|
1130
|
+
setTimeout(callback, ProgressBar.animationDuration * 1.5);
|
1059
1131
|
}
|
1060
|
-
|
1061
|
-
if (this.
|
1062
|
-
|
1132
|
+
uninstallProgressElement() {
|
1133
|
+
if (this.progressElement.parentNode) {
|
1134
|
+
document.documentElement.removeChild(this.progressElement);
|
1063
1135
|
}
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
this.formSubmission.start();
|
1136
|
+
}
|
1137
|
+
startTrickling() {
|
1138
|
+
if (!this.trickleInterval) {
|
1139
|
+
this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration);
|
1069
1140
|
}
|
1070
1141
|
}
|
1071
|
-
|
1072
|
-
|
1142
|
+
stopTrickling() {
|
1143
|
+
window.clearInterval(this.trickleInterval);
|
1144
|
+
delete this.trickleInterval;
|
1073
1145
|
}
|
1074
|
-
|
1075
|
-
|
1146
|
+
refresh() {
|
1147
|
+
requestAnimationFrame((() => {
|
1148
|
+
this.progressElement.style.width = `${10 + this.value * 90}%`;
|
1149
|
+
}));
|
1076
1150
|
}
|
1077
|
-
|
1078
|
-
|
1151
|
+
createStylesheetElement() {
|
1152
|
+
const element = document.createElement("style");
|
1153
|
+
element.type = "text/css";
|
1154
|
+
element.textContent = ProgressBar.defaultCSS;
|
1155
|
+
return element;
|
1079
1156
|
}
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1157
|
+
createProgressElement() {
|
1158
|
+
const element = document.createElement("div");
|
1159
|
+
element.className = "turbo-progress-bar";
|
1160
|
+
return element;
|
1083
1161
|
}
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
formSubmissionFinished(formSubmission) {}
|
1105
|
-
viewWillRenderSnapshot(snapshot, isPreview) {}
|
1106
|
-
viewRenderedSnapshot(snapshot, isPreview) {}
|
1107
|
-
viewInvalidated() {}
|
1108
|
-
async visit(url) {
|
1109
|
-
const request = new FetchRequest(this, FetchMethod.get, expandURL(url));
|
1110
|
-
return new Promise((resolve => {
|
1111
|
-
this.resolveVisitPromise = () => {
|
1112
|
-
this.resolveVisitPromise = () => {};
|
1113
|
-
resolve();
|
1114
|
-
};
|
1115
|
-
request.perform();
|
1116
|
-
}));
|
1117
|
-
}
|
1118
|
-
navigateFrame(element, url) {
|
1119
|
-
const frame = this.findFrameElement(element);
|
1120
|
-
frame.src = url;
|
1121
|
-
}
|
1122
|
-
findFrameElement(element) {
|
1123
|
-
var _a;
|
1124
|
-
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
1125
|
-
return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
|
1126
|
-
}
|
1127
|
-
async extractForeignFrameElement(container) {
|
1128
|
-
let element;
|
1129
|
-
const id = CSS.escape(this.id);
|
1130
|
-
if (element = activateElement(container.querySelector(`turbo-frame#${id}`))) {
|
1131
|
-
return element;
|
1132
|
-
}
|
1133
|
-
if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`))) {
|
1134
|
-
await element.loaded;
|
1135
|
-
return await this.extractForeignFrameElement(element);
|
1136
|
-
}
|
1137
|
-
console.error(`Response has no matching <turbo-frame id="${id}"> element`);
|
1138
|
-
return new FrameElement;
|
1139
|
-
}
|
1140
|
-
shouldInterceptNavigation(element) {
|
1141
|
-
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
1142
|
-
if (!this.enabled || id == "_top") {
|
1143
|
-
return false;
|
1144
|
-
}
|
1145
|
-
if (id) {
|
1146
|
-
const frameElement = getFrameElementById(id);
|
1147
|
-
if (frameElement) {
|
1148
|
-
return !frameElement.disabled;
|
1149
|
-
}
|
1150
|
-
}
|
1151
|
-
return true;
|
1152
|
-
}
|
1153
|
-
get id() {
|
1154
|
-
return this.element.id;
|
1155
|
-
}
|
1156
|
-
get enabled() {
|
1157
|
-
return !this.element.disabled;
|
1158
|
-
}
|
1159
|
-
get sourceURL() {
|
1160
|
-
return this.element.src;
|
1161
|
-
}
|
1162
|
-
get loadingStyle() {
|
1163
|
-
return this.element.loading;
|
1164
|
-
}
|
1165
|
-
get isLoading() {
|
1166
|
-
return this.formSubmission !== undefined || this.loadingURL !== undefined;
|
1167
|
-
}
|
1168
|
-
get isActive() {
|
1169
|
-
return this.element.isActive;
|
1170
|
-
}
|
1171
|
-
}
|
1172
|
-
|
1173
|
-
function getFrameElementById(id) {
|
1174
|
-
if (id != null) {
|
1175
|
-
const element = document.getElementById(id);
|
1176
|
-
if (element instanceof FrameElement) {
|
1177
|
-
return element;
|
1178
|
-
}
|
1179
|
-
}
|
1180
|
-
}
|
1181
|
-
|
1182
|
-
function activateElement(element) {
|
1183
|
-
if (element && element.ownerDocument !== document) {
|
1184
|
-
element = document.importNode(element, true);
|
1185
|
-
}
|
1186
|
-
if (element instanceof FrameElement) {
|
1187
|
-
return element;
|
1188
|
-
}
|
1189
|
-
}
|
1190
|
-
|
1191
|
-
const StreamActions = {
|
1192
|
-
append() {
|
1193
|
-
var _a;
|
1194
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.append(this.templateContent);
|
1195
|
-
},
|
1196
|
-
prepend() {
|
1197
|
-
var _a;
|
1198
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.prepend(this.templateContent);
|
1199
|
-
},
|
1200
|
-
remove() {
|
1201
|
-
var _a;
|
1202
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.remove();
|
1203
|
-
},
|
1204
|
-
replace() {
|
1205
|
-
var _a;
|
1206
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.replaceWith(this.templateContent);
|
1207
|
-
},
|
1208
|
-
update() {
|
1209
|
-
if (this.targetElement) {
|
1210
|
-
this.targetElement.innerHTML = "";
|
1211
|
-
this.targetElement.append(this.templateContent);
|
1212
|
-
}
|
1213
|
-
}
|
1214
|
-
};
|
1215
|
-
|
1216
|
-
class StreamElement extends HTMLElement {
|
1217
|
-
async connectedCallback() {
|
1218
|
-
try {
|
1219
|
-
await this.render();
|
1220
|
-
} catch (error) {
|
1221
|
-
console.error(error);
|
1222
|
-
} finally {
|
1223
|
-
this.disconnect();
|
1224
|
-
}
|
1225
|
-
}
|
1226
|
-
async render() {
|
1227
|
-
var _a;
|
1228
|
-
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
|
1229
|
-
if (this.dispatchEvent(this.beforeRenderEvent)) {
|
1230
|
-
await nextAnimationFrame();
|
1231
|
-
this.performAction();
|
1232
|
-
}
|
1233
|
-
})();
|
1234
|
-
}
|
1235
|
-
disconnect() {
|
1236
|
-
try {
|
1237
|
-
this.remove();
|
1238
|
-
} catch (_a) {}
|
1239
|
-
}
|
1240
|
-
get performAction() {
|
1241
|
-
if (this.action) {
|
1242
|
-
const actionFunction = StreamActions[this.action];
|
1243
|
-
if (actionFunction) {
|
1244
|
-
return actionFunction;
|
1245
|
-
}
|
1246
|
-
this.raise("unknown action");
|
1247
|
-
}
|
1248
|
-
this.raise("action attribute is missing");
|
1249
|
-
}
|
1250
|
-
get targetElement() {
|
1251
|
-
var _a;
|
1252
|
-
if (this.target) {
|
1253
|
-
return (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
|
1254
|
-
}
|
1255
|
-
this.raise("target attribute is missing");
|
1256
|
-
}
|
1257
|
-
get templateContent() {
|
1258
|
-
return this.templateElement.content;
|
1259
|
-
}
|
1260
|
-
get templateElement() {
|
1261
|
-
if (this.firstElementChild instanceof HTMLTemplateElement) {
|
1262
|
-
return this.firstElementChild;
|
1263
|
-
}
|
1264
|
-
this.raise("first child element must be a <template> element");
|
1265
|
-
}
|
1266
|
-
get action() {
|
1267
|
-
return this.getAttribute("action");
|
1268
|
-
}
|
1269
|
-
get target() {
|
1270
|
-
return this.getAttribute("target");
|
1271
|
-
}
|
1272
|
-
raise(message) {
|
1273
|
-
throw new Error(`${this.description}: ${message}`);
|
1274
|
-
}
|
1275
|
-
get description() {
|
1276
|
-
var _a, _b;
|
1277
|
-
return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
|
1278
|
-
}
|
1279
|
-
get beforeRenderEvent() {
|
1280
|
-
return new CustomEvent("turbo:before-stream-render", {
|
1281
|
-
bubbles: true,
|
1282
|
-
cancelable: true
|
1283
|
-
});
|
1284
|
-
}
|
1285
|
-
}
|
1286
|
-
|
1287
|
-
FrameElement.delegateConstructor = FrameController;
|
1288
|
-
|
1289
|
-
customElements.define("turbo-frame", FrameElement);
|
1290
|
-
|
1291
|
-
customElements.define("turbo-stream", StreamElement);
|
1292
|
-
|
1293
|
-
(() => {
|
1294
|
-
let element = document.currentScript;
|
1295
|
-
if (!element) return;
|
1296
|
-
if (element.hasAttribute("data-turbo-suppress-warning")) return;
|
1297
|
-
while (element = element.parentElement) {
|
1298
|
-
if (element == document.body) {
|
1299
|
-
return console.warn(unindent`
|
1300
|
-
You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
|
1301
|
-
|
1302
|
-
Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
|
1303
|
-
|
1304
|
-
For more information, see: https://turbo.hotwire.dev/handbook/building#working-with-script-elements
|
1305
|
-
|
1306
|
-
——
|
1307
|
-
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
1308
|
-
`, element.outerHTML);
|
1309
|
-
}
|
1310
|
-
}
|
1311
|
-
})();
|
1312
|
-
|
1313
|
-
class ProgressBar {
|
1314
|
-
constructor() {
|
1315
|
-
this.hiding = false;
|
1316
|
-
this.value = 0;
|
1317
|
-
this.visible = false;
|
1318
|
-
this.trickle = () => {
|
1319
|
-
this.setValue(this.value + Math.random() / 100);
|
1320
|
-
};
|
1321
|
-
this.stylesheetElement = this.createStylesheetElement();
|
1322
|
-
this.progressElement = this.createProgressElement();
|
1323
|
-
this.installStylesheetElement();
|
1324
|
-
this.setValue(0);
|
1325
|
-
}
|
1326
|
-
static get defaultCSS() {
|
1327
|
-
return unindent`
|
1328
|
-
.turbo-progress-bar {
|
1329
|
-
position: fixed;
|
1330
|
-
display: block;
|
1331
|
-
top: 0;
|
1332
|
-
left: 0;
|
1333
|
-
height: 3px;
|
1334
|
-
background: #0076ff;
|
1335
|
-
z-index: 9999;
|
1336
|
-
transition:
|
1337
|
-
width ${ProgressBar.animationDuration}ms ease-out,
|
1338
|
-
opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
|
1339
|
-
transform: translate3d(0, 0, 0);
|
1340
|
-
}
|
1341
|
-
`;
|
1342
|
-
}
|
1343
|
-
show() {
|
1344
|
-
if (!this.visible) {
|
1345
|
-
this.visible = true;
|
1346
|
-
this.installProgressElement();
|
1347
|
-
this.startTrickling();
|
1348
|
-
}
|
1349
|
-
}
|
1350
|
-
hide() {
|
1351
|
-
if (this.visible && !this.hiding) {
|
1352
|
-
this.hiding = true;
|
1353
|
-
this.fadeProgressElement((() => {
|
1354
|
-
this.uninstallProgressElement();
|
1355
|
-
this.stopTrickling();
|
1356
|
-
this.visible = false;
|
1357
|
-
this.hiding = false;
|
1358
|
-
}));
|
1359
|
-
}
|
1360
|
-
}
|
1361
|
-
setValue(value) {
|
1362
|
-
this.value = value;
|
1363
|
-
this.refresh();
|
1364
|
-
}
|
1365
|
-
installStylesheetElement() {
|
1366
|
-
document.head.insertBefore(this.stylesheetElement, document.head.firstChild);
|
1367
|
-
}
|
1368
|
-
installProgressElement() {
|
1369
|
-
this.progressElement.style.width = "0";
|
1370
|
-
this.progressElement.style.opacity = "1";
|
1371
|
-
document.documentElement.insertBefore(this.progressElement, document.body);
|
1372
|
-
this.refresh();
|
1373
|
-
}
|
1374
|
-
fadeProgressElement(callback) {
|
1375
|
-
this.progressElement.style.opacity = "0";
|
1376
|
-
setTimeout(callback, ProgressBar.animationDuration * 1.5);
|
1377
|
-
}
|
1378
|
-
uninstallProgressElement() {
|
1379
|
-
if (this.progressElement.parentNode) {
|
1380
|
-
document.documentElement.removeChild(this.progressElement);
|
1381
|
-
}
|
1382
|
-
}
|
1383
|
-
startTrickling() {
|
1384
|
-
if (!this.trickleInterval) {
|
1385
|
-
this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration);
|
1386
|
-
}
|
1387
|
-
}
|
1388
|
-
stopTrickling() {
|
1389
|
-
window.clearInterval(this.trickleInterval);
|
1390
|
-
delete this.trickleInterval;
|
1391
|
-
}
|
1392
|
-
refresh() {
|
1393
|
-
requestAnimationFrame((() => {
|
1394
|
-
this.progressElement.style.width = `${10 + this.value * 90}%`;
|
1395
|
-
}));
|
1396
|
-
}
|
1397
|
-
createStylesheetElement() {
|
1398
|
-
const element = document.createElement("style");
|
1399
|
-
element.type = "text/css";
|
1400
|
-
element.textContent = ProgressBar.defaultCSS;
|
1401
|
-
return element;
|
1402
|
-
}
|
1403
|
-
createProgressElement() {
|
1404
|
-
const element = document.createElement("div");
|
1405
|
-
element.className = "turbo-progress-bar";
|
1406
|
-
return element;
|
1407
|
-
}
|
1408
|
-
}
|
1409
|
-
|
1410
|
-
ProgressBar.animationDuration = 300;
|
1411
|
-
|
1412
|
-
class HeadSnapshot extends Snapshot {
|
1413
|
-
constructor() {
|
1414
|
-
super(...arguments);
|
1415
|
-
this.detailsByOuterHTML = this.children.reduce(((result, element) => {
|
1416
|
-
const {outerHTML: outerHTML} = element;
|
1417
|
-
const details = outerHTML in result ? result[outerHTML] : {
|
1418
|
-
type: elementType(element),
|
1419
|
-
tracked: elementIsTracked(element),
|
1420
|
-
elements: []
|
1421
|
-
};
|
1422
|
-
return Object.assign(Object.assign({}, result), {
|
1423
|
-
[outerHTML]: Object.assign(Object.assign({}, details), {
|
1424
|
-
elements: [ ...details.elements, element ]
|
1425
|
-
})
|
1426
|
-
});
|
1427
|
-
}), {});
|
1162
|
+
}
|
1163
|
+
|
1164
|
+
ProgressBar.animationDuration = 300;
|
1165
|
+
|
1166
|
+
class HeadSnapshot extends Snapshot {
|
1167
|
+
constructor() {
|
1168
|
+
super(...arguments);
|
1169
|
+
this.detailsByOuterHTML = this.children.filter((element => !elementIsNoscript(element))).reduce(((result, element) => {
|
1170
|
+
const {outerHTML: outerHTML} = element;
|
1171
|
+
const details = outerHTML in result ? result[outerHTML] : {
|
1172
|
+
type: elementType(element),
|
1173
|
+
tracked: elementIsTracked(element),
|
1174
|
+
elements: []
|
1175
|
+
};
|
1176
|
+
return Object.assign(Object.assign({}, result), {
|
1177
|
+
[outerHTML]: Object.assign(Object.assign({}, details), {
|
1178
|
+
elements: [ ...details.elements, element ]
|
1179
|
+
})
|
1180
|
+
});
|
1181
|
+
}), {});
|
1428
1182
|
}
|
1429
1183
|
get trackedElementSignature() {
|
1430
1184
|
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
|
@@ -1479,6 +1233,11 @@ function elementIsScript(element) {
|
|
1479
1233
|
return tagName == "script";
|
1480
1234
|
}
|
1481
1235
|
|
1236
|
+
function elementIsNoscript(element) {
|
1237
|
+
const tagName = element.tagName.toLowerCase();
|
1238
|
+
return tagName == "noscript";
|
1239
|
+
}
|
1240
|
+
|
1482
1241
|
function elementIsStylesheet(element) {
|
1483
1242
|
const tagName = element.tagName.toLowerCase();
|
1484
1243
|
return tagName == "style" || tagName == "link" && element.getAttribute("rel") == "stylesheet";
|
@@ -1581,6 +1340,7 @@ class Visit {
|
|
1581
1340
|
this.referrer = referrer;
|
1582
1341
|
this.snapshotHTML = snapshotHTML;
|
1583
1342
|
this.response = response;
|
1343
|
+
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
1584
1344
|
}
|
1585
1345
|
get adapter() {
|
1586
1346
|
return this.delegate.adapter;
|
@@ -1673,6 +1433,7 @@ class Visit {
|
|
1673
1433
|
const {statusCode: statusCode, responseHTML: responseHTML} = this.response;
|
1674
1434
|
this.render((async () => {
|
1675
1435
|
this.cacheSnapshot();
|
1436
|
+
if (this.view.renderPromise) await this.view.renderPromise;
|
1676
1437
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
1677
1438
|
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML));
|
1678
1439
|
this.adapter.visitRendered(this);
|
@@ -1707,10 +1468,15 @@ class Visit {
|
|
1707
1468
|
const isPreview = this.shouldIssueRequest();
|
1708
1469
|
this.render((async () => {
|
1709
1470
|
this.cacheSnapshot();
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1713
|
-
this.
|
1471
|
+
if (this.isSamePage) {
|
1472
|
+
this.adapter.visitRendered(this);
|
1473
|
+
} else {
|
1474
|
+
if (this.view.renderPromise) await this.view.renderPromise;
|
1475
|
+
await this.view.renderPage(snapshot, isPreview);
|
1476
|
+
this.adapter.visitRendered(this);
|
1477
|
+
if (!isPreview) {
|
1478
|
+
this.complete();
|
1479
|
+
}
|
1714
1480
|
}
|
1715
1481
|
}));
|
1716
1482
|
}
|
@@ -1722,6 +1488,14 @@ class Visit {
|
|
1722
1488
|
this.followedRedirect = true;
|
1723
1489
|
}
|
1724
1490
|
}
|
1491
|
+
goToSamePageAnchor() {
|
1492
|
+
if (this.isSamePage) {
|
1493
|
+
this.render((async () => {
|
1494
|
+
this.cacheSnapshot();
|
1495
|
+
this.adapter.visitRendered(this);
|
1496
|
+
}));
|
1497
|
+
}
|
1498
|
+
}
|
1725
1499
|
requestStarted() {
|
1726
1500
|
this.startRequest();
|
1727
1501
|
}
|
@@ -1764,10 +1538,13 @@ class Visit {
|
|
1764
1538
|
performScroll() {
|
1765
1539
|
if (!this.scrolled) {
|
1766
1540
|
if (this.action == "restore") {
|
1767
|
-
this.scrollToRestoredPosition() || this.scrollToTop();
|
1541
|
+
this.scrollToRestoredPosition() || this.scrollToAnchor() || this.scrollToTop();
|
1768
1542
|
} else {
|
1769
1543
|
this.scrollToAnchor() || this.scrollToTop();
|
1770
1544
|
}
|
1545
|
+
if (this.isSamePage) {
|
1546
|
+
this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);
|
1547
|
+
}
|
1771
1548
|
this.scrolled = true;
|
1772
1549
|
}
|
1773
1550
|
}
|
@@ -1779,8 +1556,9 @@ class Visit {
|
|
1779
1556
|
}
|
1780
1557
|
}
|
1781
1558
|
scrollToAnchor() {
|
1782
|
-
|
1783
|
-
|
1559
|
+
const anchor = getAnchor(this.location);
|
1560
|
+
if (anchor != null) {
|
1561
|
+
this.view.scrollToAnchor(anchor);
|
1784
1562
|
return true;
|
1785
1563
|
}
|
1786
1564
|
}
|
@@ -1810,7 +1588,13 @@ class Visit {
|
|
1810
1588
|
return typeof this.response == "object";
|
1811
1589
|
}
|
1812
1590
|
shouldIssueRequest() {
|
1813
|
-
|
1591
|
+
if (this.isSamePage) {
|
1592
|
+
return false;
|
1593
|
+
} else if (this.action == "restore") {
|
1594
|
+
return !this.hasCachedSnapshot();
|
1595
|
+
} else {
|
1596
|
+
return true;
|
1597
|
+
}
|
1814
1598
|
}
|
1815
1599
|
cacheSnapshot() {
|
1816
1600
|
if (!this.snapshotCached) {
|
@@ -1823,7 +1607,7 @@ class Visit {
|
|
1823
1607
|
await new Promise((resolve => {
|
1824
1608
|
this.frame = requestAnimationFrame((() => resolve()));
|
1825
1609
|
}));
|
1826
|
-
callback();
|
1610
|
+
await callback();
|
1827
1611
|
delete this.frame;
|
1828
1612
|
this.performScroll();
|
1829
1613
|
}
|
@@ -1853,6 +1637,7 @@ class BrowserAdapter {
|
|
1853
1637
|
visitStarted(visit) {
|
1854
1638
|
visit.issueRequest();
|
1855
1639
|
visit.changeHistory();
|
1640
|
+
visit.goToSamePageAnchor();
|
1856
1641
|
visit.loadCachedSnapshot();
|
1857
1642
|
}
|
1858
1643
|
visitRequestStarted(visit) {
|
@@ -1907,6 +1692,30 @@ class BrowserAdapter {
|
|
1907
1692
|
}
|
1908
1693
|
}
|
1909
1694
|
|
1695
|
+
class CacheObserver {
|
1696
|
+
constructor() {
|
1697
|
+
this.started = false;
|
1698
|
+
}
|
1699
|
+
start() {
|
1700
|
+
if (!this.started) {
|
1701
|
+
this.started = true;
|
1702
|
+
addEventListener("turbo:before-cache", this.removeStaleElements, false);
|
1703
|
+
}
|
1704
|
+
}
|
1705
|
+
stop() {
|
1706
|
+
if (this.started) {
|
1707
|
+
this.started = false;
|
1708
|
+
removeEventListener("turbo:before-cache", this.removeStaleElements, false);
|
1709
|
+
}
|
1710
|
+
}
|
1711
|
+
removeStaleElements() {
|
1712
|
+
const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
|
1713
|
+
for (const element of staleElements) {
|
1714
|
+
element.remove();
|
1715
|
+
}
|
1716
|
+
}
|
1717
|
+
}
|
1718
|
+
|
1910
1719
|
class FormSubmitObserver {
|
1911
1720
|
constructor(delegate) {
|
1912
1721
|
this.started = false;
|
@@ -1990,812 +1799,1295 @@ class FrameRedirector {
|
|
1990
1799
|
}
|
1991
1800
|
}
|
1992
1801
|
|
1993
|
-
class History {
|
1802
|
+
class History {
|
1803
|
+
constructor(delegate) {
|
1804
|
+
this.restorationIdentifier = uuid();
|
1805
|
+
this.restorationData = {};
|
1806
|
+
this.started = false;
|
1807
|
+
this.pageLoaded = false;
|
1808
|
+
this.onPopState = event => {
|
1809
|
+
if (this.shouldHandlePopState()) {
|
1810
|
+
const {turbo: turbo} = event.state || {};
|
1811
|
+
if (turbo) {
|
1812
|
+
this.location = new URL(window.location.href);
|
1813
|
+
const {restorationIdentifier: restorationIdentifier} = turbo;
|
1814
|
+
this.restorationIdentifier = restorationIdentifier;
|
1815
|
+
this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
|
1816
|
+
}
|
1817
|
+
}
|
1818
|
+
};
|
1819
|
+
this.onPageLoad = async event => {
|
1820
|
+
await nextMicrotask();
|
1821
|
+
this.pageLoaded = true;
|
1822
|
+
};
|
1823
|
+
this.delegate = delegate;
|
1824
|
+
}
|
1825
|
+
start() {
|
1826
|
+
if (!this.started) {
|
1827
|
+
addEventListener("popstate", this.onPopState, false);
|
1828
|
+
addEventListener("load", this.onPageLoad, false);
|
1829
|
+
this.started = true;
|
1830
|
+
this.replace(new URL(window.location.href));
|
1831
|
+
}
|
1832
|
+
}
|
1833
|
+
stop() {
|
1834
|
+
if (this.started) {
|
1835
|
+
removeEventListener("popstate", this.onPopState, false);
|
1836
|
+
removeEventListener("load", this.onPageLoad, false);
|
1837
|
+
this.started = false;
|
1838
|
+
}
|
1839
|
+
}
|
1840
|
+
push(location, restorationIdentifier) {
|
1841
|
+
this.update(history.pushState, location, restorationIdentifier);
|
1842
|
+
}
|
1843
|
+
replace(location, restorationIdentifier) {
|
1844
|
+
this.update(history.replaceState, location, restorationIdentifier);
|
1845
|
+
}
|
1846
|
+
update(method, location, restorationIdentifier = uuid()) {
|
1847
|
+
const state = {
|
1848
|
+
turbo: {
|
1849
|
+
restorationIdentifier: restorationIdentifier
|
1850
|
+
}
|
1851
|
+
};
|
1852
|
+
method.call(history, state, "", location.href);
|
1853
|
+
this.location = location;
|
1854
|
+
this.restorationIdentifier = restorationIdentifier;
|
1855
|
+
}
|
1856
|
+
getRestorationDataForIdentifier(restorationIdentifier) {
|
1857
|
+
return this.restorationData[restorationIdentifier] || {};
|
1858
|
+
}
|
1859
|
+
updateRestorationData(additionalData) {
|
1860
|
+
const {restorationIdentifier: restorationIdentifier} = this;
|
1861
|
+
const restorationData = this.restorationData[restorationIdentifier];
|
1862
|
+
this.restorationData[restorationIdentifier] = Object.assign(Object.assign({}, restorationData), additionalData);
|
1863
|
+
}
|
1864
|
+
assumeControlOfScrollRestoration() {
|
1865
|
+
var _a;
|
1866
|
+
if (!this.previousScrollRestoration) {
|
1867
|
+
this.previousScrollRestoration = (_a = history.scrollRestoration) !== null && _a !== void 0 ? _a : "auto";
|
1868
|
+
history.scrollRestoration = "manual";
|
1869
|
+
}
|
1870
|
+
}
|
1871
|
+
relinquishControlOfScrollRestoration() {
|
1872
|
+
if (this.previousScrollRestoration) {
|
1873
|
+
history.scrollRestoration = this.previousScrollRestoration;
|
1874
|
+
delete this.previousScrollRestoration;
|
1875
|
+
}
|
1876
|
+
}
|
1877
|
+
shouldHandlePopState() {
|
1878
|
+
return this.pageIsLoaded();
|
1879
|
+
}
|
1880
|
+
pageIsLoaded() {
|
1881
|
+
return this.pageLoaded || document.readyState == "complete";
|
1882
|
+
}
|
1883
|
+
}
|
1884
|
+
|
1885
|
+
class LinkClickObserver {
|
1886
|
+
constructor(delegate) {
|
1887
|
+
this.started = false;
|
1888
|
+
this.clickCaptured = () => {
|
1889
|
+
removeEventListener("click", this.clickBubbled, false);
|
1890
|
+
addEventListener("click", this.clickBubbled, false);
|
1891
|
+
};
|
1892
|
+
this.clickBubbled = event => {
|
1893
|
+
if (this.clickEventIsSignificant(event)) {
|
1894
|
+
const link = this.findLinkFromClickTarget(event.target);
|
1895
|
+
if (link) {
|
1896
|
+
const location = this.getLocationForLink(link);
|
1897
|
+
if (this.delegate.willFollowLinkToLocation(link, location)) {
|
1898
|
+
event.preventDefault();
|
1899
|
+
this.delegate.followedLinkToLocation(link, location);
|
1900
|
+
}
|
1901
|
+
}
|
1902
|
+
}
|
1903
|
+
};
|
1904
|
+
this.delegate = delegate;
|
1905
|
+
}
|
1906
|
+
start() {
|
1907
|
+
if (!this.started) {
|
1908
|
+
addEventListener("click", this.clickCaptured, true);
|
1909
|
+
this.started = true;
|
1910
|
+
}
|
1911
|
+
}
|
1912
|
+
stop() {
|
1913
|
+
if (this.started) {
|
1914
|
+
removeEventListener("click", this.clickCaptured, true);
|
1915
|
+
this.started = false;
|
1916
|
+
}
|
1917
|
+
}
|
1918
|
+
clickEventIsSignificant(event) {
|
1919
|
+
return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
|
1920
|
+
}
|
1921
|
+
findLinkFromClickTarget(target) {
|
1922
|
+
if (target instanceof Element) {
|
1923
|
+
return target.closest("a[href]:not([target^=_]):not([download])");
|
1924
|
+
}
|
1925
|
+
}
|
1926
|
+
getLocationForLink(link) {
|
1927
|
+
return expandURL(link.getAttribute("href") || "");
|
1928
|
+
}
|
1929
|
+
}
|
1930
|
+
|
1931
|
+
function isAction(action) {
|
1932
|
+
return action == "advance" || action == "replace" || action == "restore";
|
1933
|
+
}
|
1934
|
+
|
1935
|
+
class Navigator {
|
1936
|
+
constructor(delegate) {
|
1937
|
+
this.delegate = delegate;
|
1938
|
+
}
|
1939
|
+
proposeVisit(location, options = {}) {
|
1940
|
+
if (this.delegate.allowsVisitingLocation(location)) {
|
1941
|
+
this.delegate.visitProposedToLocation(location, options);
|
1942
|
+
}
|
1943
|
+
}
|
1944
|
+
startVisit(locatable, restorationIdentifier, options = {}) {
|
1945
|
+
this.stop();
|
1946
|
+
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
|
1947
|
+
referrer: this.location
|
1948
|
+
}, options));
|
1949
|
+
this.currentVisit.start();
|
1950
|
+
}
|
1951
|
+
submitForm(form, submitter) {
|
1952
|
+
this.stop();
|
1953
|
+
this.formSubmission = new FormSubmission(this, form, submitter, true);
|
1954
|
+
if (this.formSubmission.isIdempotent) {
|
1955
|
+
this.proposeVisit(this.formSubmission.fetchRequest.url, {
|
1956
|
+
action: this.getActionForFormSubmission(this.formSubmission)
|
1957
|
+
});
|
1958
|
+
} else {
|
1959
|
+
this.formSubmission.start();
|
1960
|
+
}
|
1961
|
+
}
|
1962
|
+
stop() {
|
1963
|
+
if (this.formSubmission) {
|
1964
|
+
this.formSubmission.stop();
|
1965
|
+
delete this.formSubmission;
|
1966
|
+
}
|
1967
|
+
if (this.currentVisit) {
|
1968
|
+
this.currentVisit.cancel();
|
1969
|
+
delete this.currentVisit;
|
1970
|
+
}
|
1971
|
+
}
|
1972
|
+
get adapter() {
|
1973
|
+
return this.delegate.adapter;
|
1974
|
+
}
|
1975
|
+
get view() {
|
1976
|
+
return this.delegate.view;
|
1977
|
+
}
|
1978
|
+
get history() {
|
1979
|
+
return this.delegate.history;
|
1980
|
+
}
|
1981
|
+
formSubmissionStarted(formSubmission) {}
|
1982
|
+
async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) {
|
1983
|
+
if (formSubmission == this.formSubmission) {
|
1984
|
+
const responseHTML = await fetchResponse.responseHTML;
|
1985
|
+
if (responseHTML) {
|
1986
|
+
if (formSubmission.method != FetchMethod.get) {
|
1987
|
+
this.view.clearSnapshotCache();
|
1988
|
+
}
|
1989
|
+
const {statusCode: statusCode} = fetchResponse;
|
1990
|
+
const visitOptions = {
|
1991
|
+
response: {
|
1992
|
+
statusCode: statusCode,
|
1993
|
+
responseHTML: responseHTML
|
1994
|
+
}
|
1995
|
+
};
|
1996
|
+
this.proposeVisit(fetchResponse.location, visitOptions);
|
1997
|
+
}
|
1998
|
+
}
|
1999
|
+
}
|
2000
|
+
async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
2001
|
+
const responseHTML = await fetchResponse.responseHTML;
|
2002
|
+
if (responseHTML) {
|
2003
|
+
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
2004
|
+
await this.view.renderPage(snapshot);
|
2005
|
+
this.view.clearSnapshotCache();
|
2006
|
+
}
|
2007
|
+
}
|
2008
|
+
formSubmissionErrored(formSubmission, error) {
|
2009
|
+
console.error(error);
|
2010
|
+
}
|
2011
|
+
formSubmissionFinished(formSubmission) {}
|
2012
|
+
visitStarted(visit) {
|
2013
|
+
this.delegate.visitStarted(visit);
|
2014
|
+
}
|
2015
|
+
visitCompleted(visit) {
|
2016
|
+
this.delegate.visitCompleted(visit);
|
2017
|
+
}
|
2018
|
+
locationWithActionIsSamePage(location, action) {
|
2019
|
+
return getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (getAnchor(location) != null || action == "restore");
|
2020
|
+
}
|
2021
|
+
visitScrolledToSamePageLocation(oldURL, newURL) {
|
2022
|
+
this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
|
2023
|
+
}
|
2024
|
+
get location() {
|
2025
|
+
return this.history.location;
|
2026
|
+
}
|
2027
|
+
get restorationIdentifier() {
|
2028
|
+
return this.history.restorationIdentifier;
|
2029
|
+
}
|
2030
|
+
getActionForFormSubmission(formSubmission) {
|
2031
|
+
const {formElement: formElement, submitter: submitter} = formSubmission;
|
2032
|
+
const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-action")) || formElement.getAttribute("data-turbo-action");
|
2033
|
+
return isAction(action) ? action : "advance";
|
2034
|
+
}
|
2035
|
+
}
|
2036
|
+
|
2037
|
+
var PageStage;
|
2038
|
+
|
2039
|
+
(function(PageStage) {
|
2040
|
+
PageStage[PageStage["initial"] = 0] = "initial";
|
2041
|
+
PageStage[PageStage["loading"] = 1] = "loading";
|
2042
|
+
PageStage[PageStage["interactive"] = 2] = "interactive";
|
2043
|
+
PageStage[PageStage["complete"] = 3] = "complete";
|
2044
|
+
})(PageStage || (PageStage = {}));
|
2045
|
+
|
2046
|
+
class PageObserver {
|
1994
2047
|
constructor(delegate) {
|
1995
|
-
this.
|
1996
|
-
this.restorationData = {};
|
2048
|
+
this.stage = PageStage.initial;
|
1997
2049
|
this.started = false;
|
1998
|
-
this.
|
1999
|
-
|
2000
|
-
if (
|
2001
|
-
|
2002
|
-
|
2003
|
-
|
2004
|
-
const {restorationIdentifier: restorationIdentifier} = turbo;
|
2005
|
-
this.restorationIdentifier = restorationIdentifier;
|
2006
|
-
this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
|
2007
|
-
}
|
2050
|
+
this.interpretReadyState = () => {
|
2051
|
+
const {readyState: readyState} = this;
|
2052
|
+
if (readyState == "interactive") {
|
2053
|
+
this.pageIsInteractive();
|
2054
|
+
} else if (readyState == "complete") {
|
2055
|
+
this.pageIsComplete();
|
2008
2056
|
}
|
2009
2057
|
};
|
2010
|
-
this.
|
2011
|
-
|
2012
|
-
this.pageLoaded = true;
|
2058
|
+
this.pageWillUnload = () => {
|
2059
|
+
this.delegate.pageWillUnload();
|
2013
2060
|
};
|
2014
2061
|
this.delegate = delegate;
|
2015
2062
|
}
|
2016
2063
|
start() {
|
2017
2064
|
if (!this.started) {
|
2018
|
-
|
2019
|
-
|
2065
|
+
if (this.stage == PageStage.initial) {
|
2066
|
+
this.stage = PageStage.loading;
|
2067
|
+
}
|
2068
|
+
document.addEventListener("readystatechange", this.interpretReadyState, false);
|
2069
|
+
addEventListener("pagehide", this.pageWillUnload, false);
|
2020
2070
|
this.started = true;
|
2021
|
-
this.replace(new URL(window.location.href));
|
2022
2071
|
}
|
2023
2072
|
}
|
2024
2073
|
stop() {
|
2025
2074
|
if (this.started) {
|
2026
|
-
removeEventListener("
|
2027
|
-
removeEventListener("
|
2075
|
+
document.removeEventListener("readystatechange", this.interpretReadyState, false);
|
2076
|
+
removeEventListener("pagehide", this.pageWillUnload, false);
|
2028
2077
|
this.started = false;
|
2029
2078
|
}
|
2030
2079
|
}
|
2031
|
-
|
2032
|
-
this.
|
2033
|
-
|
2034
|
-
|
2035
|
-
|
2080
|
+
pageIsInteractive() {
|
2081
|
+
if (this.stage == PageStage.loading) {
|
2082
|
+
this.stage = PageStage.interactive;
|
2083
|
+
this.delegate.pageBecameInteractive();
|
2084
|
+
}
|
2036
2085
|
}
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2040
|
-
|
2041
|
-
|
2042
|
-
}
|
2043
|
-
method.call(history, state, "", location.href);
|
2044
|
-
this.location = location;
|
2045
|
-
this.restorationIdentifier = restorationIdentifier;
|
2086
|
+
pageIsComplete() {
|
2087
|
+
this.pageIsInteractive();
|
2088
|
+
if (this.stage == PageStage.interactive) {
|
2089
|
+
this.stage = PageStage.complete;
|
2090
|
+
this.delegate.pageLoaded();
|
2091
|
+
}
|
2046
2092
|
}
|
2047
|
-
|
2048
|
-
return
|
2093
|
+
get readyState() {
|
2094
|
+
return document.readyState;
|
2049
2095
|
}
|
2050
|
-
|
2051
|
-
|
2052
|
-
|
2053
|
-
|
2096
|
+
}
|
2097
|
+
|
2098
|
+
class ScrollObserver {
|
2099
|
+
constructor(delegate) {
|
2100
|
+
this.started = false;
|
2101
|
+
this.onScroll = () => {
|
2102
|
+
this.updatePosition({
|
2103
|
+
x: window.pageXOffset,
|
2104
|
+
y: window.pageYOffset
|
2105
|
+
});
|
2106
|
+
};
|
2107
|
+
this.delegate = delegate;
|
2054
2108
|
}
|
2055
|
-
|
2056
|
-
|
2057
|
-
|
2058
|
-
this.
|
2059
|
-
|
2109
|
+
start() {
|
2110
|
+
if (!this.started) {
|
2111
|
+
addEventListener("scroll", this.onScroll, false);
|
2112
|
+
this.onScroll();
|
2113
|
+
this.started = true;
|
2060
2114
|
}
|
2061
2115
|
}
|
2062
|
-
|
2063
|
-
if (this.
|
2064
|
-
|
2065
|
-
|
2116
|
+
stop() {
|
2117
|
+
if (this.started) {
|
2118
|
+
removeEventListener("scroll", this.onScroll, false);
|
2119
|
+
this.started = false;
|
2066
2120
|
}
|
2067
2121
|
}
|
2068
|
-
|
2069
|
-
|
2070
|
-
}
|
2071
|
-
pageIsLoaded() {
|
2072
|
-
return this.pageLoaded || document.readyState == "complete";
|
2122
|
+
updatePosition(position) {
|
2123
|
+
this.delegate.scrollPositionChanged(position);
|
2073
2124
|
}
|
2074
2125
|
}
|
2075
2126
|
|
2076
|
-
class
|
2127
|
+
class StreamObserver {
|
2077
2128
|
constructor(delegate) {
|
2129
|
+
this.sources = new Set;
|
2078
2130
|
this.started = false;
|
2079
|
-
this.
|
2080
|
-
|
2081
|
-
|
2131
|
+
this.inspectFetchResponse = event => {
|
2132
|
+
const response = fetchResponseFromEvent(event);
|
2133
|
+
if (response && fetchResponseIsStream(response)) {
|
2134
|
+
event.preventDefault();
|
2135
|
+
this.receiveMessageResponse(response);
|
2136
|
+
}
|
2082
2137
|
};
|
2083
|
-
this.
|
2084
|
-
if (this.
|
2085
|
-
|
2086
|
-
if (link) {
|
2087
|
-
const location = this.getLocationForLink(link);
|
2088
|
-
if (this.delegate.willFollowLinkToLocation(link, location)) {
|
2089
|
-
event.preventDefault();
|
2090
|
-
this.delegate.followedLinkToLocation(link, location);
|
2091
|
-
}
|
2092
|
-
}
|
2138
|
+
this.receiveMessageEvent = event => {
|
2139
|
+
if (this.started && typeof event.data == "string") {
|
2140
|
+
this.receiveMessageHTML(event.data);
|
2093
2141
|
}
|
2094
2142
|
};
|
2095
2143
|
this.delegate = delegate;
|
2096
2144
|
}
|
2097
2145
|
start() {
|
2098
2146
|
if (!this.started) {
|
2099
|
-
addEventListener("click", this.clickCaptured, true);
|
2100
2147
|
this.started = true;
|
2148
|
+
addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
2101
2149
|
}
|
2102
2150
|
}
|
2103
2151
|
stop() {
|
2104
2152
|
if (this.started) {
|
2105
|
-
removeEventListener("click", this.clickCaptured, true);
|
2106
2153
|
this.started = false;
|
2154
|
+
removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
2107
2155
|
}
|
2108
2156
|
}
|
2109
|
-
|
2110
|
-
|
2157
|
+
connectStreamSource(source) {
|
2158
|
+
if (!this.streamSourceIsConnected(source)) {
|
2159
|
+
this.sources.add(source);
|
2160
|
+
source.addEventListener("message", this.receiveMessageEvent, false);
|
2161
|
+
}
|
2111
2162
|
}
|
2112
|
-
|
2113
|
-
if (
|
2114
|
-
|
2163
|
+
disconnectStreamSource(source) {
|
2164
|
+
if (this.streamSourceIsConnected(source)) {
|
2165
|
+
this.sources.delete(source);
|
2166
|
+
source.removeEventListener("message", this.receiveMessageEvent, false);
|
2115
2167
|
}
|
2116
2168
|
}
|
2117
|
-
|
2118
|
-
return
|
2169
|
+
streamSourceIsConnected(source) {
|
2170
|
+
return this.sources.has(source);
|
2171
|
+
}
|
2172
|
+
async receiveMessageResponse(response) {
|
2173
|
+
const html = await response.responseHTML;
|
2174
|
+
if (html) {
|
2175
|
+
this.receiveMessageHTML(html);
|
2176
|
+
}
|
2177
|
+
}
|
2178
|
+
receiveMessageHTML(html) {
|
2179
|
+
this.delegate.receivedMessageFromStream(new StreamMessage(html));
|
2119
2180
|
}
|
2120
2181
|
}
|
2121
2182
|
|
2122
|
-
|
2123
|
-
|
2124
|
-
|
2183
|
+
function fetchResponseFromEvent(event) {
|
2184
|
+
var _a;
|
2185
|
+
const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse;
|
2186
|
+
if (fetchResponse instanceof FetchResponse) {
|
2187
|
+
return fetchResponse;
|
2125
2188
|
}
|
2126
|
-
|
2127
|
-
|
2128
|
-
|
2189
|
+
}
|
2190
|
+
|
2191
|
+
function fetchResponseIsStream(response) {
|
2192
|
+
var _a;
|
2193
|
+
const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
|
2194
|
+
return contentType.startsWith(StreamMessage.contentType);
|
2195
|
+
}
|
2196
|
+
|
2197
|
+
class ErrorRenderer extends Renderer {
|
2198
|
+
async render() {
|
2199
|
+
this.replaceHeadAndBody();
|
2200
|
+
this.activateScriptElements();
|
2201
|
+
}
|
2202
|
+
replaceHeadAndBody() {
|
2203
|
+
const {documentElement: documentElement, head: head, body: body} = document;
|
2204
|
+
documentElement.replaceChild(this.newHead, head);
|
2205
|
+
documentElement.replaceChild(this.newElement, body);
|
2206
|
+
}
|
2207
|
+
activateScriptElements() {
|
2208
|
+
for (const replaceableElement of this.scriptElements) {
|
2209
|
+
const parentNode = replaceableElement.parentNode;
|
2210
|
+
if (parentNode) {
|
2211
|
+
const element = this.createScriptElement(replaceableElement);
|
2212
|
+
parentNode.replaceChild(element, replaceableElement);
|
2213
|
+
}
|
2129
2214
|
}
|
2130
2215
|
}
|
2131
|
-
|
2132
|
-
this.
|
2133
|
-
|
2134
|
-
|
2135
|
-
|
2136
|
-
|
2216
|
+
get newHead() {
|
2217
|
+
return this.newSnapshot.headSnapshot.element;
|
2218
|
+
}
|
2219
|
+
get scriptElements() {
|
2220
|
+
return [ ...document.documentElement.querySelectorAll("script") ];
|
2221
|
+
}
|
2222
|
+
}
|
2223
|
+
|
2224
|
+
class PageRenderer extends Renderer {
|
2225
|
+
get shouldRender() {
|
2226
|
+
return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
|
2227
|
+
}
|
2228
|
+
prepareToRender() {
|
2229
|
+
this.mergeHead();
|
2230
|
+
}
|
2231
|
+
async render() {
|
2232
|
+
this.replaceBody();
|
2233
|
+
}
|
2234
|
+
finishRendering() {
|
2235
|
+
super.finishRendering();
|
2236
|
+
if (!this.isPreview) {
|
2237
|
+
this.focusFirstAutofocusableElement();
|
2238
|
+
}
|
2239
|
+
}
|
2240
|
+
get currentHeadSnapshot() {
|
2241
|
+
return this.currentSnapshot.headSnapshot;
|
2242
|
+
}
|
2243
|
+
get newHeadSnapshot() {
|
2244
|
+
return this.newSnapshot.headSnapshot;
|
2245
|
+
}
|
2246
|
+
get newElement() {
|
2247
|
+
return this.newSnapshot.element;
|
2248
|
+
}
|
2249
|
+
mergeHead() {
|
2250
|
+
this.copyNewHeadStylesheetElements();
|
2251
|
+
this.copyNewHeadScriptElements();
|
2252
|
+
this.removeCurrentHeadProvisionalElements();
|
2253
|
+
this.copyNewHeadProvisionalElements();
|
2254
|
+
}
|
2255
|
+
replaceBody() {
|
2256
|
+
this.preservingPermanentElements((() => {
|
2257
|
+
this.activateNewBody();
|
2258
|
+
this.assignNewBody();
|
2259
|
+
}));
|
2260
|
+
}
|
2261
|
+
get trackedElementsAreIdentical() {
|
2262
|
+
return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
|
2137
2263
|
}
|
2138
|
-
|
2139
|
-
this.
|
2140
|
-
|
2141
|
-
if (this.formSubmission.fetchRequest.isIdempotent) {
|
2142
|
-
this.proposeVisit(this.formSubmission.fetchRequest.url);
|
2143
|
-
} else {
|
2144
|
-
this.formSubmission.start();
|
2264
|
+
copyNewHeadStylesheetElements() {
|
2265
|
+
for (const element of this.newHeadStylesheetElements) {
|
2266
|
+
document.head.appendChild(element);
|
2145
2267
|
}
|
2146
2268
|
}
|
2147
|
-
|
2148
|
-
|
2149
|
-
|
2150
|
-
delete this.formSubmission;
|
2151
|
-
}
|
2152
|
-
if (this.currentVisit) {
|
2153
|
-
this.currentVisit.cancel();
|
2154
|
-
delete this.currentVisit;
|
2269
|
+
copyNewHeadScriptElements() {
|
2270
|
+
for (const element of this.newHeadScriptElements) {
|
2271
|
+
document.head.appendChild(this.createScriptElement(element));
|
2155
2272
|
}
|
2156
2273
|
}
|
2157
|
-
|
2158
|
-
|
2274
|
+
removeCurrentHeadProvisionalElements() {
|
2275
|
+
for (const element of this.currentHeadProvisionalElements) {
|
2276
|
+
document.head.removeChild(element);
|
2277
|
+
}
|
2159
2278
|
}
|
2160
|
-
|
2161
|
-
|
2279
|
+
copyNewHeadProvisionalElements() {
|
2280
|
+
for (const element of this.newHeadProvisionalElements) {
|
2281
|
+
document.head.appendChild(element);
|
2282
|
+
}
|
2162
2283
|
}
|
2163
|
-
|
2164
|
-
|
2284
|
+
activateNewBody() {
|
2285
|
+
document.adoptNode(this.newElement);
|
2286
|
+
this.activateNewBodyScriptElements();
|
2165
2287
|
}
|
2166
|
-
|
2167
|
-
|
2168
|
-
|
2169
|
-
|
2170
|
-
if (responseHTML) {
|
2171
|
-
if (formSubmission.method != FetchMethod.get) {
|
2172
|
-
this.view.clearSnapshotCache();
|
2173
|
-
}
|
2174
|
-
const {statusCode: statusCode} = fetchResponse;
|
2175
|
-
const visitOptions = {
|
2176
|
-
response: {
|
2177
|
-
statusCode: statusCode,
|
2178
|
-
responseHTML: responseHTML
|
2179
|
-
}
|
2180
|
-
};
|
2181
|
-
this.proposeVisit(fetchResponse.location, visitOptions);
|
2182
|
-
}
|
2288
|
+
activateNewBodyScriptElements() {
|
2289
|
+
for (const inertScriptElement of this.newBodyScriptElements) {
|
2290
|
+
const activatedScriptElement = this.createScriptElement(inertScriptElement);
|
2291
|
+
inertScriptElement.replaceWith(activatedScriptElement);
|
2183
2292
|
}
|
2184
2293
|
}
|
2185
|
-
|
2186
|
-
|
2187
|
-
|
2188
|
-
|
2189
|
-
|
2190
|
-
this.view.clearSnapshotCache();
|
2294
|
+
assignNewBody() {
|
2295
|
+
if (document.body && this.newElement instanceof HTMLBodyElement) {
|
2296
|
+
document.body.replaceWith(this.newElement);
|
2297
|
+
} else {
|
2298
|
+
document.documentElement.appendChild(this.newElement);
|
2191
2299
|
}
|
2192
2300
|
}
|
2193
|
-
|
2194
|
-
|
2195
|
-
visitStarted(visit) {
|
2196
|
-
this.delegate.visitStarted(visit);
|
2301
|
+
get newHeadStylesheetElements() {
|
2302
|
+
return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
|
2197
2303
|
}
|
2198
|
-
|
2199
|
-
this.
|
2304
|
+
get newHeadScriptElements() {
|
2305
|
+
return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot);
|
2200
2306
|
}
|
2201
|
-
get
|
2202
|
-
return this.
|
2307
|
+
get currentHeadProvisionalElements() {
|
2308
|
+
return this.currentHeadSnapshot.provisionalElements;
|
2203
2309
|
}
|
2204
|
-
get
|
2205
|
-
return this.
|
2310
|
+
get newHeadProvisionalElements() {
|
2311
|
+
return this.newHeadSnapshot.provisionalElements;
|
2312
|
+
}
|
2313
|
+
get newBodyScriptElements() {
|
2314
|
+
return this.newElement.querySelectorAll("script");
|
2206
2315
|
}
|
2207
2316
|
}
|
2208
2317
|
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2212
|
-
|
2213
|
-
|
2214
|
-
PageStage[PageStage["interactive"] = 2] = "interactive";
|
2215
|
-
PageStage[PageStage["complete"] = 3] = "complete";
|
2216
|
-
})(PageStage || (PageStage = {}));
|
2217
|
-
|
2218
|
-
class PageObserver {
|
2219
|
-
constructor(delegate) {
|
2220
|
-
this.stage = PageStage.initial;
|
2221
|
-
this.started = false;
|
2222
|
-
this.interpretReadyState = () => {
|
2223
|
-
const {readyState: readyState} = this;
|
2224
|
-
if (readyState == "interactive") {
|
2225
|
-
this.pageIsInteractive();
|
2226
|
-
} else if (readyState == "complete") {
|
2227
|
-
this.pageIsComplete();
|
2228
|
-
}
|
2229
|
-
};
|
2230
|
-
this.pageWillUnload = () => {
|
2231
|
-
this.delegate.pageWillUnload();
|
2232
|
-
};
|
2233
|
-
this.delegate = delegate;
|
2318
|
+
class SnapshotCache {
|
2319
|
+
constructor(size) {
|
2320
|
+
this.keys = [];
|
2321
|
+
this.snapshots = {};
|
2322
|
+
this.size = size;
|
2234
2323
|
}
|
2235
|
-
|
2236
|
-
|
2237
|
-
if (this.stage == PageStage.initial) {
|
2238
|
-
this.stage = PageStage.loading;
|
2239
|
-
}
|
2240
|
-
document.addEventListener("readystatechange", this.interpretReadyState, false);
|
2241
|
-
addEventListener("pagehide", this.pageWillUnload, false);
|
2242
|
-
this.started = true;
|
2243
|
-
}
|
2324
|
+
has(location) {
|
2325
|
+
return toCacheKey(location) in this.snapshots;
|
2244
2326
|
}
|
2245
|
-
|
2246
|
-
if (this.
|
2247
|
-
|
2248
|
-
|
2249
|
-
|
2327
|
+
get(location) {
|
2328
|
+
if (this.has(location)) {
|
2329
|
+
const snapshot = this.read(location);
|
2330
|
+
this.touch(location);
|
2331
|
+
return snapshot;
|
2250
2332
|
}
|
2251
2333
|
}
|
2252
|
-
|
2253
|
-
|
2254
|
-
|
2255
|
-
|
2256
|
-
}
|
2334
|
+
put(location, snapshot) {
|
2335
|
+
this.write(location, snapshot);
|
2336
|
+
this.touch(location);
|
2337
|
+
return snapshot;
|
2257
2338
|
}
|
2258
|
-
|
2259
|
-
this.
|
2260
|
-
if (this.stage == PageStage.interactive) {
|
2261
|
-
this.stage = PageStage.complete;
|
2262
|
-
this.delegate.pageLoaded();
|
2263
|
-
}
|
2339
|
+
clear() {
|
2340
|
+
this.snapshots = {};
|
2264
2341
|
}
|
2265
|
-
|
2266
|
-
return
|
2342
|
+
read(location) {
|
2343
|
+
return this.snapshots[toCacheKey(location)];
|
2344
|
+
}
|
2345
|
+
write(location, snapshot) {
|
2346
|
+
this.snapshots[toCacheKey(location)] = snapshot;
|
2347
|
+
}
|
2348
|
+
touch(location) {
|
2349
|
+
const key = toCacheKey(location);
|
2350
|
+
const index = this.keys.indexOf(key);
|
2351
|
+
if (index > -1) this.keys.splice(index, 1);
|
2352
|
+
this.keys.unshift(key);
|
2353
|
+
this.trim();
|
2354
|
+
}
|
2355
|
+
trim() {
|
2356
|
+
for (const key of this.keys.splice(this.size)) {
|
2357
|
+
delete this.snapshots[key];
|
2358
|
+
}
|
2267
2359
|
}
|
2268
2360
|
}
|
2269
2361
|
|
2270
|
-
class
|
2271
|
-
constructor(
|
2272
|
-
|
2273
|
-
this.
|
2274
|
-
|
2275
|
-
x: window.pageXOffset,
|
2276
|
-
y: window.pageYOffset
|
2277
|
-
});
|
2278
|
-
};
|
2279
|
-
this.delegate = delegate;
|
2362
|
+
class PageView extends View {
|
2363
|
+
constructor() {
|
2364
|
+
super(...arguments);
|
2365
|
+
this.snapshotCache = new SnapshotCache(10);
|
2366
|
+
this.lastRenderedLocation = new URL(location.href);
|
2280
2367
|
}
|
2281
|
-
|
2282
|
-
|
2283
|
-
|
2284
|
-
this.onScroll();
|
2285
|
-
this.started = true;
|
2286
|
-
}
|
2368
|
+
renderPage(snapshot, isPreview = false) {
|
2369
|
+
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
|
2370
|
+
return this.render(renderer);
|
2287
2371
|
}
|
2288
|
-
|
2289
|
-
|
2290
|
-
|
2291
|
-
|
2372
|
+
renderError(snapshot) {
|
2373
|
+
const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
|
2374
|
+
return this.render(renderer);
|
2375
|
+
}
|
2376
|
+
clearSnapshotCache() {
|
2377
|
+
this.snapshotCache.clear();
|
2378
|
+
}
|
2379
|
+
async cacheSnapshot() {
|
2380
|
+
if (this.shouldCacheSnapshot) {
|
2381
|
+
this.delegate.viewWillCacheSnapshot();
|
2382
|
+
const {snapshot: snapshot, lastRenderedLocation: location} = this;
|
2383
|
+
await nextEventLoopTick();
|
2384
|
+
this.snapshotCache.put(location, snapshot.clone());
|
2292
2385
|
}
|
2293
2386
|
}
|
2294
|
-
|
2295
|
-
this.
|
2387
|
+
getCachedSnapshotForLocation(location) {
|
2388
|
+
return this.snapshotCache.get(location);
|
2389
|
+
}
|
2390
|
+
get snapshot() {
|
2391
|
+
return PageSnapshot.fromElement(this.element);
|
2392
|
+
}
|
2393
|
+
get shouldCacheSnapshot() {
|
2394
|
+
return this.snapshot.isCacheable;
|
2296
2395
|
}
|
2297
2396
|
}
|
2298
2397
|
|
2299
|
-
class
|
2300
|
-
constructor(
|
2301
|
-
this.
|
2398
|
+
class Session {
|
2399
|
+
constructor() {
|
2400
|
+
this.navigator = new Navigator(this);
|
2401
|
+
this.history = new History(this);
|
2402
|
+
this.view = new PageView(this, document.documentElement);
|
2403
|
+
this.adapter = new BrowserAdapter(this);
|
2404
|
+
this.pageObserver = new PageObserver(this);
|
2405
|
+
this.cacheObserver = new CacheObserver;
|
2406
|
+
this.linkClickObserver = new LinkClickObserver(this);
|
2407
|
+
this.formSubmitObserver = new FormSubmitObserver(this);
|
2408
|
+
this.scrollObserver = new ScrollObserver(this);
|
2409
|
+
this.streamObserver = new StreamObserver(this);
|
2410
|
+
this.frameRedirector = new FrameRedirector(document.documentElement);
|
2411
|
+
this.enabled = true;
|
2412
|
+
this.progressBarDelay = 500;
|
2302
2413
|
this.started = false;
|
2303
|
-
this.inspectFetchResponse = event => {
|
2304
|
-
const response = fetchResponseFromEvent(event);
|
2305
|
-
if (response && fetchResponseIsStream(response)) {
|
2306
|
-
event.preventDefault();
|
2307
|
-
this.receiveMessageResponse(response);
|
2308
|
-
}
|
2309
|
-
};
|
2310
|
-
this.receiveMessageEvent = event => {
|
2311
|
-
if (this.started && typeof event.data == "string") {
|
2312
|
-
this.receiveMessageHTML(event.data);
|
2313
|
-
}
|
2314
|
-
};
|
2315
|
-
this.delegate = delegate;
|
2316
2414
|
}
|
2317
2415
|
start() {
|
2318
2416
|
if (!this.started) {
|
2417
|
+
this.pageObserver.start();
|
2418
|
+
this.cacheObserver.start();
|
2419
|
+
this.linkClickObserver.start();
|
2420
|
+
this.formSubmitObserver.start();
|
2421
|
+
this.scrollObserver.start();
|
2422
|
+
this.streamObserver.start();
|
2423
|
+
this.frameRedirector.start();
|
2424
|
+
this.history.start();
|
2319
2425
|
this.started = true;
|
2320
|
-
|
2426
|
+
this.enabled = true;
|
2321
2427
|
}
|
2322
2428
|
}
|
2429
|
+
disable() {
|
2430
|
+
this.enabled = false;
|
2431
|
+
}
|
2323
2432
|
stop() {
|
2324
2433
|
if (this.started) {
|
2434
|
+
this.pageObserver.stop();
|
2435
|
+
this.cacheObserver.stop();
|
2436
|
+
this.linkClickObserver.stop();
|
2437
|
+
this.formSubmitObserver.stop();
|
2438
|
+
this.scrollObserver.stop();
|
2439
|
+
this.streamObserver.stop();
|
2440
|
+
this.frameRedirector.stop();
|
2441
|
+
this.history.stop();
|
2325
2442
|
this.started = false;
|
2326
|
-
removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
2327
2443
|
}
|
2328
2444
|
}
|
2445
|
+
registerAdapter(adapter) {
|
2446
|
+
this.adapter = adapter;
|
2447
|
+
}
|
2448
|
+
visit(location, options = {}) {
|
2449
|
+
this.navigator.proposeVisit(expandURL(location), options);
|
2450
|
+
}
|
2329
2451
|
connectStreamSource(source) {
|
2330
|
-
|
2331
|
-
this.sources.add(source);
|
2332
|
-
source.addEventListener("message", this.receiveMessageEvent, false);
|
2333
|
-
}
|
2452
|
+
this.streamObserver.connectStreamSource(source);
|
2334
2453
|
}
|
2335
2454
|
disconnectStreamSource(source) {
|
2336
|
-
|
2337
|
-
|
2338
|
-
|
2455
|
+
this.streamObserver.disconnectStreamSource(source);
|
2456
|
+
}
|
2457
|
+
renderStreamMessage(message) {
|
2458
|
+
document.documentElement.appendChild(StreamMessage.wrap(message).fragment);
|
2459
|
+
}
|
2460
|
+
clearCache() {
|
2461
|
+
this.view.clearSnapshotCache();
|
2462
|
+
}
|
2463
|
+
setProgressBarDelay(delay) {
|
2464
|
+
this.progressBarDelay = delay;
|
2465
|
+
}
|
2466
|
+
get location() {
|
2467
|
+
return this.history.location;
|
2468
|
+
}
|
2469
|
+
get restorationIdentifier() {
|
2470
|
+
return this.history.restorationIdentifier;
|
2471
|
+
}
|
2472
|
+
historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
|
2473
|
+
if (this.enabled) {
|
2474
|
+
this.navigator.startVisit(location, restorationIdentifier, {
|
2475
|
+
action: "restore",
|
2476
|
+
historyChanged: true
|
2477
|
+
});
|
2478
|
+
} else {
|
2479
|
+
this.adapter.pageInvalidated();
|
2339
2480
|
}
|
2340
2481
|
}
|
2341
|
-
|
2342
|
-
|
2482
|
+
scrollPositionChanged(position) {
|
2483
|
+
this.history.updateRestorationData({
|
2484
|
+
scrollPosition: position
|
2485
|
+
});
|
2343
2486
|
}
|
2344
|
-
|
2345
|
-
|
2346
|
-
|
2347
|
-
|
2487
|
+
willFollowLinkToLocation(link, location) {
|
2488
|
+
return elementIsNavigable(link) && this.locationIsVisitable(location) && this.applicationAllowsFollowingLinkToLocation(link, location);
|
2489
|
+
}
|
2490
|
+
followedLinkToLocation(link, location) {
|
2491
|
+
const action = this.getActionForLink(link);
|
2492
|
+
this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, {
|
2493
|
+
action: action
|
2494
|
+
});
|
2495
|
+
}
|
2496
|
+
convertLinkWithMethodClickToFormSubmission(link) {
|
2497
|
+
var _a;
|
2498
|
+
const linkMethod = link.getAttribute("data-turbo-method");
|
2499
|
+
if (linkMethod) {
|
2500
|
+
const form = document.createElement("form");
|
2501
|
+
form.method = linkMethod;
|
2502
|
+
form.action = link.getAttribute("href") || "undefined";
|
2503
|
+
(_a = link.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(form, link);
|
2504
|
+
return dispatch("submit", {
|
2505
|
+
cancelable: true,
|
2506
|
+
target: form
|
2507
|
+
});
|
2508
|
+
} else {
|
2509
|
+
return false;
|
2348
2510
|
}
|
2349
2511
|
}
|
2350
|
-
|
2351
|
-
this.
|
2512
|
+
allowsVisitingLocation(location) {
|
2513
|
+
return this.applicationAllowsVisitingLocation(location);
|
2352
2514
|
}
|
2353
|
-
|
2354
|
-
|
2355
|
-
|
2356
|
-
var _a;
|
2357
|
-
const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse;
|
2358
|
-
if (fetchResponse instanceof FetchResponse) {
|
2359
|
-
return fetchResponse;
|
2515
|
+
visitProposedToLocation(location, options) {
|
2516
|
+
extendURLWithDeprecatedProperties(location);
|
2517
|
+
this.adapter.visitProposedToLocation(location, options);
|
2360
2518
|
}
|
2361
|
-
|
2362
|
-
|
2363
|
-
|
2364
|
-
var _a;
|
2365
|
-
const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
|
2366
|
-
return contentType.startsWith(StreamMessage.contentType);
|
2367
|
-
}
|
2368
|
-
|
2369
|
-
function isAction(action) {
|
2370
|
-
return action == "advance" || action == "replace" || action == "restore";
|
2371
|
-
}
|
2372
|
-
|
2373
|
-
class ErrorRenderer extends Renderer {
|
2374
|
-
async render() {
|
2375
|
-
this.replaceHeadAndBody();
|
2376
|
-
this.activateScriptElements();
|
2519
|
+
visitStarted(visit) {
|
2520
|
+
extendURLWithDeprecatedProperties(visit.location);
|
2521
|
+
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
|
2377
2522
|
}
|
2378
|
-
|
2379
|
-
|
2380
|
-
documentElement.replaceChild(this.newHead, head);
|
2381
|
-
documentElement.replaceChild(this.newElement, body);
|
2523
|
+
visitCompleted(visit) {
|
2524
|
+
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
2382
2525
|
}
|
2383
|
-
|
2384
|
-
|
2385
|
-
const parentNode = replaceableElement.parentNode;
|
2386
|
-
if (parentNode) {
|
2387
|
-
const element = this.createScriptElement(replaceableElement);
|
2388
|
-
parentNode.replaceChild(element, replaceableElement);
|
2389
|
-
}
|
2390
|
-
}
|
2526
|
+
locationWithActionIsSamePage(location, action) {
|
2527
|
+
return this.navigator.locationWithActionIsSamePage(location, action);
|
2391
2528
|
}
|
2392
|
-
|
2393
|
-
|
2529
|
+
visitScrolledToSamePageLocation(oldURL, newURL) {
|
2530
|
+
this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
|
2394
2531
|
}
|
2395
|
-
|
2396
|
-
return
|
2532
|
+
willSubmitForm(form, submitter) {
|
2533
|
+
return elementIsNavigable(form) && elementIsNavigable(submitter);
|
2397
2534
|
}
|
2398
|
-
|
2399
|
-
|
2400
|
-
class PageRenderer extends Renderer {
|
2401
|
-
get shouldRender() {
|
2402
|
-
return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
|
2535
|
+
formSubmitted(form, submitter) {
|
2536
|
+
this.navigator.submitForm(form, submitter);
|
2403
2537
|
}
|
2404
|
-
|
2405
|
-
this.
|
2538
|
+
pageBecameInteractive() {
|
2539
|
+
this.view.lastRenderedLocation = this.location;
|
2540
|
+
this.notifyApplicationAfterPageLoad();
|
2406
2541
|
}
|
2407
|
-
|
2408
|
-
this.
|
2542
|
+
pageLoaded() {
|
2543
|
+
this.history.assumeControlOfScrollRestoration();
|
2409
2544
|
}
|
2410
|
-
|
2411
|
-
|
2412
|
-
if (this.isPreview) {
|
2413
|
-
this.focusFirstAutofocusableElement();
|
2414
|
-
}
|
2545
|
+
pageWillUnload() {
|
2546
|
+
this.history.relinquishControlOfScrollRestoration();
|
2415
2547
|
}
|
2416
|
-
|
2417
|
-
|
2548
|
+
receivedMessageFromStream(message) {
|
2549
|
+
this.renderStreamMessage(message);
|
2418
2550
|
}
|
2419
|
-
|
2420
|
-
|
2551
|
+
viewWillCacheSnapshot() {
|
2552
|
+
this.notifyApplicationBeforeCachingSnapshot();
|
2421
2553
|
}
|
2422
|
-
|
2423
|
-
|
2554
|
+
allowsImmediateRender({element: element}, resume) {
|
2555
|
+
const event = this.notifyApplicationBeforeRender(element, resume);
|
2556
|
+
return !event.defaultPrevented;
|
2424
2557
|
}
|
2425
|
-
|
2426
|
-
this.
|
2427
|
-
this.
|
2428
|
-
this.removeCurrentHeadProvisionalElements();
|
2429
|
-
this.copyNewHeadProvisionalElements();
|
2558
|
+
viewRenderedSnapshot(snapshot, isPreview) {
|
2559
|
+
this.view.lastRenderedLocation = this.history.location;
|
2560
|
+
this.notifyApplicationAfterRender();
|
2430
2561
|
}
|
2431
|
-
|
2432
|
-
this.
|
2433
|
-
this.activateNewBody();
|
2434
|
-
this.assignNewBody();
|
2435
|
-
}));
|
2562
|
+
viewInvalidated() {
|
2563
|
+
this.adapter.pageInvalidated();
|
2436
2564
|
}
|
2437
|
-
|
2438
|
-
|
2565
|
+
applicationAllowsFollowingLinkToLocation(link, location) {
|
2566
|
+
const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
|
2567
|
+
return !event.defaultPrevented;
|
2439
2568
|
}
|
2440
|
-
|
2441
|
-
|
2442
|
-
|
2443
|
-
}
|
2569
|
+
applicationAllowsVisitingLocation(location) {
|
2570
|
+
const event = this.notifyApplicationBeforeVisitingLocation(location);
|
2571
|
+
return !event.defaultPrevented;
|
2444
2572
|
}
|
2445
|
-
|
2446
|
-
|
2447
|
-
|
2448
|
-
|
2573
|
+
notifyApplicationAfterClickingLinkToLocation(link, location) {
|
2574
|
+
return dispatch("turbo:click", {
|
2575
|
+
target: link,
|
2576
|
+
detail: {
|
2577
|
+
url: location.href
|
2578
|
+
},
|
2579
|
+
cancelable: true
|
2580
|
+
});
|
2449
2581
|
}
|
2450
|
-
|
2451
|
-
|
2452
|
-
|
2453
|
-
|
2582
|
+
notifyApplicationBeforeVisitingLocation(location) {
|
2583
|
+
return dispatch("turbo:before-visit", {
|
2584
|
+
detail: {
|
2585
|
+
url: location.href
|
2586
|
+
},
|
2587
|
+
cancelable: true
|
2588
|
+
});
|
2454
2589
|
}
|
2455
|
-
|
2456
|
-
|
2457
|
-
|
2458
|
-
|
2590
|
+
notifyApplicationAfterVisitingLocation(location, action) {
|
2591
|
+
return dispatch("turbo:visit", {
|
2592
|
+
detail: {
|
2593
|
+
url: location.href,
|
2594
|
+
action: action
|
2595
|
+
}
|
2596
|
+
});
|
2459
2597
|
}
|
2460
|
-
|
2461
|
-
|
2462
|
-
this.activateNewBodyScriptElements();
|
2598
|
+
notifyApplicationBeforeCachingSnapshot() {
|
2599
|
+
return dispatch("turbo:before-cache");
|
2463
2600
|
}
|
2464
|
-
|
2465
|
-
|
2466
|
-
|
2467
|
-
|
2468
|
-
|
2601
|
+
notifyApplicationBeforeRender(newBody, resume) {
|
2602
|
+
return dispatch("turbo:before-render", {
|
2603
|
+
detail: {
|
2604
|
+
newBody: newBody,
|
2605
|
+
resume: resume
|
2606
|
+
},
|
2607
|
+
cancelable: true
|
2608
|
+
});
|
2469
2609
|
}
|
2470
|
-
|
2471
|
-
|
2472
|
-
replaceElementWithElement(document.body, this.newElement);
|
2473
|
-
} else {
|
2474
|
-
document.documentElement.appendChild(this.newElement);
|
2475
|
-
}
|
2610
|
+
notifyApplicationAfterRender() {
|
2611
|
+
return dispatch("turbo:render");
|
2476
2612
|
}
|
2477
|
-
|
2478
|
-
return
|
2613
|
+
notifyApplicationAfterPageLoad(timing = {}) {
|
2614
|
+
return dispatch("turbo:load", {
|
2615
|
+
detail: {
|
2616
|
+
url: this.location.href,
|
2617
|
+
timing: timing
|
2618
|
+
}
|
2619
|
+
});
|
2479
2620
|
}
|
2480
|
-
|
2481
|
-
|
2621
|
+
notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
|
2622
|
+
dispatchEvent(new HashChangeEvent("hashchange", {
|
2623
|
+
oldURL: oldURL.toString(),
|
2624
|
+
newURL: newURL.toString()
|
2625
|
+
}));
|
2482
2626
|
}
|
2483
|
-
|
2484
|
-
|
2627
|
+
getActionForLink(link) {
|
2628
|
+
const action = link.getAttribute("data-turbo-action");
|
2629
|
+
return isAction(action) ? action : "advance";
|
2485
2630
|
}
|
2486
|
-
|
2487
|
-
return this.
|
2631
|
+
locationIsVisitable(location) {
|
2632
|
+
return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
|
2488
2633
|
}
|
2489
|
-
get
|
2490
|
-
return
|
2634
|
+
get snapshot() {
|
2635
|
+
return this.view.snapshot;
|
2491
2636
|
}
|
2492
2637
|
}
|
2493
2638
|
|
2494
|
-
|
2495
|
-
|
2496
|
-
|
2497
|
-
|
2498
|
-
|
2639
|
+
function elementIsNavigable(element) {
|
2640
|
+
const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
|
2641
|
+
if (container) {
|
2642
|
+
return container.getAttribute("data-turbo") != "false";
|
2643
|
+
} else {
|
2644
|
+
return true;
|
2499
2645
|
}
|
2500
|
-
|
2501
|
-
|
2646
|
+
}
|
2647
|
+
|
2648
|
+
function extendURLWithDeprecatedProperties(url) {
|
2649
|
+
Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
|
2650
|
+
}
|
2651
|
+
|
2652
|
+
const deprecatedLocationPropertyDescriptors = {
|
2653
|
+
absoluteURL: {
|
2654
|
+
get() {
|
2655
|
+
return this.toString();
|
2656
|
+
}
|
2502
2657
|
}
|
2503
|
-
|
2504
|
-
|
2505
|
-
|
2506
|
-
|
2507
|
-
|
2658
|
+
};
|
2659
|
+
|
2660
|
+
class FrameController {
|
2661
|
+
constructor(element) {
|
2662
|
+
this.resolveVisitPromise = () => {};
|
2663
|
+
this.connected = false;
|
2664
|
+
this.hasBeenLoaded = false;
|
2665
|
+
this.settingSourceURL = false;
|
2666
|
+
this.element = element;
|
2667
|
+
this.view = new FrameView(this, this.element);
|
2668
|
+
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
2669
|
+
this.linkInterceptor = new LinkInterceptor(this, this.element);
|
2670
|
+
this.formInterceptor = new FormInterceptor(this, this.element);
|
2671
|
+
}
|
2672
|
+
connect() {
|
2673
|
+
if (!this.connected) {
|
2674
|
+
this.connected = true;
|
2675
|
+
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
2676
|
+
this.appearanceObserver.start();
|
2677
|
+
}
|
2678
|
+
this.linkInterceptor.start();
|
2679
|
+
this.formInterceptor.start();
|
2680
|
+
this.sourceURLChanged();
|
2508
2681
|
}
|
2509
2682
|
}
|
2510
|
-
|
2511
|
-
this.
|
2512
|
-
|
2513
|
-
|
2683
|
+
disconnect() {
|
2684
|
+
if (this.connected) {
|
2685
|
+
this.connected = false;
|
2686
|
+
this.appearanceObserver.stop();
|
2687
|
+
this.linkInterceptor.stop();
|
2688
|
+
this.formInterceptor.stop();
|
2689
|
+
}
|
2514
2690
|
}
|
2515
|
-
|
2516
|
-
this.
|
2691
|
+
disabledChanged() {
|
2692
|
+
if (this.loadingStyle == FrameLoadingStyle.eager) {
|
2693
|
+
this.loadSourceURL();
|
2694
|
+
}
|
2517
2695
|
}
|
2518
|
-
|
2519
|
-
|
2696
|
+
sourceURLChanged() {
|
2697
|
+
if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
|
2698
|
+
this.loadSourceURL();
|
2699
|
+
}
|
2520
2700
|
}
|
2521
|
-
|
2522
|
-
this.
|
2701
|
+
loadingStyleChanged() {
|
2702
|
+
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
2703
|
+
this.appearanceObserver.start();
|
2704
|
+
} else {
|
2705
|
+
this.appearanceObserver.stop();
|
2706
|
+
this.loadSourceURL();
|
2707
|
+
}
|
2523
2708
|
}
|
2524
|
-
|
2525
|
-
|
2526
|
-
|
2527
|
-
|
2528
|
-
|
2529
|
-
|
2709
|
+
async loadSourceURL() {
|
2710
|
+
if (!this.settingSourceURL && this.enabled && this.isActive && this.sourceURL != this.currentURL) {
|
2711
|
+
const previousURL = this.currentURL;
|
2712
|
+
this.currentURL = this.sourceURL;
|
2713
|
+
if (this.sourceURL) {
|
2714
|
+
try {
|
2715
|
+
this.element.loaded = this.visit(this.sourceURL);
|
2716
|
+
this.appearanceObserver.stop();
|
2717
|
+
await this.element.loaded;
|
2718
|
+
this.hasBeenLoaded = true;
|
2719
|
+
} catch (error) {
|
2720
|
+
this.currentURL = previousURL;
|
2721
|
+
throw error;
|
2722
|
+
}
|
2723
|
+
}
|
2724
|
+
}
|
2530
2725
|
}
|
2531
|
-
|
2532
|
-
|
2533
|
-
|
2726
|
+
async loadResponse(fetchResponse) {
|
2727
|
+
if (fetchResponse.redirected) {
|
2728
|
+
this.sourceURL = fetchResponse.response.url;
|
2729
|
+
}
|
2730
|
+
try {
|
2731
|
+
const html = await fetchResponse.responseHTML;
|
2732
|
+
if (html) {
|
2733
|
+
const {body: body} = parseHTMLDocument(html);
|
2734
|
+
const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
|
2735
|
+
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
|
2736
|
+
if (this.view.renderPromise) await this.view.renderPromise;
|
2737
|
+
await this.view.render(renderer);
|
2738
|
+
}
|
2739
|
+
} catch (error) {
|
2740
|
+
console.error(error);
|
2741
|
+
this.view.invalidate();
|
2534
2742
|
}
|
2535
2743
|
}
|
2536
|
-
|
2537
|
-
|
2538
|
-
class PageView extends View {
|
2539
|
-
constructor() {
|
2540
|
-
super(...arguments);
|
2541
|
-
this.snapshotCache = new SnapshotCache(10);
|
2542
|
-
this.lastRenderedLocation = new URL(location.href);
|
2744
|
+
elementAppearedInViewport(element) {
|
2745
|
+
this.loadSourceURL();
|
2543
2746
|
}
|
2544
|
-
|
2545
|
-
|
2546
|
-
|
2747
|
+
shouldInterceptLinkClick(element, url) {
|
2748
|
+
if (element.hasAttribute("data-turbo-method")) {
|
2749
|
+
return false;
|
2750
|
+
} else {
|
2751
|
+
return this.shouldInterceptNavigation(element);
|
2752
|
+
}
|
2547
2753
|
}
|
2548
|
-
|
2549
|
-
|
2550
|
-
this.render(renderer);
|
2754
|
+
linkClickIntercepted(element, url) {
|
2755
|
+
this.navigateFrame(element, url);
|
2551
2756
|
}
|
2552
|
-
|
2553
|
-
this.
|
2757
|
+
shouldInterceptFormSubmission(element, submitter) {
|
2758
|
+
return this.shouldInterceptNavigation(element, submitter);
|
2554
2759
|
}
|
2555
|
-
|
2556
|
-
if (this.
|
2557
|
-
this.
|
2558
|
-
|
2559
|
-
|
2560
|
-
|
2760
|
+
formSubmissionIntercepted(element, submitter) {
|
2761
|
+
if (this.formSubmission) {
|
2762
|
+
this.formSubmission.stop();
|
2763
|
+
}
|
2764
|
+
this.formSubmission = new FormSubmission(this, element, submitter);
|
2765
|
+
if (this.formSubmission.fetchRequest.isIdempotent) {
|
2766
|
+
this.navigateFrame(element, this.formSubmission.fetchRequest.url.href);
|
2767
|
+
} else {
|
2768
|
+
const {fetchRequest: fetchRequest} = this.formSubmission;
|
2769
|
+
this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
|
2770
|
+
this.formSubmission.start();
|
2561
2771
|
}
|
2562
2772
|
}
|
2563
|
-
|
2564
|
-
|
2773
|
+
prepareHeadersForRequest(headers, request) {
|
2774
|
+
headers["Turbo-Frame"] = this.id;
|
2565
2775
|
}
|
2566
|
-
|
2567
|
-
|
2776
|
+
requestStarted(request) {
|
2777
|
+
this.element.setAttribute("busy", "");
|
2568
2778
|
}
|
2569
|
-
|
2570
|
-
|
2779
|
+
requestPreventedHandlingResponse(request, response) {
|
2780
|
+
this.resolveVisitPromise();
|
2571
2781
|
}
|
2572
|
-
|
2573
|
-
|
2574
|
-
|
2575
|
-
constructor() {
|
2576
|
-
this.navigator = new Navigator(this);
|
2577
|
-
this.history = new History(this);
|
2578
|
-
this.view = new PageView(this, document.documentElement);
|
2579
|
-
this.adapter = new BrowserAdapter(this);
|
2580
|
-
this.pageObserver = new PageObserver(this);
|
2581
|
-
this.linkClickObserver = new LinkClickObserver(this);
|
2582
|
-
this.formSubmitObserver = new FormSubmitObserver(this);
|
2583
|
-
this.scrollObserver = new ScrollObserver(this);
|
2584
|
-
this.streamObserver = new StreamObserver(this);
|
2585
|
-
this.frameRedirector = new FrameRedirector(document.documentElement);
|
2586
|
-
this.enabled = true;
|
2587
|
-
this.progressBarDelay = 500;
|
2588
|
-
this.started = false;
|
2782
|
+
async requestSucceededWithResponse(request, response) {
|
2783
|
+
await this.loadResponse(response);
|
2784
|
+
this.resolveVisitPromise();
|
2589
2785
|
}
|
2590
|
-
|
2591
|
-
|
2592
|
-
|
2593
|
-
this.linkClickObserver.start();
|
2594
|
-
this.formSubmitObserver.start();
|
2595
|
-
this.scrollObserver.start();
|
2596
|
-
this.streamObserver.start();
|
2597
|
-
this.frameRedirector.start();
|
2598
|
-
this.history.start();
|
2599
|
-
this.started = true;
|
2600
|
-
this.enabled = true;
|
2601
|
-
}
|
2786
|
+
requestFailedWithResponse(request, response) {
|
2787
|
+
console.error(response);
|
2788
|
+
this.resolveVisitPromise();
|
2602
2789
|
}
|
2603
|
-
|
2604
|
-
|
2790
|
+
requestErrored(request, error) {
|
2791
|
+
console.error(error);
|
2792
|
+
this.resolveVisitPromise();
|
2605
2793
|
}
|
2606
|
-
|
2607
|
-
|
2608
|
-
this.pageObserver.stop();
|
2609
|
-
this.linkClickObserver.stop();
|
2610
|
-
this.formSubmitObserver.stop();
|
2611
|
-
this.scrollObserver.stop();
|
2612
|
-
this.streamObserver.stop();
|
2613
|
-
this.frameRedirector.stop();
|
2614
|
-
this.history.stop();
|
2615
|
-
this.started = false;
|
2616
|
-
}
|
2794
|
+
requestFinished(request) {
|
2795
|
+
this.element.removeAttribute("busy");
|
2617
2796
|
}
|
2618
|
-
|
2619
|
-
|
2797
|
+
formSubmissionStarted(formSubmission) {
|
2798
|
+
const frame = this.findFrameElement(formSubmission.formElement);
|
2799
|
+
frame.setAttribute("busy", "");
|
2620
2800
|
}
|
2621
|
-
|
2622
|
-
this.
|
2801
|
+
formSubmissionSucceededWithResponse(formSubmission, response) {
|
2802
|
+
const frame = this.findFrameElement(formSubmission.formElement);
|
2803
|
+
frame.delegate.loadResponse(response);
|
2623
2804
|
}
|
2624
|
-
|
2625
|
-
this.
|
2805
|
+
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
2806
|
+
this.element.delegate.loadResponse(fetchResponse);
|
2626
2807
|
}
|
2627
|
-
|
2628
|
-
|
2808
|
+
formSubmissionErrored(formSubmission, error) {
|
2809
|
+
console.error(error);
|
2629
2810
|
}
|
2630
|
-
|
2631
|
-
|
2811
|
+
formSubmissionFinished(formSubmission) {
|
2812
|
+
const frame = this.findFrameElement(formSubmission.formElement);
|
2813
|
+
frame.removeAttribute("busy");
|
2632
2814
|
}
|
2633
|
-
|
2634
|
-
|
2815
|
+
allowsImmediateRender(snapshot, resume) {
|
2816
|
+
return true;
|
2635
2817
|
}
|
2636
|
-
|
2637
|
-
|
2818
|
+
viewRenderedSnapshot(snapshot, isPreview) {}
|
2819
|
+
viewInvalidated() {}
|
2820
|
+
async visit(url) {
|
2821
|
+
const request = new FetchRequest(this, FetchMethod.get, expandURL(url));
|
2822
|
+
return new Promise((resolve => {
|
2823
|
+
this.resolveVisitPromise = () => {
|
2824
|
+
this.resolveVisitPromise = () => {};
|
2825
|
+
resolve();
|
2826
|
+
};
|
2827
|
+
request.perform();
|
2828
|
+
}));
|
2638
2829
|
}
|
2639
|
-
|
2640
|
-
|
2830
|
+
navigateFrame(element, url) {
|
2831
|
+
const frame = this.findFrameElement(element);
|
2832
|
+
frame.src = url;
|
2641
2833
|
}
|
2642
|
-
|
2643
|
-
|
2834
|
+
findFrameElement(element) {
|
2835
|
+
var _a;
|
2836
|
+
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
2837
|
+
return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
|
2644
2838
|
}
|
2645
|
-
|
2646
|
-
|
2647
|
-
|
2648
|
-
|
2649
|
-
|
2650
|
-
|
2651
|
-
|
2652
|
-
|
2839
|
+
async extractForeignFrameElement(container) {
|
2840
|
+
let element;
|
2841
|
+
const id = CSS.escape(this.id);
|
2842
|
+
try {
|
2843
|
+
if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
|
2844
|
+
return element;
|
2845
|
+
}
|
2846
|
+
if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
|
2847
|
+
await element.loaded;
|
2848
|
+
return await this.extractForeignFrameElement(element);
|
2849
|
+
}
|
2850
|
+
console.error(`Response has no matching <turbo-frame id="${id}"> element`);
|
2851
|
+
} catch (error) {
|
2852
|
+
console.error(error);
|
2653
2853
|
}
|
2854
|
+
return new FrameElement;
|
2654
2855
|
}
|
2655
|
-
|
2656
|
-
this.
|
2657
|
-
|
2658
|
-
|
2856
|
+
shouldInterceptNavigation(element, submitter) {
|
2857
|
+
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
2858
|
+
if (!this.enabled || id == "_top") {
|
2859
|
+
return false;
|
2860
|
+
}
|
2861
|
+
if (id) {
|
2862
|
+
const frameElement = getFrameElementById(id);
|
2863
|
+
if (frameElement) {
|
2864
|
+
return !frameElement.disabled;
|
2865
|
+
}
|
2866
|
+
}
|
2867
|
+
if (!elementIsNavigable(element)) {
|
2868
|
+
return false;
|
2869
|
+
}
|
2870
|
+
if (submitter && !elementIsNavigable(submitter)) {
|
2871
|
+
return false;
|
2872
|
+
}
|
2873
|
+
return true;
|
2659
2874
|
}
|
2660
|
-
|
2661
|
-
return this.
|
2875
|
+
get id() {
|
2876
|
+
return this.element.id;
|
2662
2877
|
}
|
2663
|
-
|
2664
|
-
|
2665
|
-
this.visit(location.href, {
|
2666
|
-
action: action
|
2667
|
-
});
|
2878
|
+
get enabled() {
|
2879
|
+
return !this.element.disabled;
|
2668
2880
|
}
|
2669
|
-
|
2670
|
-
|
2881
|
+
get sourceURL() {
|
2882
|
+
if (this.element.src) {
|
2883
|
+
return this.element.src;
|
2884
|
+
}
|
2671
2885
|
}
|
2672
|
-
|
2673
|
-
|
2674
|
-
this.
|
2886
|
+
set sourceURL(sourceURL) {
|
2887
|
+
this.settingSourceURL = true;
|
2888
|
+
this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
|
2889
|
+
this.currentURL = this.element.src;
|
2890
|
+
this.settingSourceURL = false;
|
2675
2891
|
}
|
2676
|
-
|
2677
|
-
|
2678
|
-
this.notifyApplicationAfterVisitingLocation(visit.location);
|
2892
|
+
get loadingStyle() {
|
2893
|
+
return this.element.loading;
|
2679
2894
|
}
|
2680
|
-
|
2681
|
-
this.
|
2895
|
+
get isLoading() {
|
2896
|
+
return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
|
2682
2897
|
}
|
2683
|
-
|
2684
|
-
return this.
|
2898
|
+
get isActive() {
|
2899
|
+
return this.element.isActive && this.connected;
|
2685
2900
|
}
|
2686
|
-
|
2687
|
-
|
2901
|
+
}
|
2902
|
+
|
2903
|
+
function getFrameElementById(id) {
|
2904
|
+
if (id != null) {
|
2905
|
+
const element = document.getElementById(id);
|
2906
|
+
if (element instanceof FrameElement) {
|
2907
|
+
return element;
|
2908
|
+
}
|
2688
2909
|
}
|
2689
|
-
|
2690
|
-
|
2691
|
-
|
2910
|
+
}
|
2911
|
+
|
2912
|
+
function activateElement(element, currentURL) {
|
2913
|
+
if (element) {
|
2914
|
+
const src = element.getAttribute("src");
|
2915
|
+
if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {
|
2916
|
+
throw new Error(`Matching <turbo-frame id="${element.id}"> element has a source URL which references itself`);
|
2917
|
+
}
|
2918
|
+
if (element.ownerDocument !== document) {
|
2919
|
+
element = document.importNode(element, true);
|
2920
|
+
}
|
2921
|
+
if (element instanceof FrameElement) {
|
2922
|
+
element.connectedCallback();
|
2923
|
+
return element;
|
2924
|
+
}
|
2692
2925
|
}
|
2693
|
-
|
2694
|
-
|
2926
|
+
}
|
2927
|
+
|
2928
|
+
const StreamActions = {
|
2929
|
+
after() {
|
2930
|
+
this.targetElements.forEach((e => {
|
2931
|
+
var _a;
|
2932
|
+
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
|
2933
|
+
}));
|
2934
|
+
},
|
2935
|
+
append() {
|
2936
|
+
this.removeDuplicateTargetChildren();
|
2937
|
+
this.targetElements.forEach((e => e.append(this.templateContent)));
|
2938
|
+
},
|
2939
|
+
before() {
|
2940
|
+
this.targetElements.forEach((e => {
|
2941
|
+
var _a;
|
2942
|
+
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
|
2943
|
+
}));
|
2944
|
+
},
|
2945
|
+
prepend() {
|
2946
|
+
this.removeDuplicateTargetChildren();
|
2947
|
+
this.targetElements.forEach((e => e.prepend(this.templateContent)));
|
2948
|
+
},
|
2949
|
+
remove() {
|
2950
|
+
this.targetElements.forEach((e => e.remove()));
|
2951
|
+
},
|
2952
|
+
replace() {
|
2953
|
+
this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
|
2954
|
+
},
|
2955
|
+
update() {
|
2956
|
+
this.targetElements.forEach((e => {
|
2957
|
+
e.innerHTML = "";
|
2958
|
+
e.append(this.templateContent);
|
2959
|
+
}));
|
2695
2960
|
}
|
2696
|
-
|
2697
|
-
|
2961
|
+
};
|
2962
|
+
|
2963
|
+
class StreamElement extends HTMLElement {
|
2964
|
+
async connectedCallback() {
|
2965
|
+
try {
|
2966
|
+
await this.render();
|
2967
|
+
} catch (error) {
|
2968
|
+
console.error(error);
|
2969
|
+
} finally {
|
2970
|
+
this.disconnect();
|
2971
|
+
}
|
2698
2972
|
}
|
2699
|
-
|
2700
|
-
|
2973
|
+
async render() {
|
2974
|
+
var _a;
|
2975
|
+
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
|
2976
|
+
if (this.dispatchEvent(this.beforeRenderEvent)) {
|
2977
|
+
await nextAnimationFrame();
|
2978
|
+
this.performAction();
|
2979
|
+
}
|
2980
|
+
})();
|
2701
2981
|
}
|
2702
|
-
|
2703
|
-
|
2982
|
+
disconnect() {
|
2983
|
+
try {
|
2984
|
+
this.remove();
|
2985
|
+
} catch (_a) {}
|
2704
2986
|
}
|
2705
|
-
|
2706
|
-
this.
|
2987
|
+
removeDuplicateTargetChildren() {
|
2988
|
+
this.duplicateChildren.forEach((c => c.remove()));
|
2707
2989
|
}
|
2708
|
-
|
2709
|
-
|
2710
|
-
this.
|
2990
|
+
get duplicateChildren() {
|
2991
|
+
var _a;
|
2992
|
+
const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
|
2993
|
+
const newChildrenIds = [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].filter((c => !!c.id)).map((c => c.id));
|
2994
|
+
return existingChildren.filter((c => newChildrenIds.includes(c.id)));
|
2711
2995
|
}
|
2712
|
-
|
2713
|
-
this.
|
2996
|
+
get performAction() {
|
2997
|
+
if (this.action) {
|
2998
|
+
const actionFunction = StreamActions[this.action];
|
2999
|
+
if (actionFunction) {
|
3000
|
+
return actionFunction;
|
3001
|
+
}
|
3002
|
+
this.raise("unknown action");
|
3003
|
+
}
|
3004
|
+
this.raise("action attribute is missing");
|
2714
3005
|
}
|
2715
|
-
|
2716
|
-
|
2717
|
-
|
3006
|
+
get targetElements() {
|
3007
|
+
if (this.target) {
|
3008
|
+
return this.targetElementsById;
|
3009
|
+
} else if (this.targets) {
|
3010
|
+
return this.targetElementsByQuery;
|
3011
|
+
} else {
|
3012
|
+
this.raise("target or targets attribute is missing");
|
3013
|
+
}
|
2718
3014
|
}
|
2719
|
-
|
2720
|
-
|
2721
|
-
return !event.defaultPrevented;
|
3015
|
+
get templateContent() {
|
3016
|
+
return this.templateElement.content.cloneNode(true);
|
2722
3017
|
}
|
2723
|
-
|
2724
|
-
|
2725
|
-
|
2726
|
-
|
2727
|
-
|
2728
|
-
},
|
2729
|
-
cancelable: true
|
2730
|
-
});
|
3018
|
+
get templateElement() {
|
3019
|
+
if (this.firstElementChild instanceof HTMLTemplateElement) {
|
3020
|
+
return this.firstElementChild;
|
3021
|
+
}
|
3022
|
+
this.raise("first child element must be a <template> element");
|
2731
3023
|
}
|
2732
|
-
|
2733
|
-
return
|
2734
|
-
detail: {
|
2735
|
-
url: location.href
|
2736
|
-
},
|
2737
|
-
cancelable: true
|
2738
|
-
});
|
3024
|
+
get action() {
|
3025
|
+
return this.getAttribute("action");
|
2739
3026
|
}
|
2740
|
-
|
2741
|
-
return
|
2742
|
-
detail: {
|
2743
|
-
url: location.href
|
2744
|
-
}
|
2745
|
-
});
|
3027
|
+
get target() {
|
3028
|
+
return this.getAttribute("target");
|
2746
3029
|
}
|
2747
|
-
|
2748
|
-
return
|
3030
|
+
get targets() {
|
3031
|
+
return this.getAttribute("targets");
|
2749
3032
|
}
|
2750
|
-
|
2751
|
-
|
2752
|
-
detail: {
|
2753
|
-
newBody: newBody
|
2754
|
-
}
|
2755
|
-
});
|
3033
|
+
raise(message) {
|
3034
|
+
throw new Error(`${this.description}: ${message}`);
|
2756
3035
|
}
|
2757
|
-
|
2758
|
-
|
3036
|
+
get description() {
|
3037
|
+
var _a, _b;
|
3038
|
+
return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
|
2759
3039
|
}
|
2760
|
-
|
2761
|
-
return
|
2762
|
-
|
2763
|
-
|
2764
|
-
timing: timing
|
2765
|
-
}
|
3040
|
+
get beforeRenderEvent() {
|
3041
|
+
return new CustomEvent("turbo:before-stream-render", {
|
3042
|
+
bubbles: true,
|
3043
|
+
cancelable: true
|
2766
3044
|
});
|
2767
3045
|
}
|
2768
|
-
|
2769
|
-
|
2770
|
-
|
2771
|
-
|
2772
|
-
|
2773
|
-
const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
|
2774
|
-
if (container) {
|
2775
|
-
return container.getAttribute("data-turbo") != "false";
|
3046
|
+
get targetElementsById() {
|
3047
|
+
var _a;
|
3048
|
+
const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
|
3049
|
+
if (element !== null) {
|
3050
|
+
return [ element ];
|
2776
3051
|
} else {
|
2777
|
-
return
|
3052
|
+
return [];
|
2778
3053
|
}
|
2779
3054
|
}
|
2780
|
-
|
2781
|
-
|
2782
|
-
|
2783
|
-
|
2784
|
-
|
3055
|
+
get targetElementsByQuery() {
|
3056
|
+
var _a;
|
3057
|
+
const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);
|
3058
|
+
if (elements.length !== 0) {
|
3059
|
+
return Array.prototype.slice.call(elements);
|
3060
|
+
} else {
|
3061
|
+
return [];
|
3062
|
+
}
|
2785
3063
|
}
|
2786
3064
|
}
|
2787
3065
|
|
2788
|
-
|
2789
|
-
Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
|
2790
|
-
}
|
3066
|
+
FrameElement.delegateConstructor = FrameController;
|
2791
3067
|
|
2792
|
-
|
2793
|
-
|
2794
|
-
|
2795
|
-
|
3068
|
+
customElements.define("turbo-frame", FrameElement);
|
3069
|
+
|
3070
|
+
customElements.define("turbo-stream", StreamElement);
|
3071
|
+
|
3072
|
+
(() => {
|
3073
|
+
let element = document.currentScript;
|
3074
|
+
if (!element) return;
|
3075
|
+
if (element.hasAttribute("data-turbo-suppress-warning")) return;
|
3076
|
+
while (element = element.parentElement) {
|
3077
|
+
if (element == document.body) {
|
3078
|
+
return console.warn(unindent`
|
3079
|
+
You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
|
3080
|
+
|
3081
|
+
Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
|
3082
|
+
|
3083
|
+
For more information, see: https://turbo.hotwired.dev/handbook/building#working-with-script-elements
|
3084
|
+
|
3085
|
+
——
|
3086
|
+
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
3087
|
+
`, element.outerHTML);
|
2796
3088
|
}
|
2797
3089
|
}
|
2798
|
-
};
|
3090
|
+
})();
|
2799
3091
|
|
2800
3092
|
const session = new Session;
|
2801
3093
|
|
@@ -2833,10 +3125,29 @@ function setProgressBarDelay(delay) {
|
|
2833
3125
|
session.setProgressBarDelay(delay);
|
2834
3126
|
}
|
2835
3127
|
|
3128
|
+
var Turbo = Object.freeze({
|
3129
|
+
__proto__: null,
|
3130
|
+
navigator: navigator,
|
3131
|
+
PageRenderer: PageRenderer,
|
3132
|
+
PageSnapshot: PageSnapshot,
|
3133
|
+
start: start,
|
3134
|
+
registerAdapter: registerAdapter,
|
3135
|
+
visit: visit,
|
3136
|
+
connectStreamSource: connectStreamSource,
|
3137
|
+
disconnectStreamSource: disconnectStreamSource,
|
3138
|
+
renderStreamMessage: renderStreamMessage,
|
3139
|
+
clearCache: clearCache,
|
3140
|
+
setProgressBarDelay: setProgressBarDelay
|
3141
|
+
});
|
3142
|
+
|
3143
|
+
window.Turbo = Turbo;
|
3144
|
+
|
2836
3145
|
start();
|
2837
3146
|
|
2838
3147
|
var turbo_es2017Esm = Object.freeze({
|
2839
3148
|
__proto__: null,
|
3149
|
+
PageRenderer: PageRenderer,
|
3150
|
+
PageSnapshot: PageSnapshot,
|
2840
3151
|
clearCache: clearCache,
|
2841
3152
|
connectStreamSource: connectStreamSource,
|
2842
3153
|
disconnectStreamSource: disconnectStreamSource,
|
@@ -2851,17 +3162,20 @@ var turbo_es2017Esm = Object.freeze({
|
|
2851
3162
|
let consumer;
|
2852
3163
|
|
2853
3164
|
async function getConsumer() {
|
2854
|
-
|
2855
|
-
const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
|
2856
|
-
return index;
|
2857
|
-
}));
|
2858
|
-
return setConsumer(createConsumer());
|
3165
|
+
return consumer || setConsumer(createConsumer().then(setConsumer));
|
2859
3166
|
}
|
2860
3167
|
|
2861
3168
|
function setConsumer(newConsumer) {
|
2862
3169
|
return consumer = newConsumer;
|
2863
3170
|
}
|
2864
3171
|
|
3172
|
+
async function createConsumer() {
|
3173
|
+
const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
|
3174
|
+
return index;
|
3175
|
+
}));
|
3176
|
+
return createConsumer();
|
3177
|
+
}
|
3178
|
+
|
2865
3179
|
async function subscribeTo(channel, mixin) {
|
2866
3180
|
const {subscriptions: subscriptions} = await getConsumer();
|
2867
3181
|
return subscriptions.create(channel, mixin);
|
@@ -2871,6 +3185,7 @@ var cable = Object.freeze({
|
|
2871
3185
|
__proto__: null,
|
2872
3186
|
getConsumer: getConsumer,
|
2873
3187
|
setConsumer: setConsumer,
|
3188
|
+
createConsumer: createConsumer,
|
2874
3189
|
subscribeTo: subscribeTo
|
2875
3190
|
});
|
2876
3191
|
|
@@ -3334,7 +3649,7 @@ function createWebSocketURL(url) {
|
|
3334
3649
|
}
|
3335
3650
|
}
|
3336
3651
|
|
3337
|
-
function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
|
3652
|
+
function createConsumer$1(url = getConfig("url") || INTERNAL.default_mount_path) {
|
3338
3653
|
return new Consumer(url);
|
3339
3654
|
}
|
3340
3655
|
|
@@ -3356,7 +3671,7 @@ var index = Object.freeze({
|
|
3356
3671
|
adapters: adapters,
|
3357
3672
|
createWebSocketURL: createWebSocketURL,
|
3358
3673
|
logger: logger,
|
3359
|
-
createConsumer: createConsumer,
|
3674
|
+
createConsumer: createConsumer$1,
|
3360
3675
|
getConfig: getConfig
|
3361
3676
|
});
|
3362
3677
|
|