turbo-rails 1.1.1 → 1.4.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/README.md +1 -1
- data/Rakefile +15 -2
- data/app/assets/javascripts/turbo.js +1214 -561
- 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/controllers/turbo/frames/frame_request.rb +15 -7
- data/app/controllers/turbo/native/navigation.rb +3 -1
- data/app/helpers/turbo/drive_helper.rb +16 -3
- data/app/helpers/turbo/streams/action_helper.rb +8 -6
- data/app/javascript/turbo/cable_stream_source_element.js +17 -2
- data/app/javascript/turbo/fetch_requests.js +50 -0
- data/app/javascript/turbo/index.js +3 -2
- data/app/models/concerns/turbo/broadcastable.rb +29 -10
- data/app/models/turbo/streams/tag_builder.rb +2 -0
- data/app/views/layouts/turbo_rails/frame.html.erb +8 -0
- data/lib/turbo/version.rb +1 -1
- metadata +5 -4
- data/app/javascript/turbo/form_submissions.js +0 -7
@@ -56,13 +56,11 @@ function clickCaptured(event) {
|
|
56
56
|
|
57
57
|
(function() {
|
58
58
|
if ("submitter" in Event.prototype) return;
|
59
|
-
let prototype;
|
59
|
+
let prototype = window.Event.prototype;
|
60
60
|
if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
|
61
61
|
prototype = window.SubmitEvent.prototype;
|
62
62
|
} else if ("SubmitEvent" in window) {
|
63
63
|
return;
|
64
|
-
} else {
|
65
|
-
prototype = window.Event.prototype;
|
66
64
|
}
|
67
65
|
addEventListener("click", clickCaptured, true);
|
68
66
|
Object.defineProperty(prototype, "submitter", {
|
@@ -82,14 +80,14 @@ var FrameLoadingStyle;
|
|
82
80
|
})(FrameLoadingStyle || (FrameLoadingStyle = {}));
|
83
81
|
|
84
82
|
class FrameElement extends HTMLElement {
|
83
|
+
static get observedAttributes() {
|
84
|
+
return [ "disabled", "complete", "loading", "src" ];
|
85
|
+
}
|
85
86
|
constructor() {
|
86
87
|
super();
|
87
88
|
this.loaded = Promise.resolve();
|
88
89
|
this.delegate = new FrameElement.delegateConstructor(this);
|
89
90
|
}
|
90
|
-
static get observedAttributes() {
|
91
|
-
return [ "disabled", "loading", "src" ];
|
92
|
-
}
|
93
91
|
connectedCallback() {
|
94
92
|
this.delegate.connect();
|
95
93
|
}
|
@@ -97,13 +95,13 @@ class FrameElement extends HTMLElement {
|
|
97
95
|
this.delegate.disconnect();
|
98
96
|
}
|
99
97
|
reload() {
|
100
|
-
|
101
|
-
this.src = null;
|
102
|
-
this.src = src;
|
98
|
+
return this.delegate.sourceURLReloaded();
|
103
99
|
}
|
104
100
|
attributeChangedCallback(name) {
|
105
101
|
if (name == "loading") {
|
106
102
|
this.delegate.loadingStyleChanged();
|
103
|
+
} else if (name == "complete") {
|
104
|
+
this.delegate.completeChanged();
|
107
105
|
} else if (name == "src") {
|
108
106
|
this.delegate.sourceURLChanged();
|
109
107
|
} else {
|
@@ -195,7 +193,7 @@ function getExtension(url) {
|
|
195
193
|
}
|
196
194
|
|
197
195
|
function isHTML(url) {
|
198
|
-
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
|
196
|
+
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
|
199
197
|
}
|
200
198
|
|
201
199
|
function isPrefixedBy(baseURL, url) {
|
@@ -282,10 +280,39 @@ class FetchResponse {
|
|
282
280
|
}
|
283
281
|
}
|
284
282
|
|
283
|
+
function activateScriptElement(element) {
|
284
|
+
if (element.getAttribute("data-turbo-eval") == "false") {
|
285
|
+
return element;
|
286
|
+
} else {
|
287
|
+
const createdScriptElement = document.createElement("script");
|
288
|
+
const cspNonce = getMetaContent("csp-nonce");
|
289
|
+
if (cspNonce) {
|
290
|
+
createdScriptElement.nonce = cspNonce;
|
291
|
+
}
|
292
|
+
createdScriptElement.textContent = element.textContent;
|
293
|
+
createdScriptElement.async = false;
|
294
|
+
copyElementAttributes(createdScriptElement, element);
|
295
|
+
return createdScriptElement;
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
299
|
+
function copyElementAttributes(destinationElement, sourceElement) {
|
300
|
+
for (const {name: name, value: value} of sourceElement.attributes) {
|
301
|
+
destinationElement.setAttribute(name, value);
|
302
|
+
}
|
303
|
+
}
|
304
|
+
|
305
|
+
function createDocumentFragment(html) {
|
306
|
+
const template = document.createElement("template");
|
307
|
+
template.innerHTML = html;
|
308
|
+
return template.content;
|
309
|
+
}
|
310
|
+
|
285
311
|
function dispatch(eventName, {target: target, cancelable: cancelable, detail: detail} = {}) {
|
286
312
|
const event = new CustomEvent(eventName, {
|
287
313
|
cancelable: cancelable,
|
288
314
|
bubbles: true,
|
315
|
+
composed: true,
|
289
316
|
detail: detail
|
290
317
|
});
|
291
318
|
if (target && target.isConnected) {
|
@@ -327,7 +354,7 @@ function interpolate(strings, values) {
|
|
327
354
|
}
|
328
355
|
|
329
356
|
function uuid() {
|
330
|
-
return Array.
|
357
|
+
return Array.from({
|
331
358
|
length: 36
|
332
359
|
}).map(((_, i) => {
|
333
360
|
if (i == 8 || i == 13 || i == 18 || i == 23) {
|
@@ -349,6 +376,10 @@ function getAttribute(attributeName, ...elements) {
|
|
349
376
|
return null;
|
350
377
|
}
|
351
378
|
|
379
|
+
function hasAttribute(attributeName, ...elements) {
|
380
|
+
return elements.some((element => element && element.hasAttribute(attributeName)));
|
381
|
+
}
|
382
|
+
|
352
383
|
function markAsBusy(...elements) {
|
353
384
|
for (const element of elements) {
|
354
385
|
if (element.localName == "turbo-frame") {
|
@@ -367,6 +398,70 @@ function clearBusyState(...elements) {
|
|
367
398
|
}
|
368
399
|
}
|
369
400
|
|
401
|
+
function waitForLoad(element, timeoutInMilliseconds = 2e3) {
|
402
|
+
return new Promise((resolve => {
|
403
|
+
const onComplete = () => {
|
404
|
+
element.removeEventListener("error", onComplete);
|
405
|
+
element.removeEventListener("load", onComplete);
|
406
|
+
resolve();
|
407
|
+
};
|
408
|
+
element.addEventListener("load", onComplete, {
|
409
|
+
once: true
|
410
|
+
});
|
411
|
+
element.addEventListener("error", onComplete, {
|
412
|
+
once: true
|
413
|
+
});
|
414
|
+
setTimeout(resolve, timeoutInMilliseconds);
|
415
|
+
}));
|
416
|
+
}
|
417
|
+
|
418
|
+
function getHistoryMethodForAction(action) {
|
419
|
+
switch (action) {
|
420
|
+
case "replace":
|
421
|
+
return history.replaceState;
|
422
|
+
|
423
|
+
case "advance":
|
424
|
+
case "restore":
|
425
|
+
return history.pushState;
|
426
|
+
}
|
427
|
+
}
|
428
|
+
|
429
|
+
function isAction(action) {
|
430
|
+
return action == "advance" || action == "replace" || action == "restore";
|
431
|
+
}
|
432
|
+
|
433
|
+
function getVisitAction(...elements) {
|
434
|
+
const action = getAttribute("data-turbo-action", ...elements);
|
435
|
+
return isAction(action) ? action : null;
|
436
|
+
}
|
437
|
+
|
438
|
+
function getMetaElement(name) {
|
439
|
+
return document.querySelector(`meta[name="${name}"]`);
|
440
|
+
}
|
441
|
+
|
442
|
+
function getMetaContent(name) {
|
443
|
+
const element = getMetaElement(name);
|
444
|
+
return element && element.content;
|
445
|
+
}
|
446
|
+
|
447
|
+
function setMetaContent(name, content) {
|
448
|
+
let element = getMetaElement(name);
|
449
|
+
if (!element) {
|
450
|
+
element = document.createElement("meta");
|
451
|
+
element.setAttribute("name", name);
|
452
|
+
document.head.appendChild(element);
|
453
|
+
}
|
454
|
+
element.setAttribute("content", content);
|
455
|
+
return element;
|
456
|
+
}
|
457
|
+
|
458
|
+
function findClosestRecursively(element, selector) {
|
459
|
+
var _a;
|
460
|
+
if (element instanceof Element) {
|
461
|
+
return element.closest(selector) || findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector);
|
462
|
+
}
|
463
|
+
}
|
464
|
+
|
370
465
|
var FetchMethod;
|
371
466
|
|
372
467
|
(function(FetchMethod) {
|
@@ -399,7 +494,7 @@ function fetchMethodFromString(method) {
|
|
399
494
|
class FetchRequest {
|
400
495
|
constructor(delegate, method, location, body = new URLSearchParams, target = null) {
|
401
496
|
this.abortController = new AbortController;
|
402
|
-
this.resolveRequestPromise =
|
497
|
+
this.resolveRequestPromise = _value => {};
|
403
498
|
this.delegate = delegate;
|
404
499
|
this.method = method;
|
405
500
|
this.headers = this.defaultHeaders;
|
@@ -420,9 +515,8 @@ class FetchRequest {
|
|
420
515
|
this.abortController.abort();
|
421
516
|
}
|
422
517
|
async perform() {
|
423
|
-
var _a, _b;
|
424
518
|
const {fetchOptions: fetchOptions} = this;
|
425
|
-
|
519
|
+
this.delegate.prepareRequest(this);
|
426
520
|
await this.allowRequestToBeIntercepted(fetchOptions);
|
427
521
|
try {
|
428
522
|
this.delegate.requestStarted(this);
|
@@ -430,7 +524,9 @@ class FetchRequest {
|
|
430
524
|
return await this.receive(response);
|
431
525
|
} catch (error) {
|
432
526
|
if (error.name !== "AbortError") {
|
433
|
-
this.
|
527
|
+
if (this.willDelegateErrorHandling(error)) {
|
528
|
+
this.delegate.requestErrored(this, error);
|
529
|
+
}
|
434
530
|
throw error;
|
435
531
|
}
|
436
532
|
} finally {
|
@@ -462,7 +558,7 @@ class FetchRequest {
|
|
462
558
|
credentials: "same-origin",
|
463
559
|
headers: this.headers,
|
464
560
|
redirect: "follow",
|
465
|
-
body: this.
|
561
|
+
body: this.isSafe ? null : this.body,
|
466
562
|
signal: this.abortSignal,
|
467
563
|
referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
|
468
564
|
};
|
@@ -472,12 +568,15 @@ class FetchRequest {
|
|
472
568
|
Accept: "text/html, application/xhtml+xml"
|
473
569
|
};
|
474
570
|
}
|
475
|
-
get
|
476
|
-
return this.method
|
571
|
+
get isSafe() {
|
572
|
+
return this.method === FetchMethod.get;
|
477
573
|
}
|
478
574
|
get abortSignal() {
|
479
575
|
return this.abortController.signal;
|
480
576
|
}
|
577
|
+
acceptResponseType(mimeType) {
|
578
|
+
this.headers["Accept"] = [ mimeType, this.headers["Accept"] ].join(", ");
|
579
|
+
}
|
481
580
|
async allowRequestToBeIntercepted(fetchOptions) {
|
482
581
|
const requestInterception = new Promise((resolve => this.resolveRequestPromise = resolve));
|
483
582
|
const event = dispatch("turbo:before-fetch-request", {
|
@@ -491,6 +590,17 @@ class FetchRequest {
|
|
491
590
|
});
|
492
591
|
if (event.defaultPrevented) await requestInterception;
|
493
592
|
}
|
593
|
+
willDelegateErrorHandling(error) {
|
594
|
+
const event = dispatch("turbo:fetch-request-error", {
|
595
|
+
target: this.target,
|
596
|
+
cancelable: true,
|
597
|
+
detail: {
|
598
|
+
request: this,
|
599
|
+
error: error
|
600
|
+
}
|
601
|
+
});
|
602
|
+
return !event.defaultPrevented;
|
603
|
+
}
|
494
604
|
}
|
495
605
|
|
496
606
|
class AppearanceObserver {
|
@@ -521,40 +631,31 @@ class AppearanceObserver {
|
|
521
631
|
}
|
522
632
|
|
523
633
|
class StreamMessage {
|
524
|
-
constructor(html) {
|
525
|
-
this.templateElement = document.createElement("template");
|
526
|
-
this.templateElement.innerHTML = html;
|
527
|
-
}
|
528
634
|
static wrap(message) {
|
529
635
|
if (typeof message == "string") {
|
530
|
-
return new this(message);
|
636
|
+
return new this(createDocumentFragment(message));
|
531
637
|
} else {
|
532
638
|
return message;
|
533
639
|
}
|
534
640
|
}
|
535
|
-
|
536
|
-
|
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);
|
641
|
+
constructor(fragment) {
|
642
|
+
this.fragment = importStreamElements(fragment);
|
553
643
|
}
|
554
644
|
}
|
555
645
|
|
556
646
|
StreamMessage.contentType = "text/vnd.turbo-stream.html";
|
557
647
|
|
648
|
+
function importStreamElements(fragment) {
|
649
|
+
for (const element of fragment.querySelectorAll("turbo-stream")) {
|
650
|
+
const streamElement = document.importNode(element, true);
|
651
|
+
for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) {
|
652
|
+
inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));
|
653
|
+
}
|
654
|
+
element.replaceWith(streamElement);
|
655
|
+
}
|
656
|
+
return fragment;
|
657
|
+
}
|
658
|
+
|
558
659
|
var FormSubmissionState;
|
559
660
|
|
560
661
|
(function(FormSubmissionState) {
|
@@ -588,6 +689,9 @@ function formEnctypeFromString(encoding) {
|
|
588
689
|
}
|
589
690
|
|
590
691
|
class FormSubmission {
|
692
|
+
static confirmMethod(message, _element, _submitter) {
|
693
|
+
return Promise.resolve(confirm(message));
|
694
|
+
}
|
591
695
|
constructor(delegate, formElement, submitter, mustRedirect = false) {
|
592
696
|
this.state = FormSubmissionState.initialized;
|
593
697
|
this.delegate = delegate;
|
@@ -601,9 +705,6 @@ class FormSubmission {
|
|
601
705
|
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
|
602
706
|
this.mustRedirect = mustRedirect;
|
603
707
|
}
|
604
|
-
static confirmMethod(message, element) {
|
605
|
-
return confirm(message);
|
606
|
-
}
|
607
708
|
get method() {
|
608
709
|
var _a;
|
609
710
|
const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
|
@@ -612,7 +713,11 @@ class FormSubmission {
|
|
612
713
|
get action() {
|
613
714
|
var _a;
|
614
715
|
const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null;
|
615
|
-
|
716
|
+
if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute("formaction")) {
|
717
|
+
return this.submitter.getAttribute("formaction") || "";
|
718
|
+
} else {
|
719
|
+
return this.formElement.getAttribute("action") || formElementAction || "";
|
720
|
+
}
|
616
721
|
}
|
617
722
|
get body() {
|
618
723
|
if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
|
@@ -625,22 +730,17 @@ class FormSubmission {
|
|
625
730
|
var _a;
|
626
731
|
return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
|
627
732
|
}
|
628
|
-
get
|
629
|
-
return this.fetchRequest.
|
733
|
+
get isSafe() {
|
734
|
+
return this.fetchRequest.isSafe;
|
630
735
|
}
|
631
736
|
get stringFormData() {
|
632
737
|
return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
|
633
738
|
}
|
634
|
-
get confirmationMessage() {
|
635
|
-
return this.formElement.getAttribute("data-turbo-confirm");
|
636
|
-
}
|
637
|
-
get needsConfirmation() {
|
638
|
-
return this.confirmationMessage !== null;
|
639
|
-
}
|
640
739
|
async start() {
|
641
740
|
const {initialized: initialized, requesting: requesting} = FormSubmissionState;
|
642
|
-
|
643
|
-
|
741
|
+
const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
|
742
|
+
if (typeof confirmationMessage === "string") {
|
743
|
+
const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);
|
644
744
|
if (!answer) {
|
645
745
|
return;
|
646
746
|
}
|
@@ -658,19 +758,22 @@ class FormSubmission {
|
|
658
758
|
return true;
|
659
759
|
}
|
660
760
|
}
|
661
|
-
|
662
|
-
if (!request.
|
761
|
+
prepareRequest(request) {
|
762
|
+
if (!request.isSafe) {
|
663
763
|
const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
|
664
764
|
if (token) {
|
665
|
-
headers["X-CSRF-Token"] = token;
|
765
|
+
request.headers["X-CSRF-Token"] = token;
|
666
766
|
}
|
667
|
-
|
767
|
+
}
|
768
|
+
if (this.requestAcceptsTurboStreamResponse(request)) {
|
769
|
+
request.acceptResponseType(StreamMessage.contentType);
|
668
770
|
}
|
669
771
|
}
|
670
|
-
requestStarted(
|
772
|
+
requestStarted(_request) {
|
671
773
|
var _a;
|
672
774
|
this.state = FormSubmissionState.waiting;
|
673
775
|
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
|
776
|
+
this.setSubmitsWith();
|
674
777
|
dispatch("turbo:submit-start", {
|
675
778
|
target: this.formElement,
|
676
779
|
detail: {
|
@@ -714,10 +817,11 @@ class FormSubmission {
|
|
714
817
|
};
|
715
818
|
this.delegate.formSubmissionErrored(this, error);
|
716
819
|
}
|
717
|
-
requestFinished(
|
820
|
+
requestFinished(_request) {
|
718
821
|
var _a;
|
719
822
|
this.state = FormSubmissionState.stopped;
|
720
823
|
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
|
824
|
+
this.resetSubmitterText();
|
721
825
|
dispatch("turbo:submit-end", {
|
722
826
|
target: this.formElement,
|
723
827
|
detail: Object.assign({
|
@@ -726,8 +830,35 @@ class FormSubmission {
|
|
726
830
|
});
|
727
831
|
this.delegate.formSubmissionFinished(this);
|
728
832
|
}
|
833
|
+
setSubmitsWith() {
|
834
|
+
if (!this.submitter || !this.submitsWith) return;
|
835
|
+
if (this.submitter.matches("button")) {
|
836
|
+
this.originalSubmitText = this.submitter.innerHTML;
|
837
|
+
this.submitter.innerHTML = this.submitsWith;
|
838
|
+
} else if (this.submitter.matches("input")) {
|
839
|
+
const input = this.submitter;
|
840
|
+
this.originalSubmitText = input.value;
|
841
|
+
input.value = this.submitsWith;
|
842
|
+
}
|
843
|
+
}
|
844
|
+
resetSubmitterText() {
|
845
|
+
if (!this.submitter || !this.originalSubmitText) return;
|
846
|
+
if (this.submitter.matches("button")) {
|
847
|
+
this.submitter.innerHTML = this.originalSubmitText;
|
848
|
+
} else if (this.submitter.matches("input")) {
|
849
|
+
const input = this.submitter;
|
850
|
+
input.value = this.originalSubmitText;
|
851
|
+
}
|
852
|
+
}
|
729
853
|
requestMustRedirect(request) {
|
730
|
-
return !request.
|
854
|
+
return !request.isSafe && this.mustRedirect;
|
855
|
+
}
|
856
|
+
requestAcceptsTurboStreamResponse(request) {
|
857
|
+
return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
|
858
|
+
}
|
859
|
+
get submitsWith() {
|
860
|
+
var _a;
|
861
|
+
return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with");
|
731
862
|
}
|
732
863
|
}
|
733
864
|
|
@@ -735,8 +866,8 @@ function buildFormData(formElement, submitter) {
|
|
735
866
|
const formData = new FormData(formElement);
|
736
867
|
const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
|
737
868
|
const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
|
738
|
-
if (name
|
739
|
-
formData.append(name, value);
|
869
|
+
if (name) {
|
870
|
+
formData.append(name, value || "");
|
740
871
|
}
|
741
872
|
return formData;
|
742
873
|
}
|
@@ -752,11 +883,6 @@ function getCookieValue(cookieName) {
|
|
752
883
|
}
|
753
884
|
}
|
754
885
|
|
755
|
-
function getMetaContent(name) {
|
756
|
-
const element = document.querySelector(`meta[name="${name}"]`);
|
757
|
-
return element && element.content;
|
758
|
-
}
|
759
|
-
|
760
886
|
function responseSucceededWithoutRedirect(response) {
|
761
887
|
return response.statusCode == 200 && !response.redirected;
|
762
888
|
}
|
@@ -775,6 +901,9 @@ class Snapshot {
|
|
775
901
|
constructor(element) {
|
776
902
|
this.element = element;
|
777
903
|
}
|
904
|
+
get activeElement() {
|
905
|
+
return this.element.ownerDocument.activeElement;
|
906
|
+
}
|
778
907
|
get children() {
|
779
908
|
return [ ...this.element.children ];
|
780
909
|
}
|
@@ -788,13 +917,17 @@ class Snapshot {
|
|
788
917
|
return this.element.isConnected;
|
789
918
|
}
|
790
919
|
get firstAutofocusableElement() {
|
791
|
-
|
920
|
+
const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
|
921
|
+
for (const element of this.element.querySelectorAll("[autofocus]")) {
|
922
|
+
if (element.closest(inertDisabledOrHidden) == null) return element; else continue;
|
923
|
+
}
|
924
|
+
return null;
|
792
925
|
}
|
793
926
|
get permanentElements() {
|
794
|
-
return
|
927
|
+
return queryPermanentElementsAll(this.element);
|
795
928
|
}
|
796
929
|
getPermanentElementById(id) {
|
797
|
-
return this.element
|
930
|
+
return getPermanentElementById(this.element, id);
|
798
931
|
}
|
799
932
|
getPermanentElementMapForSnapshot(snapshot) {
|
800
933
|
const permanentElementMap = {};
|
@@ -809,35 +942,70 @@ class Snapshot {
|
|
809
942
|
}
|
810
943
|
}
|
811
944
|
|
812
|
-
|
813
|
-
|
945
|
+
function getPermanentElementById(node, id) {
|
946
|
+
return node.querySelector(`#${id}[data-turbo-permanent]`);
|
947
|
+
}
|
948
|
+
|
949
|
+
function queryPermanentElementsAll(node) {
|
950
|
+
return node.querySelectorAll("[id][data-turbo-permanent]");
|
951
|
+
}
|
952
|
+
|
953
|
+
class FormSubmitObserver {
|
954
|
+
constructor(delegate, eventTarget) {
|
955
|
+
this.started = false;
|
956
|
+
this.submitCaptured = () => {
|
957
|
+
this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
|
958
|
+
this.eventTarget.addEventListener("submit", this.submitBubbled, false);
|
959
|
+
};
|
814
960
|
this.submitBubbled = event => {
|
815
|
-
|
816
|
-
|
961
|
+
if (!event.defaultPrevented) {
|
962
|
+
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
817
963
|
const submitter = event.submitter || undefined;
|
818
|
-
|
819
|
-
if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
|
964
|
+
if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
|
820
965
|
event.preventDefault();
|
821
966
|
event.stopImmediatePropagation();
|
822
|
-
this.delegate.
|
967
|
+
this.delegate.formSubmitted(form, submitter);
|
823
968
|
}
|
824
969
|
}
|
825
970
|
};
|
826
971
|
this.delegate = delegate;
|
827
|
-
this.
|
972
|
+
this.eventTarget = eventTarget;
|
828
973
|
}
|
829
974
|
start() {
|
830
|
-
|
975
|
+
if (!this.started) {
|
976
|
+
this.eventTarget.addEventListener("submit", this.submitCaptured, true);
|
977
|
+
this.started = true;
|
978
|
+
}
|
831
979
|
}
|
832
980
|
stop() {
|
833
|
-
|
981
|
+
if (this.started) {
|
982
|
+
this.eventTarget.removeEventListener("submit", this.submitCaptured, true);
|
983
|
+
this.started = false;
|
984
|
+
}
|
985
|
+
}
|
986
|
+
}
|
987
|
+
|
988
|
+
function submissionDoesNotDismissDialog(form, submitter) {
|
989
|
+
const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
|
990
|
+
return method != "dialog";
|
991
|
+
}
|
992
|
+
|
993
|
+
function submissionDoesNotTargetIFrame(form, submitter) {
|
994
|
+
if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute("formtarget")) || form.hasAttribute("target")) {
|
995
|
+
const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formtarget")) || form.target;
|
996
|
+
for (const element of document.getElementsByName(target)) {
|
997
|
+
if (element instanceof HTMLIFrameElement) return false;
|
998
|
+
}
|
999
|
+
return true;
|
1000
|
+
} else {
|
1001
|
+
return true;
|
834
1002
|
}
|
835
1003
|
}
|
836
1004
|
|
837
1005
|
class View {
|
838
1006
|
constructor(delegate, element) {
|
839
|
-
this.resolveRenderPromise =
|
840
|
-
this.resolveInterceptionPromise =
|
1007
|
+
this.resolveRenderPromise = _value => {};
|
1008
|
+
this.resolveInterceptionPromise = _value => {};
|
841
1009
|
this.delegate = delegate;
|
842
1010
|
this.element = element;
|
843
1011
|
}
|
@@ -888,12 +1056,17 @@ class View {
|
|
888
1056
|
try {
|
889
1057
|
this.renderPromise = new Promise((resolve => this.resolveRenderPromise = resolve));
|
890
1058
|
this.renderer = renderer;
|
891
|
-
this.prepareToRenderSnapshot(renderer);
|
1059
|
+
await this.prepareToRenderSnapshot(renderer);
|
892
1060
|
const renderInterception = new Promise((resolve => this.resolveInterceptionPromise = resolve));
|
893
|
-
const
|
1061
|
+
const options = {
|
1062
|
+
resume: this.resolveInterceptionPromise,
|
1063
|
+
render: this.renderer.renderElement
|
1064
|
+
};
|
1065
|
+
const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
|
894
1066
|
if (!immediateRender) await renderInterception;
|
895
1067
|
await this.renderSnapshot(renderer);
|
896
1068
|
this.delegate.viewRenderedSnapshot(snapshot, isPreview);
|
1069
|
+
this.delegate.preloadOnLoadLinksForView(this.element);
|
897
1070
|
this.finishRenderingSnapshot(renderer);
|
898
1071
|
} finally {
|
899
1072
|
delete this.renderer;
|
@@ -901,15 +1074,15 @@ class View {
|
|
901
1074
|
delete this.renderPromise;
|
902
1075
|
}
|
903
1076
|
} else {
|
904
|
-
this.invalidate();
|
1077
|
+
this.invalidate(renderer.reloadReason);
|
905
1078
|
}
|
906
1079
|
}
|
907
|
-
invalidate() {
|
908
|
-
this.delegate.viewInvalidated();
|
1080
|
+
invalidate(reason) {
|
1081
|
+
this.delegate.viewInvalidated(reason);
|
909
1082
|
}
|
910
|
-
prepareToRenderSnapshot(renderer) {
|
1083
|
+
async prepareToRenderSnapshot(renderer) {
|
911
1084
|
this.markAsPreview(renderer.isPreview);
|
912
|
-
renderer.prepareToRender();
|
1085
|
+
await renderer.prepareToRender();
|
913
1086
|
}
|
914
1087
|
markAsPreview(isPreview) {
|
915
1088
|
if (isPreview) {
|
@@ -927,8 +1100,8 @@ class View {
|
|
927
1100
|
}
|
928
1101
|
|
929
1102
|
class FrameView extends View {
|
930
|
-
|
931
|
-
this.element.innerHTML = ""
|
1103
|
+
missing() {
|
1104
|
+
this.element.innerHTML = `<strong class="turbo-frame-error">Content missing</strong>`;
|
932
1105
|
}
|
933
1106
|
get snapshot() {
|
934
1107
|
return new Snapshot(this.element);
|
@@ -946,15 +1119,15 @@ class LinkInterceptor {
|
|
946
1119
|
};
|
947
1120
|
this.linkClicked = event => {
|
948
1121
|
if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
|
949
|
-
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) {
|
1122
|
+
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
950
1123
|
this.clickEvent.preventDefault();
|
951
1124
|
event.preventDefault();
|
952
|
-
this.delegate.linkClickIntercepted(event.target, event.detail.url);
|
1125
|
+
this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
|
953
1126
|
}
|
954
1127
|
}
|
955
1128
|
delete this.clickEvent;
|
956
1129
|
};
|
957
|
-
this.willVisit =
|
1130
|
+
this.willVisit = _event => {
|
958
1131
|
delete this.clickEvent;
|
959
1132
|
};
|
960
1133
|
this.delegate = delegate;
|
@@ -976,19 +1149,127 @@ class LinkInterceptor {
|
|
976
1149
|
}
|
977
1150
|
}
|
978
1151
|
|
979
|
-
class
|
980
|
-
constructor(
|
981
|
-
this.
|
1152
|
+
class LinkClickObserver {
|
1153
|
+
constructor(delegate, eventTarget) {
|
1154
|
+
this.started = false;
|
1155
|
+
this.clickCaptured = () => {
|
1156
|
+
this.eventTarget.removeEventListener("click", this.clickBubbled, false);
|
1157
|
+
this.eventTarget.addEventListener("click", this.clickBubbled, false);
|
1158
|
+
};
|
1159
|
+
this.clickBubbled = event => {
|
1160
|
+
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
|
1161
|
+
const target = event.composedPath && event.composedPath()[0] || event.target;
|
1162
|
+
const link = this.findLinkFromClickTarget(target);
|
1163
|
+
if (link && doesNotTargetIFrame(link)) {
|
1164
|
+
const location = this.getLocationForLink(link);
|
1165
|
+
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
1166
|
+
event.preventDefault();
|
1167
|
+
this.delegate.followedLinkToLocation(link, location);
|
1168
|
+
}
|
1169
|
+
}
|
1170
|
+
}
|
1171
|
+
};
|
1172
|
+
this.delegate = delegate;
|
1173
|
+
this.eventTarget = eventTarget;
|
1174
|
+
}
|
1175
|
+
start() {
|
1176
|
+
if (!this.started) {
|
1177
|
+
this.eventTarget.addEventListener("click", this.clickCaptured, true);
|
1178
|
+
this.started = true;
|
1179
|
+
}
|
1180
|
+
}
|
1181
|
+
stop() {
|
1182
|
+
if (this.started) {
|
1183
|
+
this.eventTarget.removeEventListener("click", this.clickCaptured, true);
|
1184
|
+
this.started = false;
|
1185
|
+
}
|
1186
|
+
}
|
1187
|
+
clickEventIsSignificant(event) {
|
1188
|
+
return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
|
1189
|
+
}
|
1190
|
+
findLinkFromClickTarget(target) {
|
1191
|
+
return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
|
1192
|
+
}
|
1193
|
+
getLocationForLink(link) {
|
1194
|
+
return expandURL(link.getAttribute("href") || "");
|
1195
|
+
}
|
1196
|
+
}
|
1197
|
+
|
1198
|
+
function doesNotTargetIFrame(anchor) {
|
1199
|
+
if (anchor.hasAttribute("target")) {
|
1200
|
+
for (const element of document.getElementsByName(anchor.target)) {
|
1201
|
+
if (element instanceof HTMLIFrameElement) return false;
|
1202
|
+
}
|
1203
|
+
return true;
|
1204
|
+
} else {
|
1205
|
+
return true;
|
1206
|
+
}
|
1207
|
+
}
|
1208
|
+
|
1209
|
+
class FormLinkClickObserver {
|
1210
|
+
constructor(delegate, element) {
|
1211
|
+
this.delegate = delegate;
|
1212
|
+
this.linkInterceptor = new LinkClickObserver(this, element);
|
1213
|
+
}
|
1214
|
+
start() {
|
1215
|
+
this.linkInterceptor.start();
|
1216
|
+
}
|
1217
|
+
stop() {
|
1218
|
+
this.linkInterceptor.stop();
|
1219
|
+
}
|
1220
|
+
willFollowLinkToLocation(link, location, originalEvent) {
|
1221
|
+
return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
|
1222
|
+
}
|
1223
|
+
followedLinkToLocation(link, location) {
|
1224
|
+
const form = document.createElement("form");
|
1225
|
+
const type = "hidden";
|
1226
|
+
for (const [name, value] of location.searchParams) {
|
1227
|
+
form.append(Object.assign(document.createElement("input"), {
|
1228
|
+
type: type,
|
1229
|
+
name: name,
|
1230
|
+
value: value
|
1231
|
+
}));
|
1232
|
+
}
|
1233
|
+
const action = Object.assign(location, {
|
1234
|
+
search: ""
|
1235
|
+
});
|
1236
|
+
form.setAttribute("data-turbo", "true");
|
1237
|
+
form.setAttribute("action", action.href);
|
1238
|
+
form.setAttribute("hidden", "");
|
1239
|
+
const method = link.getAttribute("data-turbo-method");
|
1240
|
+
if (method) form.setAttribute("method", method);
|
1241
|
+
const turboFrame = link.getAttribute("data-turbo-frame");
|
1242
|
+
if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
|
1243
|
+
const turboAction = getVisitAction(link);
|
1244
|
+
if (turboAction) form.setAttribute("data-turbo-action", turboAction);
|
1245
|
+
const turboConfirm = link.getAttribute("data-turbo-confirm");
|
1246
|
+
if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
|
1247
|
+
const turboStream = link.hasAttribute("data-turbo-stream");
|
1248
|
+
if (turboStream) form.setAttribute("data-turbo-stream", "");
|
1249
|
+
this.delegate.submittedFormLinkToLocation(link, location, form);
|
1250
|
+
document.body.appendChild(form);
|
1251
|
+
form.addEventListener("turbo:submit-end", (() => form.remove()), {
|
1252
|
+
once: true
|
1253
|
+
});
|
1254
|
+
requestAnimationFrame((() => form.requestSubmit()));
|
982
1255
|
}
|
983
|
-
|
984
|
-
|
1256
|
+
}
|
1257
|
+
|
1258
|
+
class Bardo {
|
1259
|
+
static async preservingPermanentElements(delegate, permanentElementMap, callback) {
|
1260
|
+
const bardo = new this(delegate, permanentElementMap);
|
985
1261
|
bardo.enter();
|
986
|
-
callback();
|
1262
|
+
await callback();
|
987
1263
|
bardo.leave();
|
988
1264
|
}
|
1265
|
+
constructor(delegate, permanentElementMap) {
|
1266
|
+
this.delegate = delegate;
|
1267
|
+
this.permanentElementMap = permanentElementMap;
|
1268
|
+
}
|
989
1269
|
enter() {
|
990
1270
|
for (const id in this.permanentElementMap) {
|
991
|
-
const [, newPermanentElement] = this.permanentElementMap[id];
|
1271
|
+
const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
|
1272
|
+
this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);
|
992
1273
|
this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
|
993
1274
|
}
|
994
1275
|
}
|
@@ -997,6 +1278,7 @@ class Bardo {
|
|
997
1278
|
const [currentPermanentElement] = this.permanentElementMap[id];
|
998
1279
|
this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
|
999
1280
|
this.replacePlaceholderWithPermanentElement(currentPermanentElement);
|
1281
|
+
this.delegate.leavingBardo(currentPermanentElement);
|
1000
1282
|
}
|
1001
1283
|
}
|
1002
1284
|
replaceNewPermanentElementWithPlaceholder(permanentElement) {
|
@@ -1027,11 +1309,13 @@ function createPlaceholderForPermanentElement(permanentElement) {
|
|
1027
1309
|
}
|
1028
1310
|
|
1029
1311
|
class Renderer {
|
1030
|
-
constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
|
1312
|
+
constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
1313
|
+
this.activeElement = null;
|
1031
1314
|
this.currentSnapshot = currentSnapshot;
|
1032
1315
|
this.newSnapshot = newSnapshot;
|
1033
1316
|
this.isPreview = isPreview;
|
1034
1317
|
this.willRender = willRender;
|
1318
|
+
this.renderElement = renderElement;
|
1035
1319
|
this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
|
1036
1320
|
resolve: resolve,
|
1037
1321
|
reject: reject
|
@@ -1040,6 +1324,9 @@ class Renderer {
|
|
1040
1324
|
get shouldRender() {
|
1041
1325
|
return true;
|
1042
1326
|
}
|
1327
|
+
get reloadReason() {
|
1328
|
+
return;
|
1329
|
+
}
|
1043
1330
|
prepareToRender() {
|
1044
1331
|
return;
|
1045
1332
|
}
|
@@ -1049,22 +1336,8 @@ class Renderer {
|
|
1049
1336
|
delete this.resolvingFunctions;
|
1050
1337
|
}
|
1051
1338
|
}
|
1052
|
-
|
1053
|
-
|
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
|
-
preservingPermanentElements(callback) {
|
1067
|
-
Bardo.preservingPermanentElements(this.permanentElementMap, callback);
|
1339
|
+
async preservingPermanentElements(callback) {
|
1340
|
+
await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);
|
1068
1341
|
}
|
1069
1342
|
focusFirstAutofocusableElement() {
|
1070
1343
|
const element = this.connectedSnapshot.firstAutofocusableElement;
|
@@ -1072,6 +1345,18 @@ class Renderer {
|
|
1072
1345
|
element.focus();
|
1073
1346
|
}
|
1074
1347
|
}
|
1348
|
+
enteringBardo(currentPermanentElement) {
|
1349
|
+
if (this.activeElement) return;
|
1350
|
+
if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
|
1351
|
+
this.activeElement = this.currentSnapshot.activeElement;
|
1352
|
+
}
|
1353
|
+
}
|
1354
|
+
leavingBardo(currentPermanentElement) {
|
1355
|
+
if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {
|
1356
|
+
this.activeElement.focus();
|
1357
|
+
this.activeElement = null;
|
1358
|
+
}
|
1359
|
+
}
|
1075
1360
|
get connectedSnapshot() {
|
1076
1361
|
return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
|
1077
1362
|
}
|
@@ -1084,16 +1369,6 @@ class Renderer {
|
|
1084
1369
|
get permanentElementMap() {
|
1085
1370
|
return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
|
1086
1371
|
}
|
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
1372
|
}
|
1098
1373
|
|
1099
1374
|
function elementIsFocusable(element) {
|
@@ -1101,6 +1376,22 @@ function elementIsFocusable(element) {
|
|
1101
1376
|
}
|
1102
1377
|
|
1103
1378
|
class FrameRenderer extends Renderer {
|
1379
|
+
static renderElement(currentElement, newElement) {
|
1380
|
+
var _a;
|
1381
|
+
const destinationRange = document.createRange();
|
1382
|
+
destinationRange.selectNodeContents(currentElement);
|
1383
|
+
destinationRange.deleteContents();
|
1384
|
+
const frameElement = newElement;
|
1385
|
+
const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
|
1386
|
+
if (sourceRange) {
|
1387
|
+
sourceRange.selectNodeContents(frameElement);
|
1388
|
+
currentElement.appendChild(sourceRange.extractContents());
|
1389
|
+
}
|
1390
|
+
}
|
1391
|
+
constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
1392
|
+
super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
|
1393
|
+
this.delegate = delegate;
|
1394
|
+
}
|
1104
1395
|
get shouldRender() {
|
1105
1396
|
return true;
|
1106
1397
|
}
|
@@ -1116,24 +1407,18 @@ class FrameRenderer extends Renderer {
|
|
1116
1407
|
this.activateScriptElements();
|
1117
1408
|
}
|
1118
1409
|
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
|
-
}
|
1410
|
+
this.delegate.willRenderFrame(this.currentElement, this.newElement);
|
1411
|
+
this.renderElement(this.currentElement, this.newElement);
|
1129
1412
|
}
|
1130
1413
|
scrollFrameIntoView() {
|
1131
1414
|
if (this.currentElement.autoscroll || this.newElement.autoscroll) {
|
1132
1415
|
const element = this.currentElement.firstElementChild;
|
1133
1416
|
const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
|
1417
|
+
const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto");
|
1134
1418
|
if (element) {
|
1135
1419
|
element.scrollIntoView({
|
1136
|
-
block: block
|
1420
|
+
block: block,
|
1421
|
+
behavior: behavior
|
1137
1422
|
});
|
1138
1423
|
return true;
|
1139
1424
|
}
|
@@ -1142,7 +1427,7 @@ class FrameRenderer extends Renderer {
|
|
1142
1427
|
}
|
1143
1428
|
activateScriptElements() {
|
1144
1429
|
for (const inertScriptElement of this.newScriptElements) {
|
1145
|
-
const activatedScriptElement =
|
1430
|
+
const activatedScriptElement = activateScriptElement(inertScriptElement);
|
1146
1431
|
inertScriptElement.replaceWith(activatedScriptElement);
|
1147
1432
|
}
|
1148
1433
|
}
|
@@ -1159,19 +1444,15 @@ function readScrollLogicalPosition(value, defaultValue) {
|
|
1159
1444
|
}
|
1160
1445
|
}
|
1161
1446
|
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
this.trickle = () => {
|
1168
|
-
this.setValue(this.value + Math.random() / 100);
|
1169
|
-
};
|
1170
|
-
this.stylesheetElement = this.createStylesheetElement();
|
1171
|
-
this.progressElement = this.createProgressElement();
|
1172
|
-
this.installStylesheetElement();
|
1173
|
-
this.setValue(0);
|
1447
|
+
function readScrollBehavior(value, defaultValue) {
|
1448
|
+
if (value == "auto" || value == "smooth") {
|
1449
|
+
return value;
|
1450
|
+
} else {
|
1451
|
+
return defaultValue;
|
1174
1452
|
}
|
1453
|
+
}
|
1454
|
+
|
1455
|
+
class ProgressBar {
|
1175
1456
|
static get defaultCSS() {
|
1176
1457
|
return unindent`
|
1177
1458
|
.turbo-progress-bar {
|
@@ -1181,7 +1462,7 @@ class ProgressBar {
|
|
1181
1462
|
left: 0;
|
1182
1463
|
height: 3px;
|
1183
1464
|
background: #0076ff;
|
1184
|
-
z-index:
|
1465
|
+
z-index: 2147483647;
|
1185
1466
|
transition:
|
1186
1467
|
width ${ProgressBar.animationDuration}ms ease-out,
|
1187
1468
|
opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
|
@@ -1189,6 +1470,18 @@ class ProgressBar {
|
|
1189
1470
|
}
|
1190
1471
|
`;
|
1191
1472
|
}
|
1473
|
+
constructor() {
|
1474
|
+
this.hiding = false;
|
1475
|
+
this.value = 0;
|
1476
|
+
this.visible = false;
|
1477
|
+
this.trickle = () => {
|
1478
|
+
this.setValue(this.value + Math.random() / 100);
|
1479
|
+
};
|
1480
|
+
this.stylesheetElement = this.createStylesheetElement();
|
1481
|
+
this.progressElement = this.createProgressElement();
|
1482
|
+
this.installStylesheetElement();
|
1483
|
+
this.setValue(0);
|
1484
|
+
}
|
1192
1485
|
show() {
|
1193
1486
|
if (!this.visible) {
|
1194
1487
|
this.visible = true;
|
@@ -1247,6 +1540,9 @@ class ProgressBar {
|
|
1247
1540
|
const element = document.createElement("style");
|
1248
1541
|
element.type = "text/css";
|
1249
1542
|
element.textContent = ProgressBar.defaultCSS;
|
1543
|
+
if (this.cspNonce) {
|
1544
|
+
element.nonce = this.cspNonce;
|
1545
|
+
}
|
1250
1546
|
return element;
|
1251
1547
|
}
|
1252
1548
|
createProgressElement() {
|
@@ -1254,6 +1550,9 @@ class ProgressBar {
|
|
1254
1550
|
element.className = "turbo-progress-bar";
|
1255
1551
|
return element;
|
1256
1552
|
}
|
1553
|
+
get cspNonce() {
|
1554
|
+
return getMetaContent("csp-nonce");
|
1555
|
+
}
|
1257
1556
|
}
|
1258
1557
|
|
1259
1558
|
ProgressBar.animationDuration = 300;
|
@@ -1324,22 +1623,22 @@ function elementIsTracked(element) {
|
|
1324
1623
|
}
|
1325
1624
|
|
1326
1625
|
function elementIsScript(element) {
|
1327
|
-
const tagName = element.
|
1626
|
+
const tagName = element.localName;
|
1328
1627
|
return tagName == "script";
|
1329
1628
|
}
|
1330
1629
|
|
1331
1630
|
function elementIsNoscript(element) {
|
1332
|
-
const tagName = element.
|
1631
|
+
const tagName = element.localName;
|
1333
1632
|
return tagName == "noscript";
|
1334
1633
|
}
|
1335
1634
|
|
1336
1635
|
function elementIsStylesheet(element) {
|
1337
|
-
const tagName = element.
|
1636
|
+
const tagName = element.localName;
|
1338
1637
|
return tagName == "style" || tagName == "link" && element.getAttribute("rel") == "stylesheet";
|
1339
1638
|
}
|
1340
1639
|
|
1341
1640
|
function elementIsMetaElementWithName(element, name) {
|
1342
|
-
const tagName = element.
|
1641
|
+
const tagName = element.localName;
|
1343
1642
|
return tagName == "meta" && element.getAttribute("name") == name;
|
1344
1643
|
}
|
1345
1644
|
|
@@ -1351,10 +1650,6 @@ function elementWithoutNonce(element) {
|
|
1351
1650
|
}
|
1352
1651
|
|
1353
1652
|
class PageSnapshot extends Snapshot {
|
1354
|
-
constructor(element, headSnapshot) {
|
1355
|
-
super(element);
|
1356
|
-
this.headSnapshot = headSnapshot;
|
1357
|
-
}
|
1358
1653
|
static fromHTMLString(html = "") {
|
1359
1654
|
return this.fromDocument(parseHTMLDocument(html));
|
1360
1655
|
}
|
@@ -1364,8 +1659,23 @@ class PageSnapshot extends Snapshot {
|
|
1364
1659
|
static fromDocument({head: head, body: body}) {
|
1365
1660
|
return new this(body, new HeadSnapshot(head));
|
1366
1661
|
}
|
1662
|
+
constructor(element, headSnapshot) {
|
1663
|
+
super(element);
|
1664
|
+
this.headSnapshot = headSnapshot;
|
1665
|
+
}
|
1367
1666
|
clone() {
|
1368
|
-
|
1667
|
+
const clonedElement = this.element.cloneNode(true);
|
1668
|
+
const selectElements = this.element.querySelectorAll("select");
|
1669
|
+
const clonedSelectElements = clonedElement.querySelectorAll("select");
|
1670
|
+
for (const [index, source] of selectElements.entries()) {
|
1671
|
+
const clone = clonedSelectElements[index];
|
1672
|
+
for (const option of clone.selectedOptions) option.selected = false;
|
1673
|
+
for (const option of source.selectedOptions) clone.options[option.index].selected = true;
|
1674
|
+
}
|
1675
|
+
for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
|
1676
|
+
clonedPasswordInput.value = "";
|
1677
|
+
}
|
1678
|
+
return new PageSnapshot(clonedElement, this.headSnapshot);
|
1369
1679
|
}
|
1370
1680
|
get headElement() {
|
1371
1681
|
return this.headSnapshot.element;
|
@@ -1415,7 +1725,10 @@ const defaultOptions = {
|
|
1415
1725
|
action: "advance",
|
1416
1726
|
historyChanged: false,
|
1417
1727
|
visitCachedSnapshot: () => {},
|
1418
|
-
willRender: true
|
1728
|
+
willRender: true,
|
1729
|
+
updateHistory: true,
|
1730
|
+
shouldCacheSnapshot: true,
|
1731
|
+
acceptsStreamResponse: false
|
1419
1732
|
};
|
1420
1733
|
|
1421
1734
|
var SystemStatusCode;
|
@@ -1433,21 +1746,27 @@ class Visit {
|
|
1433
1746
|
this.followedRedirect = false;
|
1434
1747
|
this.historyChanged = false;
|
1435
1748
|
this.scrolled = false;
|
1749
|
+
this.shouldCacheSnapshot = true;
|
1750
|
+
this.acceptsStreamResponse = false;
|
1436
1751
|
this.snapshotCached = false;
|
1437
1752
|
this.state = VisitState.initialized;
|
1438
1753
|
this.delegate = delegate;
|
1439
1754
|
this.location = location;
|
1440
1755
|
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);
|
1756
|
+
const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = Object.assign(Object.assign({}, defaultOptions), options);
|
1442
1757
|
this.action = action;
|
1443
1758
|
this.historyChanged = historyChanged;
|
1444
1759
|
this.referrer = referrer;
|
1760
|
+
this.snapshot = snapshot;
|
1445
1761
|
this.snapshotHTML = snapshotHTML;
|
1446
1762
|
this.response = response;
|
1447
1763
|
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
1448
1764
|
this.visitCachedSnapshot = visitCachedSnapshot;
|
1449
1765
|
this.willRender = willRender;
|
1766
|
+
this.updateHistory = updateHistory;
|
1450
1767
|
this.scrolled = !willRender;
|
1768
|
+
this.shouldCacheSnapshot = shouldCacheSnapshot;
|
1769
|
+
this.acceptsStreamResponse = acceptsStreamResponse;
|
1451
1770
|
}
|
1452
1771
|
get adapter() {
|
1453
1772
|
return this.delegate.adapter;
|
@@ -1485,9 +1804,11 @@ class Visit {
|
|
1485
1804
|
if (this.state == VisitState.started) {
|
1486
1805
|
this.recordTimingMetric(TimingMetric.visitEnd);
|
1487
1806
|
this.state = VisitState.completed;
|
1488
|
-
this.adapter.visitCompleted(this);
|
1489
|
-
this.delegate.visitCompleted(this);
|
1490
1807
|
this.followRedirect();
|
1808
|
+
if (!this.followedRedirect) {
|
1809
|
+
this.adapter.visitCompleted(this);
|
1810
|
+
this.delegate.visitCompleted(this);
|
1811
|
+
}
|
1491
1812
|
}
|
1492
1813
|
}
|
1493
1814
|
fail() {
|
@@ -1498,9 +1819,9 @@ class Visit {
|
|
1498
1819
|
}
|
1499
1820
|
changeHistory() {
|
1500
1821
|
var _a;
|
1501
|
-
if (!this.historyChanged) {
|
1822
|
+
if (!this.historyChanged && this.updateHistory) {
|
1502
1823
|
const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
|
1503
|
-
const method =
|
1824
|
+
const method = getHistoryMethodForAction(actionForHistory);
|
1504
1825
|
this.history.update(method, this.location, this.restorationIdentifier);
|
1505
1826
|
this.historyChanged = true;
|
1506
1827
|
}
|
@@ -1543,14 +1864,15 @@ class Visit {
|
|
1543
1864
|
if (this.response) {
|
1544
1865
|
const {statusCode: statusCode, responseHTML: responseHTML} = this.response;
|
1545
1866
|
this.render((async () => {
|
1546
|
-
this.cacheSnapshot();
|
1867
|
+
if (this.shouldCacheSnapshot) this.cacheSnapshot();
|
1547
1868
|
if (this.view.renderPromise) await this.view.renderPromise;
|
1548
1869
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
1549
|
-
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
|
1870
|
+
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);
|
1871
|
+
this.performScroll();
|
1550
1872
|
this.adapter.visitRendered(this);
|
1551
1873
|
this.complete();
|
1552
1874
|
} else {
|
1553
|
-
await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
|
1875
|
+
await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
|
1554
1876
|
this.adapter.visitRendered(this);
|
1555
1877
|
this.fail();
|
1556
1878
|
}
|
@@ -1583,7 +1905,8 @@ class Visit {
|
|
1583
1905
|
this.adapter.visitRendered(this);
|
1584
1906
|
} else {
|
1585
1907
|
if (this.view.renderPromise) await this.view.renderPromise;
|
1586
|
-
await this.view.renderPage(snapshot, isPreview, this.willRender);
|
1908
|
+
await this.view.renderPage(snapshot, isPreview, this.willRender, this);
|
1909
|
+
this.performScroll();
|
1587
1910
|
this.adapter.visitRendered(this);
|
1588
1911
|
if (!isPreview) {
|
1589
1912
|
this.complete();
|
@@ -1597,7 +1920,9 @@ class Visit {
|
|
1597
1920
|
if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
|
1598
1921
|
this.adapter.visitProposedToLocation(this.redirectedToLocation, {
|
1599
1922
|
action: "replace",
|
1600
|
-
response: this.response
|
1923
|
+
response: this.response,
|
1924
|
+
shouldCacheSnapshot: false,
|
1925
|
+
willRender: false
|
1601
1926
|
});
|
1602
1927
|
this.followedRedirect = true;
|
1603
1928
|
}
|
@@ -1606,14 +1931,21 @@ class Visit {
|
|
1606
1931
|
if (this.isSamePage) {
|
1607
1932
|
this.render((async () => {
|
1608
1933
|
this.cacheSnapshot();
|
1934
|
+
this.performScroll();
|
1935
|
+
this.changeHistory();
|
1609
1936
|
this.adapter.visitRendered(this);
|
1610
1937
|
}));
|
1611
1938
|
}
|
1612
1939
|
}
|
1940
|
+
prepareRequest(request) {
|
1941
|
+
if (this.acceptsStreamResponse) {
|
1942
|
+
request.acceptResponseType(StreamMessage.contentType);
|
1943
|
+
}
|
1944
|
+
}
|
1613
1945
|
requestStarted() {
|
1614
1946
|
this.startRequest();
|
1615
1947
|
}
|
1616
|
-
requestPreventedHandlingResponse(
|
1948
|
+
requestPreventedHandlingResponse(_request, _response) {}
|
1617
1949
|
async requestSucceededWithResponse(request, response) {
|
1618
1950
|
const responseHTML = await response.responseHTML;
|
1619
1951
|
const {redirected: redirected, statusCode: statusCode} = response;
|
@@ -1647,7 +1979,7 @@ class Visit {
|
|
1647
1979
|
});
|
1648
1980
|
}
|
1649
1981
|
}
|
1650
|
-
requestErrored(
|
1982
|
+
requestErrored(_request, _error) {
|
1651
1983
|
this.recordResponse({
|
1652
1984
|
statusCode: SystemStatusCode.networkFailure,
|
1653
1985
|
redirected: false
|
@@ -1657,7 +1989,7 @@ class Visit {
|
|
1657
1989
|
this.finishRequest();
|
1658
1990
|
}
|
1659
1991
|
performScroll() {
|
1660
|
-
if (!this.scrolled) {
|
1992
|
+
if (!this.scrolled && !this.view.forceReloaded) {
|
1661
1993
|
if (this.action == "restore") {
|
1662
1994
|
this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
|
1663
1995
|
} else {
|
@@ -1713,7 +2045,7 @@ class Visit {
|
|
1713
2045
|
}
|
1714
2046
|
cacheSnapshot() {
|
1715
2047
|
if (!this.snapshotCached) {
|
1716
|
-
this.view.cacheSnapshot().then((snapshot => snapshot && this.visitCachedSnapshot(snapshot)));
|
2048
|
+
this.view.cacheSnapshot(this.snapshot).then((snapshot => snapshot && this.visitCachedSnapshot(snapshot)));
|
1717
2049
|
this.snapshotCached = true;
|
1718
2050
|
}
|
1719
2051
|
}
|
@@ -1724,7 +2056,6 @@ class Visit {
|
|
1724
2056
|
}));
|
1725
2057
|
await callback();
|
1726
2058
|
delete this.frame;
|
1727
|
-
this.performScroll();
|
1728
2059
|
}
|
1729
2060
|
cancelRender() {
|
1730
2061
|
if (this.frame) {
|
@@ -1747,12 +2078,12 @@ class BrowserAdapter {
|
|
1747
2078
|
this.session = session;
|
1748
2079
|
}
|
1749
2080
|
visitProposedToLocation(location, options) {
|
1750
|
-
this.navigator.startVisit(location, uuid(), options);
|
2081
|
+
this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
|
1751
2082
|
}
|
1752
2083
|
visitStarted(visit) {
|
2084
|
+
this.location = visit.location;
|
1753
2085
|
visit.loadCachedSnapshot();
|
1754
2086
|
visit.issueRequest();
|
1755
|
-
visit.changeHistory();
|
1756
2087
|
visit.goToSamePageAnchor();
|
1757
2088
|
}
|
1758
2089
|
visitRequestStarted(visit) {
|
@@ -1771,27 +2102,32 @@ class BrowserAdapter {
|
|
1771
2102
|
case SystemStatusCode.networkFailure:
|
1772
2103
|
case SystemStatusCode.timeoutFailure:
|
1773
2104
|
case SystemStatusCode.contentTypeMismatch:
|
1774
|
-
return this.reload(
|
2105
|
+
return this.reload({
|
2106
|
+
reason: "request_failed",
|
2107
|
+
context: {
|
2108
|
+
statusCode: statusCode
|
2109
|
+
}
|
2110
|
+
});
|
1775
2111
|
|
1776
2112
|
default:
|
1777
2113
|
return visit.loadResponse();
|
1778
2114
|
}
|
1779
2115
|
}
|
1780
|
-
visitRequestFinished(
|
2116
|
+
visitRequestFinished(_visit) {
|
1781
2117
|
this.progressBar.setValue(1);
|
1782
2118
|
this.hideVisitProgressBar();
|
1783
2119
|
}
|
1784
|
-
visitCompleted(
|
1785
|
-
pageInvalidated() {
|
1786
|
-
this.reload();
|
2120
|
+
visitCompleted(_visit) {}
|
2121
|
+
pageInvalidated(reason) {
|
2122
|
+
this.reload(reason);
|
1787
2123
|
}
|
1788
|
-
visitFailed(
|
1789
|
-
visitRendered(
|
1790
|
-
formSubmissionStarted(
|
2124
|
+
visitFailed(_visit) {}
|
2125
|
+
visitRendered(_visit) {}
|
2126
|
+
formSubmissionStarted(_formSubmission) {
|
1791
2127
|
this.progressBar.setValue(0);
|
1792
2128
|
this.showFormProgressBarAfterDelay();
|
1793
2129
|
}
|
1794
|
-
formSubmissionFinished(
|
2130
|
+
formSubmissionFinished(_formSubmission) {
|
1795
2131
|
this.progressBar.setValue(1);
|
1796
2132
|
this.hideFormProgressBar();
|
1797
2133
|
}
|
@@ -1817,8 +2153,12 @@ class BrowserAdapter {
|
|
1817
2153
|
delete this.formProgressBarTimeout;
|
1818
2154
|
}
|
1819
2155
|
}
|
1820
|
-
reload() {
|
1821
|
-
|
2156
|
+
reload(reason) {
|
2157
|
+
var _a;
|
2158
|
+
dispatch("turbo:reload", {
|
2159
|
+
detail: reason
|
2160
|
+
});
|
2161
|
+
window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;
|
1822
2162
|
}
|
1823
2163
|
get navigator() {
|
1824
2164
|
return this.session.navigator;
|
@@ -1827,95 +2167,70 @@ class BrowserAdapter {
|
|
1827
2167
|
|
1828
2168
|
class CacheObserver {
|
1829
2169
|
constructor() {
|
2170
|
+
this.selector = "[data-turbo-temporary]";
|
2171
|
+
this.deprecatedSelector = "[data-turbo-cache=false]";
|
1830
2172
|
this.started = false;
|
2173
|
+
this.removeTemporaryElements = _event => {
|
2174
|
+
for (const element of this.temporaryElements) {
|
2175
|
+
element.remove();
|
2176
|
+
}
|
2177
|
+
};
|
1831
2178
|
}
|
1832
2179
|
start() {
|
1833
2180
|
if (!this.started) {
|
1834
2181
|
this.started = true;
|
1835
|
-
addEventListener("turbo:before-cache", this.
|
2182
|
+
addEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
1836
2183
|
}
|
1837
2184
|
}
|
1838
2185
|
stop() {
|
1839
2186
|
if (this.started) {
|
1840
2187
|
this.started = false;
|
1841
|
-
removeEventListener("turbo:before-cache", this.
|
1842
|
-
}
|
1843
|
-
}
|
1844
|
-
removeStaleElements() {
|
1845
|
-
const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
|
1846
|
-
for (const element of staleElements) {
|
1847
|
-
element.remove();
|
2188
|
+
removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
1848
2189
|
}
|
1849
2190
|
}
|
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
|
-
}
|
1870
|
-
}
|
1871
|
-
};
|
1872
|
-
this.delegate = delegate;
|
1873
|
-
}
|
1874
|
-
start() {
|
1875
|
-
if (!this.started) {
|
1876
|
-
addEventListener("submit", this.submitCaptured, true);
|
1877
|
-
this.started = true;
|
1878
|
-
}
|
2191
|
+
get temporaryElements() {
|
2192
|
+
return [ ...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation ];
|
1879
2193
|
}
|
1880
|
-
|
1881
|
-
|
1882
|
-
|
1883
|
-
this.
|
2194
|
+
get temporaryElementsWithDeprecation() {
|
2195
|
+
const elements = document.querySelectorAll(this.deprecatedSelector);
|
2196
|
+
if (elements.length) {
|
2197
|
+
console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);
|
1884
2198
|
}
|
2199
|
+
return [ ...elements ];
|
1885
2200
|
}
|
1886
2201
|
}
|
1887
2202
|
|
1888
2203
|
class FrameRedirector {
|
1889
|
-
constructor(element) {
|
2204
|
+
constructor(session, element) {
|
2205
|
+
this.session = session;
|
1890
2206
|
this.element = element;
|
1891
2207
|
this.linkInterceptor = new LinkInterceptor(this, element);
|
1892
|
-
this.
|
2208
|
+
this.formSubmitObserver = new FormSubmitObserver(this, element);
|
1893
2209
|
}
|
1894
2210
|
start() {
|
1895
2211
|
this.linkInterceptor.start();
|
1896
|
-
this.
|
2212
|
+
this.formSubmitObserver.start();
|
1897
2213
|
}
|
1898
2214
|
stop() {
|
1899
2215
|
this.linkInterceptor.stop();
|
1900
|
-
this.
|
2216
|
+
this.formSubmitObserver.stop();
|
1901
2217
|
}
|
1902
|
-
shouldInterceptLinkClick(element,
|
2218
|
+
shouldInterceptLinkClick(element, _location, _event) {
|
1903
2219
|
return this.shouldRedirect(element);
|
1904
2220
|
}
|
1905
|
-
linkClickIntercepted(element, url) {
|
2221
|
+
linkClickIntercepted(element, url, event) {
|
1906
2222
|
const frame = this.findFrameElement(element);
|
1907
2223
|
if (frame) {
|
1908
|
-
frame.delegate.linkClickIntercepted(element, url);
|
2224
|
+
frame.delegate.linkClickIntercepted(element, url, event);
|
1909
2225
|
}
|
1910
2226
|
}
|
1911
|
-
|
1912
|
-
return this.shouldSubmit(element, submitter);
|
2227
|
+
willSubmitForm(element, submitter) {
|
2228
|
+
return element.closest("turbo-frame") == null && this.shouldSubmit(element, submitter) && this.shouldRedirect(element, submitter);
|
1913
2229
|
}
|
1914
|
-
|
2230
|
+
formSubmitted(element, submitter) {
|
1915
2231
|
const frame = this.findFrameElement(element, submitter);
|
1916
2232
|
if (frame) {
|
1917
|
-
frame.
|
1918
|
-
frame.delegate.formSubmissionIntercepted(element, submitter);
|
2233
|
+
frame.delegate.formSubmitted(element, submitter);
|
1919
2234
|
}
|
1920
2235
|
}
|
1921
2236
|
shouldSubmit(form, submitter) {
|
@@ -1926,8 +2241,13 @@ class FrameRedirector {
|
|
1926
2241
|
return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
|
1927
2242
|
}
|
1928
2243
|
shouldRedirect(element, submitter) {
|
1929
|
-
const
|
1930
|
-
|
2244
|
+
const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
|
2245
|
+
if (isNavigatable) {
|
2246
|
+
const frame = this.findFrameElement(element, submitter);
|
2247
|
+
return frame ? frame != element.closest("turbo-frame") : false;
|
2248
|
+
} else {
|
2249
|
+
return false;
|
2250
|
+
}
|
1931
2251
|
}
|
1932
2252
|
findFrameElement(element, submitter) {
|
1933
2253
|
const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
|
@@ -1957,7 +2277,7 @@ class History {
|
|
1957
2277
|
}
|
1958
2278
|
}
|
1959
2279
|
};
|
1960
|
-
this.onPageLoad = async
|
2280
|
+
this.onPageLoad = async _event => {
|
1961
2281
|
await nextMicrotask();
|
1962
2282
|
this.pageLoaded = true;
|
1963
2283
|
};
|
@@ -2023,57 +2343,6 @@ class History {
|
|
2023
2343
|
}
|
2024
2344
|
}
|
2025
2345
|
|
2026
|
-
class LinkClickObserver {
|
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
2346
|
class Navigator {
|
2078
2347
|
constructor(delegate) {
|
2079
2348
|
this.delegate = delegate;
|
@@ -2127,13 +2396,15 @@ class Navigator {
|
|
2127
2396
|
if (formSubmission == this.formSubmission) {
|
2128
2397
|
const responseHTML = await fetchResponse.responseHTML;
|
2129
2398
|
if (responseHTML) {
|
2130
|
-
|
2399
|
+
const shouldCacheSnapshot = formSubmission.isSafe;
|
2400
|
+
if (!shouldCacheSnapshot) {
|
2131
2401
|
this.view.clearSnapshotCache();
|
2132
2402
|
}
|
2133
2403
|
const {statusCode: statusCode, redirected: redirected} = fetchResponse;
|
2134
2404
|
const action = this.getActionForFormSubmission(formSubmission);
|
2135
2405
|
const visitOptions = {
|
2136
2406
|
action: action,
|
2407
|
+
shouldCacheSnapshot: shouldCacheSnapshot,
|
2137
2408
|
response: {
|
2138
2409
|
statusCode: statusCode,
|
2139
2410
|
responseHTML: responseHTML,
|
@@ -2149,9 +2420,9 @@ class Navigator {
|
|
2149
2420
|
if (responseHTML) {
|
2150
2421
|
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
2151
2422
|
if (fetchResponse.serverError) {
|
2152
|
-
await this.view.renderError(snapshot);
|
2423
|
+
await this.view.renderError(snapshot, this.currentVisit);
|
2153
2424
|
} else {
|
2154
|
-
await this.view.renderPage(snapshot);
|
2425
|
+
await this.view.renderPage(snapshot, false, true, this.currentVisit);
|
2155
2426
|
}
|
2156
2427
|
this.view.scrollToTop();
|
2157
2428
|
this.view.clearSnapshotCache();
|
@@ -2186,10 +2457,8 @@ class Navigator {
|
|
2186
2457
|
get restorationIdentifier() {
|
2187
2458
|
return this.history.restorationIdentifier;
|
2188
2459
|
}
|
2189
|
-
getActionForFormSubmission(
|
2190
|
-
|
2191
|
-
const action = getAttribute("data-turbo-action", submitter, formElement);
|
2192
|
-
return isAction(action) ? action : "advance";
|
2460
|
+
getActionForFormSubmission({submitter: submitter, formElement: formElement}) {
|
2461
|
+
return getVisitAction(submitter, formElement) || "advance";
|
2193
2462
|
}
|
2194
2463
|
}
|
2195
2464
|
|
@@ -2283,6 +2552,31 @@ class ScrollObserver {
|
|
2283
2552
|
}
|
2284
2553
|
}
|
2285
2554
|
|
2555
|
+
class StreamMessageRenderer {
|
2556
|
+
render({fragment: fragment}) {
|
2557
|
+
Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => document.documentElement.appendChild(fragment)));
|
2558
|
+
}
|
2559
|
+
enteringBardo(currentPermanentElement, newPermanentElement) {
|
2560
|
+
newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
|
2561
|
+
}
|
2562
|
+
leavingBardo() {}
|
2563
|
+
}
|
2564
|
+
|
2565
|
+
function getPermanentElementMapForFragment(fragment) {
|
2566
|
+
const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
|
2567
|
+
const permanentElementMap = {};
|
2568
|
+
for (const permanentElementInDocument of permanentElementsInDocument) {
|
2569
|
+
const {id: id} = permanentElementInDocument;
|
2570
|
+
for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
|
2571
|
+
const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
|
2572
|
+
if (elementInStream) {
|
2573
|
+
permanentElementMap[id] = [ permanentElementInDocument, elementInStream ];
|
2574
|
+
}
|
2575
|
+
}
|
2576
|
+
}
|
2577
|
+
return permanentElementMap;
|
2578
|
+
}
|
2579
|
+
|
2286
2580
|
class StreamObserver {
|
2287
2581
|
constructor(delegate) {
|
2288
2582
|
this.sources = new Set;
|
@@ -2335,7 +2629,7 @@ class StreamObserver {
|
|
2335
2629
|
}
|
2336
2630
|
}
|
2337
2631
|
receiveMessageHTML(html) {
|
2338
|
-
this.delegate.receivedMessageFromStream(
|
2632
|
+
this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
|
2339
2633
|
}
|
2340
2634
|
}
|
2341
2635
|
|
@@ -2354,20 +2648,24 @@ function fetchResponseIsStream(response) {
|
|
2354
2648
|
}
|
2355
2649
|
|
2356
2650
|
class ErrorRenderer extends Renderer {
|
2651
|
+
static renderElement(currentElement, newElement) {
|
2652
|
+
const {documentElement: documentElement, body: body} = document;
|
2653
|
+
documentElement.replaceChild(newElement, body);
|
2654
|
+
}
|
2357
2655
|
async render() {
|
2358
2656
|
this.replaceHeadAndBody();
|
2359
2657
|
this.activateScriptElements();
|
2360
2658
|
}
|
2361
2659
|
replaceHeadAndBody() {
|
2362
|
-
const {documentElement: documentElement, head: head
|
2660
|
+
const {documentElement: documentElement, head: head} = document;
|
2363
2661
|
documentElement.replaceChild(this.newHead, head);
|
2364
|
-
|
2662
|
+
this.renderElement(this.currentElement, this.newElement);
|
2365
2663
|
}
|
2366
2664
|
activateScriptElements() {
|
2367
2665
|
for (const replaceableElement of this.scriptElements) {
|
2368
2666
|
const parentNode = replaceableElement.parentNode;
|
2369
2667
|
if (parentNode) {
|
2370
|
-
const element =
|
2668
|
+
const element = activateScriptElement(replaceableElement);
|
2371
2669
|
parentNode.replaceChild(element, replaceableElement);
|
2372
2670
|
}
|
2373
2671
|
}
|
@@ -2376,20 +2674,39 @@ class ErrorRenderer extends Renderer {
|
|
2376
2674
|
return this.newSnapshot.headSnapshot.element;
|
2377
2675
|
}
|
2378
2676
|
get scriptElements() {
|
2379
|
-
return
|
2677
|
+
return document.documentElement.querySelectorAll("script");
|
2380
2678
|
}
|
2381
2679
|
}
|
2382
2680
|
|
2383
2681
|
class PageRenderer extends Renderer {
|
2682
|
+
static renderElement(currentElement, newElement) {
|
2683
|
+
if (document.body && newElement instanceof HTMLBodyElement) {
|
2684
|
+
document.body.replaceWith(newElement);
|
2685
|
+
} else {
|
2686
|
+
document.documentElement.appendChild(newElement);
|
2687
|
+
}
|
2688
|
+
}
|
2384
2689
|
get shouldRender() {
|
2385
2690
|
return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
|
2386
2691
|
}
|
2387
|
-
|
2388
|
-
this.
|
2692
|
+
get reloadReason() {
|
2693
|
+
if (!this.newSnapshot.isVisitable) {
|
2694
|
+
return {
|
2695
|
+
reason: "turbo_visit_control_is_reload"
|
2696
|
+
};
|
2697
|
+
}
|
2698
|
+
if (!this.trackedElementsAreIdentical) {
|
2699
|
+
return {
|
2700
|
+
reason: "tracked_element_mismatch"
|
2701
|
+
};
|
2702
|
+
}
|
2703
|
+
}
|
2704
|
+
async prepareToRender() {
|
2705
|
+
await this.mergeHead();
|
2389
2706
|
}
|
2390
2707
|
async render() {
|
2391
2708
|
if (this.willRender) {
|
2392
|
-
this.replaceBody();
|
2709
|
+
await this.replaceBody();
|
2393
2710
|
}
|
2394
2711
|
}
|
2395
2712
|
finishRendering() {
|
@@ -2407,31 +2724,64 @@ class PageRenderer extends Renderer {
|
|
2407
2724
|
get newElement() {
|
2408
2725
|
return this.newSnapshot.element;
|
2409
2726
|
}
|
2410
|
-
mergeHead() {
|
2411
|
-
this.
|
2727
|
+
async mergeHead() {
|
2728
|
+
const mergedHeadElements = this.mergeProvisionalElements();
|
2729
|
+
const newStylesheetElements = this.copyNewHeadStylesheetElements();
|
2412
2730
|
this.copyNewHeadScriptElements();
|
2413
|
-
|
2414
|
-
|
2731
|
+
await mergedHeadElements;
|
2732
|
+
await newStylesheetElements;
|
2415
2733
|
}
|
2416
|
-
replaceBody() {
|
2417
|
-
this.preservingPermanentElements((() => {
|
2734
|
+
async replaceBody() {
|
2735
|
+
await this.preservingPermanentElements((async () => {
|
2418
2736
|
this.activateNewBody();
|
2419
|
-
this.assignNewBody();
|
2737
|
+
await this.assignNewBody();
|
2420
2738
|
}));
|
2421
2739
|
}
|
2422
2740
|
get trackedElementsAreIdentical() {
|
2423
2741
|
return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
|
2424
2742
|
}
|
2425
|
-
copyNewHeadStylesheetElements() {
|
2743
|
+
async copyNewHeadStylesheetElements() {
|
2744
|
+
const loadingElements = [];
|
2426
2745
|
for (const element of this.newHeadStylesheetElements) {
|
2746
|
+
loadingElements.push(waitForLoad(element));
|
2427
2747
|
document.head.appendChild(element);
|
2428
2748
|
}
|
2749
|
+
await Promise.all(loadingElements);
|
2429
2750
|
}
|
2430
2751
|
copyNewHeadScriptElements() {
|
2431
2752
|
for (const element of this.newHeadScriptElements) {
|
2432
|
-
document.head.appendChild(
|
2753
|
+
document.head.appendChild(activateScriptElement(element));
|
2754
|
+
}
|
2755
|
+
}
|
2756
|
+
async mergeProvisionalElements() {
|
2757
|
+
const newHeadElements = [ ...this.newHeadProvisionalElements ];
|
2758
|
+
for (const element of this.currentHeadProvisionalElements) {
|
2759
|
+
if (!this.isCurrentElementInElementList(element, newHeadElements)) {
|
2760
|
+
document.head.removeChild(element);
|
2761
|
+
}
|
2762
|
+
}
|
2763
|
+
for (const element of newHeadElements) {
|
2764
|
+
document.head.appendChild(element);
|
2433
2765
|
}
|
2434
2766
|
}
|
2767
|
+
isCurrentElementInElementList(element, elementList) {
|
2768
|
+
for (const [index, newElement] of elementList.entries()) {
|
2769
|
+
if (element.tagName == "TITLE") {
|
2770
|
+
if (newElement.tagName != "TITLE") {
|
2771
|
+
continue;
|
2772
|
+
}
|
2773
|
+
if (element.innerHTML == newElement.innerHTML) {
|
2774
|
+
elementList.splice(index, 1);
|
2775
|
+
return true;
|
2776
|
+
}
|
2777
|
+
}
|
2778
|
+
if (newElement.isEqualNode(element)) {
|
2779
|
+
elementList.splice(index, 1);
|
2780
|
+
return true;
|
2781
|
+
}
|
2782
|
+
}
|
2783
|
+
return false;
|
2784
|
+
}
|
2435
2785
|
removeCurrentHeadProvisionalElements() {
|
2436
2786
|
for (const element of this.currentHeadProvisionalElements) {
|
2437
2787
|
document.head.removeChild(element);
|
@@ -2448,16 +2798,12 @@ class PageRenderer extends Renderer {
|
|
2448
2798
|
}
|
2449
2799
|
activateNewBodyScriptElements() {
|
2450
2800
|
for (const inertScriptElement of this.newBodyScriptElements) {
|
2451
|
-
const activatedScriptElement =
|
2801
|
+
const activatedScriptElement = activateScriptElement(inertScriptElement);
|
2452
2802
|
inertScriptElement.replaceWith(activatedScriptElement);
|
2453
2803
|
}
|
2454
2804
|
}
|
2455
|
-
assignNewBody() {
|
2456
|
-
|
2457
|
-
document.body.replaceWith(this.newElement);
|
2458
|
-
} else {
|
2459
|
-
document.documentElement.appendChild(this.newElement);
|
2460
|
-
}
|
2805
|
+
async assignNewBody() {
|
2806
|
+
await this.renderElement(this.currentElement, this.newElement);
|
2461
2807
|
}
|
2462
2808
|
get newHeadStylesheetElements() {
|
2463
2809
|
return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
|
@@ -2525,22 +2871,29 @@ class PageView extends View {
|
|
2525
2871
|
super(...arguments);
|
2526
2872
|
this.snapshotCache = new SnapshotCache(10);
|
2527
2873
|
this.lastRenderedLocation = new URL(location.href);
|
2874
|
+
this.forceReloaded = false;
|
2528
2875
|
}
|
2529
|
-
renderPage(snapshot, isPreview = false, willRender = true) {
|
2530
|
-
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
|
2876
|
+
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
2877
|
+
const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
|
2878
|
+
if (!renderer.shouldRender) {
|
2879
|
+
this.forceReloaded = true;
|
2880
|
+
} else {
|
2881
|
+
visit === null || visit === void 0 ? void 0 : visit.changeHistory();
|
2882
|
+
}
|
2531
2883
|
return this.render(renderer);
|
2532
2884
|
}
|
2533
|
-
renderError(snapshot) {
|
2534
|
-
|
2885
|
+
renderError(snapshot, visit) {
|
2886
|
+
visit === null || visit === void 0 ? void 0 : visit.changeHistory();
|
2887
|
+
const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
|
2535
2888
|
return this.render(renderer);
|
2536
2889
|
}
|
2537
2890
|
clearSnapshotCache() {
|
2538
2891
|
this.snapshotCache.clear();
|
2539
2892
|
}
|
2540
|
-
async cacheSnapshot() {
|
2541
|
-
if (
|
2893
|
+
async cacheSnapshot(snapshot = this.snapshot) {
|
2894
|
+
if (snapshot.isCacheable) {
|
2542
2895
|
this.delegate.viewWillCacheSnapshot();
|
2543
|
-
const {
|
2896
|
+
const {lastRenderedLocation: location} = this;
|
2544
2897
|
await nextEventLoopTick();
|
2545
2898
|
const cachedSnapshot = snapshot.clone();
|
2546
2899
|
this.snapshotCache.put(location, cachedSnapshot);
|
@@ -2553,8 +2906,46 @@ class PageView extends View {
|
|
2553
2906
|
get snapshot() {
|
2554
2907
|
return PageSnapshot.fromElement(this.element);
|
2555
2908
|
}
|
2556
|
-
|
2557
|
-
|
2909
|
+
}
|
2910
|
+
|
2911
|
+
class Preloader {
|
2912
|
+
constructor(delegate) {
|
2913
|
+
this.selector = "a[data-turbo-preload]";
|
2914
|
+
this.delegate = delegate;
|
2915
|
+
}
|
2916
|
+
get snapshotCache() {
|
2917
|
+
return this.delegate.navigator.view.snapshotCache;
|
2918
|
+
}
|
2919
|
+
start() {
|
2920
|
+
if (document.readyState === "loading") {
|
2921
|
+
return document.addEventListener("DOMContentLoaded", (() => {
|
2922
|
+
this.preloadOnLoadLinksForView(document.body);
|
2923
|
+
}));
|
2924
|
+
} else {
|
2925
|
+
this.preloadOnLoadLinksForView(document.body);
|
2926
|
+
}
|
2927
|
+
}
|
2928
|
+
preloadOnLoadLinksForView(element) {
|
2929
|
+
for (const link of element.querySelectorAll(this.selector)) {
|
2930
|
+
this.preloadURL(link);
|
2931
|
+
}
|
2932
|
+
}
|
2933
|
+
async preloadURL(link) {
|
2934
|
+
const location = new URL(link.href);
|
2935
|
+
if (this.snapshotCache.has(location)) {
|
2936
|
+
return;
|
2937
|
+
}
|
2938
|
+
try {
|
2939
|
+
const response = await fetch(location.toString(), {
|
2940
|
+
headers: {
|
2941
|
+
"VND.PREFETCH": "true",
|
2942
|
+
Accept: "text/html"
|
2943
|
+
}
|
2944
|
+
});
|
2945
|
+
const responseText = await response.text();
|
2946
|
+
const snapshot = PageSnapshot.fromHTMLString(responseText);
|
2947
|
+
this.snapshotCache.put(location, snapshot);
|
2948
|
+
} catch (_) {}
|
2558
2949
|
}
|
2559
2950
|
}
|
2560
2951
|
|
@@ -2562,30 +2953,36 @@ class Session {
|
|
2562
2953
|
constructor() {
|
2563
2954
|
this.navigator = new Navigator(this);
|
2564
2955
|
this.history = new History(this);
|
2956
|
+
this.preloader = new Preloader(this);
|
2565
2957
|
this.view = new PageView(this, document.documentElement);
|
2566
2958
|
this.adapter = new BrowserAdapter(this);
|
2567
2959
|
this.pageObserver = new PageObserver(this);
|
2568
2960
|
this.cacheObserver = new CacheObserver;
|
2569
|
-
this.linkClickObserver = new LinkClickObserver(this);
|
2570
|
-
this.formSubmitObserver = new FormSubmitObserver(this);
|
2961
|
+
this.linkClickObserver = new LinkClickObserver(this, window);
|
2962
|
+
this.formSubmitObserver = new FormSubmitObserver(this, document);
|
2571
2963
|
this.scrollObserver = new ScrollObserver(this);
|
2572
2964
|
this.streamObserver = new StreamObserver(this);
|
2573
|
-
this.
|
2965
|
+
this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
|
2966
|
+
this.frameRedirector = new FrameRedirector(this, document.documentElement);
|
2967
|
+
this.streamMessageRenderer = new StreamMessageRenderer;
|
2574
2968
|
this.drive = true;
|
2575
2969
|
this.enabled = true;
|
2576
2970
|
this.progressBarDelay = 500;
|
2577
2971
|
this.started = false;
|
2972
|
+
this.formMode = "on";
|
2578
2973
|
}
|
2579
2974
|
start() {
|
2580
2975
|
if (!this.started) {
|
2581
2976
|
this.pageObserver.start();
|
2582
2977
|
this.cacheObserver.start();
|
2978
|
+
this.formLinkClickObserver.start();
|
2583
2979
|
this.linkClickObserver.start();
|
2584
2980
|
this.formSubmitObserver.start();
|
2585
2981
|
this.scrollObserver.start();
|
2586
2982
|
this.streamObserver.start();
|
2587
2983
|
this.frameRedirector.start();
|
2588
2984
|
this.history.start();
|
2985
|
+
this.preloader.start();
|
2589
2986
|
this.started = true;
|
2590
2987
|
this.enabled = true;
|
2591
2988
|
}
|
@@ -2597,6 +2994,7 @@ class Session {
|
|
2597
2994
|
if (this.started) {
|
2598
2995
|
this.pageObserver.stop();
|
2599
2996
|
this.cacheObserver.stop();
|
2997
|
+
this.formLinkClickObserver.stop();
|
2600
2998
|
this.linkClickObserver.stop();
|
2601
2999
|
this.formSubmitObserver.stop();
|
2602
3000
|
this.scrollObserver.stop();
|
@@ -2610,7 +3008,13 @@ class Session {
|
|
2610
3008
|
this.adapter = adapter;
|
2611
3009
|
}
|
2612
3010
|
visit(location, options = {}) {
|
2613
|
-
|
3011
|
+
const frameElement = options.frame ? document.getElementById(options.frame) : null;
|
3012
|
+
if (frameElement instanceof FrameElement) {
|
3013
|
+
frameElement.src = location.toString();
|
3014
|
+
frameElement.loaded;
|
3015
|
+
} else {
|
3016
|
+
this.navigator.proposeVisit(expandURL(location), options);
|
3017
|
+
}
|
2614
3018
|
}
|
2615
3019
|
connectStreamSource(source) {
|
2616
3020
|
this.streamObserver.connectStreamSource(source);
|
@@ -2619,7 +3023,7 @@ class Session {
|
|
2619
3023
|
this.streamObserver.disconnectStreamSource(source);
|
2620
3024
|
}
|
2621
3025
|
renderStreamMessage(message) {
|
2622
|
-
|
3026
|
+
this.streamMessageRenderer.render(StreamMessage.wrap(message));
|
2623
3027
|
}
|
2624
3028
|
clearCache() {
|
2625
3029
|
this.view.clearSnapshotCache();
|
@@ -2627,6 +3031,9 @@ class Session {
|
|
2627
3031
|
setProgressBarDelay(delay) {
|
2628
3032
|
this.progressBarDelay = delay;
|
2629
3033
|
}
|
3034
|
+
setFormMode(mode) {
|
3035
|
+
this.formMode = mode;
|
3036
|
+
}
|
2630
3037
|
get location() {
|
2631
3038
|
return this.history.location;
|
2632
3039
|
}
|
@@ -2640,7 +3047,9 @@ class Session {
|
|
2640
3047
|
historyChanged: true
|
2641
3048
|
});
|
2642
3049
|
} else {
|
2643
|
-
this.adapter.pageInvalidated(
|
3050
|
+
this.adapter.pageInvalidated({
|
3051
|
+
reason: "turbo_disabled"
|
3052
|
+
});
|
2644
3053
|
}
|
2645
3054
|
}
|
2646
3055
|
scrollPositionChanged(position) {
|
@@ -2648,41 +3057,21 @@ class Session {
|
|
2648
3057
|
scrollPosition: position
|
2649
3058
|
});
|
2650
3059
|
}
|
2651
|
-
|
2652
|
-
return this.
|
3060
|
+
willSubmitFormLinkToLocation(link, location) {
|
3061
|
+
return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
|
3062
|
+
}
|
3063
|
+
submittedFormLinkToLocation() {}
|
3064
|
+
willFollowLinkToLocation(link, location, event) {
|
3065
|
+
return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
|
2653
3066
|
}
|
2654
3067
|
followedLinkToLocation(link, location) {
|
2655
3068
|
const action = this.getActionForLink(link);
|
2656
|
-
|
2657
|
-
|
3069
|
+
const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
|
3070
|
+
this.visit(location.href, {
|
3071
|
+
action: action,
|
3072
|
+
acceptsStreamResponse: acceptsStreamResponse
|
2658
3073
|
});
|
2659
3074
|
}
|
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
3075
|
allowsVisitingLocationWithAction(location, action) {
|
2687
3076
|
return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
|
2688
3077
|
}
|
@@ -2691,12 +3080,16 @@ class Session {
|
|
2691
3080
|
this.adapter.visitProposedToLocation(location, options);
|
2692
3081
|
}
|
2693
3082
|
visitStarted(visit) {
|
3083
|
+
if (!visit.acceptsStreamResponse) {
|
3084
|
+
markAsBusy(document.documentElement);
|
3085
|
+
}
|
2694
3086
|
extendURLWithDeprecatedProperties(visit.location);
|
2695
3087
|
if (!visit.silent) {
|
2696
3088
|
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
|
2697
3089
|
}
|
2698
3090
|
}
|
2699
3091
|
visitCompleted(visit) {
|
3092
|
+
clearBusyState(document.documentElement);
|
2700
3093
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
2701
3094
|
}
|
2702
3095
|
locationWithActionIsSamePage(location, action) {
|
@@ -2707,7 +3100,7 @@ class Session {
|
|
2707
3100
|
}
|
2708
3101
|
willSubmitForm(form, submitter) {
|
2709
3102
|
const action = getAction(form, submitter);
|
2710
|
-
return this.
|
3103
|
+
return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
|
2711
3104
|
}
|
2712
3105
|
formSubmitted(form, submitter) {
|
2713
3106
|
this.navigator.submitForm(form, submitter);
|
@@ -2731,16 +3124,23 @@ class Session {
|
|
2731
3124
|
this.notifyApplicationBeforeCachingSnapshot();
|
2732
3125
|
}
|
2733
3126
|
}
|
2734
|
-
allowsImmediateRender({element: element},
|
2735
|
-
const event = this.notifyApplicationBeforeRender(element,
|
2736
|
-
|
3127
|
+
allowsImmediateRender({element: element}, options) {
|
3128
|
+
const event = this.notifyApplicationBeforeRender(element, options);
|
3129
|
+
const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
|
3130
|
+
if (this.view.renderer && render) {
|
3131
|
+
this.view.renderer.renderElement = render;
|
3132
|
+
}
|
3133
|
+
return !defaultPrevented;
|
2737
3134
|
}
|
2738
|
-
viewRenderedSnapshot(
|
3135
|
+
viewRenderedSnapshot(_snapshot, _isPreview) {
|
2739
3136
|
this.view.lastRenderedLocation = this.history.location;
|
2740
3137
|
this.notifyApplicationAfterRender();
|
2741
3138
|
}
|
2742
|
-
|
2743
|
-
this.
|
3139
|
+
preloadOnLoadLinksForView(element) {
|
3140
|
+
this.preloader.preloadOnLoadLinksForView(element);
|
3141
|
+
}
|
3142
|
+
viewInvalidated(reason) {
|
3143
|
+
this.adapter.pageInvalidated(reason);
|
2744
3144
|
}
|
2745
3145
|
frameLoaded(frame) {
|
2746
3146
|
this.notifyApplicationAfterFrameLoad(frame);
|
@@ -2748,19 +3148,20 @@ class Session {
|
|
2748
3148
|
frameRendered(fetchResponse, frame) {
|
2749
3149
|
this.notifyApplicationAfterFrameRender(fetchResponse, frame);
|
2750
3150
|
}
|
2751
|
-
applicationAllowsFollowingLinkToLocation(link, location) {
|
2752
|
-
const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
|
3151
|
+
applicationAllowsFollowingLinkToLocation(link, location, ev) {
|
3152
|
+
const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
|
2753
3153
|
return !event.defaultPrevented;
|
2754
3154
|
}
|
2755
3155
|
applicationAllowsVisitingLocation(location) {
|
2756
3156
|
const event = this.notifyApplicationBeforeVisitingLocation(location);
|
2757
3157
|
return !event.defaultPrevented;
|
2758
3158
|
}
|
2759
|
-
notifyApplicationAfterClickingLinkToLocation(link, location) {
|
3159
|
+
notifyApplicationAfterClickingLinkToLocation(link, location, event) {
|
2760
3160
|
return dispatch("turbo:click", {
|
2761
3161
|
target: link,
|
2762
3162
|
detail: {
|
2763
|
-
url: location.href
|
3163
|
+
url: location.href,
|
3164
|
+
originalEvent: event
|
2764
3165
|
},
|
2765
3166
|
cancelable: true
|
2766
3167
|
});
|
@@ -2774,7 +3175,6 @@ class Session {
|
|
2774
3175
|
});
|
2775
3176
|
}
|
2776
3177
|
notifyApplicationAfterVisitingLocation(location, action) {
|
2777
|
-
markAsBusy(document.documentElement);
|
2778
3178
|
return dispatch("turbo:visit", {
|
2779
3179
|
detail: {
|
2780
3180
|
url: location.href,
|
@@ -2785,12 +3185,11 @@ class Session {
|
|
2785
3185
|
notifyApplicationBeforeCachingSnapshot() {
|
2786
3186
|
return dispatch("turbo:before-cache");
|
2787
3187
|
}
|
2788
|
-
notifyApplicationBeforeRender(newBody,
|
3188
|
+
notifyApplicationBeforeRender(newBody, options) {
|
2789
3189
|
return dispatch("turbo:before-render", {
|
2790
|
-
detail: {
|
2791
|
-
newBody: newBody
|
2792
|
-
|
2793
|
-
},
|
3190
|
+
detail: Object.assign({
|
3191
|
+
newBody: newBody
|
3192
|
+
}, options),
|
2794
3193
|
cancelable: true
|
2795
3194
|
});
|
2796
3195
|
}
|
@@ -2798,7 +3197,6 @@ class Session {
|
|
2798
3197
|
return dispatch("turbo:render");
|
2799
3198
|
}
|
2800
3199
|
notifyApplicationAfterPageLoad(timing = {}) {
|
2801
|
-
clearBusyState(document.documentElement);
|
2802
3200
|
return dispatch("turbo:load", {
|
2803
3201
|
detail: {
|
2804
3202
|
url: this.location.href,
|
@@ -2826,9 +3224,22 @@ class Session {
|
|
2826
3224
|
cancelable: true
|
2827
3225
|
});
|
2828
3226
|
}
|
2829
|
-
|
2830
|
-
|
2831
|
-
|
3227
|
+
submissionIsNavigatable(form, submitter) {
|
3228
|
+
if (this.formMode == "off") {
|
3229
|
+
return false;
|
3230
|
+
} else {
|
3231
|
+
const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
|
3232
|
+
if (this.formMode == "optin") {
|
3233
|
+
return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null;
|
3234
|
+
} else {
|
3235
|
+
return submitterIsNavigatable && this.elementIsNavigatable(form);
|
3236
|
+
}
|
3237
|
+
}
|
3238
|
+
}
|
3239
|
+
elementIsNavigatable(element) {
|
3240
|
+
const container = findClosestRecursively(element, "[data-turbo]");
|
3241
|
+
const withinFrame = findClosestRecursively(element, "turbo-frame");
|
3242
|
+
if (this.drive || withinFrame) {
|
2832
3243
|
if (container) {
|
2833
3244
|
return container.getAttribute("data-turbo") != "false";
|
2834
3245
|
} else {
|
@@ -2843,19 +3254,7 @@ class Session {
|
|
2843
3254
|
}
|
2844
3255
|
}
|
2845
3256
|
getActionForLink(link) {
|
2846
|
-
|
2847
|
-
return isAction(action) ? action : "advance";
|
2848
|
-
}
|
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
|
-
}
|
3257
|
+
return getVisitAction(link) || "advance";
|
2859
3258
|
}
|
2860
3259
|
get snapshot() {
|
2861
3260
|
return this.view.snapshot;
|
@@ -2874,8 +3273,66 @@ const deprecatedLocationPropertyDescriptors = {
|
|
2874
3273
|
}
|
2875
3274
|
};
|
2876
3275
|
|
3276
|
+
class Cache {
|
3277
|
+
constructor(session) {
|
3278
|
+
this.session = session;
|
3279
|
+
}
|
3280
|
+
clear() {
|
3281
|
+
this.session.clearCache();
|
3282
|
+
}
|
3283
|
+
resetCacheControl() {
|
3284
|
+
this.setCacheControl("");
|
3285
|
+
}
|
3286
|
+
exemptPageFromCache() {
|
3287
|
+
this.setCacheControl("no-cache");
|
3288
|
+
}
|
3289
|
+
exemptPageFromPreview() {
|
3290
|
+
this.setCacheControl("no-preview");
|
3291
|
+
}
|
3292
|
+
setCacheControl(value) {
|
3293
|
+
setMetaContent("turbo-cache-control", value);
|
3294
|
+
}
|
3295
|
+
}
|
3296
|
+
|
3297
|
+
const StreamActions = {
|
3298
|
+
after() {
|
3299
|
+
this.targetElements.forEach((e => {
|
3300
|
+
var _a;
|
3301
|
+
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
|
3302
|
+
}));
|
3303
|
+
},
|
3304
|
+
append() {
|
3305
|
+
this.removeDuplicateTargetChildren();
|
3306
|
+
this.targetElements.forEach((e => e.append(this.templateContent)));
|
3307
|
+
},
|
3308
|
+
before() {
|
3309
|
+
this.targetElements.forEach((e => {
|
3310
|
+
var _a;
|
3311
|
+
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
|
3312
|
+
}));
|
3313
|
+
},
|
3314
|
+
prepend() {
|
3315
|
+
this.removeDuplicateTargetChildren();
|
3316
|
+
this.targetElements.forEach((e => e.prepend(this.templateContent)));
|
3317
|
+
},
|
3318
|
+
remove() {
|
3319
|
+
this.targetElements.forEach((e => e.remove()));
|
3320
|
+
},
|
3321
|
+
replace() {
|
3322
|
+
this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
|
3323
|
+
},
|
3324
|
+
update() {
|
3325
|
+
this.targetElements.forEach((targetElement => {
|
3326
|
+
targetElement.innerHTML = "";
|
3327
|
+
targetElement.append(this.templateContent);
|
3328
|
+
}));
|
3329
|
+
}
|
3330
|
+
};
|
3331
|
+
|
2877
3332
|
const session = new Session;
|
2878
3333
|
|
3334
|
+
const cache = new Cache(session);
|
3335
|
+
|
2879
3336
|
const {navigator: navigator$1} = session;
|
2880
3337
|
|
2881
3338
|
function start() {
|
@@ -2903,6 +3360,7 @@ function renderStreamMessage(message) {
|
|
2903
3360
|
}
|
2904
3361
|
|
2905
3362
|
function clearCache() {
|
3363
|
+
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
3364
|
session.clearCache();
|
2907
3365
|
}
|
2908
3366
|
|
@@ -2914,12 +3372,18 @@ function setConfirmMethod(confirmMethod) {
|
|
2914
3372
|
FormSubmission.confirmMethod = confirmMethod;
|
2915
3373
|
}
|
2916
3374
|
|
3375
|
+
function setFormMode(mode) {
|
3376
|
+
session.setFormMode(mode);
|
3377
|
+
}
|
3378
|
+
|
2917
3379
|
var Turbo = Object.freeze({
|
2918
3380
|
__proto__: null,
|
2919
3381
|
navigator: navigator$1,
|
2920
3382
|
session: session,
|
3383
|
+
cache: cache,
|
2921
3384
|
PageRenderer: PageRenderer,
|
2922
3385
|
PageSnapshot: PageSnapshot,
|
3386
|
+
FrameRenderer: FrameRenderer,
|
2923
3387
|
start: start,
|
2924
3388
|
registerAdapter: registerAdapter,
|
2925
3389
|
visit: visit,
|
@@ -2928,41 +3392,57 @@ var Turbo = Object.freeze({
|
|
2928
3392
|
renderStreamMessage: renderStreamMessage,
|
2929
3393
|
clearCache: clearCache,
|
2930
3394
|
setProgressBarDelay: setProgressBarDelay,
|
2931
|
-
setConfirmMethod: setConfirmMethod
|
3395
|
+
setConfirmMethod: setConfirmMethod,
|
3396
|
+
setFormMode: setFormMode,
|
3397
|
+
StreamActions: StreamActions
|
2932
3398
|
});
|
2933
3399
|
|
3400
|
+
class TurboFrameMissingError extends Error {}
|
3401
|
+
|
2934
3402
|
class FrameController {
|
2935
3403
|
constructor(element) {
|
2936
|
-
this.fetchResponseLoaded =
|
3404
|
+
this.fetchResponseLoaded = _fetchResponse => {};
|
2937
3405
|
this.currentFetchRequest = null;
|
2938
3406
|
this.resolveVisitPromise = () => {};
|
2939
3407
|
this.connected = false;
|
2940
3408
|
this.hasBeenLoaded = false;
|
2941
|
-
this.
|
3409
|
+
this.ignoredAttributes = new Set;
|
3410
|
+
this.action = null;
|
3411
|
+
this.visitCachedSnapshot = ({element: element}) => {
|
3412
|
+
const frame = element.querySelector("#" + this.element.id);
|
3413
|
+
if (frame && this.previousFrameElement) {
|
3414
|
+
frame.replaceChildren(...this.previousFrameElement.children);
|
3415
|
+
}
|
3416
|
+
delete this.previousFrameElement;
|
3417
|
+
};
|
2942
3418
|
this.element = element;
|
2943
3419
|
this.view = new FrameView(this, this.element);
|
2944
3420
|
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
3421
|
+
this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);
|
2945
3422
|
this.linkInterceptor = new LinkInterceptor(this, this.element);
|
2946
|
-
this.
|
3423
|
+
this.restorationIdentifier = uuid();
|
3424
|
+
this.formSubmitObserver = new FormSubmitObserver(this, this.element);
|
2947
3425
|
}
|
2948
3426
|
connect() {
|
2949
3427
|
if (!this.connected) {
|
2950
3428
|
this.connected = true;
|
2951
|
-
this.reloadable = false;
|
2952
3429
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
2953
3430
|
this.appearanceObserver.start();
|
3431
|
+
} else {
|
3432
|
+
this.loadSourceURL();
|
2954
3433
|
}
|
3434
|
+
this.formLinkClickObserver.start();
|
2955
3435
|
this.linkInterceptor.start();
|
2956
|
-
this.
|
2957
|
-
this.sourceURLChanged();
|
3436
|
+
this.formSubmitObserver.start();
|
2958
3437
|
}
|
2959
3438
|
}
|
2960
3439
|
disconnect() {
|
2961
3440
|
if (this.connected) {
|
2962
3441
|
this.connected = false;
|
2963
3442
|
this.appearanceObserver.stop();
|
3443
|
+
this.formLinkClickObserver.stop();
|
2964
3444
|
this.linkInterceptor.stop();
|
2965
|
-
this.
|
3445
|
+
this.formSubmitObserver.stop();
|
2966
3446
|
}
|
2967
3447
|
}
|
2968
3448
|
disabledChanged() {
|
@@ -2971,10 +3451,27 @@ class FrameController {
|
|
2971
3451
|
}
|
2972
3452
|
}
|
2973
3453
|
sourceURLChanged() {
|
3454
|
+
if (this.isIgnoringChangesTo("src")) return;
|
3455
|
+
if (this.element.isConnected) {
|
3456
|
+
this.complete = false;
|
3457
|
+
}
|
2974
3458
|
if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
|
2975
3459
|
this.loadSourceURL();
|
2976
3460
|
}
|
2977
3461
|
}
|
3462
|
+
sourceURLReloaded() {
|
3463
|
+
const {src: src} = this.element;
|
3464
|
+
this.ignoringChangesToAttribute("complete", (() => {
|
3465
|
+
this.element.removeAttribute("complete");
|
3466
|
+
}));
|
3467
|
+
this.element.src = null;
|
3468
|
+
this.element.src = src;
|
3469
|
+
return this.element.loaded;
|
3470
|
+
}
|
3471
|
+
completeChanged() {
|
3472
|
+
if (this.isIgnoringChangesTo("complete")) return;
|
3473
|
+
this.loadSourceURL();
|
3474
|
+
}
|
2978
3475
|
loadingStyleChanged() {
|
2979
3476
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
2980
3477
|
this.appearanceObserver.start();
|
@@ -2984,20 +3481,11 @@ class FrameController {
|
|
2984
3481
|
}
|
2985
3482
|
}
|
2986
3483
|
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
|
-
}
|
3484
|
+
if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
|
3485
|
+
this.element.loaded = this.visit(expandURL(this.sourceURL));
|
3486
|
+
this.appearanceObserver.stop();
|
3487
|
+
await this.element.loaded;
|
3488
|
+
this.hasBeenLoaded = true;
|
3001
3489
|
}
|
3002
3490
|
}
|
3003
3491
|
async loadResponse(fetchResponse) {
|
@@ -3007,71 +3495,73 @@ class FrameController {
|
|
3007
3495
|
try {
|
3008
3496
|
const html = await fetchResponse.responseHTML;
|
3009
3497
|
if (html) {
|
3010
|
-
const
|
3011
|
-
const
|
3012
|
-
|
3013
|
-
|
3014
|
-
|
3015
|
-
|
3016
|
-
|
3017
|
-
this.fetchResponseLoaded(fetchResponse);
|
3498
|
+
const document = parseHTMLDocument(html);
|
3499
|
+
const pageSnapshot = PageSnapshot.fromDocument(document);
|
3500
|
+
if (pageSnapshot.isVisitable) {
|
3501
|
+
await this.loadFrameResponse(fetchResponse, document);
|
3502
|
+
} else {
|
3503
|
+
await this.handleUnvisitableFrameResponse(fetchResponse);
|
3504
|
+
}
|
3018
3505
|
}
|
3019
|
-
} catch (error) {
|
3020
|
-
console.error(error);
|
3021
|
-
this.view.invalidate();
|
3022
3506
|
} finally {
|
3023
3507
|
this.fetchResponseLoaded = () => {};
|
3024
3508
|
}
|
3025
3509
|
}
|
3026
3510
|
elementAppearedInViewport(element) {
|
3511
|
+
this.proposeVisitIfNavigatedWithAction(element, element);
|
3027
3512
|
this.loadSourceURL();
|
3028
3513
|
}
|
3029
|
-
|
3030
|
-
|
3031
|
-
|
3032
|
-
|
3033
|
-
|
3034
|
-
|
3514
|
+
willSubmitFormLinkToLocation(link) {
|
3515
|
+
return this.shouldInterceptNavigation(link);
|
3516
|
+
}
|
3517
|
+
submittedFormLinkToLocation(link, _location, form) {
|
3518
|
+
const frame = this.findFrameElement(link);
|
3519
|
+
if (frame) form.setAttribute("data-turbo-frame", frame.id);
|
3035
3520
|
}
|
3036
|
-
|
3037
|
-
this.
|
3038
|
-
this.navigateFrame(element, url);
|
3521
|
+
shouldInterceptLinkClick(element, _location, _event) {
|
3522
|
+
return this.shouldInterceptNavigation(element);
|
3039
3523
|
}
|
3040
|
-
|
3041
|
-
|
3524
|
+
linkClickIntercepted(element, location) {
|
3525
|
+
this.navigateFrame(element, location);
|
3042
3526
|
}
|
3043
|
-
|
3527
|
+
willSubmitForm(element, submitter) {
|
3528
|
+
return element.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(element, submitter);
|
3529
|
+
}
|
3530
|
+
formSubmitted(element, submitter) {
|
3044
3531
|
if (this.formSubmission) {
|
3045
3532
|
this.formSubmission.stop();
|
3046
3533
|
}
|
3047
|
-
this.reloadable = false;
|
3048
3534
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
3049
3535
|
const {fetchRequest: fetchRequest} = this.formSubmission;
|
3050
|
-
this.
|
3536
|
+
this.prepareRequest(fetchRequest);
|
3051
3537
|
this.formSubmission.start();
|
3052
3538
|
}
|
3053
|
-
|
3054
|
-
|
3539
|
+
prepareRequest(request) {
|
3540
|
+
var _a;
|
3541
|
+
request.headers["Turbo-Frame"] = this.id;
|
3542
|
+
if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-turbo-stream")) {
|
3543
|
+
request.acceptResponseType(StreamMessage.contentType);
|
3544
|
+
}
|
3055
3545
|
}
|
3056
|
-
requestStarted(
|
3546
|
+
requestStarted(_request) {
|
3057
3547
|
markAsBusy(this.element);
|
3058
3548
|
}
|
3059
|
-
requestPreventedHandlingResponse(
|
3549
|
+
requestPreventedHandlingResponse(_request, _response) {
|
3060
3550
|
this.resolveVisitPromise();
|
3061
3551
|
}
|
3062
3552
|
async requestSucceededWithResponse(request, response) {
|
3063
3553
|
await this.loadResponse(response);
|
3064
3554
|
this.resolveVisitPromise();
|
3065
3555
|
}
|
3066
|
-
requestFailedWithResponse(request, response) {
|
3067
|
-
|
3556
|
+
async requestFailedWithResponse(request, response) {
|
3557
|
+
await this.loadResponse(response);
|
3068
3558
|
this.resolveVisitPromise();
|
3069
3559
|
}
|
3070
3560
|
requestErrored(request, error) {
|
3071
3561
|
console.error(error);
|
3072
3562
|
this.resolveVisitPromise();
|
3073
3563
|
}
|
3074
|
-
requestFinished(
|
3564
|
+
requestFinished(_request) {
|
3075
3565
|
clearBusyState(this.element);
|
3076
3566
|
}
|
3077
3567
|
formSubmissionStarted({formElement: formElement}) {
|
@@ -3079,11 +3569,15 @@ class FrameController {
|
|
3079
3569
|
}
|
3080
3570
|
formSubmissionSucceededWithResponse(formSubmission, response) {
|
3081
3571
|
const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
|
3082
|
-
|
3572
|
+
frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
|
3083
3573
|
frame.delegate.loadResponse(response);
|
3574
|
+
if (!formSubmission.isSafe) {
|
3575
|
+
session.clearCache();
|
3576
|
+
}
|
3084
3577
|
}
|
3085
3578
|
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
3086
3579
|
this.element.delegate.loadResponse(fetchResponse);
|
3580
|
+
session.clearCache();
|
3087
3581
|
}
|
3088
3582
|
formSubmissionErrored(formSubmission, error) {
|
3089
3583
|
console.error(error);
|
@@ -3091,11 +3585,44 @@ class FrameController {
|
|
3091
3585
|
formSubmissionFinished({formElement: formElement}) {
|
3092
3586
|
clearBusyState(formElement, this.findFrameElement(formElement));
|
3093
3587
|
}
|
3094
|
-
allowsImmediateRender(
|
3095
|
-
|
3588
|
+
allowsImmediateRender({element: newFrame}, options) {
|
3589
|
+
const event = dispatch("turbo:before-frame-render", {
|
3590
|
+
target: this.element,
|
3591
|
+
detail: Object.assign({
|
3592
|
+
newFrame: newFrame
|
3593
|
+
}, options),
|
3594
|
+
cancelable: true
|
3595
|
+
});
|
3596
|
+
const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
|
3597
|
+
if (this.view.renderer && render) {
|
3598
|
+
this.view.renderer.renderElement = render;
|
3599
|
+
}
|
3600
|
+
return !defaultPrevented;
|
3601
|
+
}
|
3602
|
+
viewRenderedSnapshot(_snapshot, _isPreview) {}
|
3603
|
+
preloadOnLoadLinksForView(element) {
|
3604
|
+
session.preloadOnLoadLinksForView(element);
|
3096
3605
|
}
|
3097
|
-
viewRenderedSnapshot(snapshot, isPreview) {}
|
3098
3606
|
viewInvalidated() {}
|
3607
|
+
willRenderFrame(currentElement, _newElement) {
|
3608
|
+
this.previousFrameElement = currentElement.cloneNode(true);
|
3609
|
+
}
|
3610
|
+
async loadFrameResponse(fetchResponse, document) {
|
3611
|
+
const newFrameElement = await this.extractForeignFrameElement(document.body);
|
3612
|
+
if (newFrameElement) {
|
3613
|
+
const snapshot = new Snapshot(newFrameElement);
|
3614
|
+
const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
|
3615
|
+
if (this.view.renderPromise) await this.view.renderPromise;
|
3616
|
+
this.changeHistory();
|
3617
|
+
await this.view.render(renderer);
|
3618
|
+
this.complete = true;
|
3619
|
+
session.frameRendered(fetchResponse, this.element);
|
3620
|
+
session.frameLoaded(this.element);
|
3621
|
+
this.fetchResponseLoaded(fetchResponse);
|
3622
|
+
} else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
|
3623
|
+
this.handleFrameMissingFromResponse(fetchResponse);
|
3624
|
+
}
|
3625
|
+
}
|
3099
3626
|
async visit(url) {
|
3100
3627
|
var _a;
|
3101
3628
|
const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
|
@@ -3112,14 +3639,16 @@ class FrameController {
|
|
3112
3639
|
}
|
3113
3640
|
navigateFrame(element, url, submitter) {
|
3114
3641
|
const frame = this.findFrameElement(element, submitter);
|
3115
|
-
|
3116
|
-
|
3117
|
-
|
3642
|
+
frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
|
3643
|
+
this.withCurrentNavigationElement(element, (() => {
|
3644
|
+
frame.src = url;
|
3645
|
+
}));
|
3118
3646
|
}
|
3119
3647
|
proposeVisitIfNavigatedWithAction(frame, element, submitter) {
|
3120
|
-
|
3121
|
-
if (
|
3122
|
-
const
|
3648
|
+
this.action = getVisitAction(submitter, element, frame);
|
3649
|
+
if (this.action) {
|
3650
|
+
const pageSnapshot = PageSnapshot.fromElement(frame).clone();
|
3651
|
+
const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
|
3123
3652
|
frame.delegate.fetchResponseLoaded = fetchResponse => {
|
3124
3653
|
if (frame.src) {
|
3125
3654
|
const {statusCode: statusCode, redirected: redirected} = fetchResponse;
|
@@ -3129,16 +3658,70 @@ class FrameController {
|
|
3129
3658
|
redirected: redirected,
|
3130
3659
|
responseHTML: responseHTML
|
3131
3660
|
};
|
3132
|
-
|
3133
|
-
action: action,
|
3661
|
+
const options = {
|
3134
3662
|
response: response,
|
3135
3663
|
visitCachedSnapshot: visitCachedSnapshot,
|
3136
|
-
willRender: false
|
3137
|
-
|
3664
|
+
willRender: false,
|
3665
|
+
updateHistory: false,
|
3666
|
+
restorationIdentifier: this.restorationIdentifier,
|
3667
|
+
snapshot: pageSnapshot
|
3668
|
+
};
|
3669
|
+
if (this.action) options.action = this.action;
|
3670
|
+
session.visit(frame.src, options);
|
3138
3671
|
}
|
3139
3672
|
};
|
3140
3673
|
}
|
3141
3674
|
}
|
3675
|
+
changeHistory() {
|
3676
|
+
if (this.action) {
|
3677
|
+
const method = getHistoryMethodForAction(this.action);
|
3678
|
+
session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
|
3679
|
+
}
|
3680
|
+
}
|
3681
|
+
async handleUnvisitableFrameResponse(fetchResponse) {
|
3682
|
+
console.warn(`The response (${fetchResponse.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.`);
|
3683
|
+
await this.visitResponse(fetchResponse.response);
|
3684
|
+
}
|
3685
|
+
willHandleFrameMissingFromResponse(fetchResponse) {
|
3686
|
+
this.element.setAttribute("complete", "");
|
3687
|
+
const response = fetchResponse.response;
|
3688
|
+
const visit = async (url, options = {}) => {
|
3689
|
+
if (url instanceof Response) {
|
3690
|
+
this.visitResponse(url);
|
3691
|
+
} else {
|
3692
|
+
session.visit(url, options);
|
3693
|
+
}
|
3694
|
+
};
|
3695
|
+
const event = dispatch("turbo:frame-missing", {
|
3696
|
+
target: this.element,
|
3697
|
+
detail: {
|
3698
|
+
response: response,
|
3699
|
+
visit: visit
|
3700
|
+
},
|
3701
|
+
cancelable: true
|
3702
|
+
});
|
3703
|
+
return !event.defaultPrevented;
|
3704
|
+
}
|
3705
|
+
handleFrameMissingFromResponse(fetchResponse) {
|
3706
|
+
this.view.missing();
|
3707
|
+
this.throwFrameMissingError(fetchResponse);
|
3708
|
+
}
|
3709
|
+
throwFrameMissingError(fetchResponse) {
|
3710
|
+
const message = `The response (${fetchResponse.statusCode}) did not contain the expected <turbo-frame id="${this.element.id}"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;
|
3711
|
+
throw new TurboFrameMissingError(message);
|
3712
|
+
}
|
3713
|
+
async visitResponse(response) {
|
3714
|
+
const wrapped = new FetchResponse(response);
|
3715
|
+
const responseHTML = await wrapped.responseHTML;
|
3716
|
+
const {location: location, redirected: redirected, statusCode: statusCode} = wrapped;
|
3717
|
+
return session.visit(location, {
|
3718
|
+
response: {
|
3719
|
+
redirected: redirected,
|
3720
|
+
statusCode: statusCode,
|
3721
|
+
responseHTML: responseHTML
|
3722
|
+
}
|
3723
|
+
});
|
3724
|
+
}
|
3142
3725
|
findFrameElement(element, submitter) {
|
3143
3726
|
var _a;
|
3144
3727
|
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
@@ -3148,18 +3731,20 @@ class FrameController {
|
|
3148
3731
|
let element;
|
3149
3732
|
const id = CSS.escape(this.id);
|
3150
3733
|
try {
|
3151
|
-
|
3734
|
+
element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);
|
3735
|
+
if (element) {
|
3152
3736
|
return element;
|
3153
3737
|
}
|
3154
|
-
|
3738
|
+
element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);
|
3739
|
+
if (element) {
|
3155
3740
|
await element.loaded;
|
3156
3741
|
return await this.extractForeignFrameElement(element);
|
3157
3742
|
}
|
3158
|
-
console.error(`Response has no matching <turbo-frame id="${id}"> element`);
|
3159
3743
|
} catch (error) {
|
3160
3744
|
console.error(error);
|
3745
|
+
return new FrameElement;
|
3161
3746
|
}
|
3162
|
-
return
|
3747
|
+
return null;
|
3163
3748
|
}
|
3164
3749
|
formActionIsVisitable(form, submitter) {
|
3165
3750
|
const action = getAction(form, submitter);
|
@@ -3179,10 +3764,10 @@ class FrameController {
|
|
3179
3764
|
return !frameElement.disabled;
|
3180
3765
|
}
|
3181
3766
|
}
|
3182
|
-
if (!session.
|
3767
|
+
if (!session.elementIsNavigatable(element)) {
|
3183
3768
|
return false;
|
3184
3769
|
}
|
3185
|
-
if (submitter && !session.
|
3770
|
+
if (submitter && !session.elementIsNavigatable(submitter)) {
|
3186
3771
|
return false;
|
3187
3772
|
}
|
3188
3773
|
return true;
|
@@ -3198,23 +3783,10 @@ class FrameController {
|
|
3198
3783
|
return this.element.src;
|
3199
3784
|
}
|
3200
3785
|
}
|
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
3786
|
set sourceURL(sourceURL) {
|
3214
|
-
this.
|
3215
|
-
|
3216
|
-
|
3217
|
-
this.settingSourceURL = false;
|
3787
|
+
this.ignoringChangesToAttribute("src", (() => {
|
3788
|
+
this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
|
3789
|
+
}));
|
3218
3790
|
}
|
3219
3791
|
get loadingStyle() {
|
3220
3792
|
return this.element.loading;
|
@@ -3222,6 +3794,18 @@ class FrameController {
|
|
3222
3794
|
get isLoading() {
|
3223
3795
|
return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
|
3224
3796
|
}
|
3797
|
+
get complete() {
|
3798
|
+
return this.element.hasAttribute("complete");
|
3799
|
+
}
|
3800
|
+
set complete(value) {
|
3801
|
+
this.ignoringChangesToAttribute("complete", (() => {
|
3802
|
+
if (value) {
|
3803
|
+
this.element.setAttribute("complete", "");
|
3804
|
+
} else {
|
3805
|
+
this.element.removeAttribute("complete");
|
3806
|
+
}
|
3807
|
+
}));
|
3808
|
+
}
|
3225
3809
|
get isActive() {
|
3226
3810
|
return this.element.isActive && this.connected;
|
3227
3811
|
}
|
@@ -3231,17 +3815,18 @@ class FrameController {
|
|
3231
3815
|
const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
|
3232
3816
|
return expandURL(root);
|
3233
3817
|
}
|
3234
|
-
|
3235
|
-
|
3236
|
-
|
3237
|
-
|
3238
|
-
this.
|
3239
|
-
|
3240
|
-
|
3241
|
-
|
3242
|
-
|
3243
|
-
this.
|
3244
|
-
|
3818
|
+
isIgnoringChangesTo(attributeName) {
|
3819
|
+
return this.ignoredAttributes.has(attributeName);
|
3820
|
+
}
|
3821
|
+
ignoringChangesToAttribute(attributeName, callback) {
|
3822
|
+
this.ignoredAttributes.add(attributeName);
|
3823
|
+
callback();
|
3824
|
+
this.ignoredAttributes.delete(attributeName);
|
3825
|
+
}
|
3826
|
+
withCurrentNavigationElement(element, callback) {
|
3827
|
+
this.currentNavigationElement = element;
|
3828
|
+
callback();
|
3829
|
+
delete this.currentNavigationElement;
|
3245
3830
|
}
|
3246
3831
|
}
|
3247
3832
|
|
@@ -3271,42 +3856,10 @@ function activateElement(element, currentURL) {
|
|
3271
3856
|
}
|
3272
3857
|
}
|
3273
3858
|
|
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
3859
|
class StreamElement extends HTMLElement {
|
3860
|
+
static async renderElement(newElement) {
|
3861
|
+
await newElement.performAction();
|
3862
|
+
}
|
3310
3863
|
async connectedCallback() {
|
3311
3864
|
try {
|
3312
3865
|
await this.render();
|
@@ -3319,9 +3872,10 @@ class StreamElement extends HTMLElement {
|
|
3319
3872
|
async render() {
|
3320
3873
|
var _a;
|
3321
3874
|
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
|
3322
|
-
|
3875
|
+
const event = this.beforeRenderEvent;
|
3876
|
+
if (this.dispatchEvent(event)) {
|
3323
3877
|
await nextAnimationFrame();
|
3324
|
-
|
3878
|
+
await event.detail.render(this);
|
3325
3879
|
}
|
3326
3880
|
})();
|
3327
3881
|
}
|
@@ -3336,7 +3890,7 @@ class StreamElement extends HTMLElement {
|
|
3336
3890
|
get duplicateChildren() {
|
3337
3891
|
var _a;
|
3338
3892
|
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));
|
3893
|
+
const newChildrenIds = [ ...((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [] ].filter((c => !!c.id)).map((c => c.id));
|
3340
3894
|
return existingChildren.filter((c => newChildrenIds.includes(c.id)));
|
3341
3895
|
}
|
3342
3896
|
get performAction() {
|
@@ -3362,7 +3916,11 @@ class StreamElement extends HTMLElement {
|
|
3362
3916
|
return this.templateElement.content.cloneNode(true);
|
3363
3917
|
}
|
3364
3918
|
get templateElement() {
|
3365
|
-
if (this.firstElementChild
|
3919
|
+
if (this.firstElementChild === null) {
|
3920
|
+
const template = this.ownerDocument.createElement("template");
|
3921
|
+
this.appendChild(template);
|
3922
|
+
return template;
|
3923
|
+
} else if (this.firstElementChild instanceof HTMLTemplateElement) {
|
3366
3924
|
return this.firstElementChild;
|
3367
3925
|
}
|
3368
3926
|
this.raise("first child element must be a <template> element");
|
@@ -3386,7 +3944,11 @@ class StreamElement extends HTMLElement {
|
|
3386
3944
|
get beforeRenderEvent() {
|
3387
3945
|
return new CustomEvent("turbo:before-stream-render", {
|
3388
3946
|
bubbles: true,
|
3389
|
-
cancelable: true
|
3947
|
+
cancelable: true,
|
3948
|
+
detail: {
|
3949
|
+
newStream: this,
|
3950
|
+
render: StreamElement.renderElement
|
3951
|
+
}
|
3390
3952
|
});
|
3391
3953
|
}
|
3392
3954
|
get targetElementsById() {
|
@@ -3409,17 +3971,45 @@ class StreamElement extends HTMLElement {
|
|
3409
3971
|
}
|
3410
3972
|
}
|
3411
3973
|
|
3974
|
+
class StreamSourceElement extends HTMLElement {
|
3975
|
+
constructor() {
|
3976
|
+
super(...arguments);
|
3977
|
+
this.streamSource = null;
|
3978
|
+
}
|
3979
|
+
connectedCallback() {
|
3980
|
+
this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
|
3981
|
+
connectStreamSource(this.streamSource);
|
3982
|
+
}
|
3983
|
+
disconnectedCallback() {
|
3984
|
+
if (this.streamSource) {
|
3985
|
+
disconnectStreamSource(this.streamSource);
|
3986
|
+
}
|
3987
|
+
}
|
3988
|
+
get src() {
|
3989
|
+
return this.getAttribute("src") || "";
|
3990
|
+
}
|
3991
|
+
}
|
3992
|
+
|
3412
3993
|
FrameElement.delegateConstructor = FrameController;
|
3413
3994
|
|
3414
|
-
customElements.
|
3995
|
+
if (customElements.get("turbo-frame") === undefined) {
|
3996
|
+
customElements.define("turbo-frame", FrameElement);
|
3997
|
+
}
|
3998
|
+
|
3999
|
+
if (customElements.get("turbo-stream") === undefined) {
|
4000
|
+
customElements.define("turbo-stream", StreamElement);
|
4001
|
+
}
|
3415
4002
|
|
3416
|
-
customElements.
|
4003
|
+
if (customElements.get("turbo-stream-source") === undefined) {
|
4004
|
+
customElements.define("turbo-stream-source", StreamSourceElement);
|
4005
|
+
}
|
3417
4006
|
|
3418
4007
|
(() => {
|
3419
4008
|
let element = document.currentScript;
|
3420
4009
|
if (!element) return;
|
3421
4010
|
if (element.hasAttribute("data-turbo-suppress-warning")) return;
|
3422
|
-
|
4011
|
+
element = element.parentElement;
|
4012
|
+
while (element) {
|
3423
4013
|
if (element == document.body) {
|
3424
4014
|
return console.warn(unindent`
|
3425
4015
|
You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
|
@@ -3432,6 +4022,7 @@ customElements.define("turbo-stream", StreamElement);
|
|
3432
4022
|
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
3433
4023
|
`, element.outerHTML);
|
3434
4024
|
}
|
4025
|
+
element = element.parentElement;
|
3435
4026
|
}
|
3436
4027
|
})();
|
3437
4028
|
|
@@ -3441,8 +4032,17 @@ start();
|
|
3441
4032
|
|
3442
4033
|
var turbo_es2017Esm = Object.freeze({
|
3443
4034
|
__proto__: null,
|
4035
|
+
FrameElement: FrameElement,
|
4036
|
+
get FrameLoadingStyle() {
|
4037
|
+
return FrameLoadingStyle;
|
4038
|
+
},
|
4039
|
+
FrameRenderer: FrameRenderer,
|
3444
4040
|
PageRenderer: PageRenderer,
|
3445
4041
|
PageSnapshot: PageSnapshot,
|
4042
|
+
StreamActions: StreamActions,
|
4043
|
+
StreamElement: StreamElement,
|
4044
|
+
StreamSourceElement: StreamSourceElement,
|
4045
|
+
cache: cache,
|
3446
4046
|
clearCache: clearCache,
|
3447
4047
|
connectStreamSource: connectStreamSource,
|
3448
4048
|
disconnectStreamSource: disconnectStreamSource,
|
@@ -3451,6 +4051,7 @@ var turbo_es2017Esm = Object.freeze({
|
|
3451
4051
|
renderStreamMessage: renderStreamMessage,
|
3452
4052
|
session: session,
|
3453
4053
|
setConfirmMethod: setConfirmMethod,
|
4054
|
+
setFormMode: setFormMode,
|
3454
4055
|
setProgressBarDelay: setProgressBarDelay,
|
3455
4056
|
start: start,
|
3456
4057
|
visit: visit
|
@@ -3503,7 +4104,9 @@ class TurboCableStreamSourceElement extends HTMLElement {
|
|
3503
4104
|
async connectedCallback() {
|
3504
4105
|
connectStreamSource(this);
|
3505
4106
|
this.subscription = await subscribeTo(this.channel, {
|
3506
|
-
received: this.dispatchMessageEvent.bind(this)
|
4107
|
+
received: this.dispatchMessageEvent.bind(this),
|
4108
|
+
connected: this.subscriptionConnected.bind(this),
|
4109
|
+
disconnected: this.subscriptionDisconnected.bind(this)
|
3507
4110
|
});
|
3508
4111
|
}
|
3509
4112
|
disconnectedCallback() {
|
@@ -3516,6 +4119,12 @@ class TurboCableStreamSourceElement extends HTMLElement {
|
|
3516
4119
|
});
|
3517
4120
|
return this.dispatchEvent(event);
|
3518
4121
|
}
|
4122
|
+
subscriptionConnected() {
|
4123
|
+
this.setAttribute("connected", "");
|
4124
|
+
}
|
4125
|
+
subscriptionDisconnected() {
|
4126
|
+
this.removeAttribute("connected");
|
4127
|
+
}
|
3519
4128
|
get channel() {
|
3520
4129
|
const channel = this.getAttribute("channel");
|
3521
4130
|
const signed_stream_name = this.getAttribute("signed-stream-name");
|
@@ -3529,16 +4138,60 @@ class TurboCableStreamSourceElement extends HTMLElement {
|
|
3529
4138
|
}
|
3530
4139
|
}
|
3531
4140
|
|
3532
|
-
customElements.
|
4141
|
+
if (customElements.get("turbo-cable-stream-source") === undefined) {
|
4142
|
+
customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement);
|
4143
|
+
}
|
3533
4144
|
|
3534
|
-
function
|
3535
|
-
|
3536
|
-
|
3537
|
-
|
4145
|
+
function encodeMethodIntoRequestBody(event) {
|
4146
|
+
if (event.target instanceof HTMLFormElement) {
|
4147
|
+
const {target: form, detail: {fetchOptions: fetchOptions}} = event;
|
4148
|
+
form.addEventListener("turbo:submit-start", (({detail: {formSubmission: {submitter: submitter}}}) => {
|
4149
|
+
const body = isBodyInit(fetchOptions.body) ? fetchOptions.body : new URLSearchParams;
|
4150
|
+
const method = determineFetchMethod(submitter, body, form);
|
4151
|
+
if (!/get/i.test(method)) {
|
4152
|
+
if (/post/i.test(method)) {
|
4153
|
+
body.delete("_method");
|
4154
|
+
} else {
|
4155
|
+
body.set("_method", method);
|
4156
|
+
}
|
4157
|
+
fetchOptions.method = "post";
|
4158
|
+
}
|
4159
|
+
}), {
|
4160
|
+
once: true
|
4161
|
+
});
|
3538
4162
|
}
|
3539
4163
|
}
|
3540
4164
|
|
3541
|
-
|
4165
|
+
function determineFetchMethod(submitter, body, form) {
|
4166
|
+
const formMethod = determineFormMethod(submitter);
|
4167
|
+
const overrideMethod = body.get("_method");
|
4168
|
+
const method = form.getAttribute("method") || "get";
|
4169
|
+
if (typeof formMethod == "string") {
|
4170
|
+
return formMethod;
|
4171
|
+
} else if (typeof overrideMethod == "string") {
|
4172
|
+
return overrideMethod;
|
4173
|
+
} else {
|
4174
|
+
return method;
|
4175
|
+
}
|
4176
|
+
}
|
4177
|
+
|
4178
|
+
function determineFormMethod(submitter) {
|
4179
|
+
if (submitter instanceof HTMLButtonElement || submitter instanceof HTMLInputElement) {
|
4180
|
+
if (submitter.hasAttribute("formmethod")) {
|
4181
|
+
return submitter.formMethod;
|
4182
|
+
} else {
|
4183
|
+
return null;
|
4184
|
+
}
|
4185
|
+
} else {
|
4186
|
+
return null;
|
4187
|
+
}
|
4188
|
+
}
|
4189
|
+
|
4190
|
+
function isBodyInit(body) {
|
4191
|
+
return body instanceof FormData || body instanceof URLSearchParams;
|
4192
|
+
}
|
4193
|
+
|
4194
|
+
addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
|
3542
4195
|
|
3543
4196
|
var adapters = {
|
3544
4197
|
logger: self.console,
|