turbo-rails 1.5.0 → 2.0.0.pre.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascripts/turbo.js +1498 -685
- data/app/assets/javascripts/turbo.min.js +9 -5
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams/broadcasts.rb +18 -4
- data/app/controllers/concerns/turbo/request_id_tracking.rb +12 -0
- data/app/helpers/turbo/drive_helper.rb +8 -0
- data/app/helpers/turbo/frames_helper.rb +4 -1
- data/app/helpers/turbo/streams/action_helper.rb +5 -1
- data/app/jobs/turbo/streams/action_broadcast_job.rb +2 -2
- data/app/jobs/turbo/streams/broadcast_stream_job.rb +7 -0
- data/app/models/concerns/turbo/broadcastable.rb +85 -20
- data/app/models/turbo/debouncer.rb +24 -0
- data/app/models/turbo/thread_debouncer.rb +28 -0
- data/config/routes.rb +0 -1
- data/lib/turbo/engine.rb +6 -0
- data/lib/turbo/version.rb +1 -1
- data/lib/turbo-rails.rb +9 -0
- metadata +8 -4
@@ -1,19 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
const BuiltInHTMLElement = HTMLElement;
|
6
|
-
const wrapperForTheName = {
|
7
|
-
HTMLElement: function HTMLElement() {
|
8
|
-
return Reflect.construct(BuiltInHTMLElement, [], this.constructor);
|
9
|
-
}
|
10
|
-
};
|
11
|
-
window.HTMLElement = wrapperForTheName["HTMLElement"];
|
12
|
-
HTMLElement.prototype = BuiltInHTMLElement.prototype;
|
13
|
-
HTMLElement.prototype.constructor = HTMLElement;
|
14
|
-
Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);
|
15
|
-
})();
|
16
|
-
|
1
|
+
/*!
|
2
|
+
Turbo 8.0.0-beta.1
|
3
|
+
Copyright © 2023 37signals LLC
|
4
|
+
*/
|
17
5
|
(function(prototype) {
|
18
6
|
if (typeof prototype.requestSubmit == "function") return;
|
19
7
|
prototype.requestSubmit = function(submitter) {
|
@@ -44,7 +32,7 @@ const submittersByForm = new WeakMap;
|
|
44
32
|
function findSubmitterFromClickTarget(target) {
|
45
33
|
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
46
34
|
const candidate = element ? element.closest("input, button") : null;
|
47
|
-
return
|
35
|
+
return candidate?.type == "submit" ? candidate : null;
|
48
36
|
}
|
49
37
|
|
50
38
|
function clickCaptured(event) {
|
@@ -57,10 +45,13 @@ function clickCaptured(event) {
|
|
57
45
|
(function() {
|
58
46
|
if ("submitter" in Event.prototype) return;
|
59
47
|
let prototype = window.Event.prototype;
|
60
|
-
if ("SubmitEvent" in window
|
61
|
-
|
62
|
-
|
63
|
-
|
48
|
+
if ("SubmitEvent" in window) {
|
49
|
+
const prototypeOfSubmitEvent = window.SubmitEvent.prototype;
|
50
|
+
if (/Apple Computer/.test(navigator.vendor) && !("submitter" in prototypeOfSubmitEvent)) {
|
51
|
+
prototype = prototypeOfSubmitEvent;
|
52
|
+
} else {
|
53
|
+
return;
|
54
|
+
}
|
64
55
|
}
|
65
56
|
addEventListener("click", clickCaptured, true);
|
66
57
|
Object.defineProperty(prototype, "submitter", {
|
@@ -72,20 +63,19 @@ function clickCaptured(event) {
|
|
72
63
|
});
|
73
64
|
})();
|
74
65
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
FrameLoadingStyle["lazy"] = "lazy";
|
80
|
-
})(FrameLoadingStyle || (FrameLoadingStyle = {}));
|
66
|
+
const FrameLoadingStyle = {
|
67
|
+
eager: "eager",
|
68
|
+
lazy: "lazy"
|
69
|
+
};
|
81
70
|
|
82
71
|
class FrameElement extends HTMLElement {
|
72
|
+
static delegateConstructor=undefined;
|
73
|
+
loaded=Promise.resolve();
|
83
74
|
static get observedAttributes() {
|
84
75
|
return [ "disabled", "complete", "loading", "src" ];
|
85
76
|
}
|
86
77
|
constructor() {
|
87
78
|
super();
|
88
|
-
this.loaded = Promise.resolve();
|
89
79
|
this.delegate = new FrameElement.delegateConstructor(this);
|
90
80
|
}
|
91
81
|
connectedCallback() {
|
@@ -118,6 +108,16 @@ class FrameElement extends HTMLElement {
|
|
118
108
|
this.removeAttribute("src");
|
119
109
|
}
|
120
110
|
}
|
111
|
+
get refresh() {
|
112
|
+
return this.getAttribute("refresh");
|
113
|
+
}
|
114
|
+
set refresh(value) {
|
115
|
+
if (value) {
|
116
|
+
this.setAttribute("refresh", value);
|
117
|
+
} else {
|
118
|
+
this.removeAttribute("refresh");
|
119
|
+
}
|
120
|
+
}
|
121
121
|
get loading() {
|
122
122
|
return frameLoadingStyleFromString(this.getAttribute("loading") || "");
|
123
123
|
}
|
@@ -155,8 +155,7 @@ class FrameElement extends HTMLElement {
|
|
155
155
|
return this.ownerDocument === document && !this.isPreview;
|
156
156
|
}
|
157
157
|
get isPreview() {
|
158
|
-
|
159
|
-
return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute("data-turbo-preview");
|
158
|
+
return this.ownerDocument?.documentElement?.hasAttribute("data-turbo-preview");
|
160
159
|
}
|
161
160
|
}
|
162
161
|
|
@@ -183,8 +182,8 @@ function getAnchor(url) {
|
|
183
182
|
}
|
184
183
|
}
|
185
184
|
|
186
|
-
function getAction(form, submitter) {
|
187
|
-
const action =
|
185
|
+
function getAction$1(form, submitter) {
|
186
|
+
const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action;
|
188
187
|
return expandURL(action);
|
189
188
|
}
|
190
189
|
|
@@ -323,6 +322,14 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
|
|
323
322
|
return event;
|
324
323
|
}
|
325
324
|
|
325
|
+
function nextRepaint() {
|
326
|
+
if (document.visibilityState === "hidden") {
|
327
|
+
return nextEventLoopTick();
|
328
|
+
} else {
|
329
|
+
return nextAnimationFrame();
|
330
|
+
}
|
331
|
+
}
|
332
|
+
|
326
333
|
function nextAnimationFrame() {
|
327
334
|
return new Promise((resolve => requestAnimationFrame((() => resolve()))));
|
328
335
|
}
|
@@ -370,7 +377,7 @@ function uuid() {
|
|
370
377
|
}
|
371
378
|
|
372
379
|
function getAttribute(attributeName, ...elements) {
|
373
|
-
for (const value of elements.map((element => element
|
380
|
+
for (const value of elements.map((element => element?.getAttribute(attributeName)))) {
|
374
381
|
if (typeof value == "string") return value;
|
375
382
|
}
|
376
383
|
return null;
|
@@ -456,21 +463,38 @@ function setMetaContent(name, content) {
|
|
456
463
|
}
|
457
464
|
|
458
465
|
function findClosestRecursively(element, selector) {
|
459
|
-
var _a;
|
460
466
|
if (element instanceof Element) {
|
461
|
-
return element.closest(selector) || findClosestRecursively(element.assignedSlot ||
|
467
|
+
return element.closest(selector) || findClosestRecursively(element.assignedSlot || element.getRootNode()?.host, selector);
|
462
468
|
}
|
463
469
|
}
|
464
470
|
|
465
|
-
|
471
|
+
function elementIsFocusable(element) {
|
472
|
+
const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
|
473
|
+
return !!element && element.closest(inertDisabledOrHidden) == null && typeof element.focus == "function";
|
474
|
+
}
|
475
|
+
|
476
|
+
function queryAutofocusableElement(elementOrDocumentFragment) {
|
477
|
+
return Array.from(elementOrDocumentFragment.querySelectorAll("[autofocus]")).find(elementIsFocusable);
|
478
|
+
}
|
479
|
+
|
480
|
+
async function around(callback, reader) {
|
481
|
+
const before = reader();
|
482
|
+
callback();
|
483
|
+
await nextAnimationFrame();
|
484
|
+
const after = reader();
|
485
|
+
return [ before, after ];
|
486
|
+
}
|
466
487
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
488
|
+
function fetch(url, options = {}) {
|
489
|
+
const modifiedHeaders = new Headers(options.headers || {});
|
490
|
+
const requestUID = uuid();
|
491
|
+
window.Turbo.session.recentRequests.add(requestUID);
|
492
|
+
modifiedHeaders.append("X-Turbo-Request-Id", requestUID);
|
493
|
+
return window.fetch(url, {
|
494
|
+
...options,
|
495
|
+
headers: modifiedHeaders
|
496
|
+
});
|
497
|
+
}
|
474
498
|
|
475
499
|
function fetchMethodFromString(method) {
|
476
500
|
switch (method.toLowerCase()) {
|
@@ -491,16 +515,81 @@ function fetchMethodFromString(method) {
|
|
491
515
|
}
|
492
516
|
}
|
493
517
|
|
518
|
+
const FetchMethod = {
|
519
|
+
get: "get",
|
520
|
+
post: "post",
|
521
|
+
put: "put",
|
522
|
+
patch: "patch",
|
523
|
+
delete: "delete"
|
524
|
+
};
|
525
|
+
|
526
|
+
function fetchEnctypeFromString(encoding) {
|
527
|
+
switch (encoding.toLowerCase()) {
|
528
|
+
case FetchEnctype.multipart:
|
529
|
+
return FetchEnctype.multipart;
|
530
|
+
|
531
|
+
case FetchEnctype.plain:
|
532
|
+
return FetchEnctype.plain;
|
533
|
+
|
534
|
+
default:
|
535
|
+
return FetchEnctype.urlEncoded;
|
536
|
+
}
|
537
|
+
}
|
538
|
+
|
539
|
+
const FetchEnctype = {
|
540
|
+
urlEncoded: "application/x-www-form-urlencoded",
|
541
|
+
multipart: "multipart/form-data",
|
542
|
+
plain: "text/plain"
|
543
|
+
};
|
544
|
+
|
494
545
|
class FetchRequest {
|
495
|
-
|
496
|
-
|
497
|
-
|
546
|
+
abortController=new AbortController;
|
547
|
+
#resolveRequestPromise=_value => {};
|
548
|
+
constructor(delegate, method, location, requestBody = new URLSearchParams, target = null, enctype = FetchEnctype.urlEncoded) {
|
549
|
+
const [url, body] = buildResourceAndBody(expandURL(location), method, requestBody, enctype);
|
498
550
|
this.delegate = delegate;
|
499
|
-
this.
|
500
|
-
this.headers = this.defaultHeaders;
|
501
|
-
this.body = body;
|
502
|
-
this.url = location;
|
551
|
+
this.url = url;
|
503
552
|
this.target = target;
|
553
|
+
this.fetchOptions = {
|
554
|
+
credentials: "same-origin",
|
555
|
+
redirect: "follow",
|
556
|
+
method: method,
|
557
|
+
headers: {
|
558
|
+
...this.defaultHeaders
|
559
|
+
},
|
560
|
+
body: body,
|
561
|
+
signal: this.abortSignal,
|
562
|
+
referrer: this.delegate.referrer?.href
|
563
|
+
};
|
564
|
+
this.enctype = enctype;
|
565
|
+
}
|
566
|
+
get method() {
|
567
|
+
return this.fetchOptions.method;
|
568
|
+
}
|
569
|
+
set method(value) {
|
570
|
+
const fetchBody = this.isSafe ? this.url.searchParams : this.fetchOptions.body || new FormData;
|
571
|
+
const fetchMethod = fetchMethodFromString(value) || FetchMethod.get;
|
572
|
+
this.url.search = "";
|
573
|
+
const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype);
|
574
|
+
this.url = url;
|
575
|
+
this.fetchOptions.body = body;
|
576
|
+
this.fetchOptions.method = fetchMethod;
|
577
|
+
}
|
578
|
+
get headers() {
|
579
|
+
return this.fetchOptions.headers;
|
580
|
+
}
|
581
|
+
set headers(value) {
|
582
|
+
this.fetchOptions.headers = value;
|
583
|
+
}
|
584
|
+
get body() {
|
585
|
+
if (this.isSafe) {
|
586
|
+
return this.url.searchParams;
|
587
|
+
} else {
|
588
|
+
return this.fetchOptions.body;
|
589
|
+
}
|
590
|
+
}
|
591
|
+
set body(value) {
|
592
|
+
this.fetchOptions.body = value;
|
504
593
|
}
|
505
594
|
get location() {
|
506
595
|
return this.url;
|
@@ -517,14 +606,14 @@ class FetchRequest {
|
|
517
606
|
async perform() {
|
518
607
|
const {fetchOptions: fetchOptions} = this;
|
519
608
|
this.delegate.prepareRequest(this);
|
520
|
-
await this
|
609
|
+
await this.#allowRequestToBeIntercepted(fetchOptions);
|
521
610
|
try {
|
522
611
|
this.delegate.requestStarted(this);
|
523
612
|
const response = await fetch(this.url.href, fetchOptions);
|
524
613
|
return await this.receive(response);
|
525
614
|
} catch (error) {
|
526
615
|
if (error.name !== "AbortError") {
|
527
|
-
if (this
|
616
|
+
if (this.#willDelegateErrorHandling(error)) {
|
528
617
|
this.delegate.requestErrored(this, error);
|
529
618
|
}
|
530
619
|
throw error;
|
@@ -551,25 +640,13 @@ class FetchRequest {
|
|
551
640
|
}
|
552
641
|
return fetchResponse;
|
553
642
|
}
|
554
|
-
get fetchOptions() {
|
555
|
-
var _a;
|
556
|
-
return {
|
557
|
-
method: FetchMethod[this.method].toUpperCase(),
|
558
|
-
credentials: "same-origin",
|
559
|
-
headers: this.headers,
|
560
|
-
redirect: "follow",
|
561
|
-
body: this.isSafe ? null : this.body,
|
562
|
-
signal: this.abortSignal,
|
563
|
-
referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
|
564
|
-
};
|
565
|
-
}
|
566
643
|
get defaultHeaders() {
|
567
644
|
return {
|
568
645
|
Accept: "text/html, application/xhtml+xml"
|
569
646
|
};
|
570
647
|
}
|
571
648
|
get isSafe() {
|
572
|
-
return this.method
|
649
|
+
return isSafe(this.method);
|
573
650
|
}
|
574
651
|
get abortSignal() {
|
575
652
|
return this.abortController.signal;
|
@@ -577,20 +654,21 @@ class FetchRequest {
|
|
577
654
|
acceptResponseType(mimeType) {
|
578
655
|
this.headers["Accept"] = [ mimeType, this.headers["Accept"] ].join(", ");
|
579
656
|
}
|
580
|
-
async allowRequestToBeIntercepted(fetchOptions) {
|
581
|
-
const requestInterception = new Promise((resolve => this
|
657
|
+
async #allowRequestToBeIntercepted(fetchOptions) {
|
658
|
+
const requestInterception = new Promise((resolve => this.#resolveRequestPromise = resolve));
|
582
659
|
const event = dispatch("turbo:before-fetch-request", {
|
583
660
|
cancelable: true,
|
584
661
|
detail: {
|
585
662
|
fetchOptions: fetchOptions,
|
586
663
|
url: this.url,
|
587
|
-
resume: this
|
664
|
+
resume: this.#resolveRequestPromise
|
588
665
|
},
|
589
666
|
target: this.target
|
590
667
|
});
|
668
|
+
this.url = event.detail.url;
|
591
669
|
if (event.defaultPrevented) await requestInterception;
|
592
670
|
}
|
593
|
-
willDelegateErrorHandling(error) {
|
671
|
+
#willDelegateErrorHandling(error) {
|
594
672
|
const event = dispatch("turbo:fetch-request-error", {
|
595
673
|
target: this.target,
|
596
674
|
cancelable: true,
|
@@ -603,15 +681,38 @@ class FetchRequest {
|
|
603
681
|
}
|
604
682
|
}
|
605
683
|
|
684
|
+
function isSafe(fetchMethod) {
|
685
|
+
return fetchMethodFromString(fetchMethod) == FetchMethod.get;
|
686
|
+
}
|
687
|
+
|
688
|
+
function buildResourceAndBody(resource, method, requestBody, enctype) {
|
689
|
+
const searchParams = Array.from(requestBody).length > 0 ? new URLSearchParams(entriesExcludingFiles(requestBody)) : resource.searchParams;
|
690
|
+
if (isSafe(method)) {
|
691
|
+
return [ mergeIntoURLSearchParams(resource, searchParams), null ];
|
692
|
+
} else if (enctype == FetchEnctype.urlEncoded) {
|
693
|
+
return [ resource, searchParams ];
|
694
|
+
} else {
|
695
|
+
return [ resource, requestBody ];
|
696
|
+
}
|
697
|
+
}
|
698
|
+
|
699
|
+
function entriesExcludingFiles(requestBody) {
|
700
|
+
const entries = [];
|
701
|
+
for (const [name, value] of requestBody) {
|
702
|
+
if (value instanceof File) continue; else entries.push([ name, value ]);
|
703
|
+
}
|
704
|
+
return entries;
|
705
|
+
}
|
706
|
+
|
707
|
+
function mergeIntoURLSearchParams(url, requestBody) {
|
708
|
+
const searchParams = new URLSearchParams(entriesExcludingFiles(requestBody));
|
709
|
+
url.search = searchParams.toString();
|
710
|
+
return url;
|
711
|
+
}
|
712
|
+
|
606
713
|
class AppearanceObserver {
|
714
|
+
started=false;
|
607
715
|
constructor(delegate, element) {
|
608
|
-
this.started = false;
|
609
|
-
this.intersect = entries => {
|
610
|
-
const lastEntry = entries.slice(-1)[0];
|
611
|
-
if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
|
612
|
-
this.delegate.elementAppearedInViewport(this.element);
|
613
|
-
}
|
614
|
-
};
|
615
716
|
this.delegate = delegate;
|
616
717
|
this.element = element;
|
617
718
|
this.intersectionObserver = new IntersectionObserver(this.intersect);
|
@@ -628,9 +729,16 @@ class AppearanceObserver {
|
|
628
729
|
this.intersectionObserver.unobserve(this.element);
|
629
730
|
}
|
630
731
|
}
|
732
|
+
intersect=entries => {
|
733
|
+
const lastEntry = entries.slice(-1)[0];
|
734
|
+
if (lastEntry?.isIntersecting) {
|
735
|
+
this.delegate.elementAppearedInViewport(this.element);
|
736
|
+
}
|
737
|
+
};
|
631
738
|
}
|
632
739
|
|
633
740
|
class StreamMessage {
|
741
|
+
static contentType="text/vnd.turbo-stream.html";
|
634
742
|
static wrap(message) {
|
635
743
|
if (typeof message == "string") {
|
636
744
|
return new this(createDocumentFragment(message));
|
@@ -643,8 +751,6 @@ class StreamMessage {
|
|
643
751
|
}
|
644
752
|
}
|
645
753
|
|
646
|
-
StreamMessage.contentType = "text/vnd.turbo-stream.html";
|
647
|
-
|
648
754
|
function importStreamElements(fragment) {
|
649
755
|
for (const element of fragment.querySelectorAll("turbo-stream")) {
|
650
756
|
const streamElement = document.importNode(element, true);
|
@@ -656,85 +762,54 @@ function importStreamElements(fragment) {
|
|
656
762
|
return fragment;
|
657
763
|
}
|
658
764
|
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
|
668
|
-
})(FormSubmissionState || (FormSubmissionState = {}));
|
669
|
-
|
670
|
-
var FormEnctype;
|
671
|
-
|
672
|
-
(function(FormEnctype) {
|
673
|
-
FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded";
|
674
|
-
FormEnctype["multipart"] = "multipart/form-data";
|
675
|
-
FormEnctype["plain"] = "text/plain";
|
676
|
-
})(FormEnctype || (FormEnctype = {}));
|
677
|
-
|
678
|
-
function formEnctypeFromString(encoding) {
|
679
|
-
switch (encoding.toLowerCase()) {
|
680
|
-
case FormEnctype.multipart:
|
681
|
-
return FormEnctype.multipart;
|
682
|
-
|
683
|
-
case FormEnctype.plain:
|
684
|
-
return FormEnctype.plain;
|
685
|
-
|
686
|
-
default:
|
687
|
-
return FormEnctype.urlEncoded;
|
688
|
-
}
|
689
|
-
}
|
765
|
+
const FormSubmissionState = {
|
766
|
+
initialized: "initialized",
|
767
|
+
requesting: "requesting",
|
768
|
+
waiting: "waiting",
|
769
|
+
receiving: "receiving",
|
770
|
+
stopping: "stopping",
|
771
|
+
stopped: "stopped"
|
772
|
+
};
|
690
773
|
|
691
774
|
class FormSubmission {
|
775
|
+
state=FormSubmissionState.initialized;
|
692
776
|
static confirmMethod(message, _element, _submitter) {
|
693
777
|
return Promise.resolve(confirm(message));
|
694
778
|
}
|
695
779
|
constructor(delegate, formElement, submitter, mustRedirect = false) {
|
696
|
-
|
780
|
+
const method = getMethod(formElement, submitter);
|
781
|
+
const action = getAction(getFormAction(formElement, submitter), method);
|
782
|
+
const body = buildFormData(formElement, submitter);
|
783
|
+
const enctype = getEnctype(formElement, submitter);
|
697
784
|
this.delegate = delegate;
|
698
785
|
this.formElement = formElement;
|
699
786
|
this.submitter = submitter;
|
700
|
-
this.
|
701
|
-
this.location = expandURL(this.action);
|
702
|
-
if (this.method == FetchMethod.get) {
|
703
|
-
mergeFormDataEntries(this.location, [ ...this.body.entries() ]);
|
704
|
-
}
|
705
|
-
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
|
787
|
+
this.fetchRequest = new FetchRequest(this, method, action, body, formElement, enctype);
|
706
788
|
this.mustRedirect = mustRedirect;
|
707
789
|
}
|
708
790
|
get method() {
|
709
|
-
|
710
|
-
|
711
|
-
|
791
|
+
return this.fetchRequest.method;
|
792
|
+
}
|
793
|
+
set method(value) {
|
794
|
+
this.fetchRequest.method = value;
|
712
795
|
}
|
713
796
|
get action() {
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
} else {
|
719
|
-
return this.formElement.getAttribute("action") || formElementAction || "";
|
720
|
-
}
|
797
|
+
return this.fetchRequest.url.toString();
|
798
|
+
}
|
799
|
+
set action(value) {
|
800
|
+
this.fetchRequest.url = expandURL(value);
|
721
801
|
}
|
722
802
|
get body() {
|
723
|
-
|
724
|
-
return new URLSearchParams(this.stringFormData);
|
725
|
-
} else {
|
726
|
-
return this.formData;
|
727
|
-
}
|
803
|
+
return this.fetchRequest.body;
|
728
804
|
}
|
729
805
|
get enctype() {
|
730
|
-
|
731
|
-
return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
|
806
|
+
return this.fetchRequest.enctype;
|
732
807
|
}
|
733
808
|
get isSafe() {
|
734
809
|
return this.fetchRequest.isSafe;
|
735
810
|
}
|
736
|
-
get
|
737
|
-
return
|
811
|
+
get location() {
|
812
|
+
return this.fetchRequest.url;
|
738
813
|
}
|
739
814
|
async start() {
|
740
815
|
const {initialized: initialized, requesting: requesting} = FormSubmissionState;
|
@@ -770,9 +845,8 @@ class FormSubmission {
|
|
770
845
|
}
|
771
846
|
}
|
772
847
|
requestStarted(_request) {
|
773
|
-
var _a;
|
774
848
|
this.state = FormSubmissionState.waiting;
|
775
|
-
|
849
|
+
this.submitter?.setAttribute("disabled", "");
|
776
850
|
this.setSubmitsWith();
|
777
851
|
dispatch("turbo:submit-start", {
|
778
852
|
target: this.formElement,
|
@@ -818,15 +892,15 @@ class FormSubmission {
|
|
818
892
|
this.delegate.formSubmissionErrored(this, error);
|
819
893
|
}
|
820
894
|
requestFinished(_request) {
|
821
|
-
var _a;
|
822
895
|
this.state = FormSubmissionState.stopped;
|
823
|
-
|
896
|
+
this.submitter?.removeAttribute("disabled");
|
824
897
|
this.resetSubmitterText();
|
825
898
|
dispatch("turbo:submit-end", {
|
826
899
|
target: this.formElement,
|
827
|
-
detail:
|
828
|
-
formSubmission: this
|
829
|
-
|
900
|
+
detail: {
|
901
|
+
formSubmission: this,
|
902
|
+
...this.result
|
903
|
+
}
|
830
904
|
});
|
831
905
|
this.delegate.formSubmissionFinished(this);
|
832
906
|
}
|
@@ -857,15 +931,14 @@ class FormSubmission {
|
|
857
931
|
return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
|
858
932
|
}
|
859
933
|
get submitsWith() {
|
860
|
-
|
861
|
-
return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with");
|
934
|
+
return this.submitter?.getAttribute("data-turbo-submits-with");
|
862
935
|
}
|
863
936
|
}
|
864
937
|
|
865
938
|
function buildFormData(formElement, submitter) {
|
866
939
|
const formData = new FormData(formElement);
|
867
|
-
const name = submitter
|
868
|
-
const value = submitter
|
940
|
+
const name = submitter?.getAttribute("name");
|
941
|
+
const value = submitter?.getAttribute("value");
|
869
942
|
if (name) {
|
870
943
|
formData.append(name, value || "");
|
871
944
|
}
|
@@ -887,14 +960,30 @@ function responseSucceededWithoutRedirect(response) {
|
|
887
960
|
return response.statusCode == 200 && !response.redirected;
|
888
961
|
}
|
889
962
|
|
890
|
-
function
|
891
|
-
const
|
892
|
-
|
893
|
-
|
894
|
-
|
963
|
+
function getFormAction(formElement, submitter) {
|
964
|
+
const formElementAction = typeof formElement.action === "string" ? formElement.action : null;
|
965
|
+
if (submitter?.hasAttribute("formaction")) {
|
966
|
+
return submitter.getAttribute("formaction") || "";
|
967
|
+
} else {
|
968
|
+
return formElement.getAttribute("action") || formElementAction || "";
|
895
969
|
}
|
896
|
-
|
897
|
-
|
970
|
+
}
|
971
|
+
|
972
|
+
function getAction(formAction, fetchMethod) {
|
973
|
+
const action = expandURL(formAction);
|
974
|
+
if (isSafe(fetchMethod)) {
|
975
|
+
action.search = "";
|
976
|
+
}
|
977
|
+
return action;
|
978
|
+
}
|
979
|
+
|
980
|
+
function getMethod(formElement, submitter) {
|
981
|
+
const method = submitter?.getAttribute("formmethod") || formElement.getAttribute("method") || "";
|
982
|
+
return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
|
983
|
+
}
|
984
|
+
|
985
|
+
function getEnctype(formElement, submitter) {
|
986
|
+
return fetchEnctypeFromString(submitter?.getAttribute("formenctype") || formElement.enctype);
|
898
987
|
}
|
899
988
|
|
900
989
|
class Snapshot {
|
@@ -917,11 +1006,7 @@ class Snapshot {
|
|
917
1006
|
return this.element.isConnected;
|
918
1007
|
}
|
919
1008
|
get firstAutofocusableElement() {
|
920
|
-
|
921
|
-
for (const element of this.element.querySelectorAll("[autofocus]")) {
|
922
|
-
if (element.closest(inertDisabledOrHidden) == null) return element; else continue;
|
923
|
-
}
|
924
|
-
return null;
|
1009
|
+
return queryAutofocusableElement(this.element);
|
925
1010
|
}
|
926
1011
|
get permanentElements() {
|
927
1012
|
return queryPermanentElementsAll(this.element);
|
@@ -951,23 +1036,8 @@ function queryPermanentElementsAll(node) {
|
|
951
1036
|
}
|
952
1037
|
|
953
1038
|
class FormSubmitObserver {
|
1039
|
+
started=false;
|
954
1040
|
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
|
-
};
|
960
|
-
this.submitBubbled = event => {
|
961
|
-
if (!event.defaultPrevented) {
|
962
|
-
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
963
|
-
const submitter = event.submitter || undefined;
|
964
|
-
if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
|
965
|
-
event.preventDefault();
|
966
|
-
event.stopImmediatePropagation();
|
967
|
-
this.delegate.formSubmitted(form, submitter);
|
968
|
-
}
|
969
|
-
}
|
970
|
-
};
|
971
1041
|
this.delegate = delegate;
|
972
1042
|
this.eventTarget = eventTarget;
|
973
1043
|
}
|
@@ -983,16 +1053,31 @@ class FormSubmitObserver {
|
|
983
1053
|
this.started = false;
|
984
1054
|
}
|
985
1055
|
}
|
1056
|
+
submitCaptured=() => {
|
1057
|
+
this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
|
1058
|
+
this.eventTarget.addEventListener("submit", this.submitBubbled, false);
|
1059
|
+
};
|
1060
|
+
submitBubbled=event => {
|
1061
|
+
if (!event.defaultPrevented) {
|
1062
|
+
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
1063
|
+
const submitter = event.submitter || undefined;
|
1064
|
+
if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
|
1065
|
+
event.preventDefault();
|
1066
|
+
event.stopImmediatePropagation();
|
1067
|
+
this.delegate.formSubmitted(form, submitter);
|
1068
|
+
}
|
1069
|
+
}
|
1070
|
+
};
|
986
1071
|
}
|
987
1072
|
|
988
1073
|
function submissionDoesNotDismissDialog(form, submitter) {
|
989
|
-
const method =
|
1074
|
+
const method = submitter?.getAttribute("formmethod") || form.getAttribute("method");
|
990
1075
|
return method != "dialog";
|
991
1076
|
}
|
992
1077
|
|
993
1078
|
function submissionDoesNotTargetIFrame(form, submitter) {
|
994
|
-
if (
|
995
|
-
const target =
|
1079
|
+
if (submitter?.hasAttribute("formtarget") || form.hasAttribute("target")) {
|
1080
|
+
const target = submitter?.getAttribute("formtarget") || form.target;
|
996
1081
|
for (const element of document.getElementsByName(target)) {
|
997
1082
|
if (element instanceof HTMLIFrameElement) return false;
|
998
1083
|
}
|
@@ -1003,9 +1088,9 @@ function submissionDoesNotTargetIFrame(form, submitter) {
|
|
1003
1088
|
}
|
1004
1089
|
|
1005
1090
|
class View {
|
1091
|
+
#resolveRenderPromise=_value => {};
|
1092
|
+
#resolveInterceptionPromise=_value => {};
|
1006
1093
|
constructor(delegate, element) {
|
1007
|
-
this.resolveRenderPromise = _value => {};
|
1008
|
-
this.resolveInterceptionPromise = _value => {};
|
1009
1094
|
this.delegate = delegate;
|
1010
1095
|
this.element = element;
|
1011
1096
|
}
|
@@ -1054,23 +1139,23 @@ class View {
|
|
1054
1139
|
const {isPreview: isPreview, shouldRender: shouldRender, newSnapshot: snapshot} = renderer;
|
1055
1140
|
if (shouldRender) {
|
1056
1141
|
try {
|
1057
|
-
this.renderPromise = new Promise((resolve => this
|
1142
|
+
this.renderPromise = new Promise((resolve => this.#resolveRenderPromise = resolve));
|
1058
1143
|
this.renderer = renderer;
|
1059
1144
|
await this.prepareToRenderSnapshot(renderer);
|
1060
|
-
const renderInterception = new Promise((resolve => this
|
1145
|
+
const renderInterception = new Promise((resolve => this.#resolveInterceptionPromise = resolve));
|
1061
1146
|
const options = {
|
1062
|
-
resume: this
|
1147
|
+
resume: this.#resolveInterceptionPromise,
|
1063
1148
|
render: this.renderer.renderElement
|
1064
1149
|
};
|
1065
|
-
const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
|
1150
|
+
const immediateRender = this.delegate.allowsImmediateRender(snapshot, isPreview, options);
|
1066
1151
|
if (!immediateRender) await renderInterception;
|
1067
1152
|
await this.renderSnapshot(renderer);
|
1068
|
-
this.delegate.viewRenderedSnapshot(snapshot, isPreview);
|
1153
|
+
this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod);
|
1069
1154
|
this.delegate.preloadOnLoadLinksForView(this.element);
|
1070
1155
|
this.finishRenderingSnapshot(renderer);
|
1071
1156
|
} finally {
|
1072
1157
|
delete this.renderer;
|
1073
|
-
this
|
1158
|
+
this.#resolveRenderPromise(undefined);
|
1074
1159
|
delete this.renderPromise;
|
1075
1160
|
}
|
1076
1161
|
} else {
|
@@ -1110,26 +1195,6 @@ class FrameView extends View {
|
|
1110
1195
|
|
1111
1196
|
class LinkInterceptor {
|
1112
1197
|
constructor(delegate, element) {
|
1113
|
-
this.clickBubbled = event => {
|
1114
|
-
if (this.respondsToEventTarget(event.target)) {
|
1115
|
-
this.clickEvent = event;
|
1116
|
-
} else {
|
1117
|
-
delete this.clickEvent;
|
1118
|
-
}
|
1119
|
-
};
|
1120
|
-
this.linkClicked = event => {
|
1121
|
-
if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
|
1122
|
-
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
1123
|
-
this.clickEvent.preventDefault();
|
1124
|
-
event.preventDefault();
|
1125
|
-
this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
|
1126
|
-
}
|
1127
|
-
}
|
1128
|
-
delete this.clickEvent;
|
1129
|
-
};
|
1130
|
-
this.willVisit = _event => {
|
1131
|
-
delete this.clickEvent;
|
1132
|
-
};
|
1133
1198
|
this.delegate = delegate;
|
1134
1199
|
this.element = element;
|
1135
1200
|
}
|
@@ -1143,6 +1208,26 @@ class LinkInterceptor {
|
|
1143
1208
|
document.removeEventListener("turbo:click", this.linkClicked);
|
1144
1209
|
document.removeEventListener("turbo:before-visit", this.willVisit);
|
1145
1210
|
}
|
1211
|
+
clickBubbled=event => {
|
1212
|
+
if (this.respondsToEventTarget(event.target)) {
|
1213
|
+
this.clickEvent = event;
|
1214
|
+
} else {
|
1215
|
+
delete this.clickEvent;
|
1216
|
+
}
|
1217
|
+
};
|
1218
|
+
linkClicked=event => {
|
1219
|
+
if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
|
1220
|
+
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
1221
|
+
this.clickEvent.preventDefault();
|
1222
|
+
event.preventDefault();
|
1223
|
+
this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
|
1224
|
+
}
|
1225
|
+
}
|
1226
|
+
delete this.clickEvent;
|
1227
|
+
};
|
1228
|
+
willVisit=_event => {
|
1229
|
+
delete this.clickEvent;
|
1230
|
+
};
|
1146
1231
|
respondsToEventTarget(target) {
|
1147
1232
|
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
1148
1233
|
return element && element.closest("turbo-frame, html") == this.element;
|
@@ -1150,25 +1235,8 @@ class LinkInterceptor {
|
|
1150
1235
|
}
|
1151
1236
|
|
1152
1237
|
class LinkClickObserver {
|
1238
|
+
started=false;
|
1153
1239
|
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
1240
|
this.delegate = delegate;
|
1173
1241
|
this.eventTarget = eventTarget;
|
1174
1242
|
}
|
@@ -1184,6 +1252,23 @@ class LinkClickObserver {
|
|
1184
1252
|
this.started = false;
|
1185
1253
|
}
|
1186
1254
|
}
|
1255
|
+
clickCaptured=() => {
|
1256
|
+
this.eventTarget.removeEventListener("click", this.clickBubbled, false);
|
1257
|
+
this.eventTarget.addEventListener("click", this.clickBubbled, false);
|
1258
|
+
};
|
1259
|
+
clickBubbled=event => {
|
1260
|
+
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
|
1261
|
+
const target = event.composedPath && event.composedPath()[0] || event.target;
|
1262
|
+
const link = this.findLinkFromClickTarget(target);
|
1263
|
+
if (link && doesNotTargetIFrame(link)) {
|
1264
|
+
const location = this.getLocationForLink(link);
|
1265
|
+
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
1266
|
+
event.preventDefault();
|
1267
|
+
this.delegate.followedLinkToLocation(link, location);
|
1268
|
+
}
|
1269
|
+
}
|
1270
|
+
}
|
1271
|
+
};
|
1187
1272
|
clickEventIsSignificant(event) {
|
1188
1273
|
return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
|
1189
1274
|
}
|
@@ -1218,7 +1303,7 @@ class FormLinkClickObserver {
|
|
1218
1303
|
this.linkInterceptor.stop();
|
1219
1304
|
}
|
1220
1305
|
willFollowLinkToLocation(link, location, originalEvent) {
|
1221
|
-
return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
|
1306
|
+
return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && (link.hasAttribute("data-turbo-method") || link.hasAttribute("data-turbo-stream"));
|
1222
1307
|
}
|
1223
1308
|
followedLinkToLocation(link, location) {
|
1224
1309
|
const form = document.createElement("form");
|
@@ -1291,7 +1376,7 @@ class Bardo {
|
|
1291
1376
|
}
|
1292
1377
|
replacePlaceholderWithPermanentElement(permanentElement) {
|
1293
1378
|
const placeholder = this.getPlaceholderById(permanentElement.id);
|
1294
|
-
placeholder
|
1379
|
+
placeholder?.replaceWith(permanentElement);
|
1295
1380
|
}
|
1296
1381
|
getPlaceholderById(id) {
|
1297
1382
|
return this.placeholders.find((element => element.content == id));
|
@@ -1309,8 +1394,8 @@ function createPlaceholderForPermanentElement(permanentElement) {
|
|
1309
1394
|
}
|
1310
1395
|
|
1311
1396
|
class Renderer {
|
1397
|
+
#activeElement=null;
|
1312
1398
|
constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
1313
|
-
this.activeElement = null;
|
1314
1399
|
this.currentSnapshot = currentSnapshot;
|
1315
1400
|
this.newSnapshot = newSnapshot;
|
1316
1401
|
this.isPreview = isPreview;
|
@@ -1330,6 +1415,7 @@ class Renderer {
|
|
1330
1415
|
prepareToRender() {
|
1331
1416
|
return;
|
1332
1417
|
}
|
1418
|
+
render() {}
|
1333
1419
|
finishRendering() {
|
1334
1420
|
if (this.resolvingFunctions) {
|
1335
1421
|
this.resolvingFunctions.resolve();
|
@@ -1341,20 +1427,20 @@ class Renderer {
|
|
1341
1427
|
}
|
1342
1428
|
focusFirstAutofocusableElement() {
|
1343
1429
|
const element = this.connectedSnapshot.firstAutofocusableElement;
|
1344
|
-
if (
|
1430
|
+
if (element) {
|
1345
1431
|
element.focus();
|
1346
1432
|
}
|
1347
1433
|
}
|
1348
1434
|
enteringBardo(currentPermanentElement) {
|
1349
|
-
if (this
|
1435
|
+
if (this.#activeElement) return;
|
1350
1436
|
if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
|
1351
|
-
this
|
1437
|
+
this.#activeElement = this.currentSnapshot.activeElement;
|
1352
1438
|
}
|
1353
1439
|
}
|
1354
1440
|
leavingBardo(currentPermanentElement) {
|
1355
|
-
if (currentPermanentElement.contains(this
|
1356
|
-
this
|
1357
|
-
this
|
1441
|
+
if (currentPermanentElement.contains(this.#activeElement) && this.#activeElement instanceof HTMLElement) {
|
1442
|
+
this.#activeElement.focus();
|
1443
|
+
this.#activeElement = null;
|
1358
1444
|
}
|
1359
1445
|
}
|
1360
1446
|
get connectedSnapshot() {
|
@@ -1369,20 +1455,18 @@ class Renderer {
|
|
1369
1455
|
get permanentElementMap() {
|
1370
1456
|
return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
|
1371
1457
|
}
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
return element && typeof element.focus == "function";
|
1458
|
+
get renderMethod() {
|
1459
|
+
return "replace";
|
1460
|
+
}
|
1376
1461
|
}
|
1377
1462
|
|
1378
1463
|
class FrameRenderer extends Renderer {
|
1379
1464
|
static renderElement(currentElement, newElement) {
|
1380
|
-
var _a;
|
1381
1465
|
const destinationRange = document.createRange();
|
1382
1466
|
destinationRange.selectNodeContents(currentElement);
|
1383
1467
|
destinationRange.deleteContents();
|
1384
1468
|
const frameElement = newElement;
|
1385
|
-
const sourceRange =
|
1469
|
+
const sourceRange = frameElement.ownerDocument?.createRange();
|
1386
1470
|
if (sourceRange) {
|
1387
1471
|
sourceRange.selectNodeContents(frameElement);
|
1388
1472
|
currentElement.appendChild(sourceRange.extractContents());
|
@@ -1453,6 +1537,7 @@ function readScrollBehavior(value, defaultValue) {
|
|
1453
1537
|
}
|
1454
1538
|
|
1455
1539
|
class ProgressBar {
|
1540
|
+
static animationDuration=300;
|
1456
1541
|
static get defaultCSS() {
|
1457
1542
|
return unindent`
|
1458
1543
|
.turbo-progress-bar {
|
@@ -1470,13 +1555,10 @@ class ProgressBar {
|
|
1470
1555
|
}
|
1471
1556
|
`;
|
1472
1557
|
}
|
1558
|
+
hiding=false;
|
1559
|
+
value=0;
|
1560
|
+
visible=false;
|
1473
1561
|
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
1562
|
this.stylesheetElement = this.createStylesheetElement();
|
1481
1563
|
this.progressElement = this.createProgressElement();
|
1482
1564
|
this.installStylesheetElement();
|
@@ -1531,6 +1613,9 @@ class ProgressBar {
|
|
1531
1613
|
window.clearInterval(this.trickleInterval);
|
1532
1614
|
delete this.trickleInterval;
|
1533
1615
|
}
|
1616
|
+
trickle=() => {
|
1617
|
+
this.setValue(this.value + Math.random() / 100);
|
1618
|
+
};
|
1534
1619
|
refresh() {
|
1535
1620
|
requestAnimationFrame((() => {
|
1536
1621
|
this.progressElement.style.width = `${10 + this.value * 90}%`;
|
@@ -1555,25 +1640,22 @@ class ProgressBar {
|
|
1555
1640
|
}
|
1556
1641
|
}
|
1557
1642
|
|
1558
|
-
ProgressBar.animationDuration = 300;
|
1559
|
-
|
1560
1643
|
class HeadSnapshot extends Snapshot {
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
}
|
1644
|
+
detailsByOuterHTML=this.children.filter((element => !elementIsNoscript(element))).map((element => elementWithoutNonce(element))).reduce(((result, element) => {
|
1645
|
+
const {outerHTML: outerHTML} = element;
|
1646
|
+
const details = outerHTML in result ? result[outerHTML] : {
|
1647
|
+
type: elementType(element),
|
1648
|
+
tracked: elementIsTracked(element),
|
1649
|
+
elements: []
|
1650
|
+
};
|
1651
|
+
return {
|
1652
|
+
...result,
|
1653
|
+
[outerHTML]: {
|
1654
|
+
...details,
|
1655
|
+
elements: [ ...details.elements, element ]
|
1656
|
+
}
|
1657
|
+
};
|
1658
|
+
}), {});
|
1577
1659
|
get trackedElementSignature() {
|
1578
1660
|
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
|
1579
1661
|
}
|
@@ -1606,7 +1688,7 @@ class HeadSnapshot extends Snapshot {
|
|
1606
1688
|
return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
|
1607
1689
|
const {elements: [element]} = this.detailsByOuterHTML[outerHTML];
|
1608
1690
|
return elementIsMetaElementWithName(element, name) ? element : result;
|
1609
|
-
}), undefined);
|
1691
|
+
}), undefined | undefined);
|
1610
1692
|
}
|
1611
1693
|
}
|
1612
1694
|
|
@@ -1656,11 +1738,12 @@ class PageSnapshot extends Snapshot {
|
|
1656
1738
|
static fromElement(element) {
|
1657
1739
|
return this.fromDocument(element.ownerDocument);
|
1658
1740
|
}
|
1659
|
-
static fromDocument({
|
1660
|
-
return new this(body, new HeadSnapshot(head));
|
1741
|
+
static fromDocument({documentElement: documentElement, body: body, head: head}) {
|
1742
|
+
return new this(documentElement, body, new HeadSnapshot(head));
|
1661
1743
|
}
|
1662
|
-
constructor(
|
1663
|
-
super(
|
1744
|
+
constructor(documentElement, body, headSnapshot) {
|
1745
|
+
super(body);
|
1746
|
+
this.documentElement = documentElement;
|
1664
1747
|
this.headSnapshot = headSnapshot;
|
1665
1748
|
}
|
1666
1749
|
clone() {
|
@@ -1675,14 +1758,16 @@ class PageSnapshot extends Snapshot {
|
|
1675
1758
|
for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
|
1676
1759
|
clonedPasswordInput.value = "";
|
1677
1760
|
}
|
1678
|
-
return new PageSnapshot(clonedElement, this.headSnapshot);
|
1761
|
+
return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot);
|
1762
|
+
}
|
1763
|
+
get lang() {
|
1764
|
+
return this.documentElement.getAttribute("lang");
|
1679
1765
|
}
|
1680
1766
|
get headElement() {
|
1681
1767
|
return this.headSnapshot.element;
|
1682
1768
|
}
|
1683
1769
|
get rootLocation() {
|
1684
|
-
|
1685
|
-
const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
|
1770
|
+
const root = this.getSetting("root") ?? "/";
|
1686
1771
|
return expandURL(root);
|
1687
1772
|
}
|
1688
1773
|
get cacheControlValue() {
|
@@ -1697,29 +1782,38 @@ class PageSnapshot extends Snapshot {
|
|
1697
1782
|
get isVisitable() {
|
1698
1783
|
return this.getSetting("visit-control") != "reload";
|
1699
1784
|
}
|
1785
|
+
get prefersViewTransitions() {
|
1786
|
+
return this.headSnapshot.getMetaValue("view-transition") === "same-origin";
|
1787
|
+
}
|
1788
|
+
get shouldMorphPage() {
|
1789
|
+
return this.getSetting("refresh-method") === "morph";
|
1790
|
+
}
|
1791
|
+
get shouldPreserveScrollPosition() {
|
1792
|
+
return this.getSetting("refresh-scroll") === "preserve";
|
1793
|
+
}
|
1700
1794
|
getSetting(name) {
|
1701
1795
|
return this.headSnapshot.getMetaValue(`turbo-${name}`);
|
1702
1796
|
}
|
1703
1797
|
}
|
1704
1798
|
|
1705
|
-
|
1706
|
-
|
1707
|
-
(
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
1716
|
-
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
}
|
1799
|
+
class ViewTransitioner {
|
1800
|
+
#viewTransitionStarted=false;
|
1801
|
+
#lastOperation=Promise.resolve();
|
1802
|
+
renderChange(useViewTransition, render) {
|
1803
|
+
if (useViewTransition && this.viewTransitionsAvailable && !this.#viewTransitionStarted) {
|
1804
|
+
this.#viewTransitionStarted = true;
|
1805
|
+
this.#lastOperation = this.#lastOperation.then((async () => {
|
1806
|
+
await document.startViewTransition(render).finished;
|
1807
|
+
}));
|
1808
|
+
} else {
|
1809
|
+
this.#lastOperation = this.#lastOperation.then(render);
|
1810
|
+
}
|
1811
|
+
return this.#lastOperation;
|
1812
|
+
}
|
1813
|
+
get viewTransitionsAvailable() {
|
1814
|
+
return document.startViewTransition;
|
1815
|
+
}
|
1816
|
+
}
|
1723
1817
|
|
1724
1818
|
const defaultOptions = {
|
1725
1819
|
action: "advance",
|
@@ -1731,29 +1825,46 @@ const defaultOptions = {
|
|
1731
1825
|
acceptsStreamResponse: false
|
1732
1826
|
};
|
1733
1827
|
|
1734
|
-
|
1828
|
+
const TimingMetric = {
|
1829
|
+
visitStart: "visitStart",
|
1830
|
+
requestStart: "requestStart",
|
1831
|
+
requestEnd: "requestEnd",
|
1832
|
+
visitEnd: "visitEnd"
|
1833
|
+
};
|
1834
|
+
|
1835
|
+
const VisitState = {
|
1836
|
+
initialized: "initialized",
|
1837
|
+
started: "started",
|
1838
|
+
canceled: "canceled",
|
1839
|
+
failed: "failed",
|
1840
|
+
completed: "completed"
|
1841
|
+
};
|
1735
1842
|
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
1739
|
-
|
1740
|
-
}
|
1843
|
+
const SystemStatusCode = {
|
1844
|
+
networkFailure: 0,
|
1845
|
+
timeoutFailure: -1,
|
1846
|
+
contentTypeMismatch: -2
|
1847
|
+
};
|
1741
1848
|
|
1742
1849
|
class Visit {
|
1850
|
+
identifier=uuid();
|
1851
|
+
timingMetrics={};
|
1852
|
+
followedRedirect=false;
|
1853
|
+
historyChanged=false;
|
1854
|
+
scrolled=false;
|
1855
|
+
shouldCacheSnapshot=true;
|
1856
|
+
acceptsStreamResponse=false;
|
1857
|
+
snapshotCached=false;
|
1858
|
+
state=VisitState.initialized;
|
1859
|
+
viewTransitioner=new ViewTransitioner;
|
1743
1860
|
constructor(delegate, location, restorationIdentifier, options = {}) {
|
1744
|
-
this.identifier = uuid();
|
1745
|
-
this.timingMetrics = {};
|
1746
|
-
this.followedRedirect = false;
|
1747
|
-
this.historyChanged = false;
|
1748
|
-
this.scrolled = false;
|
1749
|
-
this.shouldCacheSnapshot = true;
|
1750
|
-
this.acceptsStreamResponse = false;
|
1751
|
-
this.snapshotCached = false;
|
1752
|
-
this.state = VisitState.initialized;
|
1753
1861
|
this.delegate = delegate;
|
1754
1862
|
this.location = location;
|
1755
1863
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
1756
|
-
const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} =
|
1864
|
+
const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = {
|
1865
|
+
...defaultOptions,
|
1866
|
+
...options
|
1867
|
+
};
|
1757
1868
|
this.action = action;
|
1758
1869
|
this.historyChanged = historyChanged;
|
1759
1870
|
this.referrer = referrer;
|
@@ -1815,12 +1926,12 @@ class Visit {
|
|
1815
1926
|
if (this.state == VisitState.started) {
|
1816
1927
|
this.state = VisitState.failed;
|
1817
1928
|
this.adapter.visitFailed(this);
|
1929
|
+
this.delegate.visitCompleted(this);
|
1818
1930
|
}
|
1819
1931
|
}
|
1820
1932
|
changeHistory() {
|
1821
|
-
var _a;
|
1822
1933
|
if (!this.historyChanged && this.updateHistory) {
|
1823
|
-
const actionForHistory = this.location.href ===
|
1934
|
+
const actionForHistory = this.location.href === this.referrer?.href ? "replace" : this.action;
|
1824
1935
|
const method = getHistoryMethodForAction(actionForHistory);
|
1825
1936
|
this.history.update(method, this.location, this.restorationIdentifier);
|
1826
1937
|
this.historyChanged = true;
|
@@ -1867,8 +1978,8 @@ class Visit {
|
|
1867
1978
|
if (this.shouldCacheSnapshot) this.cacheSnapshot();
|
1868
1979
|
if (this.view.renderPromise) await this.view.renderPromise;
|
1869
1980
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
1870
|
-
|
1871
|
-
this.
|
1981
|
+
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
1982
|
+
await this.renderPageSnapshot(snapshot, false);
|
1872
1983
|
this.adapter.visitRendered(this);
|
1873
1984
|
this.complete();
|
1874
1985
|
} else {
|
@@ -1905,8 +2016,7 @@ class Visit {
|
|
1905
2016
|
this.adapter.visitRendered(this);
|
1906
2017
|
} else {
|
1907
2018
|
if (this.view.renderPromise) await this.view.renderPromise;
|
1908
|
-
await this.
|
1909
|
-
this.performScroll();
|
2019
|
+
await this.renderPageSnapshot(snapshot, isPreview);
|
1910
2020
|
this.adapter.visitRendered(this);
|
1911
2021
|
if (!isPreview) {
|
1912
2022
|
this.complete();
|
@@ -1916,8 +2026,7 @@ class Visit {
|
|
1916
2026
|
}
|
1917
2027
|
}
|
1918
2028
|
followRedirect() {
|
1919
|
-
|
1920
|
-
if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
|
2029
|
+
if (this.redirectedToLocation && !this.followedRedirect && this.response?.redirected) {
|
1921
2030
|
this.adapter.visitProposedToLocation(this.redirectedToLocation, {
|
1922
2031
|
action: "replace",
|
1923
2032
|
response: this.response,
|
@@ -1989,7 +2098,7 @@ class Visit {
|
|
1989
2098
|
this.finishRequest();
|
1990
2099
|
}
|
1991
2100
|
performScroll() {
|
1992
|
-
if (!this.scrolled && !this.view.forceReloaded) {
|
2101
|
+
if (!this.scrolled && !this.view.forceReloaded && !this.view.snapshot.shouldPreserveScrollPosition) {
|
1993
2102
|
if (this.action == "restore") {
|
1994
2103
|
this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
|
1995
2104
|
} else {
|
@@ -2019,7 +2128,9 @@ class Visit {
|
|
2019
2128
|
this.timingMetrics[metric] = (new Date).getTime();
|
2020
2129
|
}
|
2021
2130
|
getTimingMetrics() {
|
2022
|
-
return
|
2131
|
+
return {
|
2132
|
+
...this.timingMetrics
|
2133
|
+
};
|
2023
2134
|
}
|
2024
2135
|
getHistoryMethodForAction(action) {
|
2025
2136
|
switch (action) {
|
@@ -2057,6 +2168,12 @@ class Visit {
|
|
2057
2168
|
await callback();
|
2058
2169
|
delete this.frame;
|
2059
2170
|
}
|
2171
|
+
async renderPageSnapshot(snapshot, isPreview) {
|
2172
|
+
await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(snapshot), (async () => {
|
2173
|
+
await this.view.renderPage(snapshot, isPreview, this.willRender, this);
|
2174
|
+
this.performScroll();
|
2175
|
+
}));
|
2176
|
+
}
|
2060
2177
|
cancelRender() {
|
2061
2178
|
if (this.frame) {
|
2062
2179
|
cancelAnimationFrame(this.frame);
|
@@ -2070,15 +2187,16 @@ function isSuccessful(statusCode) {
|
|
2070
2187
|
}
|
2071
2188
|
|
2072
2189
|
class BrowserAdapter {
|
2190
|
+
progressBar=new ProgressBar;
|
2073
2191
|
constructor(session) {
|
2074
|
-
this.progressBar = new ProgressBar;
|
2075
|
-
this.showProgressBar = () => {
|
2076
|
-
this.progressBar.show();
|
2077
|
-
};
|
2078
2192
|
this.session = session;
|
2079
2193
|
}
|
2080
2194
|
visitProposedToLocation(location, options) {
|
2081
|
-
|
2195
|
+
if (locationIsVisitable(location, this.navigator.rootLocation)) {
|
2196
|
+
this.navigator.startVisit(location, options?.restorationIdentifier || uuid(), options);
|
2197
|
+
} else {
|
2198
|
+
window.location.href = location.toString();
|
2199
|
+
}
|
2082
2200
|
}
|
2083
2201
|
visitStarted(visit) {
|
2084
2202
|
this.location = visit.location;
|
@@ -2113,15 +2231,18 @@ class BrowserAdapter {
|
|
2113
2231
|
return visit.loadResponse();
|
2114
2232
|
}
|
2115
2233
|
}
|
2116
|
-
visitRequestFinished(_visit) {
|
2234
|
+
visitRequestFinished(_visit) {}
|
2235
|
+
visitCompleted(_visit) {
|
2117
2236
|
this.progressBar.setValue(1);
|
2118
2237
|
this.hideVisitProgressBar();
|
2119
2238
|
}
|
2120
|
-
visitCompleted(_visit) {}
|
2121
2239
|
pageInvalidated(reason) {
|
2122
2240
|
this.reload(reason);
|
2123
2241
|
}
|
2124
|
-
visitFailed(_visit) {
|
2242
|
+
visitFailed(_visit) {
|
2243
|
+
this.progressBar.setValue(1);
|
2244
|
+
this.hideVisitProgressBar();
|
2245
|
+
}
|
2125
2246
|
visitRendered(_visit) {}
|
2126
2247
|
formSubmissionStarted(_formSubmission) {
|
2127
2248
|
this.progressBar.setValue(0);
|
@@ -2153,12 +2274,14 @@ class BrowserAdapter {
|
|
2153
2274
|
delete this.formProgressBarTimeout;
|
2154
2275
|
}
|
2155
2276
|
}
|
2277
|
+
showProgressBar=() => {
|
2278
|
+
this.progressBar.show();
|
2279
|
+
};
|
2156
2280
|
reload(reason) {
|
2157
|
-
var _a;
|
2158
2281
|
dispatch("turbo:reload", {
|
2159
2282
|
detail: reason
|
2160
2283
|
});
|
2161
|
-
window.location.href =
|
2284
|
+
window.location.href = this.location?.toString() || window.location.href;
|
2162
2285
|
}
|
2163
2286
|
get navigator() {
|
2164
2287
|
return this.session.navigator;
|
@@ -2166,16 +2289,9 @@ class BrowserAdapter {
|
|
2166
2289
|
}
|
2167
2290
|
|
2168
2291
|
class CacheObserver {
|
2169
|
-
|
2170
|
-
|
2171
|
-
|
2172
|
-
this.started = false;
|
2173
|
-
this.removeTemporaryElements = _event => {
|
2174
|
-
for (const element of this.temporaryElements) {
|
2175
|
-
element.remove();
|
2176
|
-
}
|
2177
|
-
};
|
2178
|
-
}
|
2292
|
+
selector="[data-turbo-temporary]";
|
2293
|
+
deprecatedSelector="[data-turbo-cache=false]";
|
2294
|
+
started=false;
|
2179
2295
|
start() {
|
2180
2296
|
if (!this.started) {
|
2181
2297
|
this.started = true;
|
@@ -2188,6 +2304,11 @@ class CacheObserver {
|
|
2188
2304
|
removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
2189
2305
|
}
|
2190
2306
|
}
|
2307
|
+
removeTemporaryElements=_event => {
|
2308
|
+
for (const element of this.temporaryElements) {
|
2309
|
+
element.remove();
|
2310
|
+
}
|
2311
|
+
};
|
2191
2312
|
get temporaryElements() {
|
2192
2313
|
return [ ...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation ];
|
2193
2314
|
}
|
@@ -2216,41 +2337,40 @@ class FrameRedirector {
|
|
2216
2337
|
this.formSubmitObserver.stop();
|
2217
2338
|
}
|
2218
2339
|
shouldInterceptLinkClick(element, _location, _event) {
|
2219
|
-
return this
|
2340
|
+
return this.#shouldRedirect(element);
|
2220
2341
|
}
|
2221
2342
|
linkClickIntercepted(element, url, event) {
|
2222
|
-
const frame = this
|
2343
|
+
const frame = this.#findFrameElement(element);
|
2223
2344
|
if (frame) {
|
2224
2345
|
frame.delegate.linkClickIntercepted(element, url, event);
|
2225
2346
|
}
|
2226
2347
|
}
|
2227
2348
|
willSubmitForm(element, submitter) {
|
2228
|
-
return element.closest("turbo-frame") == null && this
|
2349
|
+
return element.closest("turbo-frame") == null && this.#shouldSubmit(element, submitter) && this.#shouldRedirect(element, submitter);
|
2229
2350
|
}
|
2230
2351
|
formSubmitted(element, submitter) {
|
2231
|
-
const frame = this
|
2352
|
+
const frame = this.#findFrameElement(element, submitter);
|
2232
2353
|
if (frame) {
|
2233
2354
|
frame.delegate.formSubmitted(element, submitter);
|
2234
2355
|
}
|
2235
2356
|
}
|
2236
|
-
shouldSubmit(form, submitter) {
|
2237
|
-
|
2238
|
-
const action = getAction(form, submitter);
|
2357
|
+
#shouldSubmit(form, submitter) {
|
2358
|
+
const action = getAction$1(form, submitter);
|
2239
2359
|
const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
|
2240
|
-
const rootLocation = expandURL(
|
2241
|
-
return this
|
2360
|
+
const rootLocation = expandURL(meta?.content ?? "/");
|
2361
|
+
return this.#shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
|
2242
2362
|
}
|
2243
|
-
shouldRedirect(element, submitter) {
|
2363
|
+
#shouldRedirect(element, submitter) {
|
2244
2364
|
const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
|
2245
2365
|
if (isNavigatable) {
|
2246
|
-
const frame = this
|
2366
|
+
const frame = this.#findFrameElement(element, submitter);
|
2247
2367
|
return frame ? frame != element.closest("turbo-frame") : false;
|
2248
2368
|
} else {
|
2249
2369
|
return false;
|
2250
2370
|
}
|
2251
2371
|
}
|
2252
|
-
findFrameElement(element, submitter) {
|
2253
|
-
const id =
|
2372
|
+
#findFrameElement(element, submitter) {
|
2373
|
+
const id = submitter?.getAttribute("data-turbo-frame") || element.getAttribute("data-turbo-frame");
|
2254
2374
|
if (id && id != "_top") {
|
2255
2375
|
const frame = this.element.querySelector(`#${id}:not([disabled])`);
|
2256
2376
|
if (frame instanceof FrameElement) {
|
@@ -2261,26 +2381,12 @@ class FrameRedirector {
|
|
2261
2381
|
}
|
2262
2382
|
|
2263
2383
|
class History {
|
2384
|
+
location;
|
2385
|
+
restorationIdentifier=uuid();
|
2386
|
+
restorationData={};
|
2387
|
+
started=false;
|
2388
|
+
pageLoaded=false;
|
2264
2389
|
constructor(delegate) {
|
2265
|
-
this.restorationIdentifier = uuid();
|
2266
|
-
this.restorationData = {};
|
2267
|
-
this.started = false;
|
2268
|
-
this.pageLoaded = false;
|
2269
|
-
this.onPopState = event => {
|
2270
|
-
if (this.shouldHandlePopState()) {
|
2271
|
-
const {turbo: turbo} = event.state || {};
|
2272
|
-
if (turbo) {
|
2273
|
-
this.location = new URL(window.location.href);
|
2274
|
-
const {restorationIdentifier: restorationIdentifier} = turbo;
|
2275
|
-
this.restorationIdentifier = restorationIdentifier;
|
2276
|
-
this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
|
2277
|
-
}
|
2278
|
-
}
|
2279
|
-
};
|
2280
|
-
this.onPageLoad = async _event => {
|
2281
|
-
await nextMicrotask();
|
2282
|
-
this.pageLoaded = true;
|
2283
|
-
};
|
2284
2390
|
this.delegate = delegate;
|
2285
2391
|
}
|
2286
2392
|
start() {
|
@@ -2320,12 +2426,14 @@ class History {
|
|
2320
2426
|
updateRestorationData(additionalData) {
|
2321
2427
|
const {restorationIdentifier: restorationIdentifier} = this;
|
2322
2428
|
const restorationData = this.restorationData[restorationIdentifier];
|
2323
|
-
this.restorationData[restorationIdentifier] =
|
2429
|
+
this.restorationData[restorationIdentifier] = {
|
2430
|
+
...restorationData,
|
2431
|
+
...additionalData
|
2432
|
+
};
|
2324
2433
|
}
|
2325
2434
|
assumeControlOfScrollRestoration() {
|
2326
|
-
var _a;
|
2327
2435
|
if (!this.previousScrollRestoration) {
|
2328
|
-
this.previousScrollRestoration =
|
2436
|
+
this.previousScrollRestoration = history.scrollRestoration ?? "auto";
|
2329
2437
|
history.scrollRestoration = "manual";
|
2330
2438
|
}
|
2331
2439
|
}
|
@@ -2335,6 +2443,21 @@ class History {
|
|
2335
2443
|
delete this.previousScrollRestoration;
|
2336
2444
|
}
|
2337
2445
|
}
|
2446
|
+
onPopState=event => {
|
2447
|
+
if (this.shouldHandlePopState()) {
|
2448
|
+
const {turbo: turbo} = event.state || {};
|
2449
|
+
if (turbo) {
|
2450
|
+
this.location = new URL(window.location.href);
|
2451
|
+
const {restorationIdentifier: restorationIdentifier} = turbo;
|
2452
|
+
this.restorationIdentifier = restorationIdentifier;
|
2453
|
+
this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
|
2454
|
+
}
|
2455
|
+
}
|
2456
|
+
};
|
2457
|
+
onPageLoad=async _event => {
|
2458
|
+
await nextMicrotask();
|
2459
|
+
this.pageLoaded = true;
|
2460
|
+
};
|
2338
2461
|
shouldHandlePopState() {
|
2339
2462
|
return this.pageIsLoaded();
|
2340
2463
|
}
|
@@ -2349,18 +2472,15 @@ class Navigator {
|
|
2349
2472
|
}
|
2350
2473
|
proposeVisit(location, options = {}) {
|
2351
2474
|
if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
|
2352
|
-
|
2353
|
-
this.delegate.visitProposedToLocation(location, options);
|
2354
|
-
} else {
|
2355
|
-
window.location.href = location.toString();
|
2356
|
-
}
|
2475
|
+
this.delegate.visitProposedToLocation(location, options);
|
2357
2476
|
}
|
2358
2477
|
}
|
2359
2478
|
startVisit(locatable, restorationIdentifier, options = {}) {
|
2360
2479
|
this.stop();
|
2361
|
-
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier,
|
2362
|
-
referrer: this.location
|
2363
|
-
|
2480
|
+
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, {
|
2481
|
+
referrer: this.location,
|
2482
|
+
...options
|
2483
|
+
});
|
2364
2484
|
this.currentVisit.start();
|
2365
2485
|
}
|
2366
2486
|
submitForm(form, submitter) {
|
@@ -2384,6 +2504,9 @@ class Navigator {
|
|
2384
2504
|
get view() {
|
2385
2505
|
return this.delegate.view;
|
2386
2506
|
}
|
2507
|
+
get rootLocation() {
|
2508
|
+
return this.view.snapshot.rootLocation;
|
2509
|
+
}
|
2387
2510
|
get history() {
|
2388
2511
|
return this.delegate.history;
|
2389
2512
|
}
|
@@ -2401,7 +2524,7 @@ class Navigator {
|
|
2401
2524
|
this.view.clearSnapshotCache();
|
2402
2525
|
}
|
2403
2526
|
const {statusCode: statusCode, redirected: redirected} = fetchResponse;
|
2404
|
-
const action = this
|
2527
|
+
const action = this.#getActionForFormSubmission(formSubmission, fetchResponse);
|
2405
2528
|
const visitOptions = {
|
2406
2529
|
action: action,
|
2407
2530
|
shouldCacheSnapshot: shouldCacheSnapshot,
|
@@ -2424,7 +2547,9 @@ class Navigator {
|
|
2424
2547
|
} else {
|
2425
2548
|
await this.view.renderPage(snapshot, false, true, this.currentVisit);
|
2426
2549
|
}
|
2427
|
-
|
2550
|
+
if (!snapshot.shouldPreserveScrollPosition) {
|
2551
|
+
this.view.scrollToTop();
|
2552
|
+
}
|
2428
2553
|
this.view.clearSnapshotCache();
|
2429
2554
|
}
|
2430
2555
|
}
|
@@ -2457,35 +2582,27 @@ class Navigator {
|
|
2457
2582
|
get restorationIdentifier() {
|
2458
2583
|
return this.history.restorationIdentifier;
|
2459
2584
|
}
|
2460
|
-
getActionForFormSubmission(
|
2461
|
-
|
2585
|
+
#getActionForFormSubmission(formSubmission, fetchResponse) {
|
2586
|
+
const {submitter: submitter, formElement: formElement} = formSubmission;
|
2587
|
+
return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse);
|
2588
|
+
}
|
2589
|
+
#getDefaultAction(fetchResponse) {
|
2590
|
+
const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;
|
2591
|
+
return sameLocationRedirect ? "replace" : "advance";
|
2462
2592
|
}
|
2463
2593
|
}
|
2464
2594
|
|
2465
|
-
|
2466
|
-
|
2467
|
-
|
2468
|
-
|
2469
|
-
|
2470
|
-
|
2471
|
-
PageStage[PageStage["complete"] = 3] = "complete";
|
2472
|
-
})(PageStage || (PageStage = {}));
|
2595
|
+
const PageStage = {
|
2596
|
+
initial: 0,
|
2597
|
+
loading: 1,
|
2598
|
+
interactive: 2,
|
2599
|
+
complete: 3
|
2600
|
+
};
|
2473
2601
|
|
2474
2602
|
class PageObserver {
|
2603
|
+
stage=PageStage.initial;
|
2604
|
+
started=false;
|
2475
2605
|
constructor(delegate) {
|
2476
|
-
this.stage = PageStage.initial;
|
2477
|
-
this.started = false;
|
2478
|
-
this.interpretReadyState = () => {
|
2479
|
-
const {readyState: readyState} = this;
|
2480
|
-
if (readyState == "interactive") {
|
2481
|
-
this.pageIsInteractive();
|
2482
|
-
} else if (readyState == "complete") {
|
2483
|
-
this.pageIsComplete();
|
2484
|
-
}
|
2485
|
-
};
|
2486
|
-
this.pageWillUnload = () => {
|
2487
|
-
this.delegate.pageWillUnload();
|
2488
|
-
};
|
2489
2606
|
this.delegate = delegate;
|
2490
2607
|
}
|
2491
2608
|
start() {
|
@@ -2505,6 +2622,14 @@ class PageObserver {
|
|
2505
2622
|
this.started = false;
|
2506
2623
|
}
|
2507
2624
|
}
|
2625
|
+
interpretReadyState=() => {
|
2626
|
+
const {readyState: readyState} = this;
|
2627
|
+
if (readyState == "interactive") {
|
2628
|
+
this.pageIsInteractive();
|
2629
|
+
} else if (readyState == "complete") {
|
2630
|
+
this.pageIsComplete();
|
2631
|
+
}
|
2632
|
+
};
|
2508
2633
|
pageIsInteractive() {
|
2509
2634
|
if (this.stage == PageStage.loading) {
|
2510
2635
|
this.stage = PageStage.interactive;
|
@@ -2518,20 +2643,17 @@ class PageObserver {
|
|
2518
2643
|
this.delegate.pageLoaded();
|
2519
2644
|
}
|
2520
2645
|
}
|
2646
|
+
pageWillUnload=() => {
|
2647
|
+
this.delegate.pageWillUnload();
|
2648
|
+
};
|
2521
2649
|
get readyState() {
|
2522
2650
|
return document.readyState;
|
2523
2651
|
}
|
2524
2652
|
}
|
2525
2653
|
|
2526
2654
|
class ScrollObserver {
|
2655
|
+
started=false;
|
2527
2656
|
constructor(delegate) {
|
2528
|
-
this.started = false;
|
2529
|
-
this.onScroll = () => {
|
2530
|
-
this.updatePosition({
|
2531
|
-
x: window.pageXOffset,
|
2532
|
-
y: window.pageYOffset
|
2533
|
-
});
|
2534
|
-
};
|
2535
2657
|
this.delegate = delegate;
|
2536
2658
|
}
|
2537
2659
|
start() {
|
@@ -2547,6 +2669,12 @@ class ScrollObserver {
|
|
2547
2669
|
this.started = false;
|
2548
2670
|
}
|
2549
2671
|
}
|
2672
|
+
onScroll=() => {
|
2673
|
+
this.updatePosition({
|
2674
|
+
x: window.pageXOffset,
|
2675
|
+
y: window.pageYOffset
|
2676
|
+
});
|
2677
|
+
};
|
2550
2678
|
updatePosition(position) {
|
2551
2679
|
this.delegate.scrollPositionChanged(position);
|
2552
2680
|
}
|
@@ -2554,7 +2682,13 @@ class ScrollObserver {
|
|
2554
2682
|
|
2555
2683
|
class StreamMessageRenderer {
|
2556
2684
|
render({fragment: fragment}) {
|
2557
|
-
Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() =>
|
2685
|
+
Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => {
|
2686
|
+
withAutofocusFromFragment(fragment, (() => {
|
2687
|
+
withPreservedFocus((() => {
|
2688
|
+
document.documentElement.appendChild(fragment);
|
2689
|
+
}));
|
2690
|
+
}));
|
2691
|
+
}));
|
2558
2692
|
}
|
2559
2693
|
enteringBardo(currentPermanentElement, newPermanentElement) {
|
2560
2694
|
newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
|
@@ -2577,34 +2711,68 @@ function getPermanentElementMapForFragment(fragment) {
|
|
2577
2711
|
return permanentElementMap;
|
2578
2712
|
}
|
2579
2713
|
|
2580
|
-
|
2581
|
-
|
2582
|
-
|
2583
|
-
|
2584
|
-
|
2585
|
-
|
2586
|
-
|
2587
|
-
|
2588
|
-
|
2589
|
-
|
2590
|
-
};
|
2591
|
-
this.receiveMessageEvent = event => {
|
2592
|
-
if (this.started && typeof event.data == "string") {
|
2593
|
-
this.receiveMessageHTML(event.data);
|
2594
|
-
}
|
2595
|
-
};
|
2596
|
-
this.delegate = delegate;
|
2597
|
-
}
|
2598
|
-
start() {
|
2599
|
-
if (!this.started) {
|
2600
|
-
this.started = true;
|
2601
|
-
addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
2714
|
+
async function withAutofocusFromFragment(fragment, callback) {
|
2715
|
+
const generatedID = `turbo-stream-autofocus-${uuid()}`;
|
2716
|
+
const turboStreams = fragment.querySelectorAll("turbo-stream");
|
2717
|
+
const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
|
2718
|
+
let willAutofocusId = null;
|
2719
|
+
if (elementWithAutofocus) {
|
2720
|
+
if (elementWithAutofocus.id) {
|
2721
|
+
willAutofocusId = elementWithAutofocus.id;
|
2722
|
+
} else {
|
2723
|
+
willAutofocusId = generatedID;
|
2602
2724
|
}
|
2725
|
+
elementWithAutofocus.id = willAutofocusId;
|
2603
2726
|
}
|
2604
|
-
|
2605
|
-
|
2606
|
-
|
2607
|
-
|
2727
|
+
callback();
|
2728
|
+
await nextAnimationFrame();
|
2729
|
+
const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
|
2730
|
+
if (hasNoActiveElement && willAutofocusId) {
|
2731
|
+
const elementToAutofocus = document.getElementById(willAutofocusId);
|
2732
|
+
if (elementIsFocusable(elementToAutofocus)) {
|
2733
|
+
elementToAutofocus.focus();
|
2734
|
+
}
|
2735
|
+
if (elementToAutofocus && elementToAutofocus.id == generatedID) {
|
2736
|
+
elementToAutofocus.removeAttribute("id");
|
2737
|
+
}
|
2738
|
+
}
|
2739
|
+
}
|
2740
|
+
|
2741
|
+
async function withPreservedFocus(callback) {
|
2742
|
+
const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, (() => document.activeElement));
|
2743
|
+
const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;
|
2744
|
+
if (restoreFocusTo) {
|
2745
|
+
const elementToFocus = document.getElementById(restoreFocusTo);
|
2746
|
+
if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
|
2747
|
+
elementToFocus.focus();
|
2748
|
+
}
|
2749
|
+
}
|
2750
|
+
}
|
2751
|
+
|
2752
|
+
function firstAutofocusableElementInStreams(nodeListOfStreamElements) {
|
2753
|
+
for (const streamElement of nodeListOfStreamElements) {
|
2754
|
+
const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);
|
2755
|
+
if (elementWithAutofocus) return elementWithAutofocus;
|
2756
|
+
}
|
2757
|
+
return null;
|
2758
|
+
}
|
2759
|
+
|
2760
|
+
class StreamObserver {
|
2761
|
+
sources=new Set;
|
2762
|
+
#started=false;
|
2763
|
+
constructor(delegate) {
|
2764
|
+
this.delegate = delegate;
|
2765
|
+
}
|
2766
|
+
start() {
|
2767
|
+
if (!this.#started) {
|
2768
|
+
this.#started = true;
|
2769
|
+
addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
2770
|
+
}
|
2771
|
+
}
|
2772
|
+
stop() {
|
2773
|
+
if (this.#started) {
|
2774
|
+
this.#started = false;
|
2775
|
+
removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
2608
2776
|
}
|
2609
2777
|
}
|
2610
2778
|
connectStreamSource(source) {
|
@@ -2622,6 +2790,18 @@ class StreamObserver {
|
|
2622
2790
|
streamSourceIsConnected(source) {
|
2623
2791
|
return this.sources.has(source);
|
2624
2792
|
}
|
2793
|
+
inspectFetchResponse=event => {
|
2794
|
+
const response = fetchResponseFromEvent(event);
|
2795
|
+
if (response && fetchResponseIsStream(response)) {
|
2796
|
+
event.preventDefault();
|
2797
|
+
this.receiveMessageResponse(response);
|
2798
|
+
}
|
2799
|
+
};
|
2800
|
+
receiveMessageEvent=event => {
|
2801
|
+
if (this.#started && typeof event.data == "string") {
|
2802
|
+
this.receiveMessageHTML(event.data);
|
2803
|
+
}
|
2804
|
+
};
|
2625
2805
|
async receiveMessageResponse(response) {
|
2626
2806
|
const html = await response.responseHTML;
|
2627
2807
|
if (html) {
|
@@ -2634,16 +2814,14 @@ class StreamObserver {
|
|
2634
2814
|
}
|
2635
2815
|
|
2636
2816
|
function fetchResponseFromEvent(event) {
|
2637
|
-
|
2638
|
-
const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse;
|
2817
|
+
const fetchResponse = event.detail?.fetchResponse;
|
2639
2818
|
if (fetchResponse instanceof FetchResponse) {
|
2640
2819
|
return fetchResponse;
|
2641
2820
|
}
|
2642
2821
|
}
|
2643
2822
|
|
2644
2823
|
function fetchResponseIsStream(response) {
|
2645
|
-
|
2646
|
-
const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
|
2824
|
+
const contentType = response.contentType ?? "";
|
2647
2825
|
return contentType.startsWith(StreamMessage.contentType);
|
2648
2826
|
}
|
2649
2827
|
|
@@ -2678,6 +2856,575 @@ class ErrorRenderer extends Renderer {
|
|
2678
2856
|
}
|
2679
2857
|
}
|
2680
2858
|
|
2859
|
+
let EMPTY_SET = new Set;
|
2860
|
+
|
2861
|
+
function morph(oldNode, newContent, config = {}) {
|
2862
|
+
if (oldNode instanceof Document) {
|
2863
|
+
oldNode = oldNode.documentElement;
|
2864
|
+
}
|
2865
|
+
if (typeof newContent === "string") {
|
2866
|
+
newContent = parseContent(newContent);
|
2867
|
+
}
|
2868
|
+
let normalizedContent = normalizeContent(newContent);
|
2869
|
+
let ctx = createMorphContext(oldNode, normalizedContent, config);
|
2870
|
+
return morphNormalizedContent(oldNode, normalizedContent, ctx);
|
2871
|
+
}
|
2872
|
+
|
2873
|
+
function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
|
2874
|
+
if (ctx.head.block) {
|
2875
|
+
let oldHead = oldNode.querySelector("head");
|
2876
|
+
let newHead = normalizedNewContent.querySelector("head");
|
2877
|
+
if (oldHead && newHead) {
|
2878
|
+
let promises = handleHeadElement(newHead, oldHead, ctx);
|
2879
|
+
Promise.all(promises).then((function() {
|
2880
|
+
morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
|
2881
|
+
head: {
|
2882
|
+
block: false,
|
2883
|
+
ignore: true
|
2884
|
+
}
|
2885
|
+
}));
|
2886
|
+
}));
|
2887
|
+
return;
|
2888
|
+
}
|
2889
|
+
}
|
2890
|
+
if (ctx.morphStyle === "innerHTML") {
|
2891
|
+
morphChildren(normalizedNewContent, oldNode, ctx);
|
2892
|
+
return oldNode.children;
|
2893
|
+
} else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
|
2894
|
+
let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
|
2895
|
+
let previousSibling = bestMatch?.previousSibling;
|
2896
|
+
let nextSibling = bestMatch?.nextSibling;
|
2897
|
+
let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
|
2898
|
+
if (bestMatch) {
|
2899
|
+
return insertSiblings(previousSibling, morphedNode, nextSibling);
|
2900
|
+
} else {
|
2901
|
+
return [];
|
2902
|
+
}
|
2903
|
+
} else {
|
2904
|
+
throw "Do not understand how to morph style " + ctx.morphStyle;
|
2905
|
+
}
|
2906
|
+
}
|
2907
|
+
|
2908
|
+
function morphOldNodeTo(oldNode, newContent, ctx) {
|
2909
|
+
if (ctx.ignoreActive && oldNode === document.activeElement) ; else if (newContent == null) {
|
2910
|
+
if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return;
|
2911
|
+
oldNode.remove();
|
2912
|
+
ctx.callbacks.afterNodeRemoved(oldNode);
|
2913
|
+
return null;
|
2914
|
+
} else if (!isSoftMatch(oldNode, newContent)) {
|
2915
|
+
if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return;
|
2916
|
+
if (ctx.callbacks.beforeNodeAdded(newContent) === false) return;
|
2917
|
+
oldNode.parentElement.replaceChild(newContent, oldNode);
|
2918
|
+
ctx.callbacks.afterNodeAdded(newContent);
|
2919
|
+
ctx.callbacks.afterNodeRemoved(oldNode);
|
2920
|
+
return newContent;
|
2921
|
+
} else {
|
2922
|
+
if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return;
|
2923
|
+
if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
|
2924
|
+
handleHeadElement(newContent, oldNode, ctx);
|
2925
|
+
} else {
|
2926
|
+
syncNodeFrom(newContent, oldNode);
|
2927
|
+
morphChildren(newContent, oldNode, ctx);
|
2928
|
+
}
|
2929
|
+
ctx.callbacks.afterNodeMorphed(oldNode, newContent);
|
2930
|
+
return oldNode;
|
2931
|
+
}
|
2932
|
+
}
|
2933
|
+
|
2934
|
+
function morphChildren(newParent, oldParent, ctx) {
|
2935
|
+
let nextNewChild = newParent.firstChild;
|
2936
|
+
let insertionPoint = oldParent.firstChild;
|
2937
|
+
let newChild;
|
2938
|
+
while (nextNewChild) {
|
2939
|
+
newChild = nextNewChild;
|
2940
|
+
nextNewChild = newChild.nextSibling;
|
2941
|
+
if (insertionPoint == null) {
|
2942
|
+
if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
|
2943
|
+
oldParent.appendChild(newChild);
|
2944
|
+
ctx.callbacks.afterNodeAdded(newChild);
|
2945
|
+
removeIdsFromConsideration(ctx, newChild);
|
2946
|
+
continue;
|
2947
|
+
}
|
2948
|
+
if (isIdSetMatch(newChild, insertionPoint, ctx)) {
|
2949
|
+
morphOldNodeTo(insertionPoint, newChild, ctx);
|
2950
|
+
insertionPoint = insertionPoint.nextSibling;
|
2951
|
+
removeIdsFromConsideration(ctx, newChild);
|
2952
|
+
continue;
|
2953
|
+
}
|
2954
|
+
let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
|
2955
|
+
if (idSetMatch) {
|
2956
|
+
insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
|
2957
|
+
morphOldNodeTo(idSetMatch, newChild, ctx);
|
2958
|
+
removeIdsFromConsideration(ctx, newChild);
|
2959
|
+
continue;
|
2960
|
+
}
|
2961
|
+
let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
|
2962
|
+
if (softMatch) {
|
2963
|
+
insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
|
2964
|
+
morphOldNodeTo(softMatch, newChild, ctx);
|
2965
|
+
removeIdsFromConsideration(ctx, newChild);
|
2966
|
+
continue;
|
2967
|
+
}
|
2968
|
+
if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
|
2969
|
+
oldParent.insertBefore(newChild, insertionPoint);
|
2970
|
+
ctx.callbacks.afterNodeAdded(newChild);
|
2971
|
+
removeIdsFromConsideration(ctx, newChild);
|
2972
|
+
}
|
2973
|
+
while (insertionPoint !== null) {
|
2974
|
+
let tempNode = insertionPoint;
|
2975
|
+
insertionPoint = insertionPoint.nextSibling;
|
2976
|
+
removeNode(tempNode, ctx);
|
2977
|
+
}
|
2978
|
+
}
|
2979
|
+
|
2980
|
+
function syncNodeFrom(from, to) {
|
2981
|
+
let type = from.nodeType;
|
2982
|
+
if (type === 1) {
|
2983
|
+
const fromAttributes = from.attributes;
|
2984
|
+
const toAttributes = to.attributes;
|
2985
|
+
for (const fromAttribute of fromAttributes) {
|
2986
|
+
if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
|
2987
|
+
to.setAttribute(fromAttribute.name, fromAttribute.value);
|
2988
|
+
}
|
2989
|
+
}
|
2990
|
+
for (const toAttribute of toAttributes) {
|
2991
|
+
if (!from.hasAttribute(toAttribute.name)) {
|
2992
|
+
to.removeAttribute(toAttribute.name);
|
2993
|
+
}
|
2994
|
+
}
|
2995
|
+
}
|
2996
|
+
if (type === 8 || type === 3) {
|
2997
|
+
if (to.nodeValue !== from.nodeValue) {
|
2998
|
+
to.nodeValue = from.nodeValue;
|
2999
|
+
}
|
3000
|
+
}
|
3001
|
+
if (from instanceof HTMLInputElement && to instanceof HTMLInputElement && from.type !== "file") {
|
3002
|
+
to.value = from.value || "";
|
3003
|
+
syncAttribute(from, to, "value");
|
3004
|
+
syncAttribute(from, to, "checked");
|
3005
|
+
syncAttribute(from, to, "disabled");
|
3006
|
+
} else if (from instanceof HTMLOptionElement) {
|
3007
|
+
syncAttribute(from, to, "selected");
|
3008
|
+
} else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
|
3009
|
+
let fromValue = from.value;
|
3010
|
+
let toValue = to.value;
|
3011
|
+
if (fromValue !== toValue) {
|
3012
|
+
to.value = fromValue;
|
3013
|
+
}
|
3014
|
+
if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
|
3015
|
+
to.firstChild.nodeValue = fromValue;
|
3016
|
+
}
|
3017
|
+
}
|
3018
|
+
}
|
3019
|
+
|
3020
|
+
function syncAttribute(from, to, attributeName) {
|
3021
|
+
if (from[attributeName] !== to[attributeName]) {
|
3022
|
+
if (from[attributeName]) {
|
3023
|
+
to.setAttribute(attributeName, from[attributeName]);
|
3024
|
+
} else {
|
3025
|
+
to.removeAttribute(attributeName);
|
3026
|
+
}
|
3027
|
+
}
|
3028
|
+
}
|
3029
|
+
|
3030
|
+
function handleHeadElement(newHeadTag, currentHead, ctx) {
|
3031
|
+
let added = [];
|
3032
|
+
let removed = [];
|
3033
|
+
let preserved = [];
|
3034
|
+
let nodesToAppend = [];
|
3035
|
+
let headMergeStyle = ctx.head.style;
|
3036
|
+
let srcToNewHeadNodes = new Map;
|
3037
|
+
for (const newHeadChild of newHeadTag.children) {
|
3038
|
+
srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
|
3039
|
+
}
|
3040
|
+
for (const currentHeadElt of currentHead.children) {
|
3041
|
+
let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
|
3042
|
+
let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
|
3043
|
+
let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
|
3044
|
+
if (inNewContent || isPreserved) {
|
3045
|
+
if (isReAppended) {
|
3046
|
+
removed.push(currentHeadElt);
|
3047
|
+
} else {
|
3048
|
+
srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
|
3049
|
+
preserved.push(currentHeadElt);
|
3050
|
+
}
|
3051
|
+
} else {
|
3052
|
+
if (headMergeStyle === "append") {
|
3053
|
+
if (isReAppended) {
|
3054
|
+
removed.push(currentHeadElt);
|
3055
|
+
nodesToAppend.push(currentHeadElt);
|
3056
|
+
}
|
3057
|
+
} else {
|
3058
|
+
if (ctx.head.shouldRemove(currentHeadElt) !== false) {
|
3059
|
+
removed.push(currentHeadElt);
|
3060
|
+
}
|
3061
|
+
}
|
3062
|
+
}
|
3063
|
+
}
|
3064
|
+
nodesToAppend.push(...srcToNewHeadNodes.values());
|
3065
|
+
let promises = [];
|
3066
|
+
for (const newNode of nodesToAppend) {
|
3067
|
+
let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
|
3068
|
+
if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
|
3069
|
+
if (newElt.href || newElt.src) {
|
3070
|
+
let resolve = null;
|
3071
|
+
let promise = new Promise((function(_resolve) {
|
3072
|
+
resolve = _resolve;
|
3073
|
+
}));
|
3074
|
+
newElt.addEventListener("load", (function() {
|
3075
|
+
resolve();
|
3076
|
+
}));
|
3077
|
+
promises.push(promise);
|
3078
|
+
}
|
3079
|
+
currentHead.appendChild(newElt);
|
3080
|
+
ctx.callbacks.afterNodeAdded(newElt);
|
3081
|
+
added.push(newElt);
|
3082
|
+
}
|
3083
|
+
}
|
3084
|
+
for (const removedElement of removed) {
|
3085
|
+
if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
|
3086
|
+
currentHead.removeChild(removedElement);
|
3087
|
+
ctx.callbacks.afterNodeRemoved(removedElement);
|
3088
|
+
}
|
3089
|
+
}
|
3090
|
+
ctx.head.afterHeadMorphed(currentHead, {
|
3091
|
+
added: added,
|
3092
|
+
kept: preserved,
|
3093
|
+
removed: removed
|
3094
|
+
});
|
3095
|
+
return promises;
|
3096
|
+
}
|
3097
|
+
|
3098
|
+
function noOp() {}
|
3099
|
+
|
3100
|
+
function createMorphContext(oldNode, newContent, config) {
|
3101
|
+
return {
|
3102
|
+
target: oldNode,
|
3103
|
+
newContent: newContent,
|
3104
|
+
config: config,
|
3105
|
+
morphStyle: config.morphStyle,
|
3106
|
+
ignoreActive: config.ignoreActive,
|
3107
|
+
idMap: createIdMap(oldNode, newContent),
|
3108
|
+
deadIds: new Set,
|
3109
|
+
callbacks: Object.assign({
|
3110
|
+
beforeNodeAdded: noOp,
|
3111
|
+
afterNodeAdded: noOp,
|
3112
|
+
beforeNodeMorphed: noOp,
|
3113
|
+
afterNodeMorphed: noOp,
|
3114
|
+
beforeNodeRemoved: noOp,
|
3115
|
+
afterNodeRemoved: noOp
|
3116
|
+
}, config.callbacks),
|
3117
|
+
head: Object.assign({
|
3118
|
+
style: "merge",
|
3119
|
+
shouldPreserve: function(elt) {
|
3120
|
+
return elt.getAttribute("im-preserve") === "true";
|
3121
|
+
},
|
3122
|
+
shouldReAppend: function(elt) {
|
3123
|
+
return elt.getAttribute("im-re-append") === "true";
|
3124
|
+
},
|
3125
|
+
shouldRemove: noOp,
|
3126
|
+
afterHeadMorphed: noOp
|
3127
|
+
}, config.head)
|
3128
|
+
};
|
3129
|
+
}
|
3130
|
+
|
3131
|
+
function isIdSetMatch(node1, node2, ctx) {
|
3132
|
+
if (node1 == null || node2 == null) {
|
3133
|
+
return false;
|
3134
|
+
}
|
3135
|
+
if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
|
3136
|
+
if (node1.id !== "" && node1.id === node2.id) {
|
3137
|
+
return true;
|
3138
|
+
} else {
|
3139
|
+
return getIdIntersectionCount(ctx, node1, node2) > 0;
|
3140
|
+
}
|
3141
|
+
}
|
3142
|
+
return false;
|
3143
|
+
}
|
3144
|
+
|
3145
|
+
function isSoftMatch(node1, node2) {
|
3146
|
+
if (node1 == null || node2 == null) {
|
3147
|
+
return false;
|
3148
|
+
}
|
3149
|
+
return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName;
|
3150
|
+
}
|
3151
|
+
|
3152
|
+
function removeNodesBetween(startInclusive, endExclusive, ctx) {
|
3153
|
+
while (startInclusive !== endExclusive) {
|
3154
|
+
let tempNode = startInclusive;
|
3155
|
+
startInclusive = startInclusive.nextSibling;
|
3156
|
+
removeNode(tempNode, ctx);
|
3157
|
+
}
|
3158
|
+
removeIdsFromConsideration(ctx, endExclusive);
|
3159
|
+
return endExclusive.nextSibling;
|
3160
|
+
}
|
3161
|
+
|
3162
|
+
function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
|
3163
|
+
let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
|
3164
|
+
let potentialMatch = null;
|
3165
|
+
if (newChildPotentialIdCount > 0) {
|
3166
|
+
let potentialMatch = insertionPoint;
|
3167
|
+
let otherMatchCount = 0;
|
3168
|
+
while (potentialMatch != null) {
|
3169
|
+
if (isIdSetMatch(newChild, potentialMatch, ctx)) {
|
3170
|
+
return potentialMatch;
|
3171
|
+
}
|
3172
|
+
otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
|
3173
|
+
if (otherMatchCount > newChildPotentialIdCount) {
|
3174
|
+
return null;
|
3175
|
+
}
|
3176
|
+
potentialMatch = potentialMatch.nextSibling;
|
3177
|
+
}
|
3178
|
+
}
|
3179
|
+
return potentialMatch;
|
3180
|
+
}
|
3181
|
+
|
3182
|
+
function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
|
3183
|
+
let potentialSoftMatch = insertionPoint;
|
3184
|
+
let nextSibling = newChild.nextSibling;
|
3185
|
+
let siblingSoftMatchCount = 0;
|
3186
|
+
while (potentialSoftMatch != null) {
|
3187
|
+
if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
|
3188
|
+
return null;
|
3189
|
+
}
|
3190
|
+
if (isSoftMatch(newChild, potentialSoftMatch)) {
|
3191
|
+
return potentialSoftMatch;
|
3192
|
+
}
|
3193
|
+
if (isSoftMatch(nextSibling, potentialSoftMatch)) {
|
3194
|
+
siblingSoftMatchCount++;
|
3195
|
+
nextSibling = nextSibling.nextSibling;
|
3196
|
+
if (siblingSoftMatchCount >= 2) {
|
3197
|
+
return null;
|
3198
|
+
}
|
3199
|
+
}
|
3200
|
+
potentialSoftMatch = potentialSoftMatch.nextSibling;
|
3201
|
+
}
|
3202
|
+
return potentialSoftMatch;
|
3203
|
+
}
|
3204
|
+
|
3205
|
+
function parseContent(newContent) {
|
3206
|
+
let parser = new DOMParser;
|
3207
|
+
let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
|
3208
|
+
if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
|
3209
|
+
let content = parser.parseFromString(newContent, "text/html");
|
3210
|
+
if (contentWithSvgsRemoved.match(/<\/html>/)) {
|
3211
|
+
content.generatedByIdiomorph = true;
|
3212
|
+
return content;
|
3213
|
+
} else {
|
3214
|
+
let htmlElement = content.firstChild;
|
3215
|
+
if (htmlElement) {
|
3216
|
+
htmlElement.generatedByIdiomorph = true;
|
3217
|
+
return htmlElement;
|
3218
|
+
} else {
|
3219
|
+
return null;
|
3220
|
+
}
|
3221
|
+
}
|
3222
|
+
} else {
|
3223
|
+
let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
|
3224
|
+
let content = responseDoc.body.querySelector("template").content;
|
3225
|
+
content.generatedByIdiomorph = true;
|
3226
|
+
return content;
|
3227
|
+
}
|
3228
|
+
}
|
3229
|
+
|
3230
|
+
function normalizeContent(newContent) {
|
3231
|
+
if (newContent == null) {
|
3232
|
+
const dummyParent = document.createElement("div");
|
3233
|
+
return dummyParent;
|
3234
|
+
} else if (newContent.generatedByIdiomorph) {
|
3235
|
+
return newContent;
|
3236
|
+
} else if (newContent instanceof Node) {
|
3237
|
+
const dummyParent = document.createElement("div");
|
3238
|
+
dummyParent.append(newContent);
|
3239
|
+
return dummyParent;
|
3240
|
+
} else {
|
3241
|
+
const dummyParent = document.createElement("div");
|
3242
|
+
for (const elt of [ ...newContent ]) {
|
3243
|
+
dummyParent.append(elt);
|
3244
|
+
}
|
3245
|
+
return dummyParent;
|
3246
|
+
}
|
3247
|
+
}
|
3248
|
+
|
3249
|
+
function insertSiblings(previousSibling, morphedNode, nextSibling) {
|
3250
|
+
let stack = [];
|
3251
|
+
let added = [];
|
3252
|
+
while (previousSibling != null) {
|
3253
|
+
stack.push(previousSibling);
|
3254
|
+
previousSibling = previousSibling.previousSibling;
|
3255
|
+
}
|
3256
|
+
while (stack.length > 0) {
|
3257
|
+
let node = stack.pop();
|
3258
|
+
added.push(node);
|
3259
|
+
morphedNode.parentElement.insertBefore(node, morphedNode);
|
3260
|
+
}
|
3261
|
+
added.push(morphedNode);
|
3262
|
+
while (nextSibling != null) {
|
3263
|
+
stack.push(nextSibling);
|
3264
|
+
added.push(nextSibling);
|
3265
|
+
nextSibling = nextSibling.nextSibling;
|
3266
|
+
}
|
3267
|
+
while (stack.length > 0) {
|
3268
|
+
morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
|
3269
|
+
}
|
3270
|
+
return added;
|
3271
|
+
}
|
3272
|
+
|
3273
|
+
function findBestNodeMatch(newContent, oldNode, ctx) {
|
3274
|
+
let currentElement;
|
3275
|
+
currentElement = newContent.firstChild;
|
3276
|
+
let bestElement = currentElement;
|
3277
|
+
let score = 0;
|
3278
|
+
while (currentElement) {
|
3279
|
+
let newScore = scoreElement(currentElement, oldNode, ctx);
|
3280
|
+
if (newScore > score) {
|
3281
|
+
bestElement = currentElement;
|
3282
|
+
score = newScore;
|
3283
|
+
}
|
3284
|
+
currentElement = currentElement.nextSibling;
|
3285
|
+
}
|
3286
|
+
return bestElement;
|
3287
|
+
}
|
3288
|
+
|
3289
|
+
function scoreElement(node1, node2, ctx) {
|
3290
|
+
if (isSoftMatch(node1, node2)) {
|
3291
|
+
return .5 + getIdIntersectionCount(ctx, node1, node2);
|
3292
|
+
}
|
3293
|
+
return 0;
|
3294
|
+
}
|
3295
|
+
|
3296
|
+
function removeNode(tempNode, ctx) {
|
3297
|
+
removeIdsFromConsideration(ctx, tempNode);
|
3298
|
+
if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
|
3299
|
+
tempNode.remove();
|
3300
|
+
ctx.callbacks.afterNodeRemoved(tempNode);
|
3301
|
+
}
|
3302
|
+
|
3303
|
+
function isIdInConsideration(ctx, id) {
|
3304
|
+
return !ctx.deadIds.has(id);
|
3305
|
+
}
|
3306
|
+
|
3307
|
+
function idIsWithinNode(ctx, id, targetNode) {
|
3308
|
+
let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
|
3309
|
+
return idSet.has(id);
|
3310
|
+
}
|
3311
|
+
|
3312
|
+
function removeIdsFromConsideration(ctx, node) {
|
3313
|
+
let idSet = ctx.idMap.get(node) || EMPTY_SET;
|
3314
|
+
for (const id of idSet) {
|
3315
|
+
ctx.deadIds.add(id);
|
3316
|
+
}
|
3317
|
+
}
|
3318
|
+
|
3319
|
+
function getIdIntersectionCount(ctx, node1, node2) {
|
3320
|
+
let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
|
3321
|
+
let matchCount = 0;
|
3322
|
+
for (const id of sourceSet) {
|
3323
|
+
if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
|
3324
|
+
++matchCount;
|
3325
|
+
}
|
3326
|
+
}
|
3327
|
+
return matchCount;
|
3328
|
+
}
|
3329
|
+
|
3330
|
+
function populateIdMapForNode(node, idMap) {
|
3331
|
+
let nodeParent = node.parentElement;
|
3332
|
+
let idElements = node.querySelectorAll("[id]");
|
3333
|
+
for (const elt of idElements) {
|
3334
|
+
let current = elt;
|
3335
|
+
while (current !== nodeParent && current != null) {
|
3336
|
+
let idSet = idMap.get(current);
|
3337
|
+
if (idSet == null) {
|
3338
|
+
idSet = new Set;
|
3339
|
+
idMap.set(current, idSet);
|
3340
|
+
}
|
3341
|
+
idSet.add(elt.id);
|
3342
|
+
current = current.parentElement;
|
3343
|
+
}
|
3344
|
+
}
|
3345
|
+
}
|
3346
|
+
|
3347
|
+
function createIdMap(oldContent, newContent) {
|
3348
|
+
let idMap = new Map;
|
3349
|
+
populateIdMapForNode(oldContent, idMap);
|
3350
|
+
populateIdMapForNode(newContent, idMap);
|
3351
|
+
return idMap;
|
3352
|
+
}
|
3353
|
+
|
3354
|
+
var idiomorph = {
|
3355
|
+
morph: morph
|
3356
|
+
};
|
3357
|
+
|
3358
|
+
class MorphRenderer extends Renderer {
|
3359
|
+
async render() {
|
3360
|
+
if (this.willRender) await this.#morphBody();
|
3361
|
+
}
|
3362
|
+
get renderMethod() {
|
3363
|
+
return "morph";
|
3364
|
+
}
|
3365
|
+
async #morphBody() {
|
3366
|
+
this.#morphElements(this.currentElement, this.newElement);
|
3367
|
+
this.#reloadRemoteFrames();
|
3368
|
+
dispatch("turbo:morph", {
|
3369
|
+
detail: {
|
3370
|
+
currentElement: this.currentElement,
|
3371
|
+
newElement: this.newElement
|
3372
|
+
}
|
3373
|
+
});
|
3374
|
+
}
|
3375
|
+
#morphElements(currentElement, newElement, morphStyle = "outerHTML") {
|
3376
|
+
this.isMorphingTurboFrame = this.#isFrameReloadedWithMorph(currentElement);
|
3377
|
+
idiomorph.morph(currentElement, newElement, {
|
3378
|
+
morphStyle: morphStyle,
|
3379
|
+
callbacks: {
|
3380
|
+
beforeNodeAdded: this.#shouldAddElement,
|
3381
|
+
beforeNodeMorphed: this.#shouldMorphElement,
|
3382
|
+
beforeNodeRemoved: this.#shouldRemoveElement
|
3383
|
+
}
|
3384
|
+
});
|
3385
|
+
}
|
3386
|
+
#shouldAddElement=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
|
3387
|
+
#shouldMorphElement=(oldNode, newNode) => {
|
3388
|
+
if (oldNode instanceof HTMLElement) {
|
3389
|
+
return !oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode));
|
3390
|
+
} else {
|
3391
|
+
return true;
|
3392
|
+
}
|
3393
|
+
};
|
3394
|
+
#shouldRemoveElement=node => this.#shouldMorphElement(node);
|
3395
|
+
#reloadRemoteFrames() {
|
3396
|
+
this.#remoteFrames().forEach((frame => {
|
3397
|
+
if (this.#isFrameReloadedWithMorph(frame)) {
|
3398
|
+
this.#renderFrameWithMorph(frame);
|
3399
|
+
frame.reload();
|
3400
|
+
}
|
3401
|
+
}));
|
3402
|
+
}
|
3403
|
+
#renderFrameWithMorph(frame) {
|
3404
|
+
frame.addEventListener("turbo:before-frame-render", (event => {
|
3405
|
+
event.detail.render = this.#morphFrameUpdate;
|
3406
|
+
}), {
|
3407
|
+
once: true
|
3408
|
+
});
|
3409
|
+
}
|
3410
|
+
#morphFrameUpdate=(currentElement, newElement) => {
|
3411
|
+
dispatch("turbo:before-frame-morph", {
|
3412
|
+
target: currentElement,
|
3413
|
+
detail: {
|
3414
|
+
currentElement: currentElement,
|
3415
|
+
newElement: newElement
|
3416
|
+
}
|
3417
|
+
});
|
3418
|
+
this.#morphElements(currentElement, newElement.children, "innerHTML");
|
3419
|
+
};
|
3420
|
+
#isFrameReloadedWithMorph(element) {
|
3421
|
+
return element.src && element.refresh === "morph";
|
3422
|
+
}
|
3423
|
+
#remoteFrames() {
|
3424
|
+
return Array.from(document.querySelectorAll("turbo-frame[src]")).filter((frame => !frame.closest("[data-turbo-permanent]")));
|
3425
|
+
}
|
3426
|
+
}
|
3427
|
+
|
2681
3428
|
class PageRenderer extends Renderer {
|
2682
3429
|
static renderElement(currentElement, newElement) {
|
2683
3430
|
if (document.body && newElement instanceof HTMLBodyElement) {
|
@@ -2702,6 +3449,7 @@ class PageRenderer extends Renderer {
|
|
2702
3449
|
}
|
2703
3450
|
}
|
2704
3451
|
async prepareToRender() {
|
3452
|
+
this.#setLanguage();
|
2705
3453
|
await this.mergeHead();
|
2706
3454
|
}
|
2707
3455
|
async render() {
|
@@ -2724,6 +3472,15 @@ class PageRenderer extends Renderer {
|
|
2724
3472
|
get newElement() {
|
2725
3473
|
return this.newSnapshot.element;
|
2726
3474
|
}
|
3475
|
+
#setLanguage() {
|
3476
|
+
const {documentElement: documentElement} = this.currentSnapshot;
|
3477
|
+
const {lang: lang} = this.newSnapshot;
|
3478
|
+
if (lang) {
|
3479
|
+
documentElement.setAttribute("lang", lang);
|
3480
|
+
} else {
|
3481
|
+
documentElement.removeAttribute("lang");
|
3482
|
+
}
|
3483
|
+
}
|
2727
3484
|
async mergeHead() {
|
2728
3485
|
const mergedHeadElements = this.mergeProvisionalElements();
|
2729
3486
|
const newStylesheetElements = this.copyNewHeadStylesheetElements();
|
@@ -2823,9 +3580,9 @@ class PageRenderer extends Renderer {
|
|
2823
3580
|
}
|
2824
3581
|
|
2825
3582
|
class SnapshotCache {
|
3583
|
+
keys=[];
|
3584
|
+
snapshots={};
|
2826
3585
|
constructor(size) {
|
2827
|
-
this.keys = [];
|
2828
|
-
this.snapshots = {};
|
2829
3586
|
this.size = size;
|
2830
3587
|
}
|
2831
3588
|
has(location) {
|
@@ -2867,23 +3624,25 @@ class SnapshotCache {
|
|
2867
3624
|
}
|
2868
3625
|
|
2869
3626
|
class PageView extends View {
|
2870
|
-
|
2871
|
-
|
2872
|
-
|
2873
|
-
|
2874
|
-
this.
|
3627
|
+
snapshotCache=new SnapshotCache(10);
|
3628
|
+
lastRenderedLocation=new URL(location.href);
|
3629
|
+
forceReloaded=false;
|
3630
|
+
shouldTransitionTo(newSnapshot) {
|
3631
|
+
return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions;
|
2875
3632
|
}
|
2876
3633
|
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
2877
|
-
const
|
3634
|
+
const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
|
3635
|
+
const rendererClass = shouldMorphPage ? MorphRenderer : PageRenderer;
|
3636
|
+
const renderer = new rendererClass(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
|
2878
3637
|
if (!renderer.shouldRender) {
|
2879
3638
|
this.forceReloaded = true;
|
2880
3639
|
} else {
|
2881
|
-
visit
|
3640
|
+
visit?.changeHistory();
|
2882
3641
|
}
|
2883
3642
|
return this.render(renderer);
|
2884
3643
|
}
|
2885
3644
|
renderError(snapshot, visit) {
|
2886
|
-
visit
|
3645
|
+
visit?.changeHistory();
|
2887
3646
|
const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
|
2888
3647
|
return this.render(renderer);
|
2889
3648
|
}
|
@@ -2903,14 +3662,17 @@ class PageView extends View {
|
|
2903
3662
|
getCachedSnapshotForLocation(location) {
|
2904
3663
|
return this.snapshotCache.get(location);
|
2905
3664
|
}
|
3665
|
+
isPageRefresh(visit) {
|
3666
|
+
return !visit || this.lastRenderedLocation.href === visit.location.href && visit.action === "replace";
|
3667
|
+
}
|
2906
3668
|
get snapshot() {
|
2907
3669
|
return PageSnapshot.fromElement(this.element);
|
2908
3670
|
}
|
2909
3671
|
}
|
2910
3672
|
|
2911
3673
|
class Preloader {
|
3674
|
+
selector="a[data-turbo-preload]";
|
2912
3675
|
constructor(delegate) {
|
2913
|
-
this.selector = "a[data-turbo-preload]";
|
2914
3676
|
this.delegate = delegate;
|
2915
3677
|
}
|
2916
3678
|
get snapshotCache() {
|
@@ -2938,7 +3700,7 @@ class Preloader {
|
|
2938
3700
|
try {
|
2939
3701
|
const response = await fetch(location.toString(), {
|
2940
3702
|
headers: {
|
2941
|
-
"
|
3703
|
+
"Sec-Purpose": "prefetch",
|
2942
3704
|
Accept: "text/html"
|
2943
3705
|
}
|
2944
3706
|
});
|
@@ -2949,28 +3711,64 @@ class Preloader {
|
|
2949
3711
|
}
|
2950
3712
|
}
|
2951
3713
|
|
2952
|
-
class
|
2953
|
-
constructor() {
|
2954
|
-
|
2955
|
-
this.
|
2956
|
-
|
2957
|
-
|
2958
|
-
this.
|
2959
|
-
|
2960
|
-
|
2961
|
-
|
2962
|
-
|
2963
|
-
|
2964
|
-
|
2965
|
-
|
2966
|
-
|
2967
|
-
|
2968
|
-
|
2969
|
-
this.
|
2970
|
-
this.progressBarDelay = 500;
|
2971
|
-
this.started = false;
|
2972
|
-
this.formMode = "on";
|
3714
|
+
class LimitedSet extends Set {
|
3715
|
+
constructor(maxSize) {
|
3716
|
+
super();
|
3717
|
+
this.maxSize = maxSize;
|
3718
|
+
}
|
3719
|
+
add(value) {
|
3720
|
+
if (this.size >= this.maxSize) {
|
3721
|
+
const iterator = this.values();
|
3722
|
+
const oldestValue = iterator.next().value;
|
3723
|
+
this.delete(oldestValue);
|
3724
|
+
}
|
3725
|
+
super.add(value);
|
3726
|
+
}
|
3727
|
+
}
|
3728
|
+
|
3729
|
+
class Cache {
|
3730
|
+
constructor(session) {
|
3731
|
+
this.session = session;
|
2973
3732
|
}
|
3733
|
+
clear() {
|
3734
|
+
this.session.clearCache();
|
3735
|
+
}
|
3736
|
+
resetCacheControl() {
|
3737
|
+
this.#setCacheControl("");
|
3738
|
+
}
|
3739
|
+
exemptPageFromCache() {
|
3740
|
+
this.#setCacheControl("no-cache");
|
3741
|
+
}
|
3742
|
+
exemptPageFromPreview() {
|
3743
|
+
this.#setCacheControl("no-preview");
|
3744
|
+
}
|
3745
|
+
#setCacheControl(value) {
|
3746
|
+
setMetaContent("turbo-cache-control", value);
|
3747
|
+
}
|
3748
|
+
}
|
3749
|
+
|
3750
|
+
class Session {
|
3751
|
+
navigator=new Navigator(this);
|
3752
|
+
history=new History(this);
|
3753
|
+
preloader=new Preloader(this);
|
3754
|
+
view=new PageView(this, document.documentElement);
|
3755
|
+
adapter=new BrowserAdapter(this);
|
3756
|
+
pageObserver=new PageObserver(this);
|
3757
|
+
cacheObserver=new CacheObserver;
|
3758
|
+
linkClickObserver=new LinkClickObserver(this, window);
|
3759
|
+
formSubmitObserver=new FormSubmitObserver(this, document);
|
3760
|
+
scrollObserver=new ScrollObserver(this);
|
3761
|
+
streamObserver=new StreamObserver(this);
|
3762
|
+
formLinkClickObserver=new FormLinkClickObserver(this, document.documentElement);
|
3763
|
+
frameRedirector=new FrameRedirector(this, document.documentElement);
|
3764
|
+
streamMessageRenderer=new StreamMessageRenderer;
|
3765
|
+
cache=new Cache(this);
|
3766
|
+
recentRequests=new LimitedSet(20);
|
3767
|
+
drive=true;
|
3768
|
+
enabled=true;
|
3769
|
+
progressBarDelay=500;
|
3770
|
+
started=false;
|
3771
|
+
formMode="on";
|
2974
3772
|
start() {
|
2975
3773
|
if (!this.started) {
|
2976
3774
|
this.pageObserver.start();
|
@@ -3016,6 +3814,15 @@ class Session {
|
|
3016
3814
|
this.navigator.proposeVisit(expandURL(location), options);
|
3017
3815
|
}
|
3018
3816
|
}
|
3817
|
+
refresh(url, requestId) {
|
3818
|
+
const isRecentRequest = requestId && this.recentRequests.has(requestId);
|
3819
|
+
if (!isRecentRequest) {
|
3820
|
+
this.cache.exemptPageFromPreview();
|
3821
|
+
this.visit(url, {
|
3822
|
+
action: "replace"
|
3823
|
+
});
|
3824
|
+
}
|
3825
|
+
}
|
3019
3826
|
connectStreamSource(source) {
|
3020
3827
|
this.streamObserver.connectStreamSource(source);
|
3021
3828
|
}
|
@@ -3099,7 +3906,7 @@ class Session {
|
|
3099
3906
|
this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
|
3100
3907
|
}
|
3101
3908
|
willSubmitForm(form, submitter) {
|
3102
|
-
const action = getAction(form, submitter);
|
3909
|
+
const action = getAction$1(form, submitter);
|
3103
3910
|
return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
|
3104
3911
|
}
|
3105
3912
|
formSubmitted(form, submitter) {
|
@@ -3119,22 +3926,21 @@ class Session {
|
|
3119
3926
|
this.renderStreamMessage(message);
|
3120
3927
|
}
|
3121
3928
|
viewWillCacheSnapshot() {
|
3122
|
-
|
3123
|
-
if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) {
|
3929
|
+
if (!this.navigator.currentVisit?.silent) {
|
3124
3930
|
this.notifyApplicationBeforeCachingSnapshot();
|
3125
3931
|
}
|
3126
3932
|
}
|
3127
|
-
allowsImmediateRender({element: element}, options) {
|
3128
|
-
const event = this.notifyApplicationBeforeRender(element, options);
|
3933
|
+
allowsImmediateRender({element: element}, isPreview, options) {
|
3934
|
+
const event = this.notifyApplicationBeforeRender(element, isPreview, options);
|
3129
3935
|
const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
|
3130
3936
|
if (this.view.renderer && render) {
|
3131
3937
|
this.view.renderer.renderElement = render;
|
3132
3938
|
}
|
3133
3939
|
return !defaultPrevented;
|
3134
3940
|
}
|
3135
|
-
viewRenderedSnapshot(_snapshot,
|
3941
|
+
viewRenderedSnapshot(_snapshot, isPreview, renderMethod) {
|
3136
3942
|
this.view.lastRenderedLocation = this.history.location;
|
3137
|
-
this.notifyApplicationAfterRender();
|
3943
|
+
this.notifyApplicationAfterRender(isPreview, renderMethod);
|
3138
3944
|
}
|
3139
3945
|
preloadOnLoadLinksForView(element) {
|
3140
3946
|
this.preloader.preloadOnLoadLinksForView(element);
|
@@ -3185,16 +3991,23 @@ class Session {
|
|
3185
3991
|
notifyApplicationBeforeCachingSnapshot() {
|
3186
3992
|
return dispatch("turbo:before-cache");
|
3187
3993
|
}
|
3188
|
-
notifyApplicationBeforeRender(newBody, options) {
|
3994
|
+
notifyApplicationBeforeRender(newBody, isPreview, options) {
|
3189
3995
|
return dispatch("turbo:before-render", {
|
3190
|
-
detail:
|
3191
|
-
newBody: newBody
|
3192
|
-
|
3996
|
+
detail: {
|
3997
|
+
newBody: newBody,
|
3998
|
+
isPreview: isPreview,
|
3999
|
+
...options
|
4000
|
+
},
|
3193
4001
|
cancelable: true
|
3194
4002
|
});
|
3195
4003
|
}
|
3196
|
-
notifyApplicationAfterRender() {
|
3197
|
-
return dispatch("turbo:render"
|
4004
|
+
notifyApplicationAfterRender(isPreview, renderMethod) {
|
4005
|
+
return dispatch("turbo:render", {
|
4006
|
+
detail: {
|
4007
|
+
isPreview: isPreview,
|
4008
|
+
renderMethod: renderMethod
|
4009
|
+
}
|
4010
|
+
});
|
3198
4011
|
}
|
3199
4012
|
notifyApplicationAfterPageLoad(timing = {}) {
|
3200
4013
|
return dispatch("turbo:load", {
|
@@ -3273,67 +4086,9 @@ const deprecatedLocationPropertyDescriptors = {
|
|
3273
4086
|
}
|
3274
4087
|
};
|
3275
4088
|
|
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
|
-
|
3332
4089
|
const session = new Session;
|
3333
4090
|
|
3334
|
-
const cache =
|
3335
|
-
|
3336
|
-
const {navigator: navigator$1} = session;
|
4091
|
+
const {cache: cache, navigator: navigator$1} = session;
|
3337
4092
|
|
3338
4093
|
function start() {
|
3339
4094
|
session.start();
|
@@ -3384,6 +4139,7 @@ var Turbo = Object.freeze({
|
|
3384
4139
|
PageRenderer: PageRenderer,
|
3385
4140
|
PageSnapshot: PageSnapshot,
|
3386
4141
|
FrameRenderer: FrameRenderer,
|
4142
|
+
fetch: fetch,
|
3387
4143
|
start: start,
|
3388
4144
|
registerAdapter: registerAdapter,
|
3389
4145
|
visit: visit,
|
@@ -3393,28 +4149,20 @@ var Turbo = Object.freeze({
|
|
3393
4149
|
clearCache: clearCache,
|
3394
4150
|
setProgressBarDelay: setProgressBarDelay,
|
3395
4151
|
setConfirmMethod: setConfirmMethod,
|
3396
|
-
setFormMode: setFormMode
|
3397
|
-
StreamActions: StreamActions
|
4152
|
+
setFormMode: setFormMode
|
3398
4153
|
});
|
3399
4154
|
|
3400
4155
|
class TurboFrameMissingError extends Error {}
|
3401
4156
|
|
3402
4157
|
class FrameController {
|
4158
|
+
fetchResponseLoaded=_fetchResponse => Promise.resolve();
|
4159
|
+
#currentFetchRequest=null;
|
4160
|
+
#resolveVisitPromise=() => {};
|
4161
|
+
#connected=false;
|
4162
|
+
#hasBeenLoaded=false;
|
4163
|
+
#ignoredAttributes=new Set;
|
4164
|
+
action=null;
|
3403
4165
|
constructor(element) {
|
3404
|
-
this.fetchResponseLoaded = _fetchResponse => {};
|
3405
|
-
this.currentFetchRequest = null;
|
3406
|
-
this.resolveVisitPromise = () => {};
|
3407
|
-
this.connected = false;
|
3408
|
-
this.hasBeenLoaded = false;
|
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
|
-
};
|
3418
4166
|
this.element = element;
|
3419
4167
|
this.view = new FrameView(this, this.element);
|
3420
4168
|
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
@@ -3424,12 +4172,12 @@ class FrameController {
|
|
3424
4172
|
this.formSubmitObserver = new FormSubmitObserver(this, this.element);
|
3425
4173
|
}
|
3426
4174
|
connect() {
|
3427
|
-
if (!this
|
3428
|
-
this
|
4175
|
+
if (!this.#connected) {
|
4176
|
+
this.#connected = true;
|
3429
4177
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
3430
4178
|
this.appearanceObserver.start();
|
3431
4179
|
} else {
|
3432
|
-
this
|
4180
|
+
this.#loadSourceURL();
|
3433
4181
|
}
|
3434
4182
|
this.formLinkClickObserver.start();
|
3435
4183
|
this.linkInterceptor.start();
|
@@ -3437,8 +4185,8 @@ class FrameController {
|
|
3437
4185
|
}
|
3438
4186
|
}
|
3439
4187
|
disconnect() {
|
3440
|
-
if (this
|
3441
|
-
this
|
4188
|
+
if (this.#connected) {
|
4189
|
+
this.#connected = false;
|
3442
4190
|
this.appearanceObserver.stop();
|
3443
4191
|
this.formLinkClickObserver.stop();
|
3444
4192
|
this.linkInterceptor.stop();
|
@@ -3447,21 +4195,21 @@ class FrameController {
|
|
3447
4195
|
}
|
3448
4196
|
disabledChanged() {
|
3449
4197
|
if (this.loadingStyle == FrameLoadingStyle.eager) {
|
3450
|
-
this
|
4198
|
+
this.#loadSourceURL();
|
3451
4199
|
}
|
3452
4200
|
}
|
3453
4201
|
sourceURLChanged() {
|
3454
|
-
if (this
|
4202
|
+
if (this.#isIgnoringChangesTo("src")) return;
|
3455
4203
|
if (this.element.isConnected) {
|
3456
4204
|
this.complete = false;
|
3457
4205
|
}
|
3458
|
-
if (this.loadingStyle == FrameLoadingStyle.eager || this
|
3459
|
-
this
|
4206
|
+
if (this.loadingStyle == FrameLoadingStyle.eager || this.#hasBeenLoaded) {
|
4207
|
+
this.#loadSourceURL();
|
3460
4208
|
}
|
3461
4209
|
}
|
3462
4210
|
sourceURLReloaded() {
|
3463
4211
|
const {src: src} = this.element;
|
3464
|
-
this
|
4212
|
+
this.#ignoringChangesToAttribute("complete", (() => {
|
3465
4213
|
this.element.removeAttribute("complete");
|
3466
4214
|
}));
|
3467
4215
|
this.element.src = null;
|
@@ -3469,23 +4217,23 @@ class FrameController {
|
|
3469
4217
|
return this.element.loaded;
|
3470
4218
|
}
|
3471
4219
|
completeChanged() {
|
3472
|
-
if (this
|
3473
|
-
this
|
4220
|
+
if (this.#isIgnoringChangesTo("complete")) return;
|
4221
|
+
this.#loadSourceURL();
|
3474
4222
|
}
|
3475
4223
|
loadingStyleChanged() {
|
3476
4224
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
3477
4225
|
this.appearanceObserver.start();
|
3478
4226
|
} else {
|
3479
4227
|
this.appearanceObserver.stop();
|
3480
|
-
this
|
4228
|
+
this.#loadSourceURL();
|
3481
4229
|
}
|
3482
4230
|
}
|
3483
|
-
async loadSourceURL() {
|
4231
|
+
async #loadSourceURL() {
|
3484
4232
|
if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
|
3485
|
-
this.element.loaded = this
|
4233
|
+
this.element.loaded = this.#visit(expandURL(this.sourceURL));
|
3486
4234
|
this.appearanceObserver.stop();
|
3487
4235
|
await this.element.loaded;
|
3488
|
-
this
|
4236
|
+
this.#hasBeenLoaded = true;
|
3489
4237
|
}
|
3490
4238
|
}
|
3491
4239
|
async loadResponse(fetchResponse) {
|
@@ -3498,34 +4246,34 @@ class FrameController {
|
|
3498
4246
|
const document = parseHTMLDocument(html);
|
3499
4247
|
const pageSnapshot = PageSnapshot.fromDocument(document);
|
3500
4248
|
if (pageSnapshot.isVisitable) {
|
3501
|
-
await this
|
4249
|
+
await this.#loadFrameResponse(fetchResponse, document);
|
3502
4250
|
} else {
|
3503
|
-
await this
|
4251
|
+
await this.#handleUnvisitableFrameResponse(fetchResponse);
|
3504
4252
|
}
|
3505
4253
|
}
|
3506
4254
|
} finally {
|
3507
|
-
this.fetchResponseLoaded = () =>
|
4255
|
+
this.fetchResponseLoaded = () => Promise.resolve();
|
3508
4256
|
}
|
3509
4257
|
}
|
3510
4258
|
elementAppearedInViewport(element) {
|
3511
4259
|
this.proposeVisitIfNavigatedWithAction(element, element);
|
3512
|
-
this
|
4260
|
+
this.#loadSourceURL();
|
3513
4261
|
}
|
3514
4262
|
willSubmitFormLinkToLocation(link) {
|
3515
|
-
return this
|
4263
|
+
return this.#shouldInterceptNavigation(link);
|
3516
4264
|
}
|
3517
4265
|
submittedFormLinkToLocation(link, _location, form) {
|
3518
|
-
const frame = this
|
4266
|
+
const frame = this.#findFrameElement(link);
|
3519
4267
|
if (frame) form.setAttribute("data-turbo-frame", frame.id);
|
3520
4268
|
}
|
3521
4269
|
shouldInterceptLinkClick(element, _location, _event) {
|
3522
|
-
return this
|
4270
|
+
return this.#shouldInterceptNavigation(element);
|
3523
4271
|
}
|
3524
4272
|
linkClickIntercepted(element, location) {
|
3525
|
-
this
|
4273
|
+
this.#navigateFrame(element, location);
|
3526
4274
|
}
|
3527
4275
|
willSubmitForm(element, submitter) {
|
3528
|
-
return element.closest("turbo-frame") == this.element && this
|
4276
|
+
return element.closest("turbo-frame") == this.element && this.#shouldInterceptNavigation(element, submitter);
|
3529
4277
|
}
|
3530
4278
|
formSubmitted(element, submitter) {
|
3531
4279
|
if (this.formSubmission) {
|
@@ -3537,9 +4285,8 @@ class FrameController {
|
|
3537
4285
|
this.formSubmission.start();
|
3538
4286
|
}
|
3539
4287
|
prepareRequest(request) {
|
3540
|
-
var _a;
|
3541
4288
|
request.headers["Turbo-Frame"] = this.id;
|
3542
|
-
if (
|
4289
|
+
if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
|
3543
4290
|
request.acceptResponseType(StreamMessage.contentType);
|
3544
4291
|
}
|
3545
4292
|
}
|
@@ -3547,28 +4294,28 @@ class FrameController {
|
|
3547
4294
|
markAsBusy(this.element);
|
3548
4295
|
}
|
3549
4296
|
requestPreventedHandlingResponse(_request, _response) {
|
3550
|
-
this
|
4297
|
+
this.#resolveVisitPromise();
|
3551
4298
|
}
|
3552
4299
|
async requestSucceededWithResponse(request, response) {
|
3553
4300
|
await this.loadResponse(response);
|
3554
|
-
this
|
4301
|
+
this.#resolveVisitPromise();
|
3555
4302
|
}
|
3556
4303
|
async requestFailedWithResponse(request, response) {
|
3557
4304
|
await this.loadResponse(response);
|
3558
|
-
this
|
4305
|
+
this.#resolveVisitPromise();
|
3559
4306
|
}
|
3560
4307
|
requestErrored(request, error) {
|
3561
4308
|
console.error(error);
|
3562
|
-
this
|
4309
|
+
this.#resolveVisitPromise();
|
3563
4310
|
}
|
3564
4311
|
requestFinished(_request) {
|
3565
4312
|
clearBusyState(this.element);
|
3566
4313
|
}
|
3567
4314
|
formSubmissionStarted({formElement: formElement}) {
|
3568
|
-
markAsBusy(formElement, this
|
4315
|
+
markAsBusy(formElement, this.#findFrameElement(formElement));
|
3569
4316
|
}
|
3570
4317
|
formSubmissionSucceededWithResponse(formSubmission, response) {
|
3571
|
-
const frame = this
|
4318
|
+
const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter);
|
3572
4319
|
frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
|
3573
4320
|
frame.delegate.loadResponse(response);
|
3574
4321
|
if (!formSubmission.isSafe) {
|
@@ -3583,14 +4330,15 @@ class FrameController {
|
|
3583
4330
|
console.error(error);
|
3584
4331
|
}
|
3585
4332
|
formSubmissionFinished({formElement: formElement}) {
|
3586
|
-
clearBusyState(formElement, this
|
4333
|
+
clearBusyState(formElement, this.#findFrameElement(formElement));
|
3587
4334
|
}
|
3588
|
-
allowsImmediateRender({element: newFrame}, options) {
|
4335
|
+
allowsImmediateRender({element: newFrame}, _isPreview, options) {
|
3589
4336
|
const event = dispatch("turbo:before-frame-render", {
|
3590
4337
|
target: this.element,
|
3591
|
-
detail:
|
3592
|
-
newFrame: newFrame
|
3593
|
-
|
4338
|
+
detail: {
|
4339
|
+
newFrame: newFrame,
|
4340
|
+
...options
|
4341
|
+
},
|
3594
4342
|
cancelable: true
|
3595
4343
|
});
|
3596
4344
|
const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
|
@@ -3599,7 +4347,7 @@ class FrameController {
|
|
3599
4347
|
}
|
3600
4348
|
return !defaultPrevented;
|
3601
4349
|
}
|
3602
|
-
viewRenderedSnapshot(_snapshot, _isPreview) {}
|
4350
|
+
viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {}
|
3603
4351
|
preloadOnLoadLinksForView(element) {
|
3604
4352
|
session.preloadOnLoadLinksForView(element);
|
3605
4353
|
}
|
@@ -3607,7 +4355,14 @@ class FrameController {
|
|
3607
4355
|
willRenderFrame(currentElement, _newElement) {
|
3608
4356
|
this.previousFrameElement = currentElement.cloneNode(true);
|
3609
4357
|
}
|
3610
|
-
|
4358
|
+
visitCachedSnapshot=({element: element}) => {
|
4359
|
+
const frame = element.querySelector("#" + this.element.id);
|
4360
|
+
if (frame && this.previousFrameElement) {
|
4361
|
+
frame.replaceChildren(...this.previousFrameElement.children);
|
4362
|
+
}
|
4363
|
+
delete this.previousFrameElement;
|
4364
|
+
};
|
4365
|
+
async #loadFrameResponse(fetchResponse, document) {
|
3611
4366
|
const newFrameElement = await this.extractForeignFrameElement(document.body);
|
3612
4367
|
if (newFrameElement) {
|
3613
4368
|
const snapshot = new Snapshot(newFrameElement);
|
@@ -3618,29 +4373,28 @@ class FrameController {
|
|
3618
4373
|
this.complete = true;
|
3619
4374
|
session.frameRendered(fetchResponse, this.element);
|
3620
4375
|
session.frameLoaded(this.element);
|
3621
|
-
this.fetchResponseLoaded(fetchResponse);
|
3622
|
-
} else if (this
|
3623
|
-
this
|
4376
|
+
await this.fetchResponseLoaded(fetchResponse);
|
4377
|
+
} else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) {
|
4378
|
+
this.#handleFrameMissingFromResponse(fetchResponse);
|
3624
4379
|
}
|
3625
4380
|
}
|
3626
|
-
async visit(url) {
|
3627
|
-
var _a;
|
4381
|
+
async #visit(url) {
|
3628
4382
|
const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
|
3629
|
-
|
3630
|
-
this
|
4383
|
+
this.#currentFetchRequest?.cancel();
|
4384
|
+
this.#currentFetchRequest = request;
|
3631
4385
|
return new Promise((resolve => {
|
3632
|
-
this
|
3633
|
-
this
|
3634
|
-
this
|
4386
|
+
this.#resolveVisitPromise = () => {
|
4387
|
+
this.#resolveVisitPromise = () => {};
|
4388
|
+
this.#currentFetchRequest = null;
|
3635
4389
|
resolve();
|
3636
4390
|
};
|
3637
4391
|
request.perform();
|
3638
4392
|
}));
|
3639
4393
|
}
|
3640
|
-
navigateFrame(element, url, submitter) {
|
3641
|
-
const frame = this
|
4394
|
+
#navigateFrame(element, url, submitter) {
|
4395
|
+
const frame = this.#findFrameElement(element, submitter);
|
3642
4396
|
frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
|
3643
|
-
this
|
4397
|
+
this.#withCurrentNavigationElement(element, (() => {
|
3644
4398
|
frame.src = url;
|
3645
4399
|
}));
|
3646
4400
|
}
|
@@ -3649,10 +4403,10 @@ class FrameController {
|
|
3649
4403
|
if (this.action) {
|
3650
4404
|
const pageSnapshot = PageSnapshot.fromElement(frame).clone();
|
3651
4405
|
const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
|
3652
|
-
frame.delegate.fetchResponseLoaded = fetchResponse => {
|
4406
|
+
frame.delegate.fetchResponseLoaded = async fetchResponse => {
|
3653
4407
|
if (frame.src) {
|
3654
4408
|
const {statusCode: statusCode, redirected: redirected} = fetchResponse;
|
3655
|
-
const responseHTML =
|
4409
|
+
const responseHTML = await fetchResponse.responseHTML;
|
3656
4410
|
const response = {
|
3657
4411
|
statusCode: statusCode,
|
3658
4412
|
redirected: redirected,
|
@@ -3678,16 +4432,16 @@ class FrameController {
|
|
3678
4432
|
session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
|
3679
4433
|
}
|
3680
4434
|
}
|
3681
|
-
async handleUnvisitableFrameResponse(fetchResponse) {
|
4435
|
+
async #handleUnvisitableFrameResponse(fetchResponse) {
|
3682
4436
|
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
|
4437
|
+
await this.#visitResponse(fetchResponse.response);
|
3684
4438
|
}
|
3685
|
-
willHandleFrameMissingFromResponse(fetchResponse) {
|
4439
|
+
#willHandleFrameMissingFromResponse(fetchResponse) {
|
3686
4440
|
this.element.setAttribute("complete", "");
|
3687
4441
|
const response = fetchResponse.response;
|
3688
|
-
const visit = async (url, options
|
4442
|
+
const visit = async (url, options) => {
|
3689
4443
|
if (url instanceof Response) {
|
3690
|
-
this
|
4444
|
+
this.#visitResponse(url);
|
3691
4445
|
} else {
|
3692
4446
|
session.visit(url, options);
|
3693
4447
|
}
|
@@ -3702,15 +4456,15 @@ class FrameController {
|
|
3702
4456
|
});
|
3703
4457
|
return !event.defaultPrevented;
|
3704
4458
|
}
|
3705
|
-
handleFrameMissingFromResponse(fetchResponse) {
|
4459
|
+
#handleFrameMissingFromResponse(fetchResponse) {
|
3706
4460
|
this.view.missing();
|
3707
|
-
this
|
4461
|
+
this.#throwFrameMissingError(fetchResponse);
|
3708
4462
|
}
|
3709
|
-
throwFrameMissingError(fetchResponse) {
|
4463
|
+
#throwFrameMissingError(fetchResponse) {
|
3710
4464
|
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
4465
|
throw new TurboFrameMissingError(message);
|
3712
4466
|
}
|
3713
|
-
async visitResponse(response) {
|
4467
|
+
async #visitResponse(response) {
|
3714
4468
|
const wrapped = new FetchResponse(response);
|
3715
4469
|
const responseHTML = await wrapped.responseHTML;
|
3716
4470
|
const {location: location, redirected: redirected, statusCode: statusCode} = wrapped;
|
@@ -3722,10 +4476,9 @@ class FrameController {
|
|
3722
4476
|
}
|
3723
4477
|
});
|
3724
4478
|
}
|
3725
|
-
findFrameElement(element, submitter) {
|
3726
|
-
var _a;
|
4479
|
+
#findFrameElement(element, submitter) {
|
3727
4480
|
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
3728
|
-
return
|
4481
|
+
return getFrameElementById(id) ?? this.element;
|
3729
4482
|
}
|
3730
4483
|
async extractForeignFrameElement(container) {
|
3731
4484
|
let element;
|
@@ -3746,13 +4499,13 @@ class FrameController {
|
|
3746
4499
|
}
|
3747
4500
|
return null;
|
3748
4501
|
}
|
3749
|
-
formActionIsVisitable(form, submitter) {
|
3750
|
-
const action = getAction(form, submitter);
|
4502
|
+
#formActionIsVisitable(form, submitter) {
|
4503
|
+
const action = getAction$1(form, submitter);
|
3751
4504
|
return locationIsVisitable(expandURL(action), this.rootLocation);
|
3752
4505
|
}
|
3753
|
-
shouldInterceptNavigation(element, submitter) {
|
4506
|
+
#shouldInterceptNavigation(element, submitter) {
|
3754
4507
|
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
3755
|
-
if (element instanceof HTMLFormElement && !this
|
4508
|
+
if (element instanceof HTMLFormElement && !this.#formActionIsVisitable(element, submitter)) {
|
3756
4509
|
return false;
|
3757
4510
|
}
|
3758
4511
|
if (!this.enabled || id == "_top") {
|
@@ -3784,21 +4537,21 @@ class FrameController {
|
|
3784
4537
|
}
|
3785
4538
|
}
|
3786
4539
|
set sourceURL(sourceURL) {
|
3787
|
-
this
|
3788
|
-
this.element.src = sourceURL
|
4540
|
+
this.#ignoringChangesToAttribute("src", (() => {
|
4541
|
+
this.element.src = sourceURL ?? null;
|
3789
4542
|
}));
|
3790
4543
|
}
|
3791
4544
|
get loadingStyle() {
|
3792
4545
|
return this.element.loading;
|
3793
4546
|
}
|
3794
4547
|
get isLoading() {
|
3795
|
-
return this.formSubmission !== undefined || this
|
4548
|
+
return this.formSubmission !== undefined || this.#resolveVisitPromise() !== undefined;
|
3796
4549
|
}
|
3797
4550
|
get complete() {
|
3798
4551
|
return this.element.hasAttribute("complete");
|
3799
4552
|
}
|
3800
4553
|
set complete(value) {
|
3801
|
-
this
|
4554
|
+
this.#ignoringChangesToAttribute("complete", (() => {
|
3802
4555
|
if (value) {
|
3803
4556
|
this.element.setAttribute("complete", "");
|
3804
4557
|
} else {
|
@@ -3807,23 +4560,22 @@ class FrameController {
|
|
3807
4560
|
}));
|
3808
4561
|
}
|
3809
4562
|
get isActive() {
|
3810
|
-
return this.element.isActive && this
|
4563
|
+
return this.element.isActive && this.#connected;
|
3811
4564
|
}
|
3812
4565
|
get rootLocation() {
|
3813
|
-
var _a;
|
3814
4566
|
const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
|
3815
|
-
const root =
|
4567
|
+
const root = meta?.content ?? "/";
|
3816
4568
|
return expandURL(root);
|
3817
4569
|
}
|
3818
|
-
isIgnoringChangesTo(attributeName) {
|
3819
|
-
return this
|
4570
|
+
#isIgnoringChangesTo(attributeName) {
|
4571
|
+
return this.#ignoredAttributes.has(attributeName);
|
3820
4572
|
}
|
3821
|
-
ignoringChangesToAttribute(attributeName, callback) {
|
3822
|
-
this
|
4573
|
+
#ignoringChangesToAttribute(attributeName, callback) {
|
4574
|
+
this.#ignoredAttributes.add(attributeName);
|
3823
4575
|
callback();
|
3824
|
-
this
|
4576
|
+
this.#ignoredAttributes.delete(attributeName);
|
3825
4577
|
}
|
3826
|
-
withCurrentNavigationElement(element, callback) {
|
4578
|
+
#withCurrentNavigationElement(element, callback) {
|
3827
4579
|
this.currentNavigationElement = element;
|
3828
4580
|
callback();
|
3829
4581
|
delete this.currentNavigationElement;
|
@@ -3856,6 +4608,38 @@ function activateElement(element, currentURL) {
|
|
3856
4608
|
}
|
3857
4609
|
}
|
3858
4610
|
|
4611
|
+
const StreamActions = {
|
4612
|
+
after() {
|
4613
|
+
this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e.nextSibling)));
|
4614
|
+
},
|
4615
|
+
append() {
|
4616
|
+
this.removeDuplicateTargetChildren();
|
4617
|
+
this.targetElements.forEach((e => e.append(this.templateContent)));
|
4618
|
+
},
|
4619
|
+
before() {
|
4620
|
+
this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e)));
|
4621
|
+
},
|
4622
|
+
prepend() {
|
4623
|
+
this.removeDuplicateTargetChildren();
|
4624
|
+
this.targetElements.forEach((e => e.prepend(this.templateContent)));
|
4625
|
+
},
|
4626
|
+
remove() {
|
4627
|
+
this.targetElements.forEach((e => e.remove()));
|
4628
|
+
},
|
4629
|
+
replace() {
|
4630
|
+
this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
|
4631
|
+
},
|
4632
|
+
update() {
|
4633
|
+
this.targetElements.forEach((targetElement => {
|
4634
|
+
targetElement.innerHTML = "";
|
4635
|
+
targetElement.append(this.templateContent);
|
4636
|
+
}));
|
4637
|
+
},
|
4638
|
+
refresh() {
|
4639
|
+
session.refresh(this.baseURI, this.requestId);
|
4640
|
+
}
|
4641
|
+
};
|
4642
|
+
|
3859
4643
|
class StreamElement extends HTMLElement {
|
3860
4644
|
static async renderElement(newElement) {
|
3861
4645
|
await newElement.performAction();
|
@@ -3870,11 +4654,10 @@ class StreamElement extends HTMLElement {
|
|
3870
4654
|
}
|
3871
4655
|
}
|
3872
4656
|
async render() {
|
3873
|
-
|
3874
|
-
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
|
4657
|
+
return this.renderPromise ??= (async () => {
|
3875
4658
|
const event = this.beforeRenderEvent;
|
3876
4659
|
if (this.dispatchEvent(event)) {
|
3877
|
-
await
|
4660
|
+
await nextRepaint();
|
3878
4661
|
await event.detail.render(this);
|
3879
4662
|
}
|
3880
4663
|
})();
|
@@ -3882,15 +4665,14 @@ class StreamElement extends HTMLElement {
|
|
3882
4665
|
disconnect() {
|
3883
4666
|
try {
|
3884
4667
|
this.remove();
|
3885
|
-
} catch
|
4668
|
+
} catch {}
|
3886
4669
|
}
|
3887
4670
|
removeDuplicateTargetChildren() {
|
3888
4671
|
this.duplicateChildren.forEach((c => c.remove()));
|
3889
4672
|
}
|
3890
4673
|
get duplicateChildren() {
|
3891
|
-
var _a;
|
3892
4674
|
const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
|
3893
|
-
const newChildrenIds = [ ...
|
4675
|
+
const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
|
3894
4676
|
return existingChildren.filter((c => newChildrenIds.includes(c.id)));
|
3895
4677
|
}
|
3896
4678
|
get performAction() {
|
@@ -3899,9 +4681,9 @@ class StreamElement extends HTMLElement {
|
|
3899
4681
|
if (actionFunction) {
|
3900
4682
|
return actionFunction;
|
3901
4683
|
}
|
3902
|
-
this
|
4684
|
+
this.#raise("unknown action");
|
3903
4685
|
}
|
3904
|
-
this
|
4686
|
+
this.#raise("action attribute is missing");
|
3905
4687
|
}
|
3906
4688
|
get targetElements() {
|
3907
4689
|
if (this.target) {
|
@@ -3909,7 +4691,7 @@ class StreamElement extends HTMLElement {
|
|
3909
4691
|
} else if (this.targets) {
|
3910
4692
|
return this.targetElementsByQuery;
|
3911
4693
|
} else {
|
3912
|
-
this
|
4694
|
+
this.#raise("target or targets attribute is missing");
|
3913
4695
|
}
|
3914
4696
|
}
|
3915
4697
|
get templateContent() {
|
@@ -3923,7 +4705,7 @@ class StreamElement extends HTMLElement {
|
|
3923
4705
|
} else if (this.firstElementChild instanceof HTMLTemplateElement) {
|
3924
4706
|
return this.firstElementChild;
|
3925
4707
|
}
|
3926
|
-
this
|
4708
|
+
this.#raise("first child element must be a <template> element");
|
3927
4709
|
}
|
3928
4710
|
get action() {
|
3929
4711
|
return this.getAttribute("action");
|
@@ -3934,12 +4716,14 @@ class StreamElement extends HTMLElement {
|
|
3934
4716
|
get targets() {
|
3935
4717
|
return this.getAttribute("targets");
|
3936
4718
|
}
|
3937
|
-
|
4719
|
+
get requestId() {
|
4720
|
+
return this.getAttribute("request-id");
|
4721
|
+
}
|
4722
|
+
#raise(message) {
|
3938
4723
|
throw new Error(`${this.description}: ${message}`);
|
3939
4724
|
}
|
3940
4725
|
get description() {
|
3941
|
-
|
3942
|
-
return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
|
4726
|
+
return (this.outerHTML.match(/<[^>]+>/) ?? [])[0] ?? "<turbo-stream>";
|
3943
4727
|
}
|
3944
4728
|
get beforeRenderEvent() {
|
3945
4729
|
return new CustomEvent("turbo:before-stream-render", {
|
@@ -3952,8 +4736,7 @@ class StreamElement extends HTMLElement {
|
|
3952
4736
|
});
|
3953
4737
|
}
|
3954
4738
|
get targetElementsById() {
|
3955
|
-
|
3956
|
-
const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
|
4739
|
+
const element = this.ownerDocument?.getElementById(this.target);
|
3957
4740
|
if (element !== null) {
|
3958
4741
|
return [ element ];
|
3959
4742
|
} else {
|
@@ -3961,8 +4744,7 @@ class StreamElement extends HTMLElement {
|
|
3961
4744
|
}
|
3962
4745
|
}
|
3963
4746
|
get targetElementsByQuery() {
|
3964
|
-
|
3965
|
-
const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);
|
4747
|
+
const elements = this.ownerDocument?.querySelectorAll(this.targets);
|
3966
4748
|
if (elements.length !== 0) {
|
3967
4749
|
return Array.prototype.slice.call(elements);
|
3968
4750
|
} else {
|
@@ -3972,16 +4754,14 @@ class StreamElement extends HTMLElement {
|
|
3972
4754
|
}
|
3973
4755
|
|
3974
4756
|
class StreamSourceElement extends HTMLElement {
|
3975
|
-
|
3976
|
-
super(...arguments);
|
3977
|
-
this.streamSource = null;
|
3978
|
-
}
|
4757
|
+
streamSource=null;
|
3979
4758
|
connectedCallback() {
|
3980
4759
|
this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
|
3981
4760
|
connectStreamSource(this.streamSource);
|
3982
4761
|
}
|
3983
4762
|
disconnectedCallback() {
|
3984
4763
|
if (this.streamSource) {
|
4764
|
+
this.streamSource.close();
|
3985
4765
|
disconnectStreamSource(this.streamSource);
|
3986
4766
|
}
|
3987
4767
|
}
|
@@ -4032,10 +4812,12 @@ start();
|
|
4032
4812
|
|
4033
4813
|
var turbo_es2017Esm = Object.freeze({
|
4034
4814
|
__proto__: null,
|
4815
|
+
FetchEnctype: FetchEnctype,
|
4816
|
+
FetchMethod: FetchMethod,
|
4817
|
+
FetchRequest: FetchRequest,
|
4818
|
+
FetchResponse: FetchResponse,
|
4035
4819
|
FrameElement: FrameElement,
|
4036
|
-
|
4037
|
-
return FrameLoadingStyle;
|
4038
|
-
},
|
4820
|
+
FrameLoadingStyle: FrameLoadingStyle,
|
4039
4821
|
FrameRenderer: FrameRenderer,
|
4040
4822
|
PageRenderer: PageRenderer,
|
4041
4823
|
PageSnapshot: PageSnapshot,
|
@@ -4046,6 +4828,10 @@ var turbo_es2017Esm = Object.freeze({
|
|
4046
4828
|
clearCache: clearCache,
|
4047
4829
|
connectStreamSource: connectStreamSource,
|
4048
4830
|
disconnectStreamSource: disconnectStreamSource,
|
4831
|
+
fetch: fetch,
|
4832
|
+
fetchEnctypeFromString: fetchEnctypeFromString,
|
4833
|
+
fetchMethodFromString: fetchMethodFromString,
|
4834
|
+
isSafe: isSafe,
|
4049
4835
|
navigator: navigator$1,
|
4050
4836
|
registerAdapter: registerAdapter,
|
4051
4837
|
renderStreamMessage: renderStreamMessage,
|
@@ -4060,14 +4846,14 @@ var turbo_es2017Esm = Object.freeze({
|
|
4060
4846
|
let consumer;
|
4061
4847
|
|
4062
4848
|
async function getConsumer() {
|
4063
|
-
return consumer || setConsumer(createConsumer().then(setConsumer));
|
4849
|
+
return consumer || setConsumer(createConsumer$1().then(setConsumer));
|
4064
4850
|
}
|
4065
4851
|
|
4066
4852
|
function setConsumer(newConsumer) {
|
4067
4853
|
return consumer = newConsumer;
|
4068
4854
|
}
|
4069
4855
|
|
4070
|
-
async function createConsumer() {
|
4856
|
+
async function createConsumer$1() {
|
4071
4857
|
const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
|
4072
4858
|
return index;
|
4073
4859
|
}));
|
@@ -4083,7 +4869,7 @@ var cable = Object.freeze({
|
|
4083
4869
|
__proto__: null,
|
4084
4870
|
getConsumer: getConsumer,
|
4085
4871
|
setConsumer: setConsumer,
|
4086
|
-
createConsumer: createConsumer,
|
4872
|
+
createConsumer: createConsumer$1,
|
4087
4873
|
subscribeTo: subscribeTo
|
4088
4874
|
});
|
4089
4875
|
|
@@ -4309,6 +5095,8 @@ ConnectionMonitor.staleThreshold = 6;
|
|
4309
5095
|
|
4310
5096
|
ConnectionMonitor.reconnectionBackoffRate = .15;
|
4311
5097
|
|
5098
|
+
var ConnectionMonitor$1 = ConnectionMonitor;
|
5099
|
+
|
4312
5100
|
var INTERNAL = {
|
4313
5101
|
message_types: {
|
4314
5102
|
welcome: "welcome",
|
@@ -4320,7 +5108,8 @@ var INTERNAL = {
|
|
4320
5108
|
disconnect_reasons: {
|
4321
5109
|
unauthorized: "unauthorized",
|
4322
5110
|
invalid_request: "invalid_request",
|
4323
|
-
server_restart: "server_restart"
|
5111
|
+
server_restart: "server_restart",
|
5112
|
+
remote: "remote"
|
4324
5113
|
},
|
4325
5114
|
default_mount_path: "/cable",
|
4326
5115
|
protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
|
@@ -4337,7 +5126,7 @@ class Connection {
|
|
4337
5126
|
this.open = this.open.bind(this);
|
4338
5127
|
this.consumer = consumer;
|
4339
5128
|
this.subscriptions = this.consumer.subscriptions;
|
4340
|
-
this.monitor = new ConnectionMonitor(this);
|
5129
|
+
this.monitor = new ConnectionMonitor$1(this);
|
4341
5130
|
this.disconnected = true;
|
4342
5131
|
}
|
4343
5132
|
send(data) {
|
@@ -4353,11 +5142,12 @@ class Connection {
|
|
4353
5142
|
logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
|
4354
5143
|
return false;
|
4355
5144
|
} else {
|
4356
|
-
|
5145
|
+
const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
|
5146
|
+
logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
|
4357
5147
|
if (this.webSocket) {
|
4358
5148
|
this.uninstallEventHandlers();
|
4359
5149
|
}
|
4360
|
-
this.webSocket = new adapters.WebSocket(this.consumer.url,
|
5150
|
+
this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
|
4361
5151
|
this.installEventHandlers();
|
4362
5152
|
this.monitor.start();
|
4363
5153
|
return true;
|
@@ -4369,7 +5159,7 @@ class Connection {
|
|
4369
5159
|
if (!allowReconnect) {
|
4370
5160
|
this.monitor.stop();
|
4371
5161
|
}
|
4372
|
-
if (this.
|
5162
|
+
if (this.isOpen()) {
|
4373
5163
|
return this.webSocket.close();
|
4374
5164
|
}
|
4375
5165
|
}
|
@@ -4399,6 +5189,9 @@ class Connection {
|
|
4399
5189
|
isActive() {
|
4400
5190
|
return this.isState("open", "connecting");
|
4401
5191
|
}
|
5192
|
+
triedToReconnect() {
|
5193
|
+
return this.monitor.reconnectAttempts > 0;
|
5194
|
+
}
|
4402
5195
|
isProtocolSupported() {
|
4403
5196
|
return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
|
4404
5197
|
}
|
@@ -4438,6 +5231,9 @@ Connection.prototype.events = {
|
|
4438
5231
|
const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
|
4439
5232
|
switch (type) {
|
4440
5233
|
case message_types.welcome:
|
5234
|
+
if (this.triedToReconnect()) {
|
5235
|
+
this.reconnectAttempted = true;
|
5236
|
+
}
|
4441
5237
|
this.monitor.recordConnect();
|
4442
5238
|
return this.subscriptions.reload();
|
4443
5239
|
|
@@ -4452,7 +5248,16 @@ Connection.prototype.events = {
|
|
4452
5248
|
|
4453
5249
|
case message_types.confirmation:
|
4454
5250
|
this.subscriptions.confirmSubscription(identifier);
|
4455
|
-
|
5251
|
+
if (this.reconnectAttempted) {
|
5252
|
+
this.reconnectAttempted = false;
|
5253
|
+
return this.subscriptions.notify(identifier, "connected", {
|
5254
|
+
reconnected: true
|
5255
|
+
});
|
5256
|
+
} else {
|
5257
|
+
return this.subscriptions.notify(identifier, "connected", {
|
5258
|
+
reconnected: false
|
5259
|
+
});
|
5260
|
+
}
|
4456
5261
|
|
4457
5262
|
case message_types.rejection:
|
4458
5263
|
return this.subscriptions.reject(identifier);
|
@@ -4487,6 +5292,8 @@ Connection.prototype.events = {
|
|
4487
5292
|
}
|
4488
5293
|
};
|
4489
5294
|
|
5295
|
+
var Connection$1 = Connection;
|
5296
|
+
|
4490
5297
|
const extend = function(object, properties) {
|
4491
5298
|
if (properties != null) {
|
4492
5299
|
for (let key in properties) {
|
@@ -4556,10 +5363,12 @@ class SubscriptionGuarantor {
|
|
4556
5363
|
}
|
4557
5364
|
}
|
4558
5365
|
|
5366
|
+
var SubscriptionGuarantor$1 = SubscriptionGuarantor;
|
5367
|
+
|
4559
5368
|
class Subscriptions {
|
4560
5369
|
constructor(consumer) {
|
4561
5370
|
this.consumer = consumer;
|
4562
|
-
this.guarantor = new SubscriptionGuarantor(this);
|
5371
|
+
this.guarantor = new SubscriptionGuarantor$1(this);
|
4563
5372
|
this.subscriptions = [];
|
4564
5373
|
}
|
4565
5374
|
create(channelName, mixin) {
|
@@ -4636,7 +5445,8 @@ class Consumer {
|
|
4636
5445
|
constructor(url) {
|
4637
5446
|
this._url = url;
|
4638
5447
|
this.subscriptions = new Subscriptions(this);
|
4639
|
-
this.connection = new Connection(this);
|
5448
|
+
this.connection = new Connection$1(this);
|
5449
|
+
this.subprotocols = [];
|
4640
5450
|
}
|
4641
5451
|
get url() {
|
4642
5452
|
return createWebSocketURL(this._url);
|
@@ -4657,6 +5467,9 @@ class Consumer {
|
|
4657
5467
|
return this.connection.open();
|
4658
5468
|
}
|
4659
5469
|
}
|
5470
|
+
addSubProtocol(subprotocol) {
|
5471
|
+
this.subprotocols = [ ...this.subprotocols, subprotocol ];
|
5472
|
+
}
|
4660
5473
|
}
|
4661
5474
|
|
4662
5475
|
function createWebSocketURL(url) {
|
@@ -4674,7 +5487,7 @@ function createWebSocketURL(url) {
|
|
4674
5487
|
}
|
4675
5488
|
}
|
4676
5489
|
|
4677
|
-
function createConsumer
|
5490
|
+
function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
|
4678
5491
|
return new Consumer(url);
|
4679
5492
|
}
|
4680
5493
|
|
@@ -4687,17 +5500,17 @@ function getConfig(name) {
|
|
4687
5500
|
|
4688
5501
|
var index = Object.freeze({
|
4689
5502
|
__proto__: null,
|
4690
|
-
Connection: Connection,
|
4691
|
-
ConnectionMonitor: ConnectionMonitor,
|
5503
|
+
Connection: Connection$1,
|
5504
|
+
ConnectionMonitor: ConnectionMonitor$1,
|
4692
5505
|
Consumer: Consumer,
|
4693
5506
|
INTERNAL: INTERNAL,
|
4694
5507
|
Subscription: Subscription,
|
4695
5508
|
Subscriptions: Subscriptions,
|
4696
|
-
SubscriptionGuarantor: SubscriptionGuarantor,
|
5509
|
+
SubscriptionGuarantor: SubscriptionGuarantor$1,
|
4697
5510
|
adapters: adapters,
|
4698
5511
|
createWebSocketURL: createWebSocketURL,
|
4699
5512
|
logger: logger,
|
4700
|
-
createConsumer: createConsumer
|
5513
|
+
createConsumer: createConsumer,
|
4701
5514
|
getConfig: getConfig
|
4702
5515
|
});
|
4703
5516
|
|