turbo-rails 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascripts/turbo.js +1005 -525
- data/app/assets/javascripts/turbo.min.js +6 -6
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams/broadcasts.rb +1 -1
- data/app/helpers/turbo/drive_helper.rb +16 -3
- data/app/helpers/turbo/streams/action_helper.rb +7 -5
- data/app/javascript/turbo/fetch_requests.js +19 -0
- data/app/javascript/turbo/index.js +3 -2
- data/lib/turbo/version.rb +1 -1
- metadata +4 -4
- data/app/javascript/turbo/form_submissions.js +0 -7
@@ -88,7 +88,7 @@ class FrameElement extends HTMLElement {
|
|
88
88
|
this.delegate = new FrameElement.delegateConstructor(this);
|
89
89
|
}
|
90
90
|
static get observedAttributes() {
|
91
|
-
return [ "disabled", "loading", "src" ];
|
91
|
+
return [ "disabled", "complete", "loading", "src" ];
|
92
92
|
}
|
93
93
|
connectedCallback() {
|
94
94
|
this.delegate.connect();
|
@@ -98,12 +98,16 @@ class FrameElement extends HTMLElement {
|
|
98
98
|
}
|
99
99
|
reload() {
|
100
100
|
const {src: src} = this;
|
101
|
+
this.removeAttribute("complete");
|
101
102
|
this.src = null;
|
102
103
|
this.src = src;
|
104
|
+
return this.loaded;
|
103
105
|
}
|
104
106
|
attributeChangedCallback(name) {
|
105
107
|
if (name == "loading") {
|
106
108
|
this.delegate.loadingStyleChanged();
|
109
|
+
} else if (name == "complete") {
|
110
|
+
this.delegate.completeChanged();
|
107
111
|
} else if (name == "src") {
|
108
112
|
this.delegate.sourceURLChanged();
|
109
113
|
} else {
|
@@ -195,7 +199,7 @@ function getExtension(url) {
|
|
195
199
|
}
|
196
200
|
|
197
201
|
function isHTML(url) {
|
198
|
-
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
|
202
|
+
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
|
199
203
|
}
|
200
204
|
|
201
205
|
function isPrefixedBy(baseURL, url) {
|
@@ -282,6 +286,38 @@ class FetchResponse {
|
|
282
286
|
}
|
283
287
|
}
|
284
288
|
|
289
|
+
function isAction(action) {
|
290
|
+
return action == "advance" || action == "replace" || action == "restore";
|
291
|
+
}
|
292
|
+
|
293
|
+
function activateScriptElement(element) {
|
294
|
+
if (element.getAttribute("data-turbo-eval") == "false") {
|
295
|
+
return element;
|
296
|
+
} else {
|
297
|
+
const createdScriptElement = document.createElement("script");
|
298
|
+
const cspNonce = getMetaContent("csp-nonce");
|
299
|
+
if (cspNonce) {
|
300
|
+
createdScriptElement.nonce = cspNonce;
|
301
|
+
}
|
302
|
+
createdScriptElement.textContent = element.textContent;
|
303
|
+
createdScriptElement.async = false;
|
304
|
+
copyElementAttributes(createdScriptElement, element);
|
305
|
+
return createdScriptElement;
|
306
|
+
}
|
307
|
+
}
|
308
|
+
|
309
|
+
function copyElementAttributes(destinationElement, sourceElement) {
|
310
|
+
for (const {name: name, value: value} of sourceElement.attributes) {
|
311
|
+
destinationElement.setAttribute(name, value);
|
312
|
+
}
|
313
|
+
}
|
314
|
+
|
315
|
+
function createDocumentFragment(html) {
|
316
|
+
const template = document.createElement("template");
|
317
|
+
template.innerHTML = html;
|
318
|
+
return template.content;
|
319
|
+
}
|
320
|
+
|
285
321
|
function dispatch(eventName, {target: target, cancelable: cancelable, detail: detail} = {}) {
|
286
322
|
const event = new CustomEvent(eventName, {
|
287
323
|
cancelable: cancelable,
|
@@ -327,7 +363,7 @@ function interpolate(strings, values) {
|
|
327
363
|
}
|
328
364
|
|
329
365
|
function uuid() {
|
330
|
-
return Array.
|
366
|
+
return Array.from({
|
331
367
|
length: 36
|
332
368
|
}).map(((_, i) => {
|
333
369
|
if (i == 8 || i == 13 || i == 18 || i == 23) {
|
@@ -349,6 +385,10 @@ function getAttribute(attributeName, ...elements) {
|
|
349
385
|
return null;
|
350
386
|
}
|
351
387
|
|
388
|
+
function hasAttribute(attributeName, ...elements) {
|
389
|
+
return elements.some((element => element && element.hasAttribute(attributeName)));
|
390
|
+
}
|
391
|
+
|
352
392
|
function markAsBusy(...elements) {
|
353
393
|
for (const element of elements) {
|
354
394
|
if (element.localName == "turbo-frame") {
|
@@ -367,6 +407,59 @@ function clearBusyState(...elements) {
|
|
367
407
|
}
|
368
408
|
}
|
369
409
|
|
410
|
+
function waitForLoad(element, timeoutInMilliseconds = 2e3) {
|
411
|
+
return new Promise((resolve => {
|
412
|
+
const onComplete = () => {
|
413
|
+
element.removeEventListener("error", onComplete);
|
414
|
+
element.removeEventListener("load", onComplete);
|
415
|
+
resolve();
|
416
|
+
};
|
417
|
+
element.addEventListener("load", onComplete, {
|
418
|
+
once: true
|
419
|
+
});
|
420
|
+
element.addEventListener("error", onComplete, {
|
421
|
+
once: true
|
422
|
+
});
|
423
|
+
setTimeout(resolve, timeoutInMilliseconds);
|
424
|
+
}));
|
425
|
+
}
|
426
|
+
|
427
|
+
function getHistoryMethodForAction(action) {
|
428
|
+
switch (action) {
|
429
|
+
case "replace":
|
430
|
+
return history.replaceState;
|
431
|
+
|
432
|
+
case "advance":
|
433
|
+
case "restore":
|
434
|
+
return history.pushState;
|
435
|
+
}
|
436
|
+
}
|
437
|
+
|
438
|
+
function getVisitAction(...elements) {
|
439
|
+
const action = getAttribute("data-turbo-action", ...elements);
|
440
|
+
return isAction(action) ? action : null;
|
441
|
+
}
|
442
|
+
|
443
|
+
function getMetaElement(name) {
|
444
|
+
return document.querySelector(`meta[name="${name}"]`);
|
445
|
+
}
|
446
|
+
|
447
|
+
function getMetaContent(name) {
|
448
|
+
const element = getMetaElement(name);
|
449
|
+
return element && element.content;
|
450
|
+
}
|
451
|
+
|
452
|
+
function setMetaContent(name, content) {
|
453
|
+
let element = getMetaElement(name);
|
454
|
+
if (!element) {
|
455
|
+
element = document.createElement("meta");
|
456
|
+
element.setAttribute("name", name);
|
457
|
+
document.head.appendChild(element);
|
458
|
+
}
|
459
|
+
element.setAttribute("content", content);
|
460
|
+
return element;
|
461
|
+
}
|
462
|
+
|
370
463
|
var FetchMethod;
|
371
464
|
|
372
465
|
(function(FetchMethod) {
|
@@ -399,7 +492,7 @@ function fetchMethodFromString(method) {
|
|
399
492
|
class FetchRequest {
|
400
493
|
constructor(delegate, method, location, body = new URLSearchParams, target = null) {
|
401
494
|
this.abortController = new AbortController;
|
402
|
-
this.resolveRequestPromise =
|
495
|
+
this.resolveRequestPromise = _value => {};
|
403
496
|
this.delegate = delegate;
|
404
497
|
this.method = method;
|
405
498
|
this.headers = this.defaultHeaders;
|
@@ -430,7 +523,9 @@ class FetchRequest {
|
|
430
523
|
return await this.receive(response);
|
431
524
|
} catch (error) {
|
432
525
|
if (error.name !== "AbortError") {
|
433
|
-
this.
|
526
|
+
if (this.willDelegateErrorHandling(error)) {
|
527
|
+
this.delegate.requestErrored(this, error);
|
528
|
+
}
|
434
529
|
throw error;
|
435
530
|
}
|
436
531
|
} finally {
|
@@ -478,6 +573,9 @@ class FetchRequest {
|
|
478
573
|
get abortSignal() {
|
479
574
|
return this.abortController.signal;
|
480
575
|
}
|
576
|
+
acceptResponseType(mimeType) {
|
577
|
+
this.headers["Accept"] = [ mimeType, this.headers["Accept"] ].join(", ");
|
578
|
+
}
|
481
579
|
async allowRequestToBeIntercepted(fetchOptions) {
|
482
580
|
const requestInterception = new Promise((resolve => this.resolveRequestPromise = resolve));
|
483
581
|
const event = dispatch("turbo:before-fetch-request", {
|
@@ -491,6 +589,17 @@ class FetchRequest {
|
|
491
589
|
});
|
492
590
|
if (event.defaultPrevented) await requestInterception;
|
493
591
|
}
|
592
|
+
willDelegateErrorHandling(error) {
|
593
|
+
const event = dispatch("turbo:fetch-request-error", {
|
594
|
+
target: this.target,
|
595
|
+
cancelable: true,
|
596
|
+
detail: {
|
597
|
+
request: this,
|
598
|
+
error: error
|
599
|
+
}
|
600
|
+
});
|
601
|
+
return !event.defaultPrevented;
|
602
|
+
}
|
494
603
|
}
|
495
604
|
|
496
605
|
class AppearanceObserver {
|
@@ -521,40 +630,31 @@ class AppearanceObserver {
|
|
521
630
|
}
|
522
631
|
|
523
632
|
class StreamMessage {
|
524
|
-
constructor(
|
525
|
-
this.
|
526
|
-
this.templateElement.innerHTML = html;
|
633
|
+
constructor(fragment) {
|
634
|
+
this.fragment = importStreamElements(fragment);
|
527
635
|
}
|
528
636
|
static wrap(message) {
|
529
637
|
if (typeof message == "string") {
|
530
|
-
return new this(message);
|
638
|
+
return new this(createDocumentFragment(message));
|
531
639
|
} else {
|
532
640
|
return message;
|
533
641
|
}
|
534
642
|
}
|
535
|
-
get fragment() {
|
536
|
-
const fragment = document.createDocumentFragment();
|
537
|
-
for (const element of this.foreignElements) {
|
538
|
-
fragment.appendChild(document.importNode(element, true));
|
539
|
-
}
|
540
|
-
return fragment;
|
541
|
-
}
|
542
|
-
get foreignElements() {
|
543
|
-
return this.templateChildren.reduce(((streamElements, child) => {
|
544
|
-
if (child.tagName.toLowerCase() == "turbo-stream") {
|
545
|
-
return [ ...streamElements, child ];
|
546
|
-
} else {
|
547
|
-
return streamElements;
|
548
|
-
}
|
549
|
-
}), []);
|
550
|
-
}
|
551
|
-
get templateChildren() {
|
552
|
-
return Array.from(this.templateElement.content.children);
|
553
|
-
}
|
554
643
|
}
|
555
644
|
|
556
645
|
StreamMessage.contentType = "text/vnd.turbo-stream.html";
|
557
646
|
|
647
|
+
function importStreamElements(fragment) {
|
648
|
+
for (const element of fragment.querySelectorAll("turbo-stream")) {
|
649
|
+
const streamElement = document.importNode(element, true);
|
650
|
+
for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) {
|
651
|
+
inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));
|
652
|
+
}
|
653
|
+
element.replaceWith(streamElement);
|
654
|
+
}
|
655
|
+
return fragment;
|
656
|
+
}
|
657
|
+
|
558
658
|
var FormSubmissionState;
|
559
659
|
|
560
660
|
(function(FormSubmissionState) {
|
@@ -601,8 +701,8 @@ class FormSubmission {
|
|
601
701
|
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
|
602
702
|
this.mustRedirect = mustRedirect;
|
603
703
|
}
|
604
|
-
static confirmMethod(message,
|
605
|
-
return confirm(message);
|
704
|
+
static confirmMethod(message, _element, _submitter) {
|
705
|
+
return Promise.resolve(confirm(message));
|
606
706
|
}
|
607
707
|
get method() {
|
608
708
|
var _a;
|
@@ -612,7 +712,11 @@ class FormSubmission {
|
|
612
712
|
get action() {
|
613
713
|
var _a;
|
614
714
|
const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
|
615
|
-
|
715
|
+
if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
|
716
|
+
return this.submitter.getAttribute("formaction") || "";
|
717
|
+
} else {
|
718
|
+
return this.formElement.getAttribute("action") || formElementAction || "";
|
719
|
+
}
|
616
720
|
}
|
617
721
|
get body() {
|
618
722
|
if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
|
@@ -631,16 +735,11 @@ class FormSubmission {
|
|
631
735
|
get stringFormData() {
|
632
736
|
return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
|
633
737
|
}
|
634
|
-
get confirmationMessage() {
|
635
|
-
return this.formElement.getAttribute("data-turbo-confirm");
|
636
|
-
}
|
637
|
-
get needsConfirmation() {
|
638
|
-
return this.confirmationMessage !== null;
|
639
|
-
}
|
640
738
|
async start() {
|
641
739
|
const {initialized: initialized, requesting: requesting} = FormSubmissionState;
|
642
|
-
|
643
|
-
|
740
|
+
const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
|
741
|
+
if (typeof confirmationMessage === "string") {
|
742
|
+
const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);
|
644
743
|
if (!answer) {
|
645
744
|
return;
|
646
745
|
}
|
@@ -664,10 +763,12 @@ class FormSubmission {
|
|
664
763
|
if (token) {
|
665
764
|
headers["X-CSRF-Token"] = token;
|
666
765
|
}
|
667
|
-
|
766
|
+
}
|
767
|
+
if (this.requestAcceptsTurboStreamResponse(request)) {
|
768
|
+
request.acceptResponseType(StreamMessage.contentType);
|
668
769
|
}
|
669
770
|
}
|
670
|
-
requestStarted(
|
771
|
+
requestStarted(_request) {
|
671
772
|
var _a;
|
672
773
|
this.state = FormSubmissionState.waiting;
|
673
774
|
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
|
@@ -714,7 +815,7 @@ class FormSubmission {
|
|
714
815
|
};
|
715
816
|
this.delegate.formSubmissionErrored(this, error);
|
716
817
|
}
|
717
|
-
requestFinished(
|
818
|
+
requestFinished(_request) {
|
718
819
|
var _a;
|
719
820
|
this.state = FormSubmissionState.stopped;
|
720
821
|
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
|
@@ -729,14 +830,17 @@ class FormSubmission {
|
|
729
830
|
requestMustRedirect(request) {
|
730
831
|
return !request.isIdempotent && this.mustRedirect;
|
731
832
|
}
|
833
|
+
requestAcceptsTurboStreamResponse(request) {
|
834
|
+
return !request.isIdempotent || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
|
835
|
+
}
|
732
836
|
}
|
733
837
|
|
734
838
|
function buildFormData(formElement, submitter) {
|
735
839
|
const formData = new FormData(formElement);
|
736
840
|
const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
|
737
841
|
const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
|
738
|
-
if (name
|
739
|
-
formData.append(name, value);
|
842
|
+
if (name) {
|
843
|
+
formData.append(name, value || "");
|
740
844
|
}
|
741
845
|
return formData;
|
742
846
|
}
|
@@ -752,11 +856,6 @@ function getCookieValue(cookieName) {
|
|
752
856
|
}
|
753
857
|
}
|
754
858
|
|
755
|
-
function getMetaContent(name) {
|
756
|
-
const element = document.querySelector(`meta[name="${name}"]`);
|
757
|
-
return element && element.content;
|
758
|
-
}
|
759
|
-
|
760
859
|
function responseSucceededWithoutRedirect(response) {
|
761
860
|
return response.statusCode == 200 && !response.redirected;
|
762
861
|
}
|
@@ -775,6 +874,9 @@ class Snapshot {
|
|
775
874
|
constructor(element) {
|
776
875
|
this.element = element;
|
777
876
|
}
|
877
|
+
get activeElement() {
|
878
|
+
return this.element.ownerDocument.activeElement;
|
879
|
+
}
|
778
880
|
get children() {
|
779
881
|
return [ ...this.element.children ];
|
780
882
|
}
|
@@ -788,13 +890,17 @@ class Snapshot {
|
|
788
890
|
return this.element.isConnected;
|
789
891
|
}
|
790
892
|
get firstAutofocusableElement() {
|
791
|
-
|
893
|
+
const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
|
894
|
+
for (const element of this.element.querySelectorAll("[autofocus]")) {
|
895
|
+
if (element.closest(inertDisabledOrHidden) == null) return element; else continue;
|
896
|
+
}
|
897
|
+
return null;
|
792
898
|
}
|
793
899
|
get permanentElements() {
|
794
|
-
return
|
900
|
+
return queryPermanentElementsAll(this.element);
|
795
901
|
}
|
796
902
|
getPermanentElementById(id) {
|
797
|
-
return this.element
|
903
|
+
return getPermanentElementById(this.element, id);
|
798
904
|
}
|
799
905
|
getPermanentElementMapForSnapshot(snapshot) {
|
800
906
|
const permanentElementMap = {};
|
@@ -809,35 +915,65 @@ class Snapshot {
|
|
809
915
|
}
|
810
916
|
}
|
811
917
|
|
812
|
-
|
813
|
-
|
918
|
+
function getPermanentElementById(node, id) {
|
919
|
+
return node.querySelector(`#${id}[data-turbo-permanent]`);
|
920
|
+
}
|
921
|
+
|
922
|
+
function queryPermanentElementsAll(node) {
|
923
|
+
return node.querySelectorAll("[id][data-turbo-permanent]");
|
924
|
+
}
|
925
|
+
|
926
|
+
class FormSubmitObserver {
|
927
|
+
constructor(delegate, eventTarget) {
|
928
|
+
this.started = false;
|
929
|
+
this.submitCaptured = () => {
|
930
|
+
this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
|
931
|
+
this.eventTarget.addEventListener("submit", this.submitBubbled, false);
|
932
|
+
};
|
814
933
|
this.submitBubbled = event => {
|
815
|
-
|
816
|
-
|
934
|
+
if (!event.defaultPrevented) {
|
935
|
+
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
817
936
|
const submitter = event.submitter || undefined;
|
818
|
-
|
819
|
-
if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
|
937
|
+
if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
|
820
938
|
event.preventDefault();
|
821
|
-
|
822
|
-
this.delegate.formSubmissionIntercepted(form, submitter);
|
939
|
+
this.delegate.formSubmitted(form, submitter);
|
823
940
|
}
|
824
941
|
}
|
825
942
|
};
|
826
943
|
this.delegate = delegate;
|
827
|
-
this.
|
944
|
+
this.eventTarget = eventTarget;
|
828
945
|
}
|
829
946
|
start() {
|
830
|
-
|
947
|
+
if (!this.started) {
|
948
|
+
this.eventTarget.addEventListener("submit", this.submitCaptured, true);
|
949
|
+
this.started = true;
|
950
|
+
}
|
831
951
|
}
|
832
952
|
stop() {
|
833
|
-
|
953
|
+
if (this.started) {
|
954
|
+
this.eventTarget.removeEventListener("submit", this.submitCaptured, true);
|
955
|
+
this.started = false;
|
956
|
+
}
|
957
|
+
}
|
958
|
+
}
|
959
|
+
|
960
|
+
function submissionDoesNotDismissDialog(form, submitter) {
|
961
|
+
const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
|
962
|
+
return method != "dialog";
|
963
|
+
}
|
964
|
+
|
965
|
+
function submissionDoesNotTargetIFrame(form, submitter) {
|
966
|
+
const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
|
967
|
+
for (const element of document.getElementsByName(target)) {
|
968
|
+
if (element instanceof HTMLIFrameElement) return false;
|
834
969
|
}
|
970
|
+
return true;
|
835
971
|
}
|
836
972
|
|
837
973
|
class View {
|
838
974
|
constructor(delegate, element) {
|
839
|
-
this.resolveRenderPromise =
|
840
|
-
this.resolveInterceptionPromise =
|
975
|
+
this.resolveRenderPromise = _value => {};
|
976
|
+
this.resolveInterceptionPromise = _value => {};
|
841
977
|
this.delegate = delegate;
|
842
978
|
this.element = element;
|
843
979
|
}
|
@@ -888,12 +1024,17 @@ class View {
|
|
888
1024
|
try {
|
889
1025
|
this.renderPromise = new Promise((resolve => this.resolveRenderPromise = resolve));
|
890
1026
|
this.renderer = renderer;
|
891
|
-
this.prepareToRenderSnapshot(renderer);
|
1027
|
+
await this.prepareToRenderSnapshot(renderer);
|
892
1028
|
const renderInterception = new Promise((resolve => this.resolveInterceptionPromise = resolve));
|
893
|
-
const
|
1029
|
+
const options = {
|
1030
|
+
resume: this.resolveInterceptionPromise,
|
1031
|
+
render: this.renderer.renderElement
|
1032
|
+
};
|
1033
|
+
const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
|
894
1034
|
if (!immediateRender) await renderInterception;
|
895
1035
|
await this.renderSnapshot(renderer);
|
896
1036
|
this.delegate.viewRenderedSnapshot(snapshot, isPreview);
|
1037
|
+
this.delegate.preloadOnLoadLinksForView(this.element);
|
897
1038
|
this.finishRenderingSnapshot(renderer);
|
898
1039
|
} finally {
|
899
1040
|
delete this.renderer;
|
@@ -901,15 +1042,15 @@ class View {
|
|
901
1042
|
delete this.renderPromise;
|
902
1043
|
}
|
903
1044
|
} else {
|
904
|
-
this.invalidate();
|
1045
|
+
this.invalidate(renderer.reloadReason);
|
905
1046
|
}
|
906
1047
|
}
|
907
|
-
invalidate() {
|
908
|
-
this.delegate.viewInvalidated();
|
1048
|
+
invalidate(reason) {
|
1049
|
+
this.delegate.viewInvalidated(reason);
|
909
1050
|
}
|
910
|
-
prepareToRenderSnapshot(renderer) {
|
1051
|
+
async prepareToRenderSnapshot(renderer) {
|
911
1052
|
this.markAsPreview(renderer.isPreview);
|
912
|
-
renderer.prepareToRender();
|
1053
|
+
await renderer.prepareToRender();
|
913
1054
|
}
|
914
1055
|
markAsPreview(isPreview) {
|
915
1056
|
if (isPreview) {
|
@@ -935,60 +1076,115 @@ class FrameView extends View {
|
|
935
1076
|
}
|
936
1077
|
}
|
937
1078
|
|
938
|
-
class
|
939
|
-
constructor(delegate,
|
940
|
-
this.
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
delete this.clickEvent;
|
945
|
-
}
|
1079
|
+
class LinkClickObserver {
|
1080
|
+
constructor(delegate, eventTarget) {
|
1081
|
+
this.started = false;
|
1082
|
+
this.clickCaptured = () => {
|
1083
|
+
this.eventTarget.removeEventListener("click", this.clickBubbled, false);
|
1084
|
+
this.eventTarget.addEventListener("click", this.clickBubbled, false);
|
946
1085
|
};
|
947
|
-
this.
|
948
|
-
if (
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
this.
|
1086
|
+
this.clickBubbled = event => {
|
1087
|
+
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
|
1088
|
+
const target = event.composedPath && event.composedPath()[0] || event.target;
|
1089
|
+
const link = this.findLinkFromClickTarget(target);
|
1090
|
+
if (link && doesNotTargetIFrame(link)) {
|
1091
|
+
const location = this.getLocationForLink(link);
|
1092
|
+
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
1093
|
+
event.preventDefault();
|
1094
|
+
this.delegate.followedLinkToLocation(link, location);
|
1095
|
+
}
|
953
1096
|
}
|
954
1097
|
}
|
955
|
-
delete this.clickEvent;
|
956
|
-
};
|
957
|
-
this.willVisit = () => {
|
958
|
-
delete this.clickEvent;
|
959
1098
|
};
|
960
1099
|
this.delegate = delegate;
|
961
|
-
this.
|
1100
|
+
this.eventTarget = eventTarget;
|
1101
|
+
}
|
1102
|
+
start() {
|
1103
|
+
if (!this.started) {
|
1104
|
+
this.eventTarget.addEventListener("click", this.clickCaptured, true);
|
1105
|
+
this.started = true;
|
1106
|
+
}
|
1107
|
+
}
|
1108
|
+
stop() {
|
1109
|
+
if (this.started) {
|
1110
|
+
this.eventTarget.removeEventListener("click", this.clickCaptured, true);
|
1111
|
+
this.started = false;
|
1112
|
+
}
|
1113
|
+
}
|
1114
|
+
clickEventIsSignificant(event) {
|
1115
|
+
return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
|
1116
|
+
}
|
1117
|
+
findLinkFromClickTarget(target) {
|
1118
|
+
if (target instanceof Element) {
|
1119
|
+
return target.closest("a[href]:not([target^=_]):not([download])");
|
1120
|
+
}
|
1121
|
+
}
|
1122
|
+
getLocationForLink(link) {
|
1123
|
+
return expandURL(link.getAttribute("href") || "");
|
1124
|
+
}
|
1125
|
+
}
|
1126
|
+
|
1127
|
+
function doesNotTargetIFrame(anchor) {
|
1128
|
+
for (const element of document.getElementsByName(anchor.target)) {
|
1129
|
+
if (element instanceof HTMLIFrameElement) return false;
|
1130
|
+
}
|
1131
|
+
return true;
|
1132
|
+
}
|
1133
|
+
|
1134
|
+
class FormLinkClickObserver {
|
1135
|
+
constructor(delegate, element) {
|
1136
|
+
this.delegate = delegate;
|
1137
|
+
this.linkClickObserver = new LinkClickObserver(this, element);
|
962
1138
|
}
|
963
1139
|
start() {
|
964
|
-
this.
|
965
|
-
document.addEventListener("turbo:click", this.linkClicked);
|
966
|
-
document.addEventListener("turbo:before-visit", this.willVisit);
|
1140
|
+
this.linkClickObserver.start();
|
967
1141
|
}
|
968
1142
|
stop() {
|
969
|
-
this.
|
970
|
-
document.removeEventListener("turbo:click", this.linkClicked);
|
971
|
-
document.removeEventListener("turbo:before-visit", this.willVisit);
|
1143
|
+
this.linkClickObserver.stop();
|
972
1144
|
}
|
973
|
-
|
974
|
-
|
975
|
-
|
1145
|
+
willFollowLinkToLocation(link, location, originalEvent) {
|
1146
|
+
return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
|
1147
|
+
}
|
1148
|
+
followedLinkToLocation(link, location) {
|
1149
|
+
const action = location.href;
|
1150
|
+
const form = document.createElement("form");
|
1151
|
+
form.setAttribute("data-turbo", "true");
|
1152
|
+
form.setAttribute("action", action);
|
1153
|
+
form.setAttribute("hidden", "");
|
1154
|
+
const method = link.getAttribute("data-turbo-method");
|
1155
|
+
if (method) form.setAttribute("method", method);
|
1156
|
+
const turboFrame = link.getAttribute("data-turbo-frame");
|
1157
|
+
if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
|
1158
|
+
const turboAction = link.getAttribute("data-turbo-action");
|
1159
|
+
if (turboAction) form.setAttribute("data-turbo-action", turboAction);
|
1160
|
+
const turboConfirm = link.getAttribute("data-turbo-confirm");
|
1161
|
+
if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
|
1162
|
+
const turboStream = link.hasAttribute("data-turbo-stream");
|
1163
|
+
if (turboStream) form.setAttribute("data-turbo-stream", "");
|
1164
|
+
this.delegate.submittedFormLinkToLocation(link, location, form);
|
1165
|
+
document.body.appendChild(form);
|
1166
|
+
form.addEventListener("turbo:submit-end", (() => form.remove()), {
|
1167
|
+
once: true
|
1168
|
+
});
|
1169
|
+
requestAnimationFrame((() => form.requestSubmit()));
|
976
1170
|
}
|
977
1171
|
}
|
978
1172
|
|
979
1173
|
class Bardo {
|
980
|
-
constructor(permanentElementMap) {
|
1174
|
+
constructor(delegate, permanentElementMap) {
|
1175
|
+
this.delegate = delegate;
|
981
1176
|
this.permanentElementMap = permanentElementMap;
|
982
1177
|
}
|
983
|
-
static preservingPermanentElements(permanentElementMap, callback) {
|
984
|
-
const bardo = new this(permanentElementMap);
|
1178
|
+
static preservingPermanentElements(delegate, permanentElementMap, callback) {
|
1179
|
+
const bardo = new this(delegate, permanentElementMap);
|
985
1180
|
bardo.enter();
|
986
1181
|
callback();
|
987
1182
|
bardo.leave();
|
988
1183
|
}
|
989
1184
|
enter() {
|
990
1185
|
for (const id in this.permanentElementMap) {
|
991
|
-
const [, newPermanentElement] = this.permanentElementMap[id];
|
1186
|
+
const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
|
1187
|
+
this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
|
992
1188
|
this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
|
993
1189
|
}
|
994
1190
|
}
|
@@ -997,6 +1193,7 @@ class Bardo {
|
|
997
1193
|
const [currentPermanentElement] = this.permanentElementMap[id];
|
998
1194
|
this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
|
999
1195
|
this.replacePlaceholderWithPermanentElement(currentPermanentElement);
|
1196
|
+
this.delegate.leavingBardo(currentPermanentElement);
|
1000
1197
|
}
|
1001
1198
|
}
|
1002
1199
|
replaceNewPermanentElementWithPlaceholder(permanentElement) {
|
@@ -1027,11 +1224,13 @@ function createPlaceholderForPermanentElement(permanentElement) {
|
|
1027
1224
|
}
|
1028
1225
|
|
1029
1226
|
class Renderer {
|
1030
|
-
constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
|
1227
|
+
constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
1228
|
+
this.activeElement = null;
|
1031
1229
|
this.currentSnapshot = currentSnapshot;
|
1032
1230
|
this.newSnapshot = newSnapshot;
|
1033
1231
|
this.isPreview = isPreview;
|
1034
1232
|
this.willRender = willRender;
|
1233
|
+
this.renderElement = renderElement;
|
1035
1234
|
this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
|
1036
1235
|
resolve: resolve,
|
1037
1236
|
reject: reject
|
@@ -1040,6 +1239,9 @@ class Renderer {
|
|
1040
1239
|
get shouldRender() {
|
1041
1240
|
return true;
|
1042
1241
|
}
|
1242
|
+
get reloadReason() {
|
1243
|
+
return;
|
1244
|
+
}
|
1043
1245
|
prepareToRender() {
|
1044
1246
|
return;
|
1045
1247
|
}
|
@@ -1049,22 +1251,8 @@ class Renderer {
|
|
1049
1251
|
delete this.resolvingFunctions;
|
1050
1252
|
}
|
1051
1253
|
}
|
1052
|
-
createScriptElement(element) {
|
1053
|
-
if (element.getAttribute("data-turbo-eval") == "false") {
|
1054
|
-
return element;
|
1055
|
-
} else {
|
1056
|
-
const createdScriptElement = document.createElement("script");
|
1057
|
-
if (this.cspNonce) {
|
1058
|
-
createdScriptElement.nonce = this.cspNonce;
|
1059
|
-
}
|
1060
|
-
createdScriptElement.textContent = element.textContent;
|
1061
|
-
createdScriptElement.async = false;
|
1062
|
-
copyElementAttributes(createdScriptElement, element);
|
1063
|
-
return createdScriptElement;
|
1064
|
-
}
|
1065
|
-
}
|
1066
1254
|
preservingPermanentElements(callback) {
|
1067
|
-
Bardo.preservingPermanentElements(this.permanentElementMap, callback);
|
1255
|
+
Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
|
1068
1256
|
}
|
1069
1257
|
focusFirstAutofocusableElement() {
|
1070
1258
|
const element = this.connectedSnapshot.firstAutofocusableElement;
|
@@ -1072,6 +1260,18 @@ class Renderer {
|
|
1072
1260
|
element.focus();
|
1073
1261
|
}
|
1074
1262
|
}
|
1263
|
+
enteringBardo(currentPermanentElement) {
|
1264
|
+
if (this.activeElement) return;
|
1265
|
+
if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
|
1266
|
+
this.activeElement = this.currentSnapshot.activeElement;
|
1267
|
+
}
|
1268
|
+
}
|
1269
|
+
leavingBardo(currentPermanentElement) {
|
1270
|
+
if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
|
1271
|
+
this.activeElement.focus();
|
1272
|
+
this.activeElement = null;
|
1273
|
+
}
|
1274
|
+
}
|
1075
1275
|
get connectedSnapshot() {
|
1076
1276
|
return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
|
1077
1277
|
}
|
@@ -1084,16 +1284,6 @@ class Renderer {
|
|
1084
1284
|
get permanentElementMap() {
|
1085
1285
|
return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
|
1086
1286
|
}
|
1087
|
-
get cspNonce() {
|
1088
|
-
var _a;
|
1089
|
-
return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
|
1090
|
-
}
|
1091
|
-
}
|
1092
|
-
|
1093
|
-
function copyElementAttributes(destinationElement, sourceElement) {
|
1094
|
-
for (const {name: name, value: value} of [ ...sourceElement.attributes ]) {
|
1095
|
-
destinationElement.setAttribute(name, value);
|
1096
|
-
}
|
1097
1287
|
}
|
1098
1288
|
|
1099
1289
|
function elementIsFocusable(element) {
|
@@ -1101,6 +1291,22 @@ function elementIsFocusable(element) {
|
|
1101
1291
|
}
|
1102
1292
|
|
1103
1293
|
class FrameRenderer extends Renderer {
|
1294
|
+
constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
1295
|
+
super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
|
1296
|
+
this.delegate = delegate;
|
1297
|
+
}
|
1298
|
+
static renderElement(currentElement, newElement) {
|
1299
|
+
var _a;
|
1300
|
+
const destinationRange = document.createRange();
|
1301
|
+
destinationRange.selectNodeContents(currentElement);
|
1302
|
+
destinationRange.deleteContents();
|
1303
|
+
const frameElement = newElement;
|
1304
|
+
const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
|
1305
|
+
if (sourceRange) {
|
1306
|
+
sourceRange.selectNodeContents(frameElement);
|
1307
|
+
currentElement.appendChild(sourceRange.extractContents());
|
1308
|
+
}
|
1309
|
+
}
|
1104
1310
|
get shouldRender() {
|
1105
1311
|
return true;
|
1106
1312
|
}
|
@@ -1116,24 +1322,18 @@ class FrameRenderer extends Renderer {
|
|
1116
1322
|
this.activateScriptElements();
|
1117
1323
|
}
|
1118
1324
|
loadFrameElement() {
|
1119
|
-
|
1120
|
-
|
1121
|
-
destinationRange.selectNodeContents(this.currentElement);
|
1122
|
-
destinationRange.deleteContents();
|
1123
|
-
const frameElement = this.newElement;
|
1124
|
-
const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
|
1125
|
-
if (sourceRange) {
|
1126
|
-
sourceRange.selectNodeContents(frameElement);
|
1127
|
-
this.currentElement.appendChild(sourceRange.extractContents());
|
1128
|
-
}
|
1325
|
+
this.delegate.willRenderFrame(this.currentElement, this.newElement);
|
1326
|
+
this.renderElement(this.currentElement, this.newElement);
|
1129
1327
|
}
|
1130
1328
|
scrollFrameIntoView() {
|
1131
1329
|
if (this.currentElement.autoscroll || this.newElement.autoscroll) {
|
1132
1330
|
const element = this.currentElement.firstElementChild;
|
1133
1331
|
const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
|
1332
|
+
const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
|
1134
1333
|
if (element) {
|
1135
1334
|
element.scrollIntoView({
|
1136
|
-
block: block
|
1335
|
+
block: block,
|
1336
|
+
behavior: behavior
|
1137
1337
|
});
|
1138
1338
|
return true;
|
1139
1339
|
}
|
@@ -1142,7 +1342,7 @@ class FrameRenderer extends Renderer {
|
|
1142
1342
|
}
|
1143
1343
|
activateScriptElements() {
|
1144
1344
|
for (const inertScriptElement of this.newScriptElements) {
|
1145
|
-
const activatedScriptElement =
|
1345
|
+
const activatedScriptElement = activateScriptElement(inertScriptElement);
|
1146
1346
|
inertScriptElement.replaceWith(activatedScriptElement);
|
1147
1347
|
}
|
1148
1348
|
}
|
@@ -1159,6 +1359,14 @@ function readScrollLogicalPosition(value, defaultValue) {
|
|
1159
1359
|
}
|
1160
1360
|
}
|
1161
1361
|
|
1362
|
+
function readScrollBehavior(value, defaultValue) {
|
1363
|
+
if (value == "auto" || value == "smooth") {
|
1364
|
+
return value;
|
1365
|
+
} else {
|
1366
|
+
return defaultValue;
|
1367
|
+
}
|
1368
|
+
}
|
1369
|
+
|
1162
1370
|
class ProgressBar {
|
1163
1371
|
constructor() {
|
1164
1372
|
this.hiding = false;
|
@@ -1181,7 +1389,7 @@ class ProgressBar {
|
|
1181
1389
|
left: 0;
|
1182
1390
|
height: 3px;
|
1183
1391
|
background: #0076ff;
|
1184
|
-
z-index:
|
1392
|
+
z-index: 2147483647;
|
1185
1393
|
transition:
|
1186
1394
|
width ${ProgressBar.animationDuration}ms ease-out,
|
1187
1395
|
opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
|
@@ -1247,6 +1455,9 @@ class ProgressBar {
|
|
1247
1455
|
const element = document.createElement("style");
|
1248
1456
|
element.type = "text/css";
|
1249
1457
|
element.textContent = ProgressBar.defaultCSS;
|
1458
|
+
if (this.cspNonce) {
|
1459
|
+
element.nonce = this.cspNonce;
|
1460
|
+
}
|
1250
1461
|
return element;
|
1251
1462
|
}
|
1252
1463
|
createProgressElement() {
|
@@ -1254,6 +1465,9 @@ class ProgressBar {
|
|
1254
1465
|
element.className = "turbo-progress-bar";
|
1255
1466
|
return element;
|
1256
1467
|
}
|
1468
|
+
get cspNonce() {
|
1469
|
+
return getMetaContent("csp-nonce");
|
1470
|
+
}
|
1257
1471
|
}
|
1258
1472
|
|
1259
1473
|
ProgressBar.animationDuration = 300;
|
@@ -1324,22 +1538,22 @@ function elementIsTracked(element) {
|
|
1324
1538
|
}
|
1325
1539
|
|
1326
1540
|
function elementIsScript(element) {
|
1327
|
-
const tagName = element.
|
1541
|
+
const tagName = element.localName;
|
1328
1542
|
return tagName == "script";
|
1329
1543
|
}
|
1330
1544
|
|
1331
1545
|
function elementIsNoscript(element) {
|
1332
|
-
const tagName = element.
|
1546
|
+
const tagName = element.localName;
|
1333
1547
|
return tagName == "noscript";
|
1334
1548
|
}
|
1335
1549
|
|
1336
1550
|
function elementIsStylesheet(element) {
|
1337
|
-
const tagName = element.
|
1551
|
+
const tagName = element.localName;
|
1338
1552
|
return tagName == "style" || tagName == "link" && element.getAttribute("rel") == "stylesheet";
|
1339
1553
|
}
|
1340
1554
|
|
1341
1555
|
function elementIsMetaElementWithName(element, name) {
|
1342
|
-
const tagName = element.
|
1556
|
+
const tagName = element.localName;
|
1343
1557
|
return tagName == "meta" && element.getAttribute("name") == name;
|
1344
1558
|
}
|
1345
1559
|
|
@@ -1365,7 +1579,18 @@ class PageSnapshot extends Snapshot {
|
|
1365
1579
|
return new this(body, new HeadSnapshot(head));
|
1366
1580
|
}
|
1367
1581
|
clone() {
|
1368
|
-
|
1582
|
+
const clonedElement = this.element.cloneNode(true);
|
1583
|
+
const selectElements = this.element.querySelectorAll("select");
|
1584
|
+
const clonedSelectElements = clonedElement.querySelectorAll("select");
|
1585
|
+
for (const [index, source] of selectElements.entries()) {
|
1586
|
+
const clone = clonedSelectElements[index];
|
1587
|
+
for (const option of clone.selectedOptions) option.selected = false;
|
1588
|
+
for (const option of source.selectedOptions) clone.options[option.index].selected = true;
|
1589
|
+
}
|
1590
|
+
for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
|
1591
|
+
clonedPasswordInput.value = "";
|
1592
|
+
}
|
1593
|
+
return new PageSnapshot(clonedElement, this.headSnapshot);
|
1369
1594
|
}
|
1370
1595
|
get headElement() {
|
1371
1596
|
return this.headSnapshot.element;
|
@@ -1415,7 +1640,10 @@ const defaultOptions = {
|
|
1415
1640
|
action: "advance",
|
1416
1641
|
historyChanged: false,
|
1417
1642
|
visitCachedSnapshot: () => {},
|
1418
|
-
willRender: true
|
1643
|
+
willRender: true,
|
1644
|
+
updateHistory: true,
|
1645
|
+
shouldCacheSnapshot: true,
|
1646
|
+
acceptsStreamResponse: false
|
1419
1647
|
};
|
1420
1648
|
|
1421
1649
|
var SystemStatusCode;
|
@@ -1433,12 +1661,14 @@ class Visit {
|
|
1433
1661
|
this.followedRedirect = false;
|
1434
1662
|
this.historyChanged = false;
|
1435
1663
|
this.scrolled = false;
|
1664
|
+
this.shouldCacheSnapshot = true;
|
1665
|
+
this.acceptsStreamResponse = false;
|
1436
1666
|
this.snapshotCached = false;
|
1437
1667
|
this.state = VisitState.initialized;
|
1438
1668
|
this.delegate = delegate;
|
1439
1669
|
this.location = location;
|
1440
1670
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
1441
|
-
const {action: action, historyChanged: historyChanged, referrer: referrer, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender} = Object.assign(Object.assign({}, defaultOptions), options);
|
1671
|
+
const {action: action, historyChanged: historyChanged, referrer: referrer, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = Object.assign(Object.assign({}, defaultOptions), options);
|
1442
1672
|
this.action = action;
|
1443
1673
|
this.historyChanged = historyChanged;
|
1444
1674
|
this.referrer = referrer;
|
@@ -1447,7 +1677,10 @@ class Visit {
|
|
1447
1677
|
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
1448
1678
|
this.visitCachedSnapshot = visitCachedSnapshot;
|
1449
1679
|
this.willRender = willRender;
|
1680
|
+
this.updateHistory = updateHistory;
|
1450
1681
|
this.scrolled = !willRender;
|
1682
|
+
this.shouldCacheSnapshot = shouldCacheSnapshot;
|
1683
|
+
this.acceptsStreamResponse = acceptsStreamResponse;
|
1451
1684
|
}
|
1452
1685
|
get adapter() {
|
1453
1686
|
return this.delegate.adapter;
|
@@ -1485,9 +1718,11 @@ class Visit {
|
|
1485
1718
|
if (this.state == VisitState.started) {
|
1486
1719
|
this.recordTimingMetric(TimingMetric.visitEnd);
|
1487
1720
|
this.state = VisitState.completed;
|
1488
|
-
this.adapter.visitCompleted(this);
|
1489
|
-
this.delegate.visitCompleted(this);
|
1490
1721
|
this.followRedirect();
|
1722
|
+
if (!this.followedRedirect) {
|
1723
|
+
this.adapter.visitCompleted(this);
|
1724
|
+
this.delegate.visitCompleted(this);
|
1725
|
+
}
|
1491
1726
|
}
|
1492
1727
|
}
|
1493
1728
|
fail() {
|
@@ -1498,9 +1733,9 @@ class Visit {
|
|
1498
1733
|
}
|
1499
1734
|
changeHistory() {
|
1500
1735
|
var _a;
|
1501
|
-
if (!this.historyChanged) {
|
1736
|
+
if (!this.historyChanged && this.updateHistory) {
|
1502
1737
|
const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
|
1503
|
-
const method =
|
1738
|
+
const method = getHistoryMethodForAction(actionForHistory);
|
1504
1739
|
this.history.update(method, this.location, this.restorationIdentifier);
|
1505
1740
|
this.historyChanged = true;
|
1506
1741
|
}
|
@@ -1543,14 +1778,15 @@ class Visit {
|
|
1543
1778
|
if (this.response) {
|
1544
1779
|
const {statusCode: statusCode, responseHTML: responseHTML} = this.response;
|
1545
1780
|
this.render((async () => {
|
1546
|
-
this.cacheSnapshot();
|
1781
|
+
if (this.shouldCacheSnapshot) this.cacheSnapshot();
|
1547
1782
|
if (this.view.renderPromise) await this.view.renderPromise;
|
1548
1783
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
1549
|
-
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
|
1784
|
+
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
|
1785
|
+
this.performScroll();
|
1550
1786
|
this.adapter.visitRendered(this);
|
1551
1787
|
this.complete();
|
1552
1788
|
} else {
|
1553
|
-
await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
|
1789
|
+
await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
|
1554
1790
|
this.adapter.visitRendered(this);
|
1555
1791
|
this.fail();
|
1556
1792
|
}
|
@@ -1583,7 +1819,8 @@ class Visit {
|
|
1583
1819
|
this.adapter.visitRendered(this);
|
1584
1820
|
} else {
|
1585
1821
|
if (this.view.renderPromise) await this.view.renderPromise;
|
1586
|
-
await this.view.renderPage(snapshot, isPreview, this.willRender);
|
1822
|
+
await this.view.renderPage(snapshot, isPreview, this.willRender, this);
|
1823
|
+
this.performScroll();
|
1587
1824
|
this.adapter.visitRendered(this);
|
1588
1825
|
if (!isPreview) {
|
1589
1826
|
this.complete();
|
@@ -1606,14 +1843,20 @@ class Visit {
|
|
1606
1843
|
if (this.isSamePage) {
|
1607
1844
|
this.render((async () => {
|
1608
1845
|
this.cacheSnapshot();
|
1846
|
+
this.performScroll();
|
1609
1847
|
this.adapter.visitRendered(this);
|
1610
1848
|
}));
|
1611
1849
|
}
|
1612
1850
|
}
|
1851
|
+
prepareHeadersForRequest(headers, request) {
|
1852
|
+
if (this.acceptsStreamResponse) {
|
1853
|
+
request.acceptResponseType(StreamMessage.contentType);
|
1854
|
+
}
|
1855
|
+
}
|
1613
1856
|
requestStarted() {
|
1614
1857
|
this.startRequest();
|
1615
1858
|
}
|
1616
|
-
requestPreventedHandlingResponse(
|
1859
|
+
requestPreventedHandlingResponse(_request, _response) {}
|
1617
1860
|
async requestSucceededWithResponse(request, response) {
|
1618
1861
|
const responseHTML = await response.responseHTML;
|
1619
1862
|
const {redirected: redirected, statusCode: statusCode} = response;
|
@@ -1647,7 +1890,7 @@ class Visit {
|
|
1647
1890
|
});
|
1648
1891
|
}
|
1649
1892
|
}
|
1650
|
-
requestErrored(
|
1893
|
+
requestErrored(_request, _error) {
|
1651
1894
|
this.recordResponse({
|
1652
1895
|
statusCode: SystemStatusCode.networkFailure,
|
1653
1896
|
redirected: false
|
@@ -1657,7 +1900,7 @@ class Visit {
|
|
1657
1900
|
this.finishRequest();
|
1658
1901
|
}
|
1659
1902
|
performScroll() {
|
1660
|
-
if (!this.scrolled) {
|
1903
|
+
if (!this.scrolled && !this.view.forceReloaded) {
|
1661
1904
|
if (this.action == "restore") {
|
1662
1905
|
this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
|
1663
1906
|
} else {
|
@@ -1724,7 +1967,6 @@ class Visit {
|
|
1724
1967
|
}));
|
1725
1968
|
await callback();
|
1726
1969
|
delete this.frame;
|
1727
|
-
this.performScroll();
|
1728
1970
|
}
|
1729
1971
|
cancelRender() {
|
1730
1972
|
if (this.frame) {
|
@@ -1747,12 +1989,12 @@ class BrowserAdapter {
|
|
1747
1989
|
this.session = session;
|
1748
1990
|
}
|
1749
1991
|
visitProposedToLocation(location, options) {
|
1750
|
-
this.navigator.startVisit(location, uuid(), options);
|
1992
|
+
this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
|
1751
1993
|
}
|
1752
1994
|
visitStarted(visit) {
|
1995
|
+
this.location = visit.location;
|
1753
1996
|
visit.loadCachedSnapshot();
|
1754
1997
|
visit.issueRequest();
|
1755
|
-
visit.changeHistory();
|
1756
1998
|
visit.goToSamePageAnchor();
|
1757
1999
|
}
|
1758
2000
|
visitRequestStarted(visit) {
|
@@ -1771,27 +2013,32 @@ class BrowserAdapter {
|
|
1771
2013
|
case SystemStatusCode.networkFailure:
|
1772
2014
|
case SystemStatusCode.timeoutFailure:
|
1773
2015
|
case SystemStatusCode.contentTypeMismatch:
|
1774
|
-
return this.reload(
|
2016
|
+
return this.reload({
|
2017
|
+
reason: "request_failed",
|
2018
|
+
context: {
|
2019
|
+
statusCode: statusCode
|
2020
|
+
}
|
2021
|
+
});
|
1775
2022
|
|
1776
2023
|
default:
|
1777
2024
|
return visit.loadResponse();
|
1778
2025
|
}
|
1779
2026
|
}
|
1780
|
-
visitRequestFinished(
|
2027
|
+
visitRequestFinished(_visit) {
|
1781
2028
|
this.progressBar.setValue(1);
|
1782
2029
|
this.hideVisitProgressBar();
|
1783
2030
|
}
|
1784
|
-
visitCompleted(
|
1785
|
-
pageInvalidated() {
|
1786
|
-
this.reload();
|
2031
|
+
visitCompleted(_visit) {}
|
2032
|
+
pageInvalidated(reason) {
|
2033
|
+
this.reload(reason);
|
1787
2034
|
}
|
1788
|
-
visitFailed(
|
1789
|
-
visitRendered(
|
1790
|
-
formSubmissionStarted(
|
2035
|
+
visitFailed(_visit) {}
|
2036
|
+
visitRendered(_visit) {}
|
2037
|
+
formSubmissionStarted(_formSubmission) {
|
1791
2038
|
this.progressBar.setValue(0);
|
1792
2039
|
this.showFormProgressBarAfterDelay();
|
1793
2040
|
}
|
1794
|
-
formSubmissionFinished(
|
2041
|
+
formSubmissionFinished(_formSubmission) {
|
1795
2042
|
this.progressBar.setValue(1);
|
1796
2043
|
this.hideFormProgressBar();
|
1797
2044
|
}
|
@@ -1817,8 +2064,12 @@ class BrowserAdapter {
|
|
1817
2064
|
delete this.formProgressBarTimeout;
|
1818
2065
|
}
|
1819
2066
|
}
|
1820
|
-
reload() {
|
1821
|
-
|
2067
|
+
reload(reason) {
|
2068
|
+
dispatch("turbo:reload", {
|
2069
|
+
detail: reason
|
2070
|
+
});
|
2071
|
+
if (!this.location) return;
|
2072
|
+
window.location.href = this.location.toString();
|
1822
2073
|
}
|
1823
2074
|
get navigator() {
|
1824
2075
|
return this.session.navigator;
|
@@ -1828,96 +2079,71 @@ class BrowserAdapter {
|
|
1828
2079
|
class CacheObserver {
|
1829
2080
|
constructor() {
|
1830
2081
|
this.started = false;
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
addEventListener("turbo:before-cache", this.removeStaleElements, false);
|
1836
|
-
}
|
1837
|
-
}
|
1838
|
-
stop() {
|
1839
|
-
if (this.started) {
|
1840
|
-
this.started = false;
|
1841
|
-
removeEventListener("turbo:before-cache", this.removeStaleElements, false);
|
1842
|
-
}
|
1843
|
-
}
|
1844
|
-
removeStaleElements() {
|
1845
|
-
const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
|
1846
|
-
for (const element of staleElements) {
|
1847
|
-
element.remove();
|
1848
|
-
}
|
1849
|
-
}
|
1850
|
-
}
|
1851
|
-
|
1852
|
-
class FormSubmitObserver {
|
1853
|
-
constructor(delegate) {
|
1854
|
-
this.started = false;
|
1855
|
-
this.submitCaptured = () => {
|
1856
|
-
removeEventListener("submit", this.submitBubbled, false);
|
1857
|
-
addEventListener("submit", this.submitBubbled, false);
|
1858
|
-
};
|
1859
|
-
this.submitBubbled = event => {
|
1860
|
-
if (!event.defaultPrevented) {
|
1861
|
-
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
1862
|
-
const submitter = event.submitter || undefined;
|
1863
|
-
if (form) {
|
1864
|
-
const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
|
1865
|
-
if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
|
1866
|
-
event.preventDefault();
|
1867
|
-
this.delegate.formSubmitted(form, submitter);
|
1868
|
-
}
|
1869
|
-
}
|
2082
|
+
this.removeStaleElements = _event => {
|
2083
|
+
const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
|
2084
|
+
for (const element of staleElements) {
|
2085
|
+
element.remove();
|
1870
2086
|
}
|
1871
2087
|
};
|
1872
|
-
this.delegate = delegate;
|
1873
2088
|
}
|
1874
2089
|
start() {
|
1875
2090
|
if (!this.started) {
|
1876
|
-
addEventListener("submit", this.submitCaptured, true);
|
1877
2091
|
this.started = true;
|
2092
|
+
addEventListener("turbo:before-cache", this.removeStaleElements, false);
|
1878
2093
|
}
|
1879
2094
|
}
|
1880
2095
|
stop() {
|
1881
2096
|
if (this.started) {
|
1882
|
-
removeEventListener("submit", this.submitCaptured, true);
|
1883
2097
|
this.started = false;
|
2098
|
+
removeEventListener("turbo:before-cache", this.removeStaleElements, false);
|
1884
2099
|
}
|
1885
2100
|
}
|
1886
2101
|
}
|
1887
2102
|
|
1888
2103
|
class FrameRedirector {
|
1889
|
-
constructor(element) {
|
2104
|
+
constructor(session, element) {
|
2105
|
+
this.session = session;
|
1890
2106
|
this.element = element;
|
1891
|
-
this.
|
1892
|
-
this.
|
2107
|
+
this.linkClickObserver = new LinkClickObserver(this, element);
|
2108
|
+
this.formSubmitObserver = new FormSubmitObserver(this, element);
|
1893
2109
|
}
|
1894
2110
|
start() {
|
1895
|
-
this.
|
1896
|
-
this.
|
2111
|
+
this.linkClickObserver.start();
|
2112
|
+
this.formSubmitObserver.start();
|
1897
2113
|
}
|
1898
2114
|
stop() {
|
1899
|
-
this.
|
1900
|
-
this.
|
2115
|
+
this.linkClickObserver.stop();
|
2116
|
+
this.formSubmitObserver.stop();
|
1901
2117
|
}
|
1902
|
-
|
1903
|
-
return this.shouldRedirect(element);
|
2118
|
+
willFollowLinkToLocation(element, location, event) {
|
2119
|
+
return this.shouldRedirect(element) && this.frameAllowsVisitingLocation(element, location, event);
|
1904
2120
|
}
|
1905
|
-
|
2121
|
+
followedLinkToLocation(element, url) {
|
1906
2122
|
const frame = this.findFrameElement(element);
|
1907
2123
|
if (frame) {
|
1908
|
-
frame.delegate.
|
2124
|
+
frame.delegate.followedLinkToLocation(element, url);
|
1909
2125
|
}
|
1910
2126
|
}
|
1911
|
-
|
1912
|
-
return this.shouldSubmit(element, submitter);
|
2127
|
+
willSubmitForm(element, submitter) {
|
2128
|
+
return element.closest("turbo-frame") == null && this.shouldSubmit(element, submitter) && this.shouldRedirect(element, submitter);
|
1913
2129
|
}
|
1914
|
-
|
2130
|
+
formSubmitted(element, submitter) {
|
1915
2131
|
const frame = this.findFrameElement(element, submitter);
|
1916
2132
|
if (frame) {
|
1917
|
-
frame.
|
1918
|
-
frame.delegate.formSubmissionIntercepted(element, submitter);
|
2133
|
+
frame.delegate.formSubmitted(element, submitter);
|
1919
2134
|
}
|
1920
2135
|
}
|
2136
|
+
frameAllowsVisitingLocation(target, {href: url}, originalEvent) {
|
2137
|
+
const event = dispatch("turbo:click", {
|
2138
|
+
target: target,
|
2139
|
+
detail: {
|
2140
|
+
url: url,
|
2141
|
+
originalEvent: originalEvent
|
2142
|
+
},
|
2143
|
+
cancelable: true
|
2144
|
+
});
|
2145
|
+
return !event.defaultPrevented;
|
2146
|
+
}
|
1921
2147
|
shouldSubmit(form, submitter) {
|
1922
2148
|
var _a;
|
1923
2149
|
const action = getAction(form, submitter);
|
@@ -1926,8 +2152,13 @@ class FrameRedirector {
|
|
1926
2152
|
return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
|
1927
2153
|
}
|
1928
2154
|
shouldRedirect(element, submitter) {
|
1929
|
-
const
|
1930
|
-
|
2155
|
+
const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
|
2156
|
+
if (isNavigatable) {
|
2157
|
+
const frame = this.findFrameElement(element, submitter);
|
2158
|
+
return frame ? frame != element.closest("turbo-frame") : false;
|
2159
|
+
} else {
|
2160
|
+
return false;
|
2161
|
+
}
|
1931
2162
|
}
|
1932
2163
|
findFrameElement(element, submitter) {
|
1933
2164
|
const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
|
@@ -1957,7 +2188,7 @@ class History {
|
|
1957
2188
|
}
|
1958
2189
|
}
|
1959
2190
|
};
|
1960
|
-
this.onPageLoad = async
|
2191
|
+
this.onPageLoad = async _event => {
|
1961
2192
|
await nextMicrotask();
|
1962
2193
|
this.pageLoaded = true;
|
1963
2194
|
};
|
@@ -2023,58 +2254,7 @@ class History {
|
|
2023
2254
|
}
|
2024
2255
|
}
|
2025
2256
|
|
2026
|
-
class
|
2027
|
-
constructor(delegate) {
|
2028
|
-
this.started = false;
|
2029
|
-
this.clickCaptured = () => {
|
2030
|
-
removeEventListener("click", this.clickBubbled, false);
|
2031
|
-
addEventListener("click", this.clickBubbled, false);
|
2032
|
-
};
|
2033
|
-
this.clickBubbled = event => {
|
2034
|
-
if (this.clickEventIsSignificant(event)) {
|
2035
|
-
const target = event.composedPath && event.composedPath()[0] || event.target;
|
2036
|
-
const link = this.findLinkFromClickTarget(target);
|
2037
|
-
if (link) {
|
2038
|
-
const location = this.getLocationForLink(link);
|
2039
|
-
if (this.delegate.willFollowLinkToLocation(link, location)) {
|
2040
|
-
event.preventDefault();
|
2041
|
-
this.delegate.followedLinkToLocation(link, location);
|
2042
|
-
}
|
2043
|
-
}
|
2044
|
-
}
|
2045
|
-
};
|
2046
|
-
this.delegate = delegate;
|
2047
|
-
}
|
2048
|
-
start() {
|
2049
|
-
if (!this.started) {
|
2050
|
-
addEventListener("click", this.clickCaptured, true);
|
2051
|
-
this.started = true;
|
2052
|
-
}
|
2053
|
-
}
|
2054
|
-
stop() {
|
2055
|
-
if (this.started) {
|
2056
|
-
removeEventListener("click", this.clickCaptured, true);
|
2057
|
-
this.started = false;
|
2058
|
-
}
|
2059
|
-
}
|
2060
|
-
clickEventIsSignificant(event) {
|
2061
|
-
return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
|
2062
|
-
}
|
2063
|
-
findLinkFromClickTarget(target) {
|
2064
|
-
if (target instanceof Element) {
|
2065
|
-
return target.closest("a[href]:not([target^=_]):not([download])");
|
2066
|
-
}
|
2067
|
-
}
|
2068
|
-
getLocationForLink(link) {
|
2069
|
-
return expandURL(link.getAttribute("href") || "");
|
2070
|
-
}
|
2071
|
-
}
|
2072
|
-
|
2073
|
-
function isAction(action) {
|
2074
|
-
return action == "advance" || action == "replace" || action == "restore";
|
2075
|
-
}
|
2076
|
-
|
2077
|
-
class Navigator {
|
2257
|
+
class Navigator {
|
2078
2258
|
constructor(delegate) {
|
2079
2259
|
this.delegate = delegate;
|
2080
2260
|
}
|
@@ -2088,6 +2268,7 @@ class Navigator {
|
|
2088
2268
|
}
|
2089
2269
|
}
|
2090
2270
|
startVisit(locatable, restorationIdentifier, options = {}) {
|
2271
|
+
this.lastVisit = this.currentVisit;
|
2091
2272
|
this.stop();
|
2092
2273
|
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
|
2093
2274
|
referrer: this.location
|
@@ -2127,13 +2308,15 @@ class Navigator {
|
|
2127
2308
|
if (formSubmission == this.formSubmission) {
|
2128
2309
|
const responseHTML = await fetchResponse.responseHTML;
|
2129
2310
|
if (responseHTML) {
|
2130
|
-
|
2311
|
+
const shouldCacheSnapshot = formSubmission.method == FetchMethod.get;
|
2312
|
+
if (!shouldCacheSnapshot) {
|
2131
2313
|
this.view.clearSnapshotCache();
|
2132
2314
|
}
|
2133
2315
|
const {statusCode: statusCode, redirected: redirected} = fetchResponse;
|
2134
2316
|
const action = this.getActionForFormSubmission(formSubmission);
|
2135
2317
|
const visitOptions = {
|
2136
2318
|
action: action,
|
2319
|
+
shouldCacheSnapshot: shouldCacheSnapshot,
|
2137
2320
|
response: {
|
2138
2321
|
statusCode: statusCode,
|
2139
2322
|
responseHTML: responseHTML,
|
@@ -2149,9 +2332,9 @@ class Navigator {
|
|
2149
2332
|
if (responseHTML) {
|
2150
2333
|
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
2151
2334
|
if (fetchResponse.serverError) {
|
2152
|
-
await this.view.renderError(snapshot);
|
2335
|
+
await this.view.renderError(snapshot, this.currentVisit);
|
2153
2336
|
} else {
|
2154
|
-
await this.view.renderPage(snapshot);
|
2337
|
+
await this.view.renderPage(snapshot, false, true, this.currentVisit);
|
2155
2338
|
}
|
2156
2339
|
this.view.scrollToTop();
|
2157
2340
|
this.view.clearSnapshotCache();
|
@@ -2172,10 +2355,12 @@ class Navigator {
|
|
2172
2355
|
this.delegate.visitCompleted(visit);
|
2173
2356
|
}
|
2174
2357
|
locationWithActionIsSamePage(location, action) {
|
2358
|
+
var _a;
|
2175
2359
|
const anchor = getAnchor(location);
|
2176
|
-
const
|
2360
|
+
const lastLocation = ((_a = this.lastVisit) === null || _a === void 0 ? void 0 : _a.location) || this.view.lastRenderedLocation;
|
2361
|
+
const currentAnchor = getAnchor(lastLocation);
|
2177
2362
|
const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
|
2178
|
-
return action !== "replace" && getRequestURL(location) === getRequestURL(
|
2363
|
+
return action !== "replace" && getRequestURL(location) === getRequestURL(lastLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
|
2179
2364
|
}
|
2180
2365
|
visitScrolledToSamePageLocation(oldURL, newURL) {
|
2181
2366
|
this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
|
@@ -2283,6 +2468,31 @@ class ScrollObserver {
|
|
2283
2468
|
}
|
2284
2469
|
}
|
2285
2470
|
|
2471
|
+
class StreamMessageRenderer {
|
2472
|
+
render({fragment: fragment}) {
|
2473
|
+
Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => document.documentElement.appendChild(fragment)));
|
2474
|
+
}
|
2475
|
+
enteringBardo(currentPermanentElement, newPermanentElement) {
|
2476
|
+
newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
|
2477
|
+
}
|
2478
|
+
leavingBardo() {}
|
2479
|
+
}
|
2480
|
+
|
2481
|
+
function getPermanentElementMapForFragment(fragment) {
|
2482
|
+
const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
|
2483
|
+
const permanentElementMap = {};
|
2484
|
+
for (const permanentElementInDocument of permanentElementsInDocument) {
|
2485
|
+
const {id: id} = permanentElementInDocument;
|
2486
|
+
for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
|
2487
|
+
const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
|
2488
|
+
if (elementInStream) {
|
2489
|
+
permanentElementMap[id] = [ permanentElementInDocument, elementInStream ];
|
2490
|
+
}
|
2491
|
+
}
|
2492
|
+
}
|
2493
|
+
return permanentElementMap;
|
2494
|
+
}
|
2495
|
+
|
2286
2496
|
class StreamObserver {
|
2287
2497
|
constructor(delegate) {
|
2288
2498
|
this.sources = new Set;
|
@@ -2335,7 +2545,7 @@ class StreamObserver {
|
|
2335
2545
|
}
|
2336
2546
|
}
|
2337
2547
|
receiveMessageHTML(html) {
|
2338
|
-
this.delegate.receivedMessageFromStream(
|
2548
|
+
this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
|
2339
2549
|
}
|
2340
2550
|
}
|
2341
2551
|
|
@@ -2354,20 +2564,24 @@ function fetchResponseIsStream(response) {
|
|
2354
2564
|
}
|
2355
2565
|
|
2356
2566
|
class ErrorRenderer extends Renderer {
|
2567
|
+
static renderElement(currentElement, newElement) {
|
2568
|
+
const {documentElement: documentElement, body: body} = document;
|
2569
|
+
documentElement.replaceChild(newElement, body);
|
2570
|
+
}
|
2357
2571
|
async render() {
|
2358
2572
|
this.replaceHeadAndBody();
|
2359
2573
|
this.activateScriptElements();
|
2360
2574
|
}
|
2361
2575
|
replaceHeadAndBody() {
|
2362
|
-
const {documentElement: documentElement, head: head
|
2576
|
+
const {documentElement: documentElement, head: head} = document;
|
2363
2577
|
documentElement.replaceChild(this.newHead, head);
|
2364
|
-
|
2578
|
+
this.renderElement(this.currentElement, this.newElement);
|
2365
2579
|
}
|
2366
2580
|
activateScriptElements() {
|
2367
2581
|
for (const replaceableElement of this.scriptElements) {
|
2368
2582
|
const parentNode = replaceableElement.parentNode;
|
2369
2583
|
if (parentNode) {
|
2370
|
-
const element =
|
2584
|
+
const element = activateScriptElement(replaceableElement);
|
2371
2585
|
parentNode.replaceChild(element, replaceableElement);
|
2372
2586
|
}
|
2373
2587
|
}
|
@@ -2376,16 +2590,35 @@ class ErrorRenderer extends Renderer {
|
|
2376
2590
|
return this.newSnapshot.headSnapshot.element;
|
2377
2591
|
}
|
2378
2592
|
get scriptElements() {
|
2379
|
-
return
|
2593
|
+
return document.documentElement.querySelectorAll("script");
|
2380
2594
|
}
|
2381
2595
|
}
|
2382
2596
|
|
2383
2597
|
class PageRenderer extends Renderer {
|
2598
|
+
static renderElement(currentElement, newElement) {
|
2599
|
+
if (document.body && newElement instanceof HTMLBodyElement) {
|
2600
|
+
document.body.replaceWith(newElement);
|
2601
|
+
} else {
|
2602
|
+
document.documentElement.appendChild(newElement);
|
2603
|
+
}
|
2604
|
+
}
|
2384
2605
|
get shouldRender() {
|
2385
2606
|
return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
|
2386
2607
|
}
|
2387
|
-
|
2388
|
-
this.
|
2608
|
+
get reloadReason() {
|
2609
|
+
if (!this.newSnapshot.isVisitable) {
|
2610
|
+
return {
|
2611
|
+
reason: "turbo_visit_control_is_reload"
|
2612
|
+
};
|
2613
|
+
}
|
2614
|
+
if (!this.trackedElementsAreIdentical) {
|
2615
|
+
return {
|
2616
|
+
reason: "tracked_element_mismatch"
|
2617
|
+
};
|
2618
|
+
}
|
2619
|
+
}
|
2620
|
+
async prepareToRender() {
|
2621
|
+
await this.mergeHead();
|
2389
2622
|
}
|
2390
2623
|
async render() {
|
2391
2624
|
if (this.willRender) {
|
@@ -2407,11 +2640,12 @@ class PageRenderer extends Renderer {
|
|
2407
2640
|
get newElement() {
|
2408
2641
|
return this.newSnapshot.element;
|
2409
2642
|
}
|
2410
|
-
mergeHead() {
|
2411
|
-
this.copyNewHeadStylesheetElements();
|
2643
|
+
async mergeHead() {
|
2644
|
+
const newStylesheetElements = this.copyNewHeadStylesheetElements();
|
2412
2645
|
this.copyNewHeadScriptElements();
|
2413
2646
|
this.removeCurrentHeadProvisionalElements();
|
2414
2647
|
this.copyNewHeadProvisionalElements();
|
2648
|
+
await newStylesheetElements;
|
2415
2649
|
}
|
2416
2650
|
replaceBody() {
|
2417
2651
|
this.preservingPermanentElements((() => {
|
@@ -2422,14 +2656,17 @@ class PageRenderer extends Renderer {
|
|
2422
2656
|
get trackedElementsAreIdentical() {
|
2423
2657
|
return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
|
2424
2658
|
}
|
2425
|
-
copyNewHeadStylesheetElements() {
|
2659
|
+
async copyNewHeadStylesheetElements() {
|
2660
|
+
const loadingElements = [];
|
2426
2661
|
for (const element of this.newHeadStylesheetElements) {
|
2662
|
+
loadingElements.push(waitForLoad(element));
|
2427
2663
|
document.head.appendChild(element);
|
2428
2664
|
}
|
2665
|
+
await Promise.all(loadingElements);
|
2429
2666
|
}
|
2430
2667
|
copyNewHeadScriptElements() {
|
2431
2668
|
for (const element of this.newHeadScriptElements) {
|
2432
|
-
document.head.appendChild(
|
2669
|
+
document.head.appendChild(activateScriptElement(element));
|
2433
2670
|
}
|
2434
2671
|
}
|
2435
2672
|
removeCurrentHeadProvisionalElements() {
|
@@ -2448,16 +2685,12 @@ class PageRenderer extends Renderer {
|
|
2448
2685
|
}
|
2449
2686
|
activateNewBodyScriptElements() {
|
2450
2687
|
for (const inertScriptElement of this.newBodyScriptElements) {
|
2451
|
-
const activatedScriptElement =
|
2688
|
+
const activatedScriptElement = activateScriptElement(inertScriptElement);
|
2452
2689
|
inertScriptElement.replaceWith(activatedScriptElement);
|
2453
2690
|
}
|
2454
2691
|
}
|
2455
2692
|
assignNewBody() {
|
2456
|
-
|
2457
|
-
document.body.replaceWith(this.newElement);
|
2458
|
-
} else {
|
2459
|
-
document.documentElement.appendChild(this.newElement);
|
2460
|
-
}
|
2693
|
+
this.renderElement(this.currentElement, this.newElement);
|
2461
2694
|
}
|
2462
2695
|
get newHeadStylesheetElements() {
|
2463
2696
|
return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
|
@@ -2525,13 +2758,20 @@ class PageView extends View {
|
|
2525
2758
|
super(...arguments);
|
2526
2759
|
this.snapshotCache = new SnapshotCache(10);
|
2527
2760
|
this.lastRenderedLocation = new URL(location.href);
|
2761
|
+
this.forceReloaded = false;
|
2528
2762
|
}
|
2529
|
-
renderPage(snapshot, isPreview = false, willRender = true) {
|
2530
|
-
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
|
2763
|
+
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
2764
|
+
const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
|
2765
|
+
if (!renderer.shouldRender) {
|
2766
|
+
this.forceReloaded = true;
|
2767
|
+
} else {
|
2768
|
+
visit === null || visit === void 0 ? void 0 : visit.changeHistory();
|
2769
|
+
}
|
2531
2770
|
return this.render(renderer);
|
2532
2771
|
}
|
2533
|
-
renderError(snapshot) {
|
2534
|
-
|
2772
|
+
renderError(snapshot, visit) {
|
2773
|
+
visit === null || visit === void 0 ? void 0 : visit.changeHistory();
|
2774
|
+
const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
|
2535
2775
|
return this.render(renderer);
|
2536
2776
|
}
|
2537
2777
|
clearSnapshotCache() {
|
@@ -2558,34 +2798,81 @@ class PageView extends View {
|
|
2558
2798
|
}
|
2559
2799
|
}
|
2560
2800
|
|
2801
|
+
class Preloader {
|
2802
|
+
constructor(delegate) {
|
2803
|
+
this.selector = "a[data-turbo-preload]";
|
2804
|
+
this.delegate = delegate;
|
2805
|
+
}
|
2806
|
+
get snapshotCache() {
|
2807
|
+
return this.delegate.navigator.view.snapshotCache;
|
2808
|
+
}
|
2809
|
+
start() {
|
2810
|
+
if (document.readyState === "loading") {
|
2811
|
+
return document.addEventListener("DOMContentLoaded", (() => {
|
2812
|
+
this.preloadOnLoadLinksForView(document.body);
|
2813
|
+
}));
|
2814
|
+
} else {
|
2815
|
+
this.preloadOnLoadLinksForView(document.body);
|
2816
|
+
}
|
2817
|
+
}
|
2818
|
+
preloadOnLoadLinksForView(element) {
|
2819
|
+
for (const link of element.querySelectorAll(this.selector)) {
|
2820
|
+
this.preloadURL(link);
|
2821
|
+
}
|
2822
|
+
}
|
2823
|
+
async preloadURL(link) {
|
2824
|
+
const location = new URL(link.href);
|
2825
|
+
if (this.snapshotCache.has(location)) {
|
2826
|
+
return;
|
2827
|
+
}
|
2828
|
+
try {
|
2829
|
+
const response = await fetch(location.toString(), {
|
2830
|
+
headers: {
|
2831
|
+
"VND.PREFETCH": "true",
|
2832
|
+
Accept: "text/html"
|
2833
|
+
}
|
2834
|
+
});
|
2835
|
+
const responseText = await response.text();
|
2836
|
+
const snapshot = PageSnapshot.fromHTMLString(responseText);
|
2837
|
+
this.snapshotCache.put(location, snapshot);
|
2838
|
+
} catch (_) {}
|
2839
|
+
}
|
2840
|
+
}
|
2841
|
+
|
2561
2842
|
class Session {
|
2562
2843
|
constructor() {
|
2563
2844
|
this.navigator = new Navigator(this);
|
2564
2845
|
this.history = new History(this);
|
2846
|
+
this.preloader = new Preloader(this);
|
2565
2847
|
this.view = new PageView(this, document.documentElement);
|
2566
2848
|
this.adapter = new BrowserAdapter(this);
|
2567
2849
|
this.pageObserver = new PageObserver(this);
|
2568
2850
|
this.cacheObserver = new CacheObserver;
|
2569
|
-
this.linkClickObserver = new LinkClickObserver(this);
|
2570
|
-
this.formSubmitObserver = new FormSubmitObserver(this);
|
2851
|
+
this.linkClickObserver = new LinkClickObserver(this, window);
|
2852
|
+
this.formSubmitObserver = new FormSubmitObserver(this, document);
|
2571
2853
|
this.scrollObserver = new ScrollObserver(this);
|
2572
2854
|
this.streamObserver = new StreamObserver(this);
|
2573
|
-
this.
|
2855
|
+
this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
|
2856
|
+
this.frameRedirector = new FrameRedirector(this, document.documentElement);
|
2857
|
+
this.streamMessageRenderer = new StreamMessageRenderer;
|
2574
2858
|
this.drive = true;
|
2575
2859
|
this.enabled = true;
|
2576
2860
|
this.progressBarDelay = 500;
|
2577
2861
|
this.started = false;
|
2862
|
+
this.formMode = "on";
|
2578
2863
|
}
|
2579
2864
|
start() {
|
2580
2865
|
if (!this.started) {
|
2581
2866
|
this.pageObserver.start();
|
2582
2867
|
this.cacheObserver.start();
|
2868
|
+
this.formLinkClickObserver.start();
|
2583
2869
|
this.linkClickObserver.start();
|
2584
2870
|
this.formSubmitObserver.start();
|
2585
2871
|
this.scrollObserver.start();
|
2586
2872
|
this.streamObserver.start();
|
2587
2873
|
this.frameRedirector.start();
|
2588
2874
|
this.history.start();
|
2875
|
+
this.preloader.start();
|
2589
2876
|
this.started = true;
|
2590
2877
|
this.enabled = true;
|
2591
2878
|
}
|
@@ -2597,6 +2884,7 @@ class Session {
|
|
2597
2884
|
if (this.started) {
|
2598
2885
|
this.pageObserver.stop();
|
2599
2886
|
this.cacheObserver.stop();
|
2887
|
+
this.formLinkClickObserver.stop();
|
2600
2888
|
this.linkClickObserver.stop();
|
2601
2889
|
this.formSubmitObserver.stop();
|
2602
2890
|
this.scrollObserver.stop();
|
@@ -2610,7 +2898,13 @@ class Session {
|
|
2610
2898
|
this.adapter = adapter;
|
2611
2899
|
}
|
2612
2900
|
visit(location, options = {}) {
|
2613
|
-
|
2901
|
+
const frameElement = options.frame ? document.getElementById(options.frame) : null;
|
2902
|
+
if (frameElement instanceof FrameElement) {
|
2903
|
+
frameElement.src = location.toString();
|
2904
|
+
frameElement.loaded;
|
2905
|
+
} else {
|
2906
|
+
this.navigator.proposeVisit(expandURL(location), options);
|
2907
|
+
}
|
2614
2908
|
}
|
2615
2909
|
connectStreamSource(source) {
|
2616
2910
|
this.streamObserver.connectStreamSource(source);
|
@@ -2619,7 +2913,7 @@ class Session {
|
|
2619
2913
|
this.streamObserver.disconnectStreamSource(source);
|
2620
2914
|
}
|
2621
2915
|
renderStreamMessage(message) {
|
2622
|
-
|
2916
|
+
this.streamMessageRenderer.render(StreamMessage.wrap(message));
|
2623
2917
|
}
|
2624
2918
|
clearCache() {
|
2625
2919
|
this.view.clearSnapshotCache();
|
@@ -2627,6 +2921,9 @@ class Session {
|
|
2627
2921
|
setProgressBarDelay(delay) {
|
2628
2922
|
this.progressBarDelay = delay;
|
2629
2923
|
}
|
2924
|
+
setFormMode(mode) {
|
2925
|
+
this.formMode = mode;
|
2926
|
+
}
|
2630
2927
|
get location() {
|
2631
2928
|
return this.history.location;
|
2632
2929
|
}
|
@@ -2640,7 +2937,9 @@ class Session {
|
|
2640
2937
|
historyChanged: true
|
2641
2938
|
});
|
2642
2939
|
} else {
|
2643
|
-
this.adapter.pageInvalidated(
|
2940
|
+
this.adapter.pageInvalidated({
|
2941
|
+
reason: "turbo_disabled"
|
2942
|
+
});
|
2644
2943
|
}
|
2645
2944
|
}
|
2646
2945
|
scrollPositionChanged(position) {
|
@@ -2648,41 +2947,21 @@ class Session {
|
|
2648
2947
|
scrollPosition: position
|
2649
2948
|
});
|
2650
2949
|
}
|
2651
|
-
|
2652
|
-
return this.
|
2950
|
+
willSubmitFormLinkToLocation(link, location) {
|
2951
|
+
return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
|
2952
|
+
}
|
2953
|
+
submittedFormLinkToLocation() {}
|
2954
|
+
willFollowLinkToLocation(link, location, event) {
|
2955
|
+
return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
|
2653
2956
|
}
|
2654
2957
|
followedLinkToLocation(link, location) {
|
2655
2958
|
const action = this.getActionForLink(link);
|
2656
|
-
|
2657
|
-
|
2959
|
+
const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
|
2960
|
+
this.visit(location.href, {
|
2961
|
+
action: action,
|
2962
|
+
acceptsStreamResponse: acceptsStreamResponse
|
2658
2963
|
});
|
2659
2964
|
}
|
2660
|
-
convertLinkWithMethodClickToFormSubmission(link) {
|
2661
|
-
const linkMethod = link.getAttribute("data-turbo-method");
|
2662
|
-
if (linkMethod) {
|
2663
|
-
const form = document.createElement("form");
|
2664
|
-
form.method = linkMethod;
|
2665
|
-
form.action = link.getAttribute("href") || "undefined";
|
2666
|
-
form.hidden = true;
|
2667
|
-
if (link.hasAttribute("data-turbo-confirm")) {
|
2668
|
-
form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
|
2669
|
-
}
|
2670
|
-
const frame = this.getTargetFrameForLink(link);
|
2671
|
-
if (frame) {
|
2672
|
-
form.setAttribute("data-turbo-frame", frame);
|
2673
|
-
form.addEventListener("turbo:submit-start", (() => form.remove()));
|
2674
|
-
} else {
|
2675
|
-
form.addEventListener("submit", (() => form.remove()));
|
2676
|
-
}
|
2677
|
-
document.body.appendChild(form);
|
2678
|
-
return dispatch("submit", {
|
2679
|
-
cancelable: true,
|
2680
|
-
target: form
|
2681
|
-
});
|
2682
|
-
} else {
|
2683
|
-
return false;
|
2684
|
-
}
|
2685
|
-
}
|
2686
2965
|
allowsVisitingLocationWithAction(location, action) {
|
2687
2966
|
return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
|
2688
2967
|
}
|
@@ -2691,12 +2970,16 @@ class Session {
|
|
2691
2970
|
this.adapter.visitProposedToLocation(location, options);
|
2692
2971
|
}
|
2693
2972
|
visitStarted(visit) {
|
2973
|
+
if (!visit.acceptsStreamResponse) {
|
2974
|
+
markAsBusy(document.documentElement);
|
2975
|
+
}
|
2694
2976
|
extendURLWithDeprecatedProperties(visit.location);
|
2695
2977
|
if (!visit.silent) {
|
2696
2978
|
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
|
2697
2979
|
}
|
2698
2980
|
}
|
2699
2981
|
visitCompleted(visit) {
|
2982
|
+
clearBusyState(document.documentElement);
|
2700
2983
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
2701
2984
|
}
|
2702
2985
|
locationWithActionIsSamePage(location, action) {
|
@@ -2707,7 +2990,7 @@ class Session {
|
|
2707
2990
|
}
|
2708
2991
|
willSubmitForm(form, submitter) {
|
2709
2992
|
const action = getAction(form, submitter);
|
2710
|
-
return this.
|
2993
|
+
return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
|
2711
2994
|
}
|
2712
2995
|
formSubmitted(form, submitter) {
|
2713
2996
|
this.navigator.submitForm(form, submitter);
|
@@ -2731,16 +3014,23 @@ class Session {
|
|
2731
3014
|
this.notifyApplicationBeforeCachingSnapshot();
|
2732
3015
|
}
|
2733
3016
|
}
|
2734
|
-
allowsImmediateRender({element: element},
|
2735
|
-
const event = this.notifyApplicationBeforeRender(element,
|
2736
|
-
|
3017
|
+
allowsImmediateRender({element: element}, options) {
|
3018
|
+
const event = this.notifyApplicationBeforeRender(element, options);
|
3019
|
+
const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
|
3020
|
+
if (this.view.renderer && render) {
|
3021
|
+
this.view.renderer.renderElement = render;
|
3022
|
+
}
|
3023
|
+
return !defaultPrevented;
|
2737
3024
|
}
|
2738
|
-
viewRenderedSnapshot(
|
3025
|
+
viewRenderedSnapshot(_snapshot, _isPreview) {
|
2739
3026
|
this.view.lastRenderedLocation = this.history.location;
|
2740
3027
|
this.notifyApplicationAfterRender();
|
2741
3028
|
}
|
2742
|
-
|
2743
|
-
this.
|
3029
|
+
preloadOnLoadLinksForView(element) {
|
3030
|
+
this.preloader.preloadOnLoadLinksForView(element);
|
3031
|
+
}
|
3032
|
+
viewInvalidated(reason) {
|
3033
|
+
this.adapter.pageInvalidated(reason);
|
2744
3034
|
}
|
2745
3035
|
frameLoaded(frame) {
|
2746
3036
|
this.notifyApplicationAfterFrameLoad(frame);
|
@@ -2748,19 +3038,20 @@ class Session {
|
|
2748
3038
|
frameRendered(fetchResponse, frame) {
|
2749
3039
|
this.notifyApplicationAfterFrameRender(fetchResponse, frame);
|
2750
3040
|
}
|
2751
|
-
applicationAllowsFollowingLinkToLocation(link, location) {
|
2752
|
-
const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
|
3041
|
+
applicationAllowsFollowingLinkToLocation(link, location, ev) {
|
3042
|
+
const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
|
2753
3043
|
return !event.defaultPrevented;
|
2754
3044
|
}
|
2755
3045
|
applicationAllowsVisitingLocation(location) {
|
2756
3046
|
const event = this.notifyApplicationBeforeVisitingLocation(location);
|
2757
3047
|
return !event.defaultPrevented;
|
2758
3048
|
}
|
2759
|
-
notifyApplicationAfterClickingLinkToLocation(link, location) {
|
3049
|
+
notifyApplicationAfterClickingLinkToLocation(link, location, event) {
|
2760
3050
|
return dispatch("turbo:click", {
|
2761
3051
|
target: link,
|
2762
3052
|
detail: {
|
2763
|
-
url: location.href
|
3053
|
+
url: location.href,
|
3054
|
+
originalEvent: event
|
2764
3055
|
},
|
2765
3056
|
cancelable: true
|
2766
3057
|
});
|
@@ -2774,7 +3065,6 @@ class Session {
|
|
2774
3065
|
});
|
2775
3066
|
}
|
2776
3067
|
notifyApplicationAfterVisitingLocation(location, action) {
|
2777
|
-
markAsBusy(document.documentElement);
|
2778
3068
|
return dispatch("turbo:visit", {
|
2779
3069
|
detail: {
|
2780
3070
|
url: location.href,
|
@@ -2785,12 +3075,11 @@ class Session {
|
|
2785
3075
|
notifyApplicationBeforeCachingSnapshot() {
|
2786
3076
|
return dispatch("turbo:before-cache");
|
2787
3077
|
}
|
2788
|
-
notifyApplicationBeforeRender(newBody,
|
3078
|
+
notifyApplicationBeforeRender(newBody, options) {
|
2789
3079
|
return dispatch("turbo:before-render", {
|
2790
|
-
detail: {
|
2791
|
-
newBody: newBody
|
2792
|
-
|
2793
|
-
},
|
3080
|
+
detail: Object.assign({
|
3081
|
+
newBody: newBody
|
3082
|
+
}, options),
|
2794
3083
|
cancelable: true
|
2795
3084
|
});
|
2796
3085
|
}
|
@@ -2798,7 +3087,6 @@ class Session {
|
|
2798
3087
|
return dispatch("turbo:render");
|
2799
3088
|
}
|
2800
3089
|
notifyApplicationAfterPageLoad(timing = {}) {
|
2801
|
-
clearBusyState(document.documentElement);
|
2802
3090
|
return dispatch("turbo:load", {
|
2803
3091
|
detail: {
|
2804
3092
|
url: this.location.href,
|
@@ -2826,9 +3114,22 @@ class Session {
|
|
2826
3114
|
cancelable: true
|
2827
3115
|
});
|
2828
3116
|
}
|
2829
|
-
|
2830
|
-
|
2831
|
-
|
3117
|
+
submissionIsNavigatable(form, submitter) {
|
3118
|
+
if (this.formMode == "off") {
|
3119
|
+
return false;
|
3120
|
+
} else {
|
3121
|
+
const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
|
3122
|
+
if (this.formMode == "optin") {
|
3123
|
+
return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null;
|
3124
|
+
} else {
|
3125
|
+
return submitterIsNavigatable && this.elementIsNavigatable(form);
|
3126
|
+
}
|
3127
|
+
}
|
3128
|
+
}
|
3129
|
+
elementIsNavigatable(element) {
|
3130
|
+
const container = element.closest("[data-turbo]");
|
3131
|
+
const withinFrame = element.closest("turbo-frame");
|
3132
|
+
if (this.drive || withinFrame) {
|
2832
3133
|
if (container) {
|
2833
3134
|
return container.getAttribute("data-turbo") != "false";
|
2834
3135
|
} else {
|
@@ -2846,17 +3147,6 @@ class Session {
|
|
2846
3147
|
const action = link.getAttribute("data-turbo-action");
|
2847
3148
|
return isAction(action) ? action : "advance";
|
2848
3149
|
}
|
2849
|
-
getTargetFrameForLink(link) {
|
2850
|
-
const frame = link.getAttribute("data-turbo-frame");
|
2851
|
-
if (frame) {
|
2852
|
-
return frame;
|
2853
|
-
} else {
|
2854
|
-
const container = link.closest("turbo-frame");
|
2855
|
-
if (container) {
|
2856
|
-
return container.id;
|
2857
|
-
}
|
2858
|
-
}
|
2859
|
-
}
|
2860
3150
|
get snapshot() {
|
2861
3151
|
return this.view.snapshot;
|
2862
3152
|
}
|
@@ -2874,8 +3164,63 @@ const deprecatedLocationPropertyDescriptors = {
|
|
2874
3164
|
}
|
2875
3165
|
};
|
2876
3166
|
|
3167
|
+
class Cache {
|
3168
|
+
constructor(session) {
|
3169
|
+
this.session = session;
|
3170
|
+
}
|
3171
|
+
clear() {
|
3172
|
+
this.session.clearCache();
|
3173
|
+
}
|
3174
|
+
resetCacheControl() {
|
3175
|
+
this.setCacheControl("");
|
3176
|
+
}
|
3177
|
+
exemptPageFromCache() {
|
3178
|
+
this.setCacheControl("no-cache");
|
3179
|
+
}
|
3180
|
+
exemptPageFromPreview() {
|
3181
|
+
this.setCacheControl("no-preview");
|
3182
|
+
}
|
3183
|
+
setCacheControl(value) {
|
3184
|
+
setMetaContent("turbo-cache-control", value);
|
3185
|
+
}
|
3186
|
+
}
|
3187
|
+
|
3188
|
+
const StreamActions = {
|
3189
|
+
after() {
|
3190
|
+
this.targetElements.forEach((e => {
|
3191
|
+
var _a;
|
3192
|
+
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
|
3193
|
+
}));
|
3194
|
+
},
|
3195
|
+
append() {
|
3196
|
+
this.removeDuplicateTargetChildren();
|
3197
|
+
this.targetElements.forEach((e => e.append(this.templateContent)));
|
3198
|
+
},
|
3199
|
+
before() {
|
3200
|
+
this.targetElements.forEach((e => {
|
3201
|
+
var _a;
|
3202
|
+
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
|
3203
|
+
}));
|
3204
|
+
},
|
3205
|
+
prepend() {
|
3206
|
+
this.removeDuplicateTargetChildren();
|
3207
|
+
this.targetElements.forEach((e => e.prepend(this.templateContent)));
|
3208
|
+
},
|
3209
|
+
remove() {
|
3210
|
+
this.targetElements.forEach((e => e.remove()));
|
3211
|
+
},
|
3212
|
+
replace() {
|
3213
|
+
this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
|
3214
|
+
},
|
3215
|
+
update() {
|
3216
|
+
this.targetElements.forEach((e => e.replaceChildren(this.templateContent)));
|
3217
|
+
}
|
3218
|
+
};
|
3219
|
+
|
2877
3220
|
const session = new Session;
|
2878
3221
|
|
3222
|
+
const cache = new Cache(session);
|
3223
|
+
|
2879
3224
|
const {navigator: navigator$1} = session;
|
2880
3225
|
|
2881
3226
|
function start() {
|
@@ -2903,6 +3248,7 @@ function renderStreamMessage(message) {
|
|
2903
3248
|
}
|
2904
3249
|
|
2905
3250
|
function clearCache() {
|
3251
|
+
console.warn("Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
|
2906
3252
|
session.clearCache();
|
2907
3253
|
}
|
2908
3254
|
|
@@ -2914,12 +3260,18 @@ function setConfirmMethod(confirmMethod) {
|
|
2914
3260
|
FormSubmission.confirmMethod = confirmMethod;
|
2915
3261
|
}
|
2916
3262
|
|
3263
|
+
function setFormMode(mode) {
|
3264
|
+
session.setFormMode(mode);
|
3265
|
+
}
|
3266
|
+
|
2917
3267
|
var Turbo = Object.freeze({
|
2918
3268
|
__proto__: null,
|
2919
3269
|
navigator: navigator$1,
|
2920
3270
|
session: session,
|
3271
|
+
cache: cache,
|
2921
3272
|
PageRenderer: PageRenderer,
|
2922
3273
|
PageSnapshot: PageSnapshot,
|
3274
|
+
FrameRenderer: FrameRenderer,
|
2923
3275
|
start: start,
|
2924
3276
|
registerAdapter: registerAdapter,
|
2925
3277
|
visit: visit,
|
@@ -2928,41 +3280,55 @@ var Turbo = Object.freeze({
|
|
2928
3280
|
renderStreamMessage: renderStreamMessage,
|
2929
3281
|
clearCache: clearCache,
|
2930
3282
|
setProgressBarDelay: setProgressBarDelay,
|
2931
|
-
setConfirmMethod: setConfirmMethod
|
3283
|
+
setConfirmMethod: setConfirmMethod,
|
3284
|
+
setFormMode: setFormMode,
|
3285
|
+
StreamActions: StreamActions
|
2932
3286
|
});
|
2933
3287
|
|
2934
3288
|
class FrameController {
|
2935
3289
|
constructor(element) {
|
2936
|
-
this.fetchResponseLoaded =
|
3290
|
+
this.fetchResponseLoaded = _fetchResponse => {};
|
2937
3291
|
this.currentFetchRequest = null;
|
2938
3292
|
this.resolveVisitPromise = () => {};
|
2939
3293
|
this.connected = false;
|
2940
3294
|
this.hasBeenLoaded = false;
|
2941
|
-
this.
|
3295
|
+
this.ignoredAttributes = new Set;
|
3296
|
+
this.action = null;
|
3297
|
+
this.visitCachedSnapshot = ({element: element}) => {
|
3298
|
+
const frame = element.querySelector("#" + this.element.id);
|
3299
|
+
if (frame && this.previousFrameElement) {
|
3300
|
+
frame.replaceChildren(...this.previousFrameElement.children);
|
3301
|
+
}
|
3302
|
+
delete this.previousFrameElement;
|
3303
|
+
};
|
2942
3304
|
this.element = element;
|
2943
3305
|
this.view = new FrameView(this, this.element);
|
2944
3306
|
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
2945
|
-
this.
|
2946
|
-
this.
|
3307
|
+
this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);
|
3308
|
+
this.linkClickObserver = new LinkClickObserver(this, this.element);
|
3309
|
+
this.restorationIdentifier = uuid();
|
3310
|
+
this.formSubmitObserver = new FormSubmitObserver(this, this.element);
|
2947
3311
|
}
|
2948
3312
|
connect() {
|
2949
3313
|
if (!this.connected) {
|
2950
3314
|
this.connected = true;
|
2951
|
-
this.reloadable = false;
|
2952
3315
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
2953
3316
|
this.appearanceObserver.start();
|
3317
|
+
} else {
|
3318
|
+
this.loadSourceURL();
|
2954
3319
|
}
|
2955
|
-
this.
|
2956
|
-
this.
|
2957
|
-
this.
|
3320
|
+
this.formLinkClickObserver.start();
|
3321
|
+
this.linkClickObserver.start();
|
3322
|
+
this.formSubmitObserver.start();
|
2958
3323
|
}
|
2959
3324
|
}
|
2960
3325
|
disconnect() {
|
2961
3326
|
if (this.connected) {
|
2962
3327
|
this.connected = false;
|
2963
3328
|
this.appearanceObserver.stop();
|
2964
|
-
this.
|
2965
|
-
this.
|
3329
|
+
this.formLinkClickObserver.stop();
|
3330
|
+
this.linkClickObserver.stop();
|
3331
|
+
this.formSubmitObserver.stop();
|
2966
3332
|
}
|
2967
3333
|
}
|
2968
3334
|
disabledChanged() {
|
@@ -2971,10 +3337,18 @@ class FrameController {
|
|
2971
3337
|
}
|
2972
3338
|
}
|
2973
3339
|
sourceURLChanged() {
|
3340
|
+
if (this.isIgnoringChangesTo("src")) return;
|
3341
|
+
if (this.element.isConnected) {
|
3342
|
+
this.complete = false;
|
3343
|
+
}
|
2974
3344
|
if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
|
2975
3345
|
this.loadSourceURL();
|
2976
3346
|
}
|
2977
3347
|
}
|
3348
|
+
completeChanged() {
|
3349
|
+
if (this.isIgnoringChangesTo("complete")) return;
|
3350
|
+
this.loadSourceURL();
|
3351
|
+
}
|
2978
3352
|
loadingStyleChanged() {
|
2979
3353
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
2980
3354
|
this.appearanceObserver.start();
|
@@ -2984,20 +3358,11 @@ class FrameController {
|
|
2984
3358
|
}
|
2985
3359
|
}
|
2986
3360
|
async loadSourceURL() {
|
2987
|
-
if (
|
2988
|
-
|
2989
|
-
this.
|
2990
|
-
|
2991
|
-
|
2992
|
-
this.element.loaded = this.visit(expandURL(this.sourceURL));
|
2993
|
-
this.appearanceObserver.stop();
|
2994
|
-
await this.element.loaded;
|
2995
|
-
this.hasBeenLoaded = true;
|
2996
|
-
} catch (error) {
|
2997
|
-
this.currentURL = previousURL;
|
2998
|
-
throw error;
|
2999
|
-
}
|
3000
|
-
}
|
3361
|
+
if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
|
3362
|
+
this.element.loaded = this.visit(expandURL(this.sourceURL));
|
3363
|
+
this.appearanceObserver.stop();
|
3364
|
+
await this.element.loaded;
|
3365
|
+
this.hasBeenLoaded = true;
|
3001
3366
|
}
|
3002
3367
|
}
|
3003
3368
|
async loadResponse(fetchResponse) {
|
@@ -3008,13 +3373,21 @@ class FrameController {
|
|
3008
3373
|
const html = await fetchResponse.responseHTML;
|
3009
3374
|
if (html) {
|
3010
3375
|
const {body: body} = parseHTMLDocument(html);
|
3011
|
-
const
|
3012
|
-
|
3013
|
-
|
3014
|
-
|
3015
|
-
|
3016
|
-
|
3017
|
-
|
3376
|
+
const newFrameElement = await this.extractForeignFrameElement(body);
|
3377
|
+
if (newFrameElement) {
|
3378
|
+
const snapshot = new Snapshot(newFrameElement);
|
3379
|
+
const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
|
3380
|
+
if (this.view.renderPromise) await this.view.renderPromise;
|
3381
|
+
this.changeHistory();
|
3382
|
+
await this.view.render(renderer);
|
3383
|
+
this.complete = true;
|
3384
|
+
session.frameRendered(fetchResponse, this.element);
|
3385
|
+
session.frameLoaded(this.element);
|
3386
|
+
this.fetchResponseLoaded(fetchResponse);
|
3387
|
+
} else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
|
3388
|
+
console.warn(`A matching frame for #${this.element.id} was missing from the response, transforming into full-page Visit.`);
|
3389
|
+
this.visitResponse(fetchResponse.response);
|
3390
|
+
}
|
3018
3391
|
}
|
3019
3392
|
} catch (error) {
|
3020
3393
|
console.error(error);
|
@@ -3023,55 +3396,61 @@ class FrameController {
|
|
3023
3396
|
this.fetchResponseLoaded = () => {};
|
3024
3397
|
}
|
3025
3398
|
}
|
3026
|
-
elementAppearedInViewport(
|
3399
|
+
elementAppearedInViewport(_element) {
|
3027
3400
|
this.loadSourceURL();
|
3028
3401
|
}
|
3029
|
-
|
3030
|
-
|
3031
|
-
|
3032
|
-
|
3033
|
-
|
3034
|
-
|
3402
|
+
willSubmitFormLinkToLocation(link) {
|
3403
|
+
return link.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(link);
|
3404
|
+
}
|
3405
|
+
submittedFormLinkToLocation(link, _location, form) {
|
3406
|
+
const frame = this.findFrameElement(link);
|
3407
|
+
if (frame) form.setAttribute("data-turbo-frame", frame.id);
|
3035
3408
|
}
|
3036
|
-
|
3037
|
-
this.
|
3038
|
-
this.navigateFrame(element, url);
|
3409
|
+
willFollowLinkToLocation(element, location, event) {
|
3410
|
+
return this.shouldInterceptNavigation(element) && this.frameAllowsVisitingLocation(element, location, event);
|
3039
3411
|
}
|
3040
|
-
|
3041
|
-
|
3412
|
+
followedLinkToLocation(element, location) {
|
3413
|
+
this.navigateFrame(element, location.href);
|
3042
3414
|
}
|
3043
|
-
|
3415
|
+
willSubmitForm(element, submitter) {
|
3416
|
+
return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
|
3417
|
+
}
|
3418
|
+
formSubmitted(element, submitter) {
|
3044
3419
|
if (this.formSubmission) {
|
3045
3420
|
this.formSubmission.stop();
|
3046
3421
|
}
|
3047
|
-
this.reloadable = false;
|
3048
3422
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
3049
3423
|
const {fetchRequest: fetchRequest} = this.formSubmission;
|
3050
3424
|
this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
|
3051
3425
|
this.formSubmission.start();
|
3052
3426
|
}
|
3053
3427
|
prepareHeadersForRequest(headers, request) {
|
3428
|
+
var _a;
|
3054
3429
|
headers["Turbo-Frame"] = this.id;
|
3430
|
+
if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
|
3431
|
+
request.acceptResponseType(StreamMessage.contentType);
|
3432
|
+
}
|
3055
3433
|
}
|
3056
|
-
requestStarted(
|
3434
|
+
requestStarted(_request) {
|
3057
3435
|
markAsBusy(this.element);
|
3058
3436
|
}
|
3059
|
-
requestPreventedHandlingResponse(
|
3437
|
+
requestPreventedHandlingResponse(_request, _response) {
|
3060
3438
|
this.resolveVisitPromise();
|
3061
3439
|
}
|
3062
3440
|
async requestSucceededWithResponse(request, response) {
|
3063
3441
|
await this.loadResponse(response);
|
3064
3442
|
this.resolveVisitPromise();
|
3065
3443
|
}
|
3066
|
-
requestFailedWithResponse(request, response) {
|
3444
|
+
async requestFailedWithResponse(request, response) {
|
3067
3445
|
console.error(response);
|
3446
|
+
await this.loadResponse(response);
|
3068
3447
|
this.resolveVisitPromise();
|
3069
3448
|
}
|
3070
3449
|
requestErrored(request, error) {
|
3071
3450
|
console.error(error);
|
3072
3451
|
this.resolveVisitPromise();
|
3073
3452
|
}
|
3074
|
-
requestFinished(
|
3453
|
+
requestFinished(_request) {
|
3075
3454
|
clearBusyState(this.element);
|
3076
3455
|
}
|
3077
3456
|
formSubmissionStarted({formElement: formElement}) {
|
@@ -3091,11 +3470,28 @@ class FrameController {
|
|
3091
3470
|
formSubmissionFinished({formElement: formElement}) {
|
3092
3471
|
clearBusyState(formElement, this.findFrameElement(formElement));
|
3093
3472
|
}
|
3094
|
-
allowsImmediateRender(
|
3095
|
-
|
3473
|
+
allowsImmediateRender({element: newFrame}, options) {
|
3474
|
+
const event = dispatch("turbo:before-frame-render", {
|
3475
|
+
target: this.element,
|
3476
|
+
detail: Object.assign({
|
3477
|
+
newFrame: newFrame
|
3478
|
+
}, options),
|
3479
|
+
cancelable: true
|
3480
|
+
});
|
3481
|
+
const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
|
3482
|
+
if (this.view.renderer && render) {
|
3483
|
+
this.view.renderer.renderElement = render;
|
3484
|
+
}
|
3485
|
+
return !defaultPrevented;
|
3486
|
+
}
|
3487
|
+
viewRenderedSnapshot(_snapshot, _isPreview) {}
|
3488
|
+
preloadOnLoadLinksForView(element) {
|
3489
|
+
session.preloadOnLoadLinksForView(element);
|
3096
3490
|
}
|
3097
|
-
viewRenderedSnapshot(snapshot, isPreview) {}
|
3098
3491
|
viewInvalidated() {}
|
3492
|
+
willRenderFrame(currentElement, _newElement) {
|
3493
|
+
this.previousFrameElement = currentElement.cloneNode(true);
|
3494
|
+
}
|
3099
3495
|
async visit(url) {
|
3100
3496
|
var _a;
|
3101
3497
|
const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
|
@@ -3113,13 +3509,15 @@ class FrameController {
|
|
3113
3509
|
navigateFrame(element, url, submitter) {
|
3114
3510
|
const frame = this.findFrameElement(element, submitter);
|
3115
3511
|
this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
|
3116
|
-
|
3117
|
-
|
3512
|
+
this.withCurrentNavigationElement(element, (() => {
|
3513
|
+
frame.src = url;
|
3514
|
+
}));
|
3118
3515
|
}
|
3119
3516
|
proposeVisitIfNavigatedWithAction(frame, element, submitter) {
|
3120
|
-
|
3121
|
-
|
3122
|
-
|
3517
|
+
this.action = getVisitAction(submitter, element, frame);
|
3518
|
+
this.frame = frame;
|
3519
|
+
if (isAction(this.action)) {
|
3520
|
+
const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
|
3123
3521
|
frame.delegate.fetchResponseLoaded = fetchResponse => {
|
3124
3522
|
if (frame.src) {
|
3125
3523
|
const {statusCode: statusCode, redirected: redirected} = fetchResponse;
|
@@ -3129,16 +3527,57 @@ class FrameController {
|
|
3129
3527
|
redirected: redirected,
|
3130
3528
|
responseHTML: responseHTML
|
3131
3529
|
};
|
3132
|
-
|
3133
|
-
action: action,
|
3530
|
+
const options = {
|
3134
3531
|
response: response,
|
3135
3532
|
visitCachedSnapshot: visitCachedSnapshot,
|
3136
|
-
willRender: false
|
3137
|
-
|
3533
|
+
willRender: false,
|
3534
|
+
updateHistory: false,
|
3535
|
+
restorationIdentifier: this.restorationIdentifier
|
3536
|
+
};
|
3537
|
+
if (this.action) options.action = this.action;
|
3538
|
+
session.visit(frame.src, options);
|
3138
3539
|
}
|
3139
3540
|
};
|
3140
3541
|
}
|
3141
3542
|
}
|
3543
|
+
changeHistory() {
|
3544
|
+
if (this.action && this.frame) {
|
3545
|
+
const method = getHistoryMethodForAction(this.action);
|
3546
|
+
session.history.update(method, expandURL(this.frame.src || ""), this.restorationIdentifier);
|
3547
|
+
}
|
3548
|
+
}
|
3549
|
+
willHandleFrameMissingFromResponse(fetchResponse) {
|
3550
|
+
this.element.setAttribute("complete", "");
|
3551
|
+
const response = fetchResponse.response;
|
3552
|
+
const visit = async (url, options = {}) => {
|
3553
|
+
if (url instanceof Response) {
|
3554
|
+
this.visitResponse(url);
|
3555
|
+
} else {
|
3556
|
+
session.visit(url, options);
|
3557
|
+
}
|
3558
|
+
};
|
3559
|
+
const event = dispatch("turbo:frame-missing", {
|
3560
|
+
target: this.element,
|
3561
|
+
detail: {
|
3562
|
+
response: response,
|
3563
|
+
visit: visit
|
3564
|
+
},
|
3565
|
+
cancelable: true
|
3566
|
+
});
|
3567
|
+
return !event.defaultPrevented;
|
3568
|
+
}
|
3569
|
+
async visitResponse(response) {
|
3570
|
+
const wrapped = new FetchResponse(response);
|
3571
|
+
const responseHTML = await wrapped.responseHTML;
|
3572
|
+
const {location: location, redirected: redirected, statusCode: statusCode} = wrapped;
|
3573
|
+
return session.visit(location, {
|
3574
|
+
response: {
|
3575
|
+
redirected: redirected,
|
3576
|
+
statusCode: statusCode,
|
3577
|
+
responseHTML: responseHTML
|
3578
|
+
}
|
3579
|
+
});
|
3580
|
+
}
|
3142
3581
|
findFrameElement(element, submitter) {
|
3143
3582
|
var _a;
|
3144
3583
|
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
@@ -3148,18 +3587,20 @@ class FrameController {
|
|
3148
3587
|
let element;
|
3149
3588
|
const id = CSS.escape(this.id);
|
3150
3589
|
try {
|
3151
|
-
|
3590
|
+
element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
|
3591
|
+
if (element) {
|
3152
3592
|
return element;
|
3153
3593
|
}
|
3154
|
-
|
3594
|
+
element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);
|
3595
|
+
if (element) {
|
3155
3596
|
await element.loaded;
|
3156
3597
|
return await this.extractForeignFrameElement(element);
|
3157
3598
|
}
|
3158
|
-
console.error(`Response has no matching <turbo-frame id="${id}"> element`);
|
3159
3599
|
} catch (error) {
|
3160
3600
|
console.error(error);
|
3601
|
+
return new FrameElement;
|
3161
3602
|
}
|
3162
|
-
return
|
3603
|
+
return null;
|
3163
3604
|
}
|
3164
3605
|
formActionIsVisitable(form, submitter) {
|
3165
3606
|
const action = getAction(form, submitter);
|
@@ -3179,10 +3620,10 @@ class FrameController {
|
|
3179
3620
|
return !frameElement.disabled;
|
3180
3621
|
}
|
3181
3622
|
}
|
3182
|
-
if (!session.
|
3623
|
+
if (!session.elementIsNavigatable(element)) {
|
3183
3624
|
return false;
|
3184
3625
|
}
|
3185
|
-
if (submitter && !session.
|
3626
|
+
if (submitter && !session.elementIsNavigatable(submitter)) {
|
3186
3627
|
return false;
|
3187
3628
|
}
|
3188
3629
|
return true;
|
@@ -3198,23 +3639,10 @@ class FrameController {
|
|
3198
3639
|
return this.element.src;
|
3199
3640
|
}
|
3200
3641
|
}
|
3201
|
-
get reloadable() {
|
3202
|
-
const frame = this.findFrameElement(this.element);
|
3203
|
-
return frame.hasAttribute("reloadable");
|
3204
|
-
}
|
3205
|
-
set reloadable(value) {
|
3206
|
-
const frame = this.findFrameElement(this.element);
|
3207
|
-
if (value) {
|
3208
|
-
frame.setAttribute("reloadable", "");
|
3209
|
-
} else {
|
3210
|
-
frame.removeAttribute("reloadable");
|
3211
|
-
}
|
3212
|
-
}
|
3213
3642
|
set sourceURL(sourceURL) {
|
3214
|
-
this.
|
3215
|
-
|
3216
|
-
|
3217
|
-
this.settingSourceURL = false;
|
3643
|
+
this.ignoringChangesToAttribute("src", (() => {
|
3644
|
+
this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
|
3645
|
+
}));
|
3218
3646
|
}
|
3219
3647
|
get loadingStyle() {
|
3220
3648
|
return this.element.loading;
|
@@ -3222,6 +3650,18 @@ class FrameController {
|
|
3222
3650
|
get isLoading() {
|
3223
3651
|
return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
|
3224
3652
|
}
|
3653
|
+
get complete() {
|
3654
|
+
return this.element.hasAttribute("complete");
|
3655
|
+
}
|
3656
|
+
set complete(value) {
|
3657
|
+
this.ignoringChangesToAttribute("complete", (() => {
|
3658
|
+
if (value) {
|
3659
|
+
this.element.setAttribute("complete", "");
|
3660
|
+
} else {
|
3661
|
+
this.element.removeAttribute("complete");
|
3662
|
+
}
|
3663
|
+
}));
|
3664
|
+
}
|
3225
3665
|
get isActive() {
|
3226
3666
|
return this.element.isActive && this.connected;
|
3227
3667
|
}
|
@@ -3231,17 +3671,29 @@ class FrameController {
|
|
3231
3671
|
const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
|
3232
3672
|
return expandURL(root);
|
3233
3673
|
}
|
3234
|
-
}
|
3235
|
-
|
3236
|
-
|
3237
|
-
|
3238
|
-
|
3239
|
-
|
3240
|
-
|
3241
|
-
|
3242
|
-
};
|
3243
|
-
|
3244
|
-
|
3674
|
+
frameAllowsVisitingLocation(target, {href: url}, originalEvent) {
|
3675
|
+
const event = dispatch("turbo:click", {
|
3676
|
+
target: target,
|
3677
|
+
detail: {
|
3678
|
+
url: url,
|
3679
|
+
originalEvent: originalEvent
|
3680
|
+
},
|
3681
|
+
cancelable: true
|
3682
|
+
});
|
3683
|
+
return !event.defaultPrevented;
|
3684
|
+
}
|
3685
|
+
isIgnoringChangesTo(attributeName) {
|
3686
|
+
return this.ignoredAttributes.has(attributeName);
|
3687
|
+
}
|
3688
|
+
ignoringChangesToAttribute(attributeName, callback) {
|
3689
|
+
this.ignoredAttributes.add(attributeName);
|
3690
|
+
callback();
|
3691
|
+
this.ignoredAttributes.delete(attributeName);
|
3692
|
+
}
|
3693
|
+
withCurrentNavigationElement(element, callback) {
|
3694
|
+
this.currentNavigationElement = element;
|
3695
|
+
callback();
|
3696
|
+
delete this.currentNavigationElement;
|
3245
3697
|
}
|
3246
3698
|
}
|
3247
3699
|
|
@@ -3271,42 +3723,10 @@ function activateElement(element, currentURL) {
|
|
3271
3723
|
}
|
3272
3724
|
}
|
3273
3725
|
|
3274
|
-
const StreamActions = {
|
3275
|
-
after() {
|
3276
|
-
this.targetElements.forEach((e => {
|
3277
|
-
var _a;
|
3278
|
-
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
|
3279
|
-
}));
|
3280
|
-
},
|
3281
|
-
append() {
|
3282
|
-
this.removeDuplicateTargetChildren();
|
3283
|
-
this.targetElements.forEach((e => e.append(this.templateContent)));
|
3284
|
-
},
|
3285
|
-
before() {
|
3286
|
-
this.targetElements.forEach((e => {
|
3287
|
-
var _a;
|
3288
|
-
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
|
3289
|
-
}));
|
3290
|
-
},
|
3291
|
-
prepend() {
|
3292
|
-
this.removeDuplicateTargetChildren();
|
3293
|
-
this.targetElements.forEach((e => e.prepend(this.templateContent)));
|
3294
|
-
},
|
3295
|
-
remove() {
|
3296
|
-
this.targetElements.forEach((e => e.remove()));
|
3297
|
-
},
|
3298
|
-
replace() {
|
3299
|
-
this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
|
3300
|
-
},
|
3301
|
-
update() {
|
3302
|
-
this.targetElements.forEach((e => {
|
3303
|
-
e.innerHTML = "";
|
3304
|
-
e.append(this.templateContent);
|
3305
|
-
}));
|
3306
|
-
}
|
3307
|
-
};
|
3308
|
-
|
3309
3726
|
class StreamElement extends HTMLElement {
|
3727
|
+
static async renderElement(newElement) {
|
3728
|
+
await newElement.performAction();
|
3729
|
+
}
|
3310
3730
|
async connectedCallback() {
|
3311
3731
|
try {
|
3312
3732
|
await this.render();
|
@@ -3319,9 +3739,10 @@ class StreamElement extends HTMLElement {
|
|
3319
3739
|
async render() {
|
3320
3740
|
var _a;
|
3321
3741
|
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
|
3322
|
-
|
3742
|
+
const event = this.beforeRenderEvent;
|
3743
|
+
if (this.dispatchEvent(event)) {
|
3323
3744
|
await nextAnimationFrame();
|
3324
|
-
|
3745
|
+
await event.detail.render(this);
|
3325
3746
|
}
|
3326
3747
|
})();
|
3327
3748
|
}
|
@@ -3336,7 +3757,7 @@ class StreamElement extends HTMLElement {
|
|
3336
3757
|
get duplicateChildren() {
|
3337
3758
|
var _a;
|
3338
3759
|
const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
|
3339
|
-
const newChildrenIds = [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].filter((c => !!c.id)).map((c => c.id));
|
3760
|
+
const newChildrenIds = [ ...((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [] ].filter((c => !!c.id)).map((c => c.id));
|
3340
3761
|
return existingChildren.filter((c => newChildrenIds.includes(c.id)));
|
3341
3762
|
}
|
3342
3763
|
get performAction() {
|
@@ -3362,7 +3783,11 @@ class StreamElement extends HTMLElement {
|
|
3362
3783
|
return this.templateElement.content.cloneNode(true);
|
3363
3784
|
}
|
3364
3785
|
get templateElement() {
|
3365
|
-
if (this.firstElementChild
|
3786
|
+
if (this.firstElementChild === null) {
|
3787
|
+
const template = this.ownerDocument.createElement("template");
|
3788
|
+
this.appendChild(template);
|
3789
|
+
return template;
|
3790
|
+
} else if (this.firstElementChild instanceof HTMLTemplateElement) {
|
3366
3791
|
return this.firstElementChild;
|
3367
3792
|
}
|
3368
3793
|
this.raise("first child element must be a <template> element");
|
@@ -3386,7 +3811,11 @@ class StreamElement extends HTMLElement {
|
|
3386
3811
|
get beforeRenderEvent() {
|
3387
3812
|
return new CustomEvent("turbo:before-stream-render", {
|
3388
3813
|
bubbles: true,
|
3389
|
-
cancelable: true
|
3814
|
+
cancelable: true,
|
3815
|
+
detail: {
|
3816
|
+
newStream: this,
|
3817
|
+
render: StreamElement.renderElement
|
3818
|
+
}
|
3390
3819
|
});
|
3391
3820
|
}
|
3392
3821
|
get targetElementsById() {
|
@@ -3409,17 +3838,45 @@ class StreamElement extends HTMLElement {
|
|
3409
3838
|
}
|
3410
3839
|
}
|
3411
3840
|
|
3841
|
+
class StreamSourceElement extends HTMLElement {
|
3842
|
+
constructor() {
|
3843
|
+
super(...arguments);
|
3844
|
+
this.streamSource = null;
|
3845
|
+
}
|
3846
|
+
connectedCallback() {
|
3847
|
+
this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
|
3848
|
+
connectStreamSource(this.streamSource);
|
3849
|
+
}
|
3850
|
+
disconnectedCallback() {
|
3851
|
+
if (this.streamSource) {
|
3852
|
+
disconnectStreamSource(this.streamSource);
|
3853
|
+
}
|
3854
|
+
}
|
3855
|
+
get src() {
|
3856
|
+
return this.getAttribute("src") || "";
|
3857
|
+
}
|
3858
|
+
}
|
3859
|
+
|
3412
3860
|
FrameElement.delegateConstructor = FrameController;
|
3413
3861
|
|
3414
|
-
customElements.
|
3862
|
+
if (customElements.get("turbo-frame") === undefined) {
|
3863
|
+
customElements.define("turbo-frame", FrameElement);
|
3864
|
+
}
|
3415
3865
|
|
3416
|
-
customElements.
|
3866
|
+
if (customElements.get("turbo-stream") === undefined) {
|
3867
|
+
customElements.define("turbo-stream", StreamElement);
|
3868
|
+
}
|
3869
|
+
|
3870
|
+
if (customElements.get("turbo-stream-source") === undefined) {
|
3871
|
+
customElements.define("turbo-stream-source", StreamSourceElement);
|
3872
|
+
}
|
3417
3873
|
|
3418
3874
|
(() => {
|
3419
3875
|
let element = document.currentScript;
|
3420
3876
|
if (!element) return;
|
3421
3877
|
if (element.hasAttribute("data-turbo-suppress-warning")) return;
|
3422
|
-
|
3878
|
+
element = element.parentElement;
|
3879
|
+
while (element) {
|
3423
3880
|
if (element == document.body) {
|
3424
3881
|
return console.warn(unindent`
|
3425
3882
|
You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
|
@@ -3432,6 +3889,7 @@ customElements.define("turbo-stream", StreamElement);
|
|
3432
3889
|
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
3433
3890
|
`, element.outerHTML);
|
3434
3891
|
}
|
3892
|
+
element = element.parentElement;
|
3435
3893
|
}
|
3436
3894
|
})();
|
3437
3895
|
|
@@ -3441,8 +3899,17 @@ start();
|
|
3441
3899
|
|
3442
3900
|
var turbo_es2017Esm = Object.freeze({
|
3443
3901
|
__proto__: null,
|
3902
|
+
FrameElement: FrameElement,
|
3903
|
+
get FrameLoadingStyle() {
|
3904
|
+
return FrameLoadingStyle;
|
3905
|
+
},
|
3906
|
+
FrameRenderer: FrameRenderer,
|
3444
3907
|
PageRenderer: PageRenderer,
|
3445
3908
|
PageSnapshot: PageSnapshot,
|
3909
|
+
StreamActions: StreamActions,
|
3910
|
+
StreamElement: StreamElement,
|
3911
|
+
StreamSourceElement: StreamSourceElement,
|
3912
|
+
cache: cache,
|
3446
3913
|
clearCache: clearCache,
|
3447
3914
|
connectStreamSource: connectStreamSource,
|
3448
3915
|
disconnectStreamSource: disconnectStreamSource,
|
@@ -3451,6 +3918,7 @@ var turbo_es2017Esm = Object.freeze({
|
|
3451
3918
|
renderStreamMessage: renderStreamMessage,
|
3452
3919
|
session: session,
|
3453
3920
|
setConfirmMethod: setConfirmMethod,
|
3921
|
+
setFormMode: setFormMode,
|
3454
3922
|
setProgressBarDelay: setProgressBarDelay,
|
3455
3923
|
start: start,
|
3456
3924
|
visit: visit
|
@@ -3531,14 +3999,26 @@ class TurboCableStreamSourceElement extends HTMLElement {
|
|
3531
3999
|
|
3532
4000
|
customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement);
|
3533
4001
|
|
3534
|
-
function
|
3535
|
-
|
3536
|
-
|
3537
|
-
|
4002
|
+
function encodeMethodIntoRequestBody(event) {
|
4003
|
+
if (event.target instanceof HTMLFormElement) {
|
4004
|
+
const {target: form, detail: {fetchOptions: fetchOptions}} = event;
|
4005
|
+
form.addEventListener("turbo:submit-start", (({detail: {formSubmission: {submitter: submitter}}}) => {
|
4006
|
+
const method = submitter && submitter.formMethod || fetchOptions.body && fetchOptions.body.get("_method") || form.getAttribute("method");
|
4007
|
+
if (!/get/i.test(method)) {
|
4008
|
+
if (/post/i.test(method)) {
|
4009
|
+
fetchOptions.body.delete("_method");
|
4010
|
+
} else {
|
4011
|
+
fetchOptions.body.set("_method", method);
|
4012
|
+
}
|
4013
|
+
fetchOptions.method = "post";
|
4014
|
+
}
|
4015
|
+
}), {
|
4016
|
+
once: true
|
4017
|
+
});
|
3538
4018
|
}
|
3539
4019
|
}
|
3540
4020
|
|
3541
|
-
addEventListener("turbo:
|
4021
|
+
addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
|
3542
4022
|
|
3543
4023
|
var adapters = {
|
3544
4024
|
logger: self.console,
|