@hotwired/turbo-rails 7.2.5 → 8.0.0-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.
|
@@ -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) {
|
|
@@ -56,13 +44,14 @@ function clickCaptured(event) {
|
|
|
56
44
|
|
|
57
45
|
(function() {
|
|
58
46
|
if ("submitter" in Event.prototype) return;
|
|
59
|
-
let prototype;
|
|
60
|
-
if ("SubmitEvent" in window
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
let prototype = window.Event.prototype;
|
|
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
|
+
}
|
|
66
55
|
}
|
|
67
56
|
addEventListener("click", clickCaptured, true);
|
|
68
57
|
Object.defineProperty(prototype, "submitter", {
|
|
@@ -74,22 +63,21 @@ function clickCaptured(event) {
|
|
|
74
63
|
});
|
|
75
64
|
})();
|
|
76
65
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
FrameLoadingStyle["lazy"] = "lazy";
|
|
82
|
-
})(FrameLoadingStyle || (FrameLoadingStyle = {}));
|
|
66
|
+
const FrameLoadingStyle = {
|
|
67
|
+
eager: "eager",
|
|
68
|
+
lazy: "lazy"
|
|
69
|
+
};
|
|
83
70
|
|
|
84
71
|
class FrameElement extends HTMLElement {
|
|
72
|
+
static delegateConstructor=undefined;
|
|
73
|
+
loaded=Promise.resolve();
|
|
74
|
+
static get observedAttributes() {
|
|
75
|
+
return [ "disabled", "complete", "loading", "src" ];
|
|
76
|
+
}
|
|
85
77
|
constructor() {
|
|
86
78
|
super();
|
|
87
|
-
this.loaded = Promise.resolve();
|
|
88
79
|
this.delegate = new FrameElement.delegateConstructor(this);
|
|
89
80
|
}
|
|
90
|
-
static get observedAttributes() {
|
|
91
|
-
return [ "disabled", "complete", "loading", "src" ];
|
|
92
|
-
}
|
|
93
81
|
connectedCallback() {
|
|
94
82
|
this.delegate.connect();
|
|
95
83
|
}
|
|
@@ -120,6 +108,16 @@ class FrameElement extends HTMLElement {
|
|
|
120
108
|
this.removeAttribute("src");
|
|
121
109
|
}
|
|
122
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
|
+
}
|
|
123
121
|
get loading() {
|
|
124
122
|
return frameLoadingStyleFromString(this.getAttribute("loading") || "");
|
|
125
123
|
}
|
|
@@ -157,8 +155,7 @@ class FrameElement extends HTMLElement {
|
|
|
157
155
|
return this.ownerDocument === document && !this.isPreview;
|
|
158
156
|
}
|
|
159
157
|
get isPreview() {
|
|
160
|
-
|
|
161
|
-
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");
|
|
162
159
|
}
|
|
163
160
|
}
|
|
164
161
|
|
|
@@ -185,8 +182,8 @@ function getAnchor(url) {
|
|
|
185
182
|
}
|
|
186
183
|
}
|
|
187
184
|
|
|
188
|
-
function getAction(form, submitter) {
|
|
189
|
-
const action =
|
|
185
|
+
function getAction$1(form, submitter) {
|
|
186
|
+
const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action;
|
|
190
187
|
return expandURL(action);
|
|
191
188
|
}
|
|
192
189
|
|
|
@@ -325,6 +322,14 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
|
|
|
325
322
|
return event;
|
|
326
323
|
}
|
|
327
324
|
|
|
325
|
+
function nextRepaint() {
|
|
326
|
+
if (document.visibilityState === "hidden") {
|
|
327
|
+
return nextEventLoopTick();
|
|
328
|
+
} else {
|
|
329
|
+
return nextAnimationFrame();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
328
333
|
function nextAnimationFrame() {
|
|
329
334
|
return new Promise((resolve => requestAnimationFrame((() => resolve()))));
|
|
330
335
|
}
|
|
@@ -372,7 +377,7 @@ function uuid() {
|
|
|
372
377
|
}
|
|
373
378
|
|
|
374
379
|
function getAttribute(attributeName, ...elements) {
|
|
375
|
-
for (const value of elements.map((element => element
|
|
380
|
+
for (const value of elements.map((element => element?.getAttribute(attributeName)))) {
|
|
376
381
|
if (typeof value == "string") return value;
|
|
377
382
|
}
|
|
378
383
|
return null;
|
|
@@ -458,21 +463,38 @@ function setMetaContent(name, content) {
|
|
|
458
463
|
}
|
|
459
464
|
|
|
460
465
|
function findClosestRecursively(element, selector) {
|
|
461
|
-
var _a;
|
|
462
466
|
if (element instanceof Element) {
|
|
463
|
-
return element.closest(selector) || findClosestRecursively(element.assignedSlot ||
|
|
467
|
+
return element.closest(selector) || findClosestRecursively(element.assignedSlot || element.getRootNode()?.host, selector);
|
|
464
468
|
}
|
|
465
469
|
}
|
|
466
470
|
|
|
467
|
-
|
|
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
|
+
}
|
|
468
487
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
+
}
|
|
476
498
|
|
|
477
499
|
function fetchMethodFromString(method) {
|
|
478
500
|
switch (method.toLowerCase()) {
|
|
@@ -493,16 +515,81 @@ function fetchMethodFromString(method) {
|
|
|
493
515
|
}
|
|
494
516
|
}
|
|
495
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
|
+
|
|
496
545
|
class FetchRequest {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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);
|
|
500
550
|
this.delegate = delegate;
|
|
501
|
-
this.
|
|
502
|
-
this.headers = this.defaultHeaders;
|
|
503
|
-
this.body = body;
|
|
504
|
-
this.url = location;
|
|
551
|
+
this.url = url;
|
|
505
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;
|
|
506
593
|
}
|
|
507
594
|
get location() {
|
|
508
595
|
return this.url;
|
|
@@ -519,14 +606,14 @@ class FetchRequest {
|
|
|
519
606
|
async perform() {
|
|
520
607
|
const {fetchOptions: fetchOptions} = this;
|
|
521
608
|
this.delegate.prepareRequest(this);
|
|
522
|
-
await this
|
|
609
|
+
await this.#allowRequestToBeIntercepted(fetchOptions);
|
|
523
610
|
try {
|
|
524
611
|
this.delegate.requestStarted(this);
|
|
525
612
|
const response = await fetch(this.url.href, fetchOptions);
|
|
526
613
|
return await this.receive(response);
|
|
527
614
|
} catch (error) {
|
|
528
615
|
if (error.name !== "AbortError") {
|
|
529
|
-
if (this
|
|
616
|
+
if (this.#willDelegateErrorHandling(error)) {
|
|
530
617
|
this.delegate.requestErrored(this, error);
|
|
531
618
|
}
|
|
532
619
|
throw error;
|
|
@@ -553,25 +640,13 @@ class FetchRequest {
|
|
|
553
640
|
}
|
|
554
641
|
return fetchResponse;
|
|
555
642
|
}
|
|
556
|
-
get fetchOptions() {
|
|
557
|
-
var _a;
|
|
558
|
-
return {
|
|
559
|
-
method: FetchMethod[this.method].toUpperCase(),
|
|
560
|
-
credentials: "same-origin",
|
|
561
|
-
headers: this.headers,
|
|
562
|
-
redirect: "follow",
|
|
563
|
-
body: this.isIdempotent ? null : this.body,
|
|
564
|
-
signal: this.abortSignal,
|
|
565
|
-
referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
643
|
get defaultHeaders() {
|
|
569
644
|
return {
|
|
570
645
|
Accept: "text/html, application/xhtml+xml"
|
|
571
646
|
};
|
|
572
647
|
}
|
|
573
|
-
get
|
|
574
|
-
return this.method
|
|
648
|
+
get isSafe() {
|
|
649
|
+
return isSafe(this.method);
|
|
575
650
|
}
|
|
576
651
|
get abortSignal() {
|
|
577
652
|
return this.abortController.signal;
|
|
@@ -579,20 +654,21 @@ class FetchRequest {
|
|
|
579
654
|
acceptResponseType(mimeType) {
|
|
580
655
|
this.headers["Accept"] = [ mimeType, this.headers["Accept"] ].join(", ");
|
|
581
656
|
}
|
|
582
|
-
async allowRequestToBeIntercepted(fetchOptions) {
|
|
583
|
-
const requestInterception = new Promise((resolve => this
|
|
657
|
+
async #allowRequestToBeIntercepted(fetchOptions) {
|
|
658
|
+
const requestInterception = new Promise((resolve => this.#resolveRequestPromise = resolve));
|
|
584
659
|
const event = dispatch("turbo:before-fetch-request", {
|
|
585
660
|
cancelable: true,
|
|
586
661
|
detail: {
|
|
587
662
|
fetchOptions: fetchOptions,
|
|
588
663
|
url: this.url,
|
|
589
|
-
resume: this
|
|
664
|
+
resume: this.#resolveRequestPromise
|
|
590
665
|
},
|
|
591
666
|
target: this.target
|
|
592
667
|
});
|
|
668
|
+
this.url = event.detail.url;
|
|
593
669
|
if (event.defaultPrevented) await requestInterception;
|
|
594
670
|
}
|
|
595
|
-
willDelegateErrorHandling(error) {
|
|
671
|
+
#willDelegateErrorHandling(error) {
|
|
596
672
|
const event = dispatch("turbo:fetch-request-error", {
|
|
597
673
|
target: this.target,
|
|
598
674
|
cancelable: true,
|
|
@@ -605,15 +681,38 @@ class FetchRequest {
|
|
|
605
681
|
}
|
|
606
682
|
}
|
|
607
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
|
+
|
|
608
713
|
class AppearanceObserver {
|
|
714
|
+
started=false;
|
|
609
715
|
constructor(delegate, element) {
|
|
610
|
-
this.started = false;
|
|
611
|
-
this.intersect = entries => {
|
|
612
|
-
const lastEntry = entries.slice(-1)[0];
|
|
613
|
-
if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
|
|
614
|
-
this.delegate.elementAppearedInViewport(this.element);
|
|
615
|
-
}
|
|
616
|
-
};
|
|
617
716
|
this.delegate = delegate;
|
|
618
717
|
this.element = element;
|
|
619
718
|
this.intersectionObserver = new IntersectionObserver(this.intersect);
|
|
@@ -630,12 +729,16 @@ class AppearanceObserver {
|
|
|
630
729
|
this.intersectionObserver.unobserve(this.element);
|
|
631
730
|
}
|
|
632
731
|
}
|
|
732
|
+
intersect=entries => {
|
|
733
|
+
const lastEntry = entries.slice(-1)[0];
|
|
734
|
+
if (lastEntry?.isIntersecting) {
|
|
735
|
+
this.delegate.elementAppearedInViewport(this.element);
|
|
736
|
+
}
|
|
737
|
+
};
|
|
633
738
|
}
|
|
634
739
|
|
|
635
740
|
class StreamMessage {
|
|
636
|
-
|
|
637
|
-
this.fragment = importStreamElements(fragment);
|
|
638
|
-
}
|
|
741
|
+
static contentType="text/vnd.turbo-stream.html";
|
|
639
742
|
static wrap(message) {
|
|
640
743
|
if (typeof message == "string") {
|
|
641
744
|
return new this(createDocumentFragment(message));
|
|
@@ -643,10 +746,11 @@ class StreamMessage {
|
|
|
643
746
|
return message;
|
|
644
747
|
}
|
|
645
748
|
}
|
|
749
|
+
constructor(fragment) {
|
|
750
|
+
this.fragment = importStreamElements(fragment);
|
|
751
|
+
}
|
|
646
752
|
}
|
|
647
753
|
|
|
648
|
-
StreamMessage.contentType = "text/vnd.turbo-stream.html";
|
|
649
|
-
|
|
650
754
|
function importStreamElements(fragment) {
|
|
651
755
|
for (const element of fragment.querySelectorAll("turbo-stream")) {
|
|
652
756
|
const streamElement = document.importNode(element, true);
|
|
@@ -658,85 +762,54 @@ function importStreamElements(fragment) {
|
|
|
658
762
|
return fragment;
|
|
659
763
|
}
|
|
660
764
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
|
|
670
|
-
})(FormSubmissionState || (FormSubmissionState = {}));
|
|
671
|
-
|
|
672
|
-
var FormEnctype;
|
|
673
|
-
|
|
674
|
-
(function(FormEnctype) {
|
|
675
|
-
FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded";
|
|
676
|
-
FormEnctype["multipart"] = "multipart/form-data";
|
|
677
|
-
FormEnctype["plain"] = "text/plain";
|
|
678
|
-
})(FormEnctype || (FormEnctype = {}));
|
|
679
|
-
|
|
680
|
-
function formEnctypeFromString(encoding) {
|
|
681
|
-
switch (encoding.toLowerCase()) {
|
|
682
|
-
case FormEnctype.multipart:
|
|
683
|
-
return FormEnctype.multipart;
|
|
684
|
-
|
|
685
|
-
case FormEnctype.plain:
|
|
686
|
-
return FormEnctype.plain;
|
|
687
|
-
|
|
688
|
-
default:
|
|
689
|
-
return FormEnctype.urlEncoded;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
765
|
+
const FormSubmissionState = {
|
|
766
|
+
initialized: "initialized",
|
|
767
|
+
requesting: "requesting",
|
|
768
|
+
waiting: "waiting",
|
|
769
|
+
receiving: "receiving",
|
|
770
|
+
stopping: "stopping",
|
|
771
|
+
stopped: "stopped"
|
|
772
|
+
};
|
|
692
773
|
|
|
693
774
|
class FormSubmission {
|
|
775
|
+
state=FormSubmissionState.initialized;
|
|
776
|
+
static confirmMethod(message, _element, _submitter) {
|
|
777
|
+
return Promise.resolve(confirm(message));
|
|
778
|
+
}
|
|
694
779
|
constructor(delegate, formElement, submitter, mustRedirect = false) {
|
|
695
|
-
|
|
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);
|
|
696
784
|
this.delegate = delegate;
|
|
697
785
|
this.formElement = formElement;
|
|
698
786
|
this.submitter = submitter;
|
|
699
|
-
this.
|
|
700
|
-
this.location = expandURL(this.action);
|
|
701
|
-
if (this.method == FetchMethod.get) {
|
|
702
|
-
mergeFormDataEntries(this.location, [ ...this.body.entries() ]);
|
|
703
|
-
}
|
|
704
|
-
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
|
|
787
|
+
this.fetchRequest = new FetchRequest(this, method, action, body, formElement, enctype);
|
|
705
788
|
this.mustRedirect = mustRedirect;
|
|
706
789
|
}
|
|
707
|
-
static confirmMethod(message, _element, _submitter) {
|
|
708
|
-
return Promise.resolve(confirm(message));
|
|
709
|
-
}
|
|
710
790
|
get method() {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
791
|
+
return this.fetchRequest.method;
|
|
792
|
+
}
|
|
793
|
+
set method(value) {
|
|
794
|
+
this.fetchRequest.method = value;
|
|
714
795
|
}
|
|
715
796
|
get action() {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
} else {
|
|
721
|
-
return this.formElement.getAttribute("action") || formElementAction || "";
|
|
722
|
-
}
|
|
797
|
+
return this.fetchRequest.url.toString();
|
|
798
|
+
}
|
|
799
|
+
set action(value) {
|
|
800
|
+
this.fetchRequest.url = expandURL(value);
|
|
723
801
|
}
|
|
724
802
|
get body() {
|
|
725
|
-
|
|
726
|
-
return new URLSearchParams(this.stringFormData);
|
|
727
|
-
} else {
|
|
728
|
-
return this.formData;
|
|
729
|
-
}
|
|
803
|
+
return this.fetchRequest.body;
|
|
730
804
|
}
|
|
731
805
|
get enctype() {
|
|
732
|
-
|
|
733
|
-
return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
|
|
806
|
+
return this.fetchRequest.enctype;
|
|
734
807
|
}
|
|
735
|
-
get
|
|
736
|
-
return this.fetchRequest.
|
|
808
|
+
get isSafe() {
|
|
809
|
+
return this.fetchRequest.isSafe;
|
|
737
810
|
}
|
|
738
|
-
get
|
|
739
|
-
return
|
|
811
|
+
get location() {
|
|
812
|
+
return this.fetchRequest.url;
|
|
740
813
|
}
|
|
741
814
|
async start() {
|
|
742
815
|
const {initialized: initialized, requesting: requesting} = FormSubmissionState;
|
|
@@ -761,7 +834,7 @@ class FormSubmission {
|
|
|
761
834
|
}
|
|
762
835
|
}
|
|
763
836
|
prepareRequest(request) {
|
|
764
|
-
if (!request.
|
|
837
|
+
if (!request.isSafe) {
|
|
765
838
|
const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
|
|
766
839
|
if (token) {
|
|
767
840
|
request.headers["X-CSRF-Token"] = token;
|
|
@@ -772,9 +845,9 @@ class FormSubmission {
|
|
|
772
845
|
}
|
|
773
846
|
}
|
|
774
847
|
requestStarted(_request) {
|
|
775
|
-
var _a;
|
|
776
848
|
this.state = FormSubmissionState.waiting;
|
|
777
|
-
|
|
849
|
+
this.submitter?.setAttribute("disabled", "");
|
|
850
|
+
this.setSubmitsWith();
|
|
778
851
|
dispatch("turbo:submit-start", {
|
|
779
852
|
target: this.formElement,
|
|
780
853
|
detail: {
|
|
@@ -819,29 +892,53 @@ class FormSubmission {
|
|
|
819
892
|
this.delegate.formSubmissionErrored(this, error);
|
|
820
893
|
}
|
|
821
894
|
requestFinished(_request) {
|
|
822
|
-
var _a;
|
|
823
895
|
this.state = FormSubmissionState.stopped;
|
|
824
|
-
|
|
896
|
+
this.submitter?.removeAttribute("disabled");
|
|
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
|
}
|
|
907
|
+
setSubmitsWith() {
|
|
908
|
+
if (!this.submitter || !this.submitsWith) return;
|
|
909
|
+
if (this.submitter.matches("button")) {
|
|
910
|
+
this.originalSubmitText = this.submitter.innerHTML;
|
|
911
|
+
this.submitter.innerHTML = this.submitsWith;
|
|
912
|
+
} else if (this.submitter.matches("input")) {
|
|
913
|
+
const input = this.submitter;
|
|
914
|
+
this.originalSubmitText = input.value;
|
|
915
|
+
input.value = this.submitsWith;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
resetSubmitterText() {
|
|
919
|
+
if (!this.submitter || !this.originalSubmitText) return;
|
|
920
|
+
if (this.submitter.matches("button")) {
|
|
921
|
+
this.submitter.innerHTML = this.originalSubmitText;
|
|
922
|
+
} else if (this.submitter.matches("input")) {
|
|
923
|
+
const input = this.submitter;
|
|
924
|
+
input.value = this.originalSubmitText;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
833
927
|
requestMustRedirect(request) {
|
|
834
|
-
return !request.
|
|
928
|
+
return !request.isSafe && this.mustRedirect;
|
|
835
929
|
}
|
|
836
930
|
requestAcceptsTurboStreamResponse(request) {
|
|
837
|
-
return !request.
|
|
931
|
+
return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
|
|
932
|
+
}
|
|
933
|
+
get submitsWith() {
|
|
934
|
+
return this.submitter?.getAttribute("data-turbo-submits-with");
|
|
838
935
|
}
|
|
839
936
|
}
|
|
840
937
|
|
|
841
938
|
function buildFormData(formElement, submitter) {
|
|
842
939
|
const formData = new FormData(formElement);
|
|
843
|
-
const name = submitter
|
|
844
|
-
const value = submitter
|
|
940
|
+
const name = submitter?.getAttribute("name");
|
|
941
|
+
const value = submitter?.getAttribute("value");
|
|
845
942
|
if (name) {
|
|
846
943
|
formData.append(name, value || "");
|
|
847
944
|
}
|
|
@@ -863,14 +960,30 @@ function responseSucceededWithoutRedirect(response) {
|
|
|
863
960
|
return response.statusCode == 200 && !response.redirected;
|
|
864
961
|
}
|
|
865
962
|
|
|
866
|
-
function
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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 || "";
|
|
871
969
|
}
|
|
872
|
-
|
|
873
|
-
|
|
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);
|
|
874
987
|
}
|
|
875
988
|
|
|
876
989
|
class Snapshot {
|
|
@@ -893,11 +1006,7 @@ class Snapshot {
|
|
|
893
1006
|
return this.element.isConnected;
|
|
894
1007
|
}
|
|
895
1008
|
get firstAutofocusableElement() {
|
|
896
|
-
|
|
897
|
-
for (const element of this.element.querySelectorAll("[autofocus]")) {
|
|
898
|
-
if (element.closest(inertDisabledOrHidden) == null) return element; else continue;
|
|
899
|
-
}
|
|
900
|
-
return null;
|
|
1009
|
+
return queryAutofocusableElement(this.element);
|
|
901
1010
|
}
|
|
902
1011
|
get permanentElements() {
|
|
903
1012
|
return queryPermanentElementsAll(this.element);
|
|
@@ -927,23 +1036,8 @@ function queryPermanentElementsAll(node) {
|
|
|
927
1036
|
}
|
|
928
1037
|
|
|
929
1038
|
class FormSubmitObserver {
|
|
1039
|
+
started=false;
|
|
930
1040
|
constructor(delegate, eventTarget) {
|
|
931
|
-
this.started = false;
|
|
932
|
-
this.submitCaptured = () => {
|
|
933
|
-
this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
|
|
934
|
-
this.eventTarget.addEventListener("submit", this.submitBubbled, false);
|
|
935
|
-
};
|
|
936
|
-
this.submitBubbled = event => {
|
|
937
|
-
if (!event.defaultPrevented) {
|
|
938
|
-
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
|
939
|
-
const submitter = event.submitter || undefined;
|
|
940
|
-
if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
|
|
941
|
-
event.preventDefault();
|
|
942
|
-
event.stopImmediatePropagation();
|
|
943
|
-
this.delegate.formSubmitted(form, submitter);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
};
|
|
947
1041
|
this.delegate = delegate;
|
|
948
1042
|
this.eventTarget = eventTarget;
|
|
949
1043
|
}
|
|
@@ -959,16 +1053,31 @@ class FormSubmitObserver {
|
|
|
959
1053
|
this.started = false;
|
|
960
1054
|
}
|
|
961
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
|
+
};
|
|
962
1071
|
}
|
|
963
1072
|
|
|
964
1073
|
function submissionDoesNotDismissDialog(form, submitter) {
|
|
965
|
-
const method =
|
|
1074
|
+
const method = submitter?.getAttribute("formmethod") || form.getAttribute("method");
|
|
966
1075
|
return method != "dialog";
|
|
967
1076
|
}
|
|
968
1077
|
|
|
969
1078
|
function submissionDoesNotTargetIFrame(form, submitter) {
|
|
970
|
-
if (
|
|
971
|
-
const target =
|
|
1079
|
+
if (submitter?.hasAttribute("formtarget") || form.hasAttribute("target")) {
|
|
1080
|
+
const target = submitter?.getAttribute("formtarget") || form.target;
|
|
972
1081
|
for (const element of document.getElementsByName(target)) {
|
|
973
1082
|
if (element instanceof HTMLIFrameElement) return false;
|
|
974
1083
|
}
|
|
@@ -979,9 +1088,9 @@ function submissionDoesNotTargetIFrame(form, submitter) {
|
|
|
979
1088
|
}
|
|
980
1089
|
|
|
981
1090
|
class View {
|
|
1091
|
+
#resolveRenderPromise=_value => {};
|
|
1092
|
+
#resolveInterceptionPromise=_value => {};
|
|
982
1093
|
constructor(delegate, element) {
|
|
983
|
-
this.resolveRenderPromise = _value => {};
|
|
984
|
-
this.resolveInterceptionPromise = _value => {};
|
|
985
1094
|
this.delegate = delegate;
|
|
986
1095
|
this.element = element;
|
|
987
1096
|
}
|
|
@@ -1030,23 +1139,23 @@ class View {
|
|
|
1030
1139
|
const {isPreview: isPreview, shouldRender: shouldRender, newSnapshot: snapshot} = renderer;
|
|
1031
1140
|
if (shouldRender) {
|
|
1032
1141
|
try {
|
|
1033
|
-
this.renderPromise = new Promise((resolve => this
|
|
1142
|
+
this.renderPromise = new Promise((resolve => this.#resolveRenderPromise = resolve));
|
|
1034
1143
|
this.renderer = renderer;
|
|
1035
1144
|
await this.prepareToRenderSnapshot(renderer);
|
|
1036
|
-
const renderInterception = new Promise((resolve => this
|
|
1145
|
+
const renderInterception = new Promise((resolve => this.#resolveInterceptionPromise = resolve));
|
|
1037
1146
|
const options = {
|
|
1038
|
-
resume: this
|
|
1147
|
+
resume: this.#resolveInterceptionPromise,
|
|
1039
1148
|
render: this.renderer.renderElement
|
|
1040
1149
|
};
|
|
1041
|
-
const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
|
|
1150
|
+
const immediateRender = this.delegate.allowsImmediateRender(snapshot, isPreview, options);
|
|
1042
1151
|
if (!immediateRender) await renderInterception;
|
|
1043
1152
|
await this.renderSnapshot(renderer);
|
|
1044
|
-
this.delegate.viewRenderedSnapshot(snapshot, isPreview);
|
|
1153
|
+
this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod);
|
|
1045
1154
|
this.delegate.preloadOnLoadLinksForView(this.element);
|
|
1046
1155
|
this.finishRenderingSnapshot(renderer);
|
|
1047
1156
|
} finally {
|
|
1048
1157
|
delete this.renderer;
|
|
1049
|
-
this
|
|
1158
|
+
this.#resolveRenderPromise(undefined);
|
|
1050
1159
|
delete this.renderPromise;
|
|
1051
1160
|
}
|
|
1052
1161
|
} else {
|
|
@@ -1076,8 +1185,8 @@ class View {
|
|
|
1076
1185
|
}
|
|
1077
1186
|
|
|
1078
1187
|
class FrameView extends View {
|
|
1079
|
-
|
|
1080
|
-
this.element.innerHTML = ""
|
|
1188
|
+
missing() {
|
|
1189
|
+
this.element.innerHTML = `<strong class="turbo-frame-error">Content missing</strong>`;
|
|
1081
1190
|
}
|
|
1082
1191
|
get snapshot() {
|
|
1083
1192
|
return new Snapshot(this.element);
|
|
@@ -1086,26 +1195,6 @@ class FrameView extends View {
|
|
|
1086
1195
|
|
|
1087
1196
|
class LinkInterceptor {
|
|
1088
1197
|
constructor(delegate, element) {
|
|
1089
|
-
this.clickBubbled = event => {
|
|
1090
|
-
if (this.respondsToEventTarget(event.target)) {
|
|
1091
|
-
this.clickEvent = event;
|
|
1092
|
-
} else {
|
|
1093
|
-
delete this.clickEvent;
|
|
1094
|
-
}
|
|
1095
|
-
};
|
|
1096
|
-
this.linkClicked = event => {
|
|
1097
|
-
if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
|
|
1098
|
-
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
|
1099
|
-
this.clickEvent.preventDefault();
|
|
1100
|
-
event.preventDefault();
|
|
1101
|
-
this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
delete this.clickEvent;
|
|
1105
|
-
};
|
|
1106
|
-
this.willVisit = _event => {
|
|
1107
|
-
delete this.clickEvent;
|
|
1108
|
-
};
|
|
1109
1198
|
this.delegate = delegate;
|
|
1110
1199
|
this.element = element;
|
|
1111
1200
|
}
|
|
@@ -1119,6 +1208,26 @@ class LinkInterceptor {
|
|
|
1119
1208
|
document.removeEventListener("turbo:click", this.linkClicked);
|
|
1120
1209
|
document.removeEventListener("turbo:before-visit", this.willVisit);
|
|
1121
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
|
+
};
|
|
1122
1231
|
respondsToEventTarget(target) {
|
|
1123
1232
|
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
|
1124
1233
|
return element && element.closest("turbo-frame, html") == this.element;
|
|
@@ -1126,25 +1235,8 @@ class LinkInterceptor {
|
|
|
1126
1235
|
}
|
|
1127
1236
|
|
|
1128
1237
|
class LinkClickObserver {
|
|
1238
|
+
started=false;
|
|
1129
1239
|
constructor(delegate, eventTarget) {
|
|
1130
|
-
this.started = false;
|
|
1131
|
-
this.clickCaptured = () => {
|
|
1132
|
-
this.eventTarget.removeEventListener("click", this.clickBubbled, false);
|
|
1133
|
-
this.eventTarget.addEventListener("click", this.clickBubbled, false);
|
|
1134
|
-
};
|
|
1135
|
-
this.clickBubbled = event => {
|
|
1136
|
-
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
|
|
1137
|
-
const target = event.composedPath && event.composedPath()[0] || event.target;
|
|
1138
|
-
const link = this.findLinkFromClickTarget(target);
|
|
1139
|
-
if (link && doesNotTargetIFrame(link)) {
|
|
1140
|
-
const location = this.getLocationForLink(link);
|
|
1141
|
-
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
|
1142
|
-
event.preventDefault();
|
|
1143
|
-
this.delegate.followedLinkToLocation(link, location);
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
};
|
|
1148
1240
|
this.delegate = delegate;
|
|
1149
1241
|
this.eventTarget = eventTarget;
|
|
1150
1242
|
}
|
|
@@ -1160,6 +1252,23 @@ class LinkClickObserver {
|
|
|
1160
1252
|
this.started = false;
|
|
1161
1253
|
}
|
|
1162
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
|
+
};
|
|
1163
1272
|
clickEventIsSignificant(event) {
|
|
1164
1273
|
return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
|
|
1165
1274
|
}
|
|
@@ -1194,7 +1303,7 @@ class FormLinkClickObserver {
|
|
|
1194
1303
|
this.linkInterceptor.stop();
|
|
1195
1304
|
}
|
|
1196
1305
|
willFollowLinkToLocation(link, location, originalEvent) {
|
|
1197
|
-
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"));
|
|
1198
1307
|
}
|
|
1199
1308
|
followedLinkToLocation(link, location) {
|
|
1200
1309
|
const form = document.createElement("form");
|
|
@@ -1232,16 +1341,16 @@ class FormLinkClickObserver {
|
|
|
1232
1341
|
}
|
|
1233
1342
|
|
|
1234
1343
|
class Bardo {
|
|
1235
|
-
constructor(delegate, permanentElementMap) {
|
|
1236
|
-
this.delegate = delegate;
|
|
1237
|
-
this.permanentElementMap = permanentElementMap;
|
|
1238
|
-
}
|
|
1239
1344
|
static async preservingPermanentElements(delegate, permanentElementMap, callback) {
|
|
1240
1345
|
const bardo = new this(delegate, permanentElementMap);
|
|
1241
1346
|
bardo.enter();
|
|
1242
1347
|
await callback();
|
|
1243
1348
|
bardo.leave();
|
|
1244
1349
|
}
|
|
1350
|
+
constructor(delegate, permanentElementMap) {
|
|
1351
|
+
this.delegate = delegate;
|
|
1352
|
+
this.permanentElementMap = permanentElementMap;
|
|
1353
|
+
}
|
|
1245
1354
|
enter() {
|
|
1246
1355
|
for (const id in this.permanentElementMap) {
|
|
1247
1356
|
const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];
|
|
@@ -1267,7 +1376,7 @@ class Bardo {
|
|
|
1267
1376
|
}
|
|
1268
1377
|
replacePlaceholderWithPermanentElement(permanentElement) {
|
|
1269
1378
|
const placeholder = this.getPlaceholderById(permanentElement.id);
|
|
1270
|
-
placeholder
|
|
1379
|
+
placeholder?.replaceWith(permanentElement);
|
|
1271
1380
|
}
|
|
1272
1381
|
getPlaceholderById(id) {
|
|
1273
1382
|
return this.placeholders.find((element => element.content == id));
|
|
@@ -1285,8 +1394,8 @@ function createPlaceholderForPermanentElement(permanentElement) {
|
|
|
1285
1394
|
}
|
|
1286
1395
|
|
|
1287
1396
|
class Renderer {
|
|
1397
|
+
#activeElement=null;
|
|
1288
1398
|
constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
|
1289
|
-
this.activeElement = null;
|
|
1290
1399
|
this.currentSnapshot = currentSnapshot;
|
|
1291
1400
|
this.newSnapshot = newSnapshot;
|
|
1292
1401
|
this.isPreview = isPreview;
|
|
@@ -1306,6 +1415,7 @@ class Renderer {
|
|
|
1306
1415
|
prepareToRender() {
|
|
1307
1416
|
return;
|
|
1308
1417
|
}
|
|
1418
|
+
render() {}
|
|
1309
1419
|
finishRendering() {
|
|
1310
1420
|
if (this.resolvingFunctions) {
|
|
1311
1421
|
this.resolvingFunctions.resolve();
|
|
@@ -1317,20 +1427,20 @@ class Renderer {
|
|
|
1317
1427
|
}
|
|
1318
1428
|
focusFirstAutofocusableElement() {
|
|
1319
1429
|
const element = this.connectedSnapshot.firstAutofocusableElement;
|
|
1320
|
-
if (
|
|
1430
|
+
if (element) {
|
|
1321
1431
|
element.focus();
|
|
1322
1432
|
}
|
|
1323
1433
|
}
|
|
1324
1434
|
enteringBardo(currentPermanentElement) {
|
|
1325
|
-
if (this
|
|
1435
|
+
if (this.#activeElement) return;
|
|
1326
1436
|
if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
|
|
1327
|
-
this
|
|
1437
|
+
this.#activeElement = this.currentSnapshot.activeElement;
|
|
1328
1438
|
}
|
|
1329
1439
|
}
|
|
1330
1440
|
leavingBardo(currentPermanentElement) {
|
|
1331
|
-
if (currentPermanentElement.contains(this
|
|
1332
|
-
this
|
|
1333
|
-
this
|
|
1441
|
+
if (currentPermanentElement.contains(this.#activeElement) && this.#activeElement instanceof HTMLElement) {
|
|
1442
|
+
this.#activeElement.focus();
|
|
1443
|
+
this.#activeElement = null;
|
|
1334
1444
|
}
|
|
1335
1445
|
}
|
|
1336
1446
|
get connectedSnapshot() {
|
|
@@ -1345,29 +1455,27 @@ class Renderer {
|
|
|
1345
1455
|
get permanentElementMap() {
|
|
1346
1456
|
return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
|
|
1347
1457
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
return element && typeof element.focus == "function";
|
|
1458
|
+
get renderMethod() {
|
|
1459
|
+
return "replace";
|
|
1460
|
+
}
|
|
1352
1461
|
}
|
|
1353
1462
|
|
|
1354
1463
|
class FrameRenderer extends Renderer {
|
|
1355
|
-
constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
|
1356
|
-
super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
|
|
1357
|
-
this.delegate = delegate;
|
|
1358
|
-
}
|
|
1359
1464
|
static renderElement(currentElement, newElement) {
|
|
1360
|
-
var _a;
|
|
1361
1465
|
const destinationRange = document.createRange();
|
|
1362
1466
|
destinationRange.selectNodeContents(currentElement);
|
|
1363
1467
|
destinationRange.deleteContents();
|
|
1364
1468
|
const frameElement = newElement;
|
|
1365
|
-
const sourceRange =
|
|
1469
|
+
const sourceRange = frameElement.ownerDocument?.createRange();
|
|
1366
1470
|
if (sourceRange) {
|
|
1367
1471
|
sourceRange.selectNodeContents(frameElement);
|
|
1368
1472
|
currentElement.appendChild(sourceRange.extractContents());
|
|
1369
1473
|
}
|
|
1370
1474
|
}
|
|
1475
|
+
constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
|
1476
|
+
super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);
|
|
1477
|
+
this.delegate = delegate;
|
|
1478
|
+
}
|
|
1371
1479
|
get shouldRender() {
|
|
1372
1480
|
return true;
|
|
1373
1481
|
}
|
|
@@ -1429,18 +1537,7 @@ function readScrollBehavior(value, defaultValue) {
|
|
|
1429
1537
|
}
|
|
1430
1538
|
|
|
1431
1539
|
class ProgressBar {
|
|
1432
|
-
|
|
1433
|
-
this.hiding = false;
|
|
1434
|
-
this.value = 0;
|
|
1435
|
-
this.visible = false;
|
|
1436
|
-
this.trickle = () => {
|
|
1437
|
-
this.setValue(this.value + Math.random() / 100);
|
|
1438
|
-
};
|
|
1439
|
-
this.stylesheetElement = this.createStylesheetElement();
|
|
1440
|
-
this.progressElement = this.createProgressElement();
|
|
1441
|
-
this.installStylesheetElement();
|
|
1442
|
-
this.setValue(0);
|
|
1443
|
-
}
|
|
1540
|
+
static animationDuration=300;
|
|
1444
1541
|
static get defaultCSS() {
|
|
1445
1542
|
return unindent`
|
|
1446
1543
|
.turbo-progress-bar {
|
|
@@ -1458,6 +1555,15 @@ class ProgressBar {
|
|
|
1458
1555
|
}
|
|
1459
1556
|
`;
|
|
1460
1557
|
}
|
|
1558
|
+
hiding=false;
|
|
1559
|
+
value=0;
|
|
1560
|
+
visible=false;
|
|
1561
|
+
constructor() {
|
|
1562
|
+
this.stylesheetElement = this.createStylesheetElement();
|
|
1563
|
+
this.progressElement = this.createProgressElement();
|
|
1564
|
+
this.installStylesheetElement();
|
|
1565
|
+
this.setValue(0);
|
|
1566
|
+
}
|
|
1461
1567
|
show() {
|
|
1462
1568
|
if (!this.visible) {
|
|
1463
1569
|
this.visible = true;
|
|
@@ -1507,6 +1613,9 @@ class ProgressBar {
|
|
|
1507
1613
|
window.clearInterval(this.trickleInterval);
|
|
1508
1614
|
delete this.trickleInterval;
|
|
1509
1615
|
}
|
|
1616
|
+
trickle=() => {
|
|
1617
|
+
this.setValue(this.value + Math.random() / 100);
|
|
1618
|
+
};
|
|
1510
1619
|
refresh() {
|
|
1511
1620
|
requestAnimationFrame((() => {
|
|
1512
1621
|
this.progressElement.style.width = `${10 + this.value * 90}%`;
|
|
@@ -1531,25 +1640,22 @@ class ProgressBar {
|
|
|
1531
1640
|
}
|
|
1532
1641
|
}
|
|
1533
1642
|
|
|
1534
|
-
ProgressBar.animationDuration = 300;
|
|
1535
|
-
|
|
1536
1643
|
class HeadSnapshot extends Snapshot {
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
}
|
|
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
|
+
}), {});
|
|
1553
1659
|
get trackedElementSignature() {
|
|
1554
1660
|
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
|
|
1555
1661
|
}
|
|
@@ -1582,7 +1688,7 @@ class HeadSnapshot extends Snapshot {
|
|
|
1582
1688
|
return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
|
|
1583
1689
|
const {elements: [element]} = this.detailsByOuterHTML[outerHTML];
|
|
1584
1690
|
return elementIsMetaElementWithName(element, name) ? element : result;
|
|
1585
|
-
}), undefined);
|
|
1691
|
+
}), undefined | undefined);
|
|
1586
1692
|
}
|
|
1587
1693
|
}
|
|
1588
1694
|
|
|
@@ -1626,18 +1732,19 @@ function elementWithoutNonce(element) {
|
|
|
1626
1732
|
}
|
|
1627
1733
|
|
|
1628
1734
|
class PageSnapshot extends Snapshot {
|
|
1629
|
-
constructor(element, headSnapshot) {
|
|
1630
|
-
super(element);
|
|
1631
|
-
this.headSnapshot = headSnapshot;
|
|
1632
|
-
}
|
|
1633
1735
|
static fromHTMLString(html = "") {
|
|
1634
1736
|
return this.fromDocument(parseHTMLDocument(html));
|
|
1635
1737
|
}
|
|
1636
1738
|
static fromElement(element) {
|
|
1637
1739
|
return this.fromDocument(element.ownerDocument);
|
|
1638
1740
|
}
|
|
1639
|
-
static fromDocument({
|
|
1640
|
-
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));
|
|
1743
|
+
}
|
|
1744
|
+
constructor(documentElement, body, headSnapshot) {
|
|
1745
|
+
super(body);
|
|
1746
|
+
this.documentElement = documentElement;
|
|
1747
|
+
this.headSnapshot = headSnapshot;
|
|
1641
1748
|
}
|
|
1642
1749
|
clone() {
|
|
1643
1750
|
const clonedElement = this.element.cloneNode(true);
|
|
@@ -1651,14 +1758,16 @@ class PageSnapshot extends Snapshot {
|
|
|
1651
1758
|
for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
|
|
1652
1759
|
clonedPasswordInput.value = "";
|
|
1653
1760
|
}
|
|
1654
|
-
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");
|
|
1655
1765
|
}
|
|
1656
1766
|
get headElement() {
|
|
1657
1767
|
return this.headSnapshot.element;
|
|
1658
1768
|
}
|
|
1659
1769
|
get rootLocation() {
|
|
1660
|
-
|
|
1661
|
-
const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
|
|
1770
|
+
const root = this.getSetting("root") ?? "/";
|
|
1662
1771
|
return expandURL(root);
|
|
1663
1772
|
}
|
|
1664
1773
|
get cacheControlValue() {
|
|
@@ -1673,29 +1782,38 @@ class PageSnapshot extends Snapshot {
|
|
|
1673
1782
|
get isVisitable() {
|
|
1674
1783
|
return this.getSetting("visit-control") != "reload";
|
|
1675
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
|
+
}
|
|
1676
1794
|
getSetting(name) {
|
|
1677
1795
|
return this.headSnapshot.getMetaValue(`turbo-${name}`);
|
|
1678
1796
|
}
|
|
1679
1797
|
}
|
|
1680
1798
|
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
(
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
}
|
|
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
|
+
}
|
|
1699
1817
|
|
|
1700
1818
|
const defaultOptions = {
|
|
1701
1819
|
action: "advance",
|
|
@@ -1707,29 +1825,46 @@ const defaultOptions = {
|
|
|
1707
1825
|
acceptsStreamResponse: false
|
|
1708
1826
|
};
|
|
1709
1827
|
|
|
1710
|
-
|
|
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
|
+
};
|
|
1711
1842
|
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
}
|
|
1843
|
+
const SystemStatusCode = {
|
|
1844
|
+
networkFailure: 0,
|
|
1845
|
+
timeoutFailure: -1,
|
|
1846
|
+
contentTypeMismatch: -2
|
|
1847
|
+
};
|
|
1717
1848
|
|
|
1718
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;
|
|
1719
1860
|
constructor(delegate, location, restorationIdentifier, options = {}) {
|
|
1720
|
-
this.identifier = uuid();
|
|
1721
|
-
this.timingMetrics = {};
|
|
1722
|
-
this.followedRedirect = false;
|
|
1723
|
-
this.historyChanged = false;
|
|
1724
|
-
this.scrolled = false;
|
|
1725
|
-
this.shouldCacheSnapshot = true;
|
|
1726
|
-
this.acceptsStreamResponse = false;
|
|
1727
|
-
this.snapshotCached = false;
|
|
1728
|
-
this.state = VisitState.initialized;
|
|
1729
1861
|
this.delegate = delegate;
|
|
1730
1862
|
this.location = location;
|
|
1731
1863
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
|
1732
|
-
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
|
+
};
|
|
1733
1868
|
this.action = action;
|
|
1734
1869
|
this.historyChanged = historyChanged;
|
|
1735
1870
|
this.referrer = referrer;
|
|
@@ -1791,12 +1926,12 @@ class Visit {
|
|
|
1791
1926
|
if (this.state == VisitState.started) {
|
|
1792
1927
|
this.state = VisitState.failed;
|
|
1793
1928
|
this.adapter.visitFailed(this);
|
|
1929
|
+
this.delegate.visitCompleted(this);
|
|
1794
1930
|
}
|
|
1795
1931
|
}
|
|
1796
1932
|
changeHistory() {
|
|
1797
|
-
var _a;
|
|
1798
1933
|
if (!this.historyChanged && this.updateHistory) {
|
|
1799
|
-
const actionForHistory = this.location.href ===
|
|
1934
|
+
const actionForHistory = this.location.href === this.referrer?.href ? "replace" : this.action;
|
|
1800
1935
|
const method = getHistoryMethodForAction(actionForHistory);
|
|
1801
1936
|
this.history.update(method, this.location, this.restorationIdentifier);
|
|
1802
1937
|
this.historyChanged = true;
|
|
@@ -1843,8 +1978,8 @@ class Visit {
|
|
|
1843
1978
|
if (this.shouldCacheSnapshot) this.cacheSnapshot();
|
|
1844
1979
|
if (this.view.renderPromise) await this.view.renderPromise;
|
|
1845
1980
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
|
1846
|
-
|
|
1847
|
-
this.
|
|
1981
|
+
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
|
1982
|
+
await this.renderPageSnapshot(snapshot, false);
|
|
1848
1983
|
this.adapter.visitRendered(this);
|
|
1849
1984
|
this.complete();
|
|
1850
1985
|
} else {
|
|
@@ -1881,8 +2016,7 @@ class Visit {
|
|
|
1881
2016
|
this.adapter.visitRendered(this);
|
|
1882
2017
|
} else {
|
|
1883
2018
|
if (this.view.renderPromise) await this.view.renderPromise;
|
|
1884
|
-
await this.
|
|
1885
|
-
this.performScroll();
|
|
2019
|
+
await this.renderPageSnapshot(snapshot, isPreview);
|
|
1886
2020
|
this.adapter.visitRendered(this);
|
|
1887
2021
|
if (!isPreview) {
|
|
1888
2022
|
this.complete();
|
|
@@ -1892,8 +2026,7 @@ class Visit {
|
|
|
1892
2026
|
}
|
|
1893
2027
|
}
|
|
1894
2028
|
followRedirect() {
|
|
1895
|
-
|
|
1896
|
-
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) {
|
|
1897
2030
|
this.adapter.visitProposedToLocation(this.redirectedToLocation, {
|
|
1898
2031
|
action: "replace",
|
|
1899
2032
|
response: this.response,
|
|
@@ -1965,7 +2098,7 @@ class Visit {
|
|
|
1965
2098
|
this.finishRequest();
|
|
1966
2099
|
}
|
|
1967
2100
|
performScroll() {
|
|
1968
|
-
if (!this.scrolled && !this.view.forceReloaded) {
|
|
2101
|
+
if (!this.scrolled && !this.view.forceReloaded && !this.view.snapshot.shouldPreserveScrollPosition) {
|
|
1969
2102
|
if (this.action == "restore") {
|
|
1970
2103
|
this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
|
|
1971
2104
|
} else {
|
|
@@ -1995,7 +2128,9 @@ class Visit {
|
|
|
1995
2128
|
this.timingMetrics[metric] = (new Date).getTime();
|
|
1996
2129
|
}
|
|
1997
2130
|
getTimingMetrics() {
|
|
1998
|
-
return
|
|
2131
|
+
return {
|
|
2132
|
+
...this.timingMetrics
|
|
2133
|
+
};
|
|
1999
2134
|
}
|
|
2000
2135
|
getHistoryMethodForAction(action) {
|
|
2001
2136
|
switch (action) {
|
|
@@ -2033,6 +2168,12 @@ class Visit {
|
|
|
2033
2168
|
await callback();
|
|
2034
2169
|
delete this.frame;
|
|
2035
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
|
+
}
|
|
2036
2177
|
cancelRender() {
|
|
2037
2178
|
if (this.frame) {
|
|
2038
2179
|
cancelAnimationFrame(this.frame);
|
|
@@ -2046,15 +2187,16 @@ function isSuccessful(statusCode) {
|
|
|
2046
2187
|
}
|
|
2047
2188
|
|
|
2048
2189
|
class BrowserAdapter {
|
|
2190
|
+
progressBar=new ProgressBar;
|
|
2049
2191
|
constructor(session) {
|
|
2050
|
-
this.progressBar = new ProgressBar;
|
|
2051
|
-
this.showProgressBar = () => {
|
|
2052
|
-
this.progressBar.show();
|
|
2053
|
-
};
|
|
2054
2192
|
this.session = session;
|
|
2055
2193
|
}
|
|
2056
2194
|
visitProposedToLocation(location, options) {
|
|
2057
|
-
|
|
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
|
+
}
|
|
2058
2200
|
}
|
|
2059
2201
|
visitStarted(visit) {
|
|
2060
2202
|
this.location = visit.location;
|
|
@@ -2089,15 +2231,18 @@ class BrowserAdapter {
|
|
|
2089
2231
|
return visit.loadResponse();
|
|
2090
2232
|
}
|
|
2091
2233
|
}
|
|
2092
|
-
visitRequestFinished(_visit) {
|
|
2234
|
+
visitRequestFinished(_visit) {}
|
|
2235
|
+
visitCompleted(_visit) {
|
|
2093
2236
|
this.progressBar.setValue(1);
|
|
2094
2237
|
this.hideVisitProgressBar();
|
|
2095
2238
|
}
|
|
2096
|
-
visitCompleted(_visit) {}
|
|
2097
2239
|
pageInvalidated(reason) {
|
|
2098
2240
|
this.reload(reason);
|
|
2099
2241
|
}
|
|
2100
|
-
visitFailed(_visit) {
|
|
2242
|
+
visitFailed(_visit) {
|
|
2243
|
+
this.progressBar.setValue(1);
|
|
2244
|
+
this.hideVisitProgressBar();
|
|
2245
|
+
}
|
|
2101
2246
|
visitRendered(_visit) {}
|
|
2102
2247
|
formSubmissionStarted(_formSubmission) {
|
|
2103
2248
|
this.progressBar.setValue(0);
|
|
@@ -2129,12 +2274,14 @@ class BrowserAdapter {
|
|
|
2129
2274
|
delete this.formProgressBarTimeout;
|
|
2130
2275
|
}
|
|
2131
2276
|
}
|
|
2277
|
+
showProgressBar=() => {
|
|
2278
|
+
this.progressBar.show();
|
|
2279
|
+
};
|
|
2132
2280
|
reload(reason) {
|
|
2133
|
-
var _a;
|
|
2134
2281
|
dispatch("turbo:reload", {
|
|
2135
2282
|
detail: reason
|
|
2136
2283
|
});
|
|
2137
|
-
window.location.href =
|
|
2284
|
+
window.location.href = this.location?.toString() || window.location.href;
|
|
2138
2285
|
}
|
|
2139
2286
|
get navigator() {
|
|
2140
2287
|
return this.session.navigator;
|
|
@@ -2142,27 +2289,36 @@ class BrowserAdapter {
|
|
|
2142
2289
|
}
|
|
2143
2290
|
|
|
2144
2291
|
class CacheObserver {
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
const staleElements = [ ...document.querySelectorAll('[data-turbo-cache="false"]') ];
|
|
2149
|
-
for (const element of staleElements) {
|
|
2150
|
-
element.remove();
|
|
2151
|
-
}
|
|
2152
|
-
};
|
|
2153
|
-
}
|
|
2292
|
+
selector="[data-turbo-temporary]";
|
|
2293
|
+
deprecatedSelector="[data-turbo-cache=false]";
|
|
2294
|
+
started=false;
|
|
2154
2295
|
start() {
|
|
2155
2296
|
if (!this.started) {
|
|
2156
2297
|
this.started = true;
|
|
2157
|
-
addEventListener("turbo:before-cache", this.
|
|
2298
|
+
addEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
|
2158
2299
|
}
|
|
2159
2300
|
}
|
|
2160
2301
|
stop() {
|
|
2161
2302
|
if (this.started) {
|
|
2162
2303
|
this.started = false;
|
|
2163
|
-
removeEventListener("turbo:before-cache", this.
|
|
2304
|
+
removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
|
2164
2305
|
}
|
|
2165
2306
|
}
|
|
2307
|
+
removeTemporaryElements=_event => {
|
|
2308
|
+
for (const element of this.temporaryElements) {
|
|
2309
|
+
element.remove();
|
|
2310
|
+
}
|
|
2311
|
+
};
|
|
2312
|
+
get temporaryElements() {
|
|
2313
|
+
return [ ...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation ];
|
|
2314
|
+
}
|
|
2315
|
+
get temporaryElementsWithDeprecation() {
|
|
2316
|
+
const elements = document.querySelectorAll(this.deprecatedSelector);
|
|
2317
|
+
if (elements.length) {
|
|
2318
|
+
console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);
|
|
2319
|
+
}
|
|
2320
|
+
return [ ...elements ];
|
|
2321
|
+
}
|
|
2166
2322
|
}
|
|
2167
2323
|
|
|
2168
2324
|
class FrameRedirector {
|
|
@@ -2181,41 +2337,40 @@ class FrameRedirector {
|
|
|
2181
2337
|
this.formSubmitObserver.stop();
|
|
2182
2338
|
}
|
|
2183
2339
|
shouldInterceptLinkClick(element, _location, _event) {
|
|
2184
|
-
return this
|
|
2340
|
+
return this.#shouldRedirect(element);
|
|
2185
2341
|
}
|
|
2186
2342
|
linkClickIntercepted(element, url, event) {
|
|
2187
|
-
const frame = this
|
|
2343
|
+
const frame = this.#findFrameElement(element);
|
|
2188
2344
|
if (frame) {
|
|
2189
2345
|
frame.delegate.linkClickIntercepted(element, url, event);
|
|
2190
2346
|
}
|
|
2191
2347
|
}
|
|
2192
2348
|
willSubmitForm(element, submitter) {
|
|
2193
|
-
return element.closest("turbo-frame") == null && this
|
|
2349
|
+
return element.closest("turbo-frame") == null && this.#shouldSubmit(element, submitter) && this.#shouldRedirect(element, submitter);
|
|
2194
2350
|
}
|
|
2195
2351
|
formSubmitted(element, submitter) {
|
|
2196
|
-
const frame = this
|
|
2352
|
+
const frame = this.#findFrameElement(element, submitter);
|
|
2197
2353
|
if (frame) {
|
|
2198
2354
|
frame.delegate.formSubmitted(element, submitter);
|
|
2199
2355
|
}
|
|
2200
2356
|
}
|
|
2201
|
-
shouldSubmit(form, submitter) {
|
|
2202
|
-
|
|
2203
|
-
const action = getAction(form, submitter);
|
|
2357
|
+
#shouldSubmit(form, submitter) {
|
|
2358
|
+
const action = getAction$1(form, submitter);
|
|
2204
2359
|
const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
|
|
2205
|
-
const rootLocation = expandURL(
|
|
2206
|
-
return this
|
|
2360
|
+
const rootLocation = expandURL(meta?.content ?? "/");
|
|
2361
|
+
return this.#shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
|
|
2207
2362
|
}
|
|
2208
|
-
shouldRedirect(element, submitter) {
|
|
2363
|
+
#shouldRedirect(element, submitter) {
|
|
2209
2364
|
const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
|
|
2210
2365
|
if (isNavigatable) {
|
|
2211
|
-
const frame = this
|
|
2366
|
+
const frame = this.#findFrameElement(element, submitter);
|
|
2212
2367
|
return frame ? frame != element.closest("turbo-frame") : false;
|
|
2213
2368
|
} else {
|
|
2214
2369
|
return false;
|
|
2215
2370
|
}
|
|
2216
2371
|
}
|
|
2217
|
-
findFrameElement(element, submitter) {
|
|
2218
|
-
const id =
|
|
2372
|
+
#findFrameElement(element, submitter) {
|
|
2373
|
+
const id = submitter?.getAttribute("data-turbo-frame") || element.getAttribute("data-turbo-frame");
|
|
2219
2374
|
if (id && id != "_top") {
|
|
2220
2375
|
const frame = this.element.querySelector(`#${id}:not([disabled])`);
|
|
2221
2376
|
if (frame instanceof FrameElement) {
|
|
@@ -2226,26 +2381,12 @@ class FrameRedirector {
|
|
|
2226
2381
|
}
|
|
2227
2382
|
|
|
2228
2383
|
class History {
|
|
2384
|
+
location;
|
|
2385
|
+
restorationIdentifier=uuid();
|
|
2386
|
+
restorationData={};
|
|
2387
|
+
started=false;
|
|
2388
|
+
pageLoaded=false;
|
|
2229
2389
|
constructor(delegate) {
|
|
2230
|
-
this.restorationIdentifier = uuid();
|
|
2231
|
-
this.restorationData = {};
|
|
2232
|
-
this.started = false;
|
|
2233
|
-
this.pageLoaded = false;
|
|
2234
|
-
this.onPopState = event => {
|
|
2235
|
-
if (this.shouldHandlePopState()) {
|
|
2236
|
-
const {turbo: turbo} = event.state || {};
|
|
2237
|
-
if (turbo) {
|
|
2238
|
-
this.location = new URL(window.location.href);
|
|
2239
|
-
const {restorationIdentifier: restorationIdentifier} = turbo;
|
|
2240
|
-
this.restorationIdentifier = restorationIdentifier;
|
|
2241
|
-
this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
};
|
|
2245
|
-
this.onPageLoad = async _event => {
|
|
2246
|
-
await nextMicrotask();
|
|
2247
|
-
this.pageLoaded = true;
|
|
2248
|
-
};
|
|
2249
2390
|
this.delegate = delegate;
|
|
2250
2391
|
}
|
|
2251
2392
|
start() {
|
|
@@ -2285,12 +2426,14 @@ class History {
|
|
|
2285
2426
|
updateRestorationData(additionalData) {
|
|
2286
2427
|
const {restorationIdentifier: restorationIdentifier} = this;
|
|
2287
2428
|
const restorationData = this.restorationData[restorationIdentifier];
|
|
2288
|
-
this.restorationData[restorationIdentifier] =
|
|
2429
|
+
this.restorationData[restorationIdentifier] = {
|
|
2430
|
+
...restorationData,
|
|
2431
|
+
...additionalData
|
|
2432
|
+
};
|
|
2289
2433
|
}
|
|
2290
2434
|
assumeControlOfScrollRestoration() {
|
|
2291
|
-
var _a;
|
|
2292
2435
|
if (!this.previousScrollRestoration) {
|
|
2293
|
-
this.previousScrollRestoration =
|
|
2436
|
+
this.previousScrollRestoration = history.scrollRestoration ?? "auto";
|
|
2294
2437
|
history.scrollRestoration = "manual";
|
|
2295
2438
|
}
|
|
2296
2439
|
}
|
|
@@ -2300,6 +2443,21 @@ class History {
|
|
|
2300
2443
|
delete this.previousScrollRestoration;
|
|
2301
2444
|
}
|
|
2302
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
|
+
};
|
|
2303
2461
|
shouldHandlePopState() {
|
|
2304
2462
|
return this.pageIsLoaded();
|
|
2305
2463
|
}
|
|
@@ -2314,18 +2472,15 @@ class Navigator {
|
|
|
2314
2472
|
}
|
|
2315
2473
|
proposeVisit(location, options = {}) {
|
|
2316
2474
|
if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
|
|
2317
|
-
|
|
2318
|
-
this.delegate.visitProposedToLocation(location, options);
|
|
2319
|
-
} else {
|
|
2320
|
-
window.location.href = location.toString();
|
|
2321
|
-
}
|
|
2475
|
+
this.delegate.visitProposedToLocation(location, options);
|
|
2322
2476
|
}
|
|
2323
2477
|
}
|
|
2324
2478
|
startVisit(locatable, restorationIdentifier, options = {}) {
|
|
2325
2479
|
this.stop();
|
|
2326
|
-
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier,
|
|
2327
|
-
referrer: this.location
|
|
2328
|
-
|
|
2480
|
+
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, {
|
|
2481
|
+
referrer: this.location,
|
|
2482
|
+
...options
|
|
2483
|
+
});
|
|
2329
2484
|
this.currentVisit.start();
|
|
2330
2485
|
}
|
|
2331
2486
|
submitForm(form, submitter) {
|
|
@@ -2349,6 +2504,9 @@ class Navigator {
|
|
|
2349
2504
|
get view() {
|
|
2350
2505
|
return this.delegate.view;
|
|
2351
2506
|
}
|
|
2507
|
+
get rootLocation() {
|
|
2508
|
+
return this.view.snapshot.rootLocation;
|
|
2509
|
+
}
|
|
2352
2510
|
get history() {
|
|
2353
2511
|
return this.delegate.history;
|
|
2354
2512
|
}
|
|
@@ -2361,12 +2519,12 @@ class Navigator {
|
|
|
2361
2519
|
if (formSubmission == this.formSubmission) {
|
|
2362
2520
|
const responseHTML = await fetchResponse.responseHTML;
|
|
2363
2521
|
if (responseHTML) {
|
|
2364
|
-
const shouldCacheSnapshot = formSubmission.
|
|
2522
|
+
const shouldCacheSnapshot = formSubmission.isSafe;
|
|
2365
2523
|
if (!shouldCacheSnapshot) {
|
|
2366
2524
|
this.view.clearSnapshotCache();
|
|
2367
2525
|
}
|
|
2368
2526
|
const {statusCode: statusCode, redirected: redirected} = fetchResponse;
|
|
2369
|
-
const action = this
|
|
2527
|
+
const action = this.#getActionForFormSubmission(formSubmission, fetchResponse);
|
|
2370
2528
|
const visitOptions = {
|
|
2371
2529
|
action: action,
|
|
2372
2530
|
shouldCacheSnapshot: shouldCacheSnapshot,
|
|
@@ -2389,7 +2547,9 @@ class Navigator {
|
|
|
2389
2547
|
} else {
|
|
2390
2548
|
await this.view.renderPage(snapshot, false, true, this.currentVisit);
|
|
2391
2549
|
}
|
|
2392
|
-
|
|
2550
|
+
if (!snapshot.shouldPreserveScrollPosition) {
|
|
2551
|
+
this.view.scrollToTop();
|
|
2552
|
+
}
|
|
2393
2553
|
this.view.clearSnapshotCache();
|
|
2394
2554
|
}
|
|
2395
2555
|
}
|
|
@@ -2422,35 +2582,27 @@ class Navigator {
|
|
|
2422
2582
|
get restorationIdentifier() {
|
|
2423
2583
|
return this.history.restorationIdentifier;
|
|
2424
2584
|
}
|
|
2425
|
-
getActionForFormSubmission(
|
|
2426
|
-
|
|
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";
|
|
2427
2592
|
}
|
|
2428
2593
|
}
|
|
2429
2594
|
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
PageStage[PageStage["complete"] = 3] = "complete";
|
|
2437
|
-
})(PageStage || (PageStage = {}));
|
|
2595
|
+
const PageStage = {
|
|
2596
|
+
initial: 0,
|
|
2597
|
+
loading: 1,
|
|
2598
|
+
interactive: 2,
|
|
2599
|
+
complete: 3
|
|
2600
|
+
};
|
|
2438
2601
|
|
|
2439
2602
|
class PageObserver {
|
|
2603
|
+
stage=PageStage.initial;
|
|
2604
|
+
started=false;
|
|
2440
2605
|
constructor(delegate) {
|
|
2441
|
-
this.stage = PageStage.initial;
|
|
2442
|
-
this.started = false;
|
|
2443
|
-
this.interpretReadyState = () => {
|
|
2444
|
-
const {readyState: readyState} = this;
|
|
2445
|
-
if (readyState == "interactive") {
|
|
2446
|
-
this.pageIsInteractive();
|
|
2447
|
-
} else if (readyState == "complete") {
|
|
2448
|
-
this.pageIsComplete();
|
|
2449
|
-
}
|
|
2450
|
-
};
|
|
2451
|
-
this.pageWillUnload = () => {
|
|
2452
|
-
this.delegate.pageWillUnload();
|
|
2453
|
-
};
|
|
2454
2606
|
this.delegate = delegate;
|
|
2455
2607
|
}
|
|
2456
2608
|
start() {
|
|
@@ -2470,6 +2622,14 @@ class PageObserver {
|
|
|
2470
2622
|
this.started = false;
|
|
2471
2623
|
}
|
|
2472
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
|
+
};
|
|
2473
2633
|
pageIsInteractive() {
|
|
2474
2634
|
if (this.stage == PageStage.loading) {
|
|
2475
2635
|
this.stage = PageStage.interactive;
|
|
@@ -2483,20 +2643,17 @@ class PageObserver {
|
|
|
2483
2643
|
this.delegate.pageLoaded();
|
|
2484
2644
|
}
|
|
2485
2645
|
}
|
|
2646
|
+
pageWillUnload=() => {
|
|
2647
|
+
this.delegate.pageWillUnload();
|
|
2648
|
+
};
|
|
2486
2649
|
get readyState() {
|
|
2487
2650
|
return document.readyState;
|
|
2488
2651
|
}
|
|
2489
2652
|
}
|
|
2490
2653
|
|
|
2491
2654
|
class ScrollObserver {
|
|
2655
|
+
started=false;
|
|
2492
2656
|
constructor(delegate) {
|
|
2493
|
-
this.started = false;
|
|
2494
|
-
this.onScroll = () => {
|
|
2495
|
-
this.updatePosition({
|
|
2496
|
-
x: window.pageXOffset,
|
|
2497
|
-
y: window.pageYOffset
|
|
2498
|
-
});
|
|
2499
|
-
};
|
|
2500
2657
|
this.delegate = delegate;
|
|
2501
2658
|
}
|
|
2502
2659
|
start() {
|
|
@@ -2512,6 +2669,12 @@ class ScrollObserver {
|
|
|
2512
2669
|
this.started = false;
|
|
2513
2670
|
}
|
|
2514
2671
|
}
|
|
2672
|
+
onScroll=() => {
|
|
2673
|
+
this.updatePosition({
|
|
2674
|
+
x: window.pageXOffset,
|
|
2675
|
+
y: window.pageYOffset
|
|
2676
|
+
});
|
|
2677
|
+
};
|
|
2515
2678
|
updatePosition(position) {
|
|
2516
2679
|
this.delegate.scrollPositionChanged(position);
|
|
2517
2680
|
}
|
|
@@ -2519,7 +2682,13 @@ class ScrollObserver {
|
|
|
2519
2682
|
|
|
2520
2683
|
class StreamMessageRenderer {
|
|
2521
2684
|
render({fragment: fragment}) {
|
|
2522
|
-
Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() =>
|
|
2685
|
+
Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => {
|
|
2686
|
+
withAutofocusFromFragment(fragment, (() => {
|
|
2687
|
+
withPreservedFocus((() => {
|
|
2688
|
+
document.documentElement.appendChild(fragment);
|
|
2689
|
+
}));
|
|
2690
|
+
}));
|
|
2691
|
+
}));
|
|
2523
2692
|
}
|
|
2524
2693
|
enteringBardo(currentPermanentElement, newPermanentElement) {
|
|
2525
2694
|
newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
|
|
@@ -2542,33 +2711,67 @@ function getPermanentElementMapForFragment(fragment) {
|
|
|
2542
2711
|
return permanentElementMap;
|
|
2543
2712
|
}
|
|
2544
2713
|
|
|
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;
|
|
2724
|
+
}
|
|
2725
|
+
elementWithAutofocus.id = willAutofocusId;
|
|
2726
|
+
}
|
|
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
|
+
|
|
2545
2760
|
class StreamObserver {
|
|
2761
|
+
sources=new Set;
|
|
2762
|
+
#started=false;
|
|
2546
2763
|
constructor(delegate) {
|
|
2547
|
-
this.sources = new Set;
|
|
2548
|
-
this.started = false;
|
|
2549
|
-
this.inspectFetchResponse = event => {
|
|
2550
|
-
const response = fetchResponseFromEvent(event);
|
|
2551
|
-
if (response && fetchResponseIsStream(response)) {
|
|
2552
|
-
event.preventDefault();
|
|
2553
|
-
this.receiveMessageResponse(response);
|
|
2554
|
-
}
|
|
2555
|
-
};
|
|
2556
|
-
this.receiveMessageEvent = event => {
|
|
2557
|
-
if (this.started && typeof event.data == "string") {
|
|
2558
|
-
this.receiveMessageHTML(event.data);
|
|
2559
|
-
}
|
|
2560
|
-
};
|
|
2561
2764
|
this.delegate = delegate;
|
|
2562
2765
|
}
|
|
2563
2766
|
start() {
|
|
2564
|
-
if (!this
|
|
2565
|
-
this
|
|
2767
|
+
if (!this.#started) {
|
|
2768
|
+
this.#started = true;
|
|
2566
2769
|
addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
|
2567
2770
|
}
|
|
2568
2771
|
}
|
|
2569
2772
|
stop() {
|
|
2570
|
-
if (this
|
|
2571
|
-
this
|
|
2773
|
+
if (this.#started) {
|
|
2774
|
+
this.#started = false;
|
|
2572
2775
|
removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
|
2573
2776
|
}
|
|
2574
2777
|
}
|
|
@@ -2587,6 +2790,18 @@ class StreamObserver {
|
|
|
2587
2790
|
streamSourceIsConnected(source) {
|
|
2588
2791
|
return this.sources.has(source);
|
|
2589
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
|
+
};
|
|
2590
2805
|
async receiveMessageResponse(response) {
|
|
2591
2806
|
const html = await response.responseHTML;
|
|
2592
2807
|
if (html) {
|
|
@@ -2599,16 +2814,14 @@ class StreamObserver {
|
|
|
2599
2814
|
}
|
|
2600
2815
|
|
|
2601
2816
|
function fetchResponseFromEvent(event) {
|
|
2602
|
-
|
|
2603
|
-
const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse;
|
|
2817
|
+
const fetchResponse = event.detail?.fetchResponse;
|
|
2604
2818
|
if (fetchResponse instanceof FetchResponse) {
|
|
2605
2819
|
return fetchResponse;
|
|
2606
2820
|
}
|
|
2607
2821
|
}
|
|
2608
2822
|
|
|
2609
2823
|
function fetchResponseIsStream(response) {
|
|
2610
|
-
|
|
2611
|
-
const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
|
|
2824
|
+
const contentType = response.contentType ?? "";
|
|
2612
2825
|
return contentType.startsWith(StreamMessage.contentType);
|
|
2613
2826
|
}
|
|
2614
2827
|
|
|
@@ -2643,6 +2856,575 @@ class ErrorRenderer extends Renderer {
|
|
|
2643
2856
|
}
|
|
2644
2857
|
}
|
|
2645
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
|
+
|
|
2646
3428
|
class PageRenderer extends Renderer {
|
|
2647
3429
|
static renderElement(currentElement, newElement) {
|
|
2648
3430
|
if (document.body && newElement instanceof HTMLBodyElement) {
|
|
@@ -2667,6 +3449,7 @@ class PageRenderer extends Renderer {
|
|
|
2667
3449
|
}
|
|
2668
3450
|
}
|
|
2669
3451
|
async prepareToRender() {
|
|
3452
|
+
this.#setLanguage();
|
|
2670
3453
|
await this.mergeHead();
|
|
2671
3454
|
}
|
|
2672
3455
|
async render() {
|
|
@@ -2689,6 +3472,15 @@ class PageRenderer extends Renderer {
|
|
|
2689
3472
|
get newElement() {
|
|
2690
3473
|
return this.newSnapshot.element;
|
|
2691
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
|
+
}
|
|
2692
3484
|
async mergeHead() {
|
|
2693
3485
|
const mergedHeadElements = this.mergeProvisionalElements();
|
|
2694
3486
|
const newStylesheetElements = this.copyNewHeadStylesheetElements();
|
|
@@ -2788,9 +3580,9 @@ class PageRenderer extends Renderer {
|
|
|
2788
3580
|
}
|
|
2789
3581
|
|
|
2790
3582
|
class SnapshotCache {
|
|
3583
|
+
keys=[];
|
|
3584
|
+
snapshots={};
|
|
2791
3585
|
constructor(size) {
|
|
2792
|
-
this.keys = [];
|
|
2793
|
-
this.snapshots = {};
|
|
2794
3586
|
this.size = size;
|
|
2795
3587
|
}
|
|
2796
3588
|
has(location) {
|
|
@@ -2832,23 +3624,25 @@ class SnapshotCache {
|
|
|
2832
3624
|
}
|
|
2833
3625
|
|
|
2834
3626
|
class PageView extends View {
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
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;
|
|
2840
3632
|
}
|
|
2841
3633
|
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
|
2842
|
-
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);
|
|
2843
3637
|
if (!renderer.shouldRender) {
|
|
2844
3638
|
this.forceReloaded = true;
|
|
2845
3639
|
} else {
|
|
2846
|
-
visit
|
|
3640
|
+
visit?.changeHistory();
|
|
2847
3641
|
}
|
|
2848
3642
|
return this.render(renderer);
|
|
2849
3643
|
}
|
|
2850
3644
|
renderError(snapshot, visit) {
|
|
2851
|
-
visit
|
|
3645
|
+
visit?.changeHistory();
|
|
2852
3646
|
const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
|
|
2853
3647
|
return this.render(renderer);
|
|
2854
3648
|
}
|
|
@@ -2868,14 +3662,17 @@ class PageView extends View {
|
|
|
2868
3662
|
getCachedSnapshotForLocation(location) {
|
|
2869
3663
|
return this.snapshotCache.get(location);
|
|
2870
3664
|
}
|
|
3665
|
+
isPageRefresh(visit) {
|
|
3666
|
+
return !visit || this.lastRenderedLocation.href === visit.location.href && visit.action === "replace";
|
|
3667
|
+
}
|
|
2871
3668
|
get snapshot() {
|
|
2872
3669
|
return PageSnapshot.fromElement(this.element);
|
|
2873
3670
|
}
|
|
2874
3671
|
}
|
|
2875
3672
|
|
|
2876
3673
|
class Preloader {
|
|
3674
|
+
selector="a[data-turbo-preload]";
|
|
2877
3675
|
constructor(delegate) {
|
|
2878
|
-
this.selector = "a[data-turbo-preload]";
|
|
2879
3676
|
this.delegate = delegate;
|
|
2880
3677
|
}
|
|
2881
3678
|
get snapshotCache() {
|
|
@@ -2903,7 +3700,7 @@ class Preloader {
|
|
|
2903
3700
|
try {
|
|
2904
3701
|
const response = await fetch(location.toString(), {
|
|
2905
3702
|
headers: {
|
|
2906
|
-
"
|
|
3703
|
+
"Sec-Purpose": "prefetch",
|
|
2907
3704
|
Accept: "text/html"
|
|
2908
3705
|
}
|
|
2909
3706
|
});
|
|
@@ -2914,28 +3711,64 @@ class Preloader {
|
|
|
2914
3711
|
}
|
|
2915
3712
|
}
|
|
2916
3713
|
|
|
2917
|
-
class
|
|
2918
|
-
constructor() {
|
|
2919
|
-
|
|
2920
|
-
this.
|
|
2921
|
-
this.preloader = new Preloader(this);
|
|
2922
|
-
this.view = new PageView(this, document.documentElement);
|
|
2923
|
-
this.adapter = new BrowserAdapter(this);
|
|
2924
|
-
this.pageObserver = new PageObserver(this);
|
|
2925
|
-
this.cacheObserver = new CacheObserver;
|
|
2926
|
-
this.linkClickObserver = new LinkClickObserver(this, window);
|
|
2927
|
-
this.formSubmitObserver = new FormSubmitObserver(this, document);
|
|
2928
|
-
this.scrollObserver = new ScrollObserver(this);
|
|
2929
|
-
this.streamObserver = new StreamObserver(this);
|
|
2930
|
-
this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
|
|
2931
|
-
this.frameRedirector = new FrameRedirector(this, document.documentElement);
|
|
2932
|
-
this.streamMessageRenderer = new StreamMessageRenderer;
|
|
2933
|
-
this.drive = true;
|
|
2934
|
-
this.enabled = true;
|
|
2935
|
-
this.progressBarDelay = 500;
|
|
2936
|
-
this.started = false;
|
|
2937
|
-
this.formMode = "on";
|
|
3714
|
+
class LimitedSet extends Set {
|
|
3715
|
+
constructor(maxSize) {
|
|
3716
|
+
super();
|
|
3717
|
+
this.maxSize = maxSize;
|
|
2938
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;
|
|
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";
|
|
2939
3772
|
start() {
|
|
2940
3773
|
if (!this.started) {
|
|
2941
3774
|
this.pageObserver.start();
|
|
@@ -2981,6 +3814,15 @@ class Session {
|
|
|
2981
3814
|
this.navigator.proposeVisit(expandURL(location), options);
|
|
2982
3815
|
}
|
|
2983
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
|
+
}
|
|
2984
3826
|
connectStreamSource(source) {
|
|
2985
3827
|
this.streamObserver.connectStreamSource(source);
|
|
2986
3828
|
}
|
|
@@ -3064,7 +3906,7 @@ class Session {
|
|
|
3064
3906
|
this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
|
|
3065
3907
|
}
|
|
3066
3908
|
willSubmitForm(form, submitter) {
|
|
3067
|
-
const action = getAction(form, submitter);
|
|
3909
|
+
const action = getAction$1(form, submitter);
|
|
3068
3910
|
return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
|
|
3069
3911
|
}
|
|
3070
3912
|
formSubmitted(form, submitter) {
|
|
@@ -3084,22 +3926,21 @@ class Session {
|
|
|
3084
3926
|
this.renderStreamMessage(message);
|
|
3085
3927
|
}
|
|
3086
3928
|
viewWillCacheSnapshot() {
|
|
3087
|
-
|
|
3088
|
-
if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) {
|
|
3929
|
+
if (!this.navigator.currentVisit?.silent) {
|
|
3089
3930
|
this.notifyApplicationBeforeCachingSnapshot();
|
|
3090
3931
|
}
|
|
3091
3932
|
}
|
|
3092
|
-
allowsImmediateRender({element: element}, options) {
|
|
3093
|
-
const event = this.notifyApplicationBeforeRender(element, options);
|
|
3933
|
+
allowsImmediateRender({element: element}, isPreview, options) {
|
|
3934
|
+
const event = this.notifyApplicationBeforeRender(element, isPreview, options);
|
|
3094
3935
|
const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
|
|
3095
3936
|
if (this.view.renderer && render) {
|
|
3096
3937
|
this.view.renderer.renderElement = render;
|
|
3097
3938
|
}
|
|
3098
3939
|
return !defaultPrevented;
|
|
3099
3940
|
}
|
|
3100
|
-
viewRenderedSnapshot(_snapshot,
|
|
3941
|
+
viewRenderedSnapshot(_snapshot, isPreview, renderMethod) {
|
|
3101
3942
|
this.view.lastRenderedLocation = this.history.location;
|
|
3102
|
-
this.notifyApplicationAfterRender();
|
|
3943
|
+
this.notifyApplicationAfterRender(isPreview, renderMethod);
|
|
3103
3944
|
}
|
|
3104
3945
|
preloadOnLoadLinksForView(element) {
|
|
3105
3946
|
this.preloader.preloadOnLoadLinksForView(element);
|
|
@@ -3150,16 +3991,23 @@ class Session {
|
|
|
3150
3991
|
notifyApplicationBeforeCachingSnapshot() {
|
|
3151
3992
|
return dispatch("turbo:before-cache");
|
|
3152
3993
|
}
|
|
3153
|
-
notifyApplicationBeforeRender(newBody, options) {
|
|
3994
|
+
notifyApplicationBeforeRender(newBody, isPreview, options) {
|
|
3154
3995
|
return dispatch("turbo:before-render", {
|
|
3155
|
-
detail:
|
|
3156
|
-
newBody: newBody
|
|
3157
|
-
|
|
3996
|
+
detail: {
|
|
3997
|
+
newBody: newBody,
|
|
3998
|
+
isPreview: isPreview,
|
|
3999
|
+
...options
|
|
4000
|
+
},
|
|
3158
4001
|
cancelable: true
|
|
3159
4002
|
});
|
|
3160
4003
|
}
|
|
3161
|
-
notifyApplicationAfterRender() {
|
|
3162
|
-
return dispatch("turbo:render"
|
|
4004
|
+
notifyApplicationAfterRender(isPreview, renderMethod) {
|
|
4005
|
+
return dispatch("turbo:render", {
|
|
4006
|
+
detail: {
|
|
4007
|
+
isPreview: isPreview,
|
|
4008
|
+
renderMethod: renderMethod
|
|
4009
|
+
}
|
|
4010
|
+
});
|
|
3163
4011
|
}
|
|
3164
4012
|
notifyApplicationAfterPageLoad(timing = {}) {
|
|
3165
4013
|
return dispatch("turbo:load", {
|
|
@@ -3238,67 +4086,9 @@ const deprecatedLocationPropertyDescriptors = {
|
|
|
3238
4086
|
}
|
|
3239
4087
|
};
|
|
3240
4088
|
|
|
3241
|
-
class Cache {
|
|
3242
|
-
constructor(session) {
|
|
3243
|
-
this.session = session;
|
|
3244
|
-
}
|
|
3245
|
-
clear() {
|
|
3246
|
-
this.session.clearCache();
|
|
3247
|
-
}
|
|
3248
|
-
resetCacheControl() {
|
|
3249
|
-
this.setCacheControl("");
|
|
3250
|
-
}
|
|
3251
|
-
exemptPageFromCache() {
|
|
3252
|
-
this.setCacheControl("no-cache");
|
|
3253
|
-
}
|
|
3254
|
-
exemptPageFromPreview() {
|
|
3255
|
-
this.setCacheControl("no-preview");
|
|
3256
|
-
}
|
|
3257
|
-
setCacheControl(value) {
|
|
3258
|
-
setMetaContent("turbo-cache-control", value);
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
|
-
|
|
3262
|
-
const StreamActions = {
|
|
3263
|
-
after() {
|
|
3264
|
-
this.targetElements.forEach((e => {
|
|
3265
|
-
var _a;
|
|
3266
|
-
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
|
|
3267
|
-
}));
|
|
3268
|
-
},
|
|
3269
|
-
append() {
|
|
3270
|
-
this.removeDuplicateTargetChildren();
|
|
3271
|
-
this.targetElements.forEach((e => e.append(this.templateContent)));
|
|
3272
|
-
},
|
|
3273
|
-
before() {
|
|
3274
|
-
this.targetElements.forEach((e => {
|
|
3275
|
-
var _a;
|
|
3276
|
-
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
|
|
3277
|
-
}));
|
|
3278
|
-
},
|
|
3279
|
-
prepend() {
|
|
3280
|
-
this.removeDuplicateTargetChildren();
|
|
3281
|
-
this.targetElements.forEach((e => e.prepend(this.templateContent)));
|
|
3282
|
-
},
|
|
3283
|
-
remove() {
|
|
3284
|
-
this.targetElements.forEach((e => e.remove()));
|
|
3285
|
-
},
|
|
3286
|
-
replace() {
|
|
3287
|
-
this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
|
|
3288
|
-
},
|
|
3289
|
-
update() {
|
|
3290
|
-
this.targetElements.forEach((targetElement => {
|
|
3291
|
-
targetElement.innerHTML = "";
|
|
3292
|
-
targetElement.append(this.templateContent);
|
|
3293
|
-
}));
|
|
3294
|
-
}
|
|
3295
|
-
};
|
|
3296
|
-
|
|
3297
4089
|
const session = new Session;
|
|
3298
4090
|
|
|
3299
|
-
const cache =
|
|
3300
|
-
|
|
3301
|
-
const {navigator: navigator$1} = session;
|
|
4091
|
+
const {cache: cache, navigator: navigator$1} = session;
|
|
3302
4092
|
|
|
3303
4093
|
function start() {
|
|
3304
4094
|
session.start();
|
|
@@ -3349,6 +4139,7 @@ var Turbo = Object.freeze({
|
|
|
3349
4139
|
PageRenderer: PageRenderer,
|
|
3350
4140
|
PageSnapshot: PageSnapshot,
|
|
3351
4141
|
FrameRenderer: FrameRenderer,
|
|
4142
|
+
fetch: fetch,
|
|
3352
4143
|
start: start,
|
|
3353
4144
|
registerAdapter: registerAdapter,
|
|
3354
4145
|
visit: visit,
|
|
@@ -3358,26 +4149,20 @@ var Turbo = Object.freeze({
|
|
|
3358
4149
|
clearCache: clearCache,
|
|
3359
4150
|
setProgressBarDelay: setProgressBarDelay,
|
|
3360
4151
|
setConfirmMethod: setConfirmMethod,
|
|
3361
|
-
setFormMode: setFormMode
|
|
3362
|
-
StreamActions: StreamActions
|
|
4152
|
+
setFormMode: setFormMode
|
|
3363
4153
|
});
|
|
3364
4154
|
|
|
4155
|
+
class TurboFrameMissingError extends Error {}
|
|
4156
|
+
|
|
3365
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;
|
|
3366
4165
|
constructor(element) {
|
|
3367
|
-
this.fetchResponseLoaded = _fetchResponse => {};
|
|
3368
|
-
this.currentFetchRequest = null;
|
|
3369
|
-
this.resolveVisitPromise = () => {};
|
|
3370
|
-
this.connected = false;
|
|
3371
|
-
this.hasBeenLoaded = false;
|
|
3372
|
-
this.ignoredAttributes = new Set;
|
|
3373
|
-
this.action = null;
|
|
3374
|
-
this.visitCachedSnapshot = ({element: element}) => {
|
|
3375
|
-
const frame = element.querySelector("#" + this.element.id);
|
|
3376
|
-
if (frame && this.previousFrameElement) {
|
|
3377
|
-
frame.replaceChildren(...this.previousFrameElement.children);
|
|
3378
|
-
}
|
|
3379
|
-
delete this.previousFrameElement;
|
|
3380
|
-
};
|
|
3381
4166
|
this.element = element;
|
|
3382
4167
|
this.view = new FrameView(this, this.element);
|
|
3383
4168
|
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
|
@@ -3387,12 +4172,12 @@ class FrameController {
|
|
|
3387
4172
|
this.formSubmitObserver = new FormSubmitObserver(this, this.element);
|
|
3388
4173
|
}
|
|
3389
4174
|
connect() {
|
|
3390
|
-
if (!this
|
|
3391
|
-
this
|
|
4175
|
+
if (!this.#connected) {
|
|
4176
|
+
this.#connected = true;
|
|
3392
4177
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
|
3393
4178
|
this.appearanceObserver.start();
|
|
3394
4179
|
} else {
|
|
3395
|
-
this
|
|
4180
|
+
this.#loadSourceURL();
|
|
3396
4181
|
}
|
|
3397
4182
|
this.formLinkClickObserver.start();
|
|
3398
4183
|
this.linkInterceptor.start();
|
|
@@ -3400,8 +4185,8 @@ class FrameController {
|
|
|
3400
4185
|
}
|
|
3401
4186
|
}
|
|
3402
4187
|
disconnect() {
|
|
3403
|
-
if (this
|
|
3404
|
-
this
|
|
4188
|
+
if (this.#connected) {
|
|
4189
|
+
this.#connected = false;
|
|
3405
4190
|
this.appearanceObserver.stop();
|
|
3406
4191
|
this.formLinkClickObserver.stop();
|
|
3407
4192
|
this.linkInterceptor.stop();
|
|
@@ -3410,21 +4195,21 @@ class FrameController {
|
|
|
3410
4195
|
}
|
|
3411
4196
|
disabledChanged() {
|
|
3412
4197
|
if (this.loadingStyle == FrameLoadingStyle.eager) {
|
|
3413
|
-
this
|
|
4198
|
+
this.#loadSourceURL();
|
|
3414
4199
|
}
|
|
3415
4200
|
}
|
|
3416
4201
|
sourceURLChanged() {
|
|
3417
|
-
if (this
|
|
4202
|
+
if (this.#isIgnoringChangesTo("src")) return;
|
|
3418
4203
|
if (this.element.isConnected) {
|
|
3419
4204
|
this.complete = false;
|
|
3420
4205
|
}
|
|
3421
|
-
if (this.loadingStyle == FrameLoadingStyle.eager || this
|
|
3422
|
-
this
|
|
4206
|
+
if (this.loadingStyle == FrameLoadingStyle.eager || this.#hasBeenLoaded) {
|
|
4207
|
+
this.#loadSourceURL();
|
|
3423
4208
|
}
|
|
3424
4209
|
}
|
|
3425
4210
|
sourceURLReloaded() {
|
|
3426
4211
|
const {src: src} = this.element;
|
|
3427
|
-
this
|
|
4212
|
+
this.#ignoringChangesToAttribute("complete", (() => {
|
|
3428
4213
|
this.element.removeAttribute("complete");
|
|
3429
4214
|
}));
|
|
3430
4215
|
this.element.src = null;
|
|
@@ -3432,23 +4217,23 @@ class FrameController {
|
|
|
3432
4217
|
return this.element.loaded;
|
|
3433
4218
|
}
|
|
3434
4219
|
completeChanged() {
|
|
3435
|
-
if (this
|
|
3436
|
-
this
|
|
4220
|
+
if (this.#isIgnoringChangesTo("complete")) return;
|
|
4221
|
+
this.#loadSourceURL();
|
|
3437
4222
|
}
|
|
3438
4223
|
loadingStyleChanged() {
|
|
3439
4224
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
|
3440
4225
|
this.appearanceObserver.start();
|
|
3441
4226
|
} else {
|
|
3442
4227
|
this.appearanceObserver.stop();
|
|
3443
|
-
this
|
|
4228
|
+
this.#loadSourceURL();
|
|
3444
4229
|
}
|
|
3445
4230
|
}
|
|
3446
|
-
async loadSourceURL() {
|
|
4231
|
+
async #loadSourceURL() {
|
|
3447
4232
|
if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
|
|
3448
|
-
this.element.loaded = this
|
|
4233
|
+
this.element.loaded = this.#visit(expandURL(this.sourceURL));
|
|
3449
4234
|
this.appearanceObserver.stop();
|
|
3450
4235
|
await this.element.loaded;
|
|
3451
|
-
this
|
|
4236
|
+
this.#hasBeenLoaded = true;
|
|
3452
4237
|
}
|
|
3453
4238
|
}
|
|
3454
4239
|
async loadResponse(fetchResponse) {
|
|
@@ -3458,49 +4243,37 @@ class FrameController {
|
|
|
3458
4243
|
try {
|
|
3459
4244
|
const html = await fetchResponse.responseHTML;
|
|
3460
4245
|
if (html) {
|
|
3461
|
-
const
|
|
3462
|
-
const
|
|
3463
|
-
if (
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
this.changeHistory();
|
|
3468
|
-
await this.view.render(renderer);
|
|
3469
|
-
this.complete = true;
|
|
3470
|
-
session.frameRendered(fetchResponse, this.element);
|
|
3471
|
-
session.frameLoaded(this.element);
|
|
3472
|
-
this.fetchResponseLoaded(fetchResponse);
|
|
3473
|
-
} else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
|
|
3474
|
-
console.warn(`A matching frame for #${this.element.id} was missing from the response, transforming into full-page Visit.`);
|
|
3475
|
-
this.visitResponse(fetchResponse.response);
|
|
4246
|
+
const document = parseHTMLDocument(html);
|
|
4247
|
+
const pageSnapshot = PageSnapshot.fromDocument(document);
|
|
4248
|
+
if (pageSnapshot.isVisitable) {
|
|
4249
|
+
await this.#loadFrameResponse(fetchResponse, document);
|
|
4250
|
+
} else {
|
|
4251
|
+
await this.#handleUnvisitableFrameResponse(fetchResponse);
|
|
3476
4252
|
}
|
|
3477
4253
|
}
|
|
3478
|
-
} catch (error) {
|
|
3479
|
-
console.error(error);
|
|
3480
|
-
this.view.invalidate();
|
|
3481
4254
|
} finally {
|
|
3482
|
-
this.fetchResponseLoaded = () =>
|
|
4255
|
+
this.fetchResponseLoaded = () => Promise.resolve();
|
|
3483
4256
|
}
|
|
3484
4257
|
}
|
|
3485
4258
|
elementAppearedInViewport(element) {
|
|
3486
4259
|
this.proposeVisitIfNavigatedWithAction(element, element);
|
|
3487
|
-
this
|
|
4260
|
+
this.#loadSourceURL();
|
|
3488
4261
|
}
|
|
3489
4262
|
willSubmitFormLinkToLocation(link) {
|
|
3490
|
-
return this
|
|
4263
|
+
return this.#shouldInterceptNavigation(link);
|
|
3491
4264
|
}
|
|
3492
4265
|
submittedFormLinkToLocation(link, _location, form) {
|
|
3493
|
-
const frame = this
|
|
4266
|
+
const frame = this.#findFrameElement(link);
|
|
3494
4267
|
if (frame) form.setAttribute("data-turbo-frame", frame.id);
|
|
3495
4268
|
}
|
|
3496
4269
|
shouldInterceptLinkClick(element, _location, _event) {
|
|
3497
|
-
return this
|
|
4270
|
+
return this.#shouldInterceptNavigation(element);
|
|
3498
4271
|
}
|
|
3499
4272
|
linkClickIntercepted(element, location) {
|
|
3500
|
-
this
|
|
4273
|
+
this.#navigateFrame(element, location);
|
|
3501
4274
|
}
|
|
3502
4275
|
willSubmitForm(element, submitter) {
|
|
3503
|
-
return element.closest("turbo-frame") == this.element && this
|
|
4276
|
+
return element.closest("turbo-frame") == this.element && this.#shouldInterceptNavigation(element, submitter);
|
|
3504
4277
|
}
|
|
3505
4278
|
formSubmitted(element, submitter) {
|
|
3506
4279
|
if (this.formSubmission) {
|
|
@@ -3512,9 +4285,8 @@ class FrameController {
|
|
|
3512
4285
|
this.formSubmission.start();
|
|
3513
4286
|
}
|
|
3514
4287
|
prepareRequest(request) {
|
|
3515
|
-
var _a;
|
|
3516
4288
|
request.headers["Turbo-Frame"] = this.id;
|
|
3517
|
-
if (
|
|
4289
|
+
if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
|
|
3518
4290
|
request.acceptResponseType(StreamMessage.contentType);
|
|
3519
4291
|
}
|
|
3520
4292
|
}
|
|
@@ -3522,47 +4294,51 @@ class FrameController {
|
|
|
3522
4294
|
markAsBusy(this.element);
|
|
3523
4295
|
}
|
|
3524
4296
|
requestPreventedHandlingResponse(_request, _response) {
|
|
3525
|
-
this
|
|
4297
|
+
this.#resolveVisitPromise();
|
|
3526
4298
|
}
|
|
3527
4299
|
async requestSucceededWithResponse(request, response) {
|
|
3528
4300
|
await this.loadResponse(response);
|
|
3529
|
-
this
|
|
4301
|
+
this.#resolveVisitPromise();
|
|
3530
4302
|
}
|
|
3531
4303
|
async requestFailedWithResponse(request, response) {
|
|
3532
|
-
console.error(response);
|
|
3533
4304
|
await this.loadResponse(response);
|
|
3534
|
-
this
|
|
4305
|
+
this.#resolveVisitPromise();
|
|
3535
4306
|
}
|
|
3536
4307
|
requestErrored(request, error) {
|
|
3537
4308
|
console.error(error);
|
|
3538
|
-
this
|
|
4309
|
+
this.#resolveVisitPromise();
|
|
3539
4310
|
}
|
|
3540
4311
|
requestFinished(_request) {
|
|
3541
4312
|
clearBusyState(this.element);
|
|
3542
4313
|
}
|
|
3543
4314
|
formSubmissionStarted({formElement: formElement}) {
|
|
3544
|
-
markAsBusy(formElement, this
|
|
4315
|
+
markAsBusy(formElement, this.#findFrameElement(formElement));
|
|
3545
4316
|
}
|
|
3546
4317
|
formSubmissionSucceededWithResponse(formSubmission, response) {
|
|
3547
|
-
const frame = this
|
|
4318
|
+
const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter);
|
|
3548
4319
|
frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
|
|
3549
4320
|
frame.delegate.loadResponse(response);
|
|
4321
|
+
if (!formSubmission.isSafe) {
|
|
4322
|
+
session.clearCache();
|
|
4323
|
+
}
|
|
3550
4324
|
}
|
|
3551
4325
|
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
|
3552
4326
|
this.element.delegate.loadResponse(fetchResponse);
|
|
4327
|
+
session.clearCache();
|
|
3553
4328
|
}
|
|
3554
4329
|
formSubmissionErrored(formSubmission, error) {
|
|
3555
4330
|
console.error(error);
|
|
3556
4331
|
}
|
|
3557
4332
|
formSubmissionFinished({formElement: formElement}) {
|
|
3558
|
-
clearBusyState(formElement, this
|
|
4333
|
+
clearBusyState(formElement, this.#findFrameElement(formElement));
|
|
3559
4334
|
}
|
|
3560
|
-
allowsImmediateRender({element: newFrame}, options) {
|
|
4335
|
+
allowsImmediateRender({element: newFrame}, _isPreview, options) {
|
|
3561
4336
|
const event = dispatch("turbo:before-frame-render", {
|
|
3562
4337
|
target: this.element,
|
|
3563
|
-
detail:
|
|
3564
|
-
newFrame: newFrame
|
|
3565
|
-
|
|
4338
|
+
detail: {
|
|
4339
|
+
newFrame: newFrame,
|
|
4340
|
+
...options
|
|
4341
|
+
},
|
|
3566
4342
|
cancelable: true
|
|
3567
4343
|
});
|
|
3568
4344
|
const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
|
|
@@ -3571,7 +4347,7 @@ class FrameController {
|
|
|
3571
4347
|
}
|
|
3572
4348
|
return !defaultPrevented;
|
|
3573
4349
|
}
|
|
3574
|
-
viewRenderedSnapshot(_snapshot, _isPreview) {}
|
|
4350
|
+
viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {}
|
|
3575
4351
|
preloadOnLoadLinksForView(element) {
|
|
3576
4352
|
session.preloadOnLoadLinksForView(element);
|
|
3577
4353
|
}
|
|
@@ -3579,24 +4355,46 @@ class FrameController {
|
|
|
3579
4355
|
willRenderFrame(currentElement, _newElement) {
|
|
3580
4356
|
this.previousFrameElement = currentElement.cloneNode(true);
|
|
3581
4357
|
}
|
|
3582
|
-
|
|
3583
|
-
|
|
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) {
|
|
4366
|
+
const newFrameElement = await this.extractForeignFrameElement(document.body);
|
|
4367
|
+
if (newFrameElement) {
|
|
4368
|
+
const snapshot = new Snapshot(newFrameElement);
|
|
4369
|
+
const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);
|
|
4370
|
+
if (this.view.renderPromise) await this.view.renderPromise;
|
|
4371
|
+
this.changeHistory();
|
|
4372
|
+
await this.view.render(renderer);
|
|
4373
|
+
this.complete = true;
|
|
4374
|
+
session.frameRendered(fetchResponse, this.element);
|
|
4375
|
+
session.frameLoaded(this.element);
|
|
4376
|
+
await this.fetchResponseLoaded(fetchResponse);
|
|
4377
|
+
} else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) {
|
|
4378
|
+
this.#handleFrameMissingFromResponse(fetchResponse);
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
async #visit(url) {
|
|
3584
4382
|
const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
|
|
3585
|
-
|
|
3586
|
-
this
|
|
4383
|
+
this.#currentFetchRequest?.cancel();
|
|
4384
|
+
this.#currentFetchRequest = request;
|
|
3587
4385
|
return new Promise((resolve => {
|
|
3588
|
-
this
|
|
3589
|
-
this
|
|
3590
|
-
this
|
|
4386
|
+
this.#resolveVisitPromise = () => {
|
|
4387
|
+
this.#resolveVisitPromise = () => {};
|
|
4388
|
+
this.#currentFetchRequest = null;
|
|
3591
4389
|
resolve();
|
|
3592
4390
|
};
|
|
3593
4391
|
request.perform();
|
|
3594
4392
|
}));
|
|
3595
4393
|
}
|
|
3596
|
-
navigateFrame(element, url, submitter) {
|
|
3597
|
-
const frame = this
|
|
4394
|
+
#navigateFrame(element, url, submitter) {
|
|
4395
|
+
const frame = this.#findFrameElement(element, submitter);
|
|
3598
4396
|
frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);
|
|
3599
|
-
this
|
|
4397
|
+
this.#withCurrentNavigationElement(element, (() => {
|
|
3600
4398
|
frame.src = url;
|
|
3601
4399
|
}));
|
|
3602
4400
|
}
|
|
@@ -3605,10 +4403,10 @@ class FrameController {
|
|
|
3605
4403
|
if (this.action) {
|
|
3606
4404
|
const pageSnapshot = PageSnapshot.fromElement(frame).clone();
|
|
3607
4405
|
const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
|
|
3608
|
-
frame.delegate.fetchResponseLoaded = fetchResponse => {
|
|
4406
|
+
frame.delegate.fetchResponseLoaded = async fetchResponse => {
|
|
3609
4407
|
if (frame.src) {
|
|
3610
4408
|
const {statusCode: statusCode, redirected: redirected} = fetchResponse;
|
|
3611
|
-
const responseHTML =
|
|
4409
|
+
const responseHTML = await fetchResponse.responseHTML;
|
|
3612
4410
|
const response = {
|
|
3613
4411
|
statusCode: statusCode,
|
|
3614
4412
|
redirected: redirected,
|
|
@@ -3634,12 +4432,16 @@ class FrameController {
|
|
|
3634
4432
|
session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
|
|
3635
4433
|
}
|
|
3636
4434
|
}
|
|
3637
|
-
|
|
4435
|
+
async #handleUnvisitableFrameResponse(fetchResponse) {
|
|
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.`);
|
|
4437
|
+
await this.#visitResponse(fetchResponse.response);
|
|
4438
|
+
}
|
|
4439
|
+
#willHandleFrameMissingFromResponse(fetchResponse) {
|
|
3638
4440
|
this.element.setAttribute("complete", "");
|
|
3639
4441
|
const response = fetchResponse.response;
|
|
3640
|
-
const visit = async (url, options
|
|
4442
|
+
const visit = async (url, options) => {
|
|
3641
4443
|
if (url instanceof Response) {
|
|
3642
|
-
this
|
|
4444
|
+
this.#visitResponse(url);
|
|
3643
4445
|
} else {
|
|
3644
4446
|
session.visit(url, options);
|
|
3645
4447
|
}
|
|
@@ -3654,7 +4456,15 @@ class FrameController {
|
|
|
3654
4456
|
});
|
|
3655
4457
|
return !event.defaultPrevented;
|
|
3656
4458
|
}
|
|
3657
|
-
|
|
4459
|
+
#handleFrameMissingFromResponse(fetchResponse) {
|
|
4460
|
+
this.view.missing();
|
|
4461
|
+
this.#throwFrameMissingError(fetchResponse);
|
|
4462
|
+
}
|
|
4463
|
+
#throwFrameMissingError(fetchResponse) {
|
|
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.`;
|
|
4465
|
+
throw new TurboFrameMissingError(message);
|
|
4466
|
+
}
|
|
4467
|
+
async #visitResponse(response) {
|
|
3658
4468
|
const wrapped = new FetchResponse(response);
|
|
3659
4469
|
const responseHTML = await wrapped.responseHTML;
|
|
3660
4470
|
const {location: location, redirected: redirected, statusCode: statusCode} = wrapped;
|
|
@@ -3666,10 +4476,9 @@ class FrameController {
|
|
|
3666
4476
|
}
|
|
3667
4477
|
});
|
|
3668
4478
|
}
|
|
3669
|
-
findFrameElement(element, submitter) {
|
|
3670
|
-
var _a;
|
|
4479
|
+
#findFrameElement(element, submitter) {
|
|
3671
4480
|
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
|
3672
|
-
return
|
|
4481
|
+
return getFrameElementById(id) ?? this.element;
|
|
3673
4482
|
}
|
|
3674
4483
|
async extractForeignFrameElement(container) {
|
|
3675
4484
|
let element;
|
|
@@ -3690,13 +4499,13 @@ class FrameController {
|
|
|
3690
4499
|
}
|
|
3691
4500
|
return null;
|
|
3692
4501
|
}
|
|
3693
|
-
formActionIsVisitable(form, submitter) {
|
|
3694
|
-
const action = getAction(form, submitter);
|
|
4502
|
+
#formActionIsVisitable(form, submitter) {
|
|
4503
|
+
const action = getAction$1(form, submitter);
|
|
3695
4504
|
return locationIsVisitable(expandURL(action), this.rootLocation);
|
|
3696
4505
|
}
|
|
3697
|
-
shouldInterceptNavigation(element, submitter) {
|
|
4506
|
+
#shouldInterceptNavigation(element, submitter) {
|
|
3698
4507
|
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
|
3699
|
-
if (element instanceof HTMLFormElement && !this
|
|
4508
|
+
if (element instanceof HTMLFormElement && !this.#formActionIsVisitable(element, submitter)) {
|
|
3700
4509
|
return false;
|
|
3701
4510
|
}
|
|
3702
4511
|
if (!this.enabled || id == "_top") {
|
|
@@ -3728,21 +4537,21 @@ class FrameController {
|
|
|
3728
4537
|
}
|
|
3729
4538
|
}
|
|
3730
4539
|
set sourceURL(sourceURL) {
|
|
3731
|
-
this
|
|
3732
|
-
this.element.src = sourceURL
|
|
4540
|
+
this.#ignoringChangesToAttribute("src", (() => {
|
|
4541
|
+
this.element.src = sourceURL ?? null;
|
|
3733
4542
|
}));
|
|
3734
4543
|
}
|
|
3735
4544
|
get loadingStyle() {
|
|
3736
4545
|
return this.element.loading;
|
|
3737
4546
|
}
|
|
3738
4547
|
get isLoading() {
|
|
3739
|
-
return this.formSubmission !== undefined || this
|
|
4548
|
+
return this.formSubmission !== undefined || this.#resolveVisitPromise() !== undefined;
|
|
3740
4549
|
}
|
|
3741
4550
|
get complete() {
|
|
3742
4551
|
return this.element.hasAttribute("complete");
|
|
3743
4552
|
}
|
|
3744
4553
|
set complete(value) {
|
|
3745
|
-
this
|
|
4554
|
+
this.#ignoringChangesToAttribute("complete", (() => {
|
|
3746
4555
|
if (value) {
|
|
3747
4556
|
this.element.setAttribute("complete", "");
|
|
3748
4557
|
} else {
|
|
@@ -3751,23 +4560,22 @@ class FrameController {
|
|
|
3751
4560
|
}));
|
|
3752
4561
|
}
|
|
3753
4562
|
get isActive() {
|
|
3754
|
-
return this.element.isActive && this
|
|
4563
|
+
return this.element.isActive && this.#connected;
|
|
3755
4564
|
}
|
|
3756
4565
|
get rootLocation() {
|
|
3757
|
-
var _a;
|
|
3758
4566
|
const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
|
|
3759
|
-
const root =
|
|
4567
|
+
const root = meta?.content ?? "/";
|
|
3760
4568
|
return expandURL(root);
|
|
3761
4569
|
}
|
|
3762
|
-
isIgnoringChangesTo(attributeName) {
|
|
3763
|
-
return this
|
|
4570
|
+
#isIgnoringChangesTo(attributeName) {
|
|
4571
|
+
return this.#ignoredAttributes.has(attributeName);
|
|
3764
4572
|
}
|
|
3765
|
-
ignoringChangesToAttribute(attributeName, callback) {
|
|
3766
|
-
this
|
|
4573
|
+
#ignoringChangesToAttribute(attributeName, callback) {
|
|
4574
|
+
this.#ignoredAttributes.add(attributeName);
|
|
3767
4575
|
callback();
|
|
3768
|
-
this
|
|
4576
|
+
this.#ignoredAttributes.delete(attributeName);
|
|
3769
4577
|
}
|
|
3770
|
-
withCurrentNavigationElement(element, callback) {
|
|
4578
|
+
#withCurrentNavigationElement(element, callback) {
|
|
3771
4579
|
this.currentNavigationElement = element;
|
|
3772
4580
|
callback();
|
|
3773
4581
|
delete this.currentNavigationElement;
|
|
@@ -3800,6 +4608,38 @@ function activateElement(element, currentURL) {
|
|
|
3800
4608
|
}
|
|
3801
4609
|
}
|
|
3802
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
|
+
|
|
3803
4643
|
class StreamElement extends HTMLElement {
|
|
3804
4644
|
static async renderElement(newElement) {
|
|
3805
4645
|
await newElement.performAction();
|
|
@@ -3814,11 +4654,10 @@ class StreamElement extends HTMLElement {
|
|
|
3814
4654
|
}
|
|
3815
4655
|
}
|
|
3816
4656
|
async render() {
|
|
3817
|
-
|
|
3818
|
-
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
|
|
4657
|
+
return this.renderPromise ??= (async () => {
|
|
3819
4658
|
const event = this.beforeRenderEvent;
|
|
3820
4659
|
if (this.dispatchEvent(event)) {
|
|
3821
|
-
await
|
|
4660
|
+
await nextRepaint();
|
|
3822
4661
|
await event.detail.render(this);
|
|
3823
4662
|
}
|
|
3824
4663
|
})();
|
|
@@ -3826,15 +4665,14 @@ class StreamElement extends HTMLElement {
|
|
|
3826
4665
|
disconnect() {
|
|
3827
4666
|
try {
|
|
3828
4667
|
this.remove();
|
|
3829
|
-
} catch
|
|
4668
|
+
} catch {}
|
|
3830
4669
|
}
|
|
3831
4670
|
removeDuplicateTargetChildren() {
|
|
3832
4671
|
this.duplicateChildren.forEach((c => c.remove()));
|
|
3833
4672
|
}
|
|
3834
4673
|
get duplicateChildren() {
|
|
3835
|
-
var _a;
|
|
3836
4674
|
const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
|
|
3837
|
-
const newChildrenIds = [ ...
|
|
4675
|
+
const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
|
|
3838
4676
|
return existingChildren.filter((c => newChildrenIds.includes(c.id)));
|
|
3839
4677
|
}
|
|
3840
4678
|
get performAction() {
|
|
@@ -3843,9 +4681,9 @@ class StreamElement extends HTMLElement {
|
|
|
3843
4681
|
if (actionFunction) {
|
|
3844
4682
|
return actionFunction;
|
|
3845
4683
|
}
|
|
3846
|
-
this
|
|
4684
|
+
this.#raise("unknown action");
|
|
3847
4685
|
}
|
|
3848
|
-
this
|
|
4686
|
+
this.#raise("action attribute is missing");
|
|
3849
4687
|
}
|
|
3850
4688
|
get targetElements() {
|
|
3851
4689
|
if (this.target) {
|
|
@@ -3853,7 +4691,7 @@ class StreamElement extends HTMLElement {
|
|
|
3853
4691
|
} else if (this.targets) {
|
|
3854
4692
|
return this.targetElementsByQuery;
|
|
3855
4693
|
} else {
|
|
3856
|
-
this
|
|
4694
|
+
this.#raise("target or targets attribute is missing");
|
|
3857
4695
|
}
|
|
3858
4696
|
}
|
|
3859
4697
|
get templateContent() {
|
|
@@ -3867,7 +4705,7 @@ class StreamElement extends HTMLElement {
|
|
|
3867
4705
|
} else if (this.firstElementChild instanceof HTMLTemplateElement) {
|
|
3868
4706
|
return this.firstElementChild;
|
|
3869
4707
|
}
|
|
3870
|
-
this
|
|
4708
|
+
this.#raise("first child element must be a <template> element");
|
|
3871
4709
|
}
|
|
3872
4710
|
get action() {
|
|
3873
4711
|
return this.getAttribute("action");
|
|
@@ -3878,12 +4716,14 @@ class StreamElement extends HTMLElement {
|
|
|
3878
4716
|
get targets() {
|
|
3879
4717
|
return this.getAttribute("targets");
|
|
3880
4718
|
}
|
|
3881
|
-
|
|
4719
|
+
get requestId() {
|
|
4720
|
+
return this.getAttribute("request-id");
|
|
4721
|
+
}
|
|
4722
|
+
#raise(message) {
|
|
3882
4723
|
throw new Error(`${this.description}: ${message}`);
|
|
3883
4724
|
}
|
|
3884
4725
|
get description() {
|
|
3885
|
-
|
|
3886
|
-
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>";
|
|
3887
4727
|
}
|
|
3888
4728
|
get beforeRenderEvent() {
|
|
3889
4729
|
return new CustomEvent("turbo:before-stream-render", {
|
|
@@ -3896,8 +4736,7 @@ class StreamElement extends HTMLElement {
|
|
|
3896
4736
|
});
|
|
3897
4737
|
}
|
|
3898
4738
|
get targetElementsById() {
|
|
3899
|
-
|
|
3900
|
-
const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
|
|
4739
|
+
const element = this.ownerDocument?.getElementById(this.target);
|
|
3901
4740
|
if (element !== null) {
|
|
3902
4741
|
return [ element ];
|
|
3903
4742
|
} else {
|
|
@@ -3905,8 +4744,7 @@ class StreamElement extends HTMLElement {
|
|
|
3905
4744
|
}
|
|
3906
4745
|
}
|
|
3907
4746
|
get targetElementsByQuery() {
|
|
3908
|
-
|
|
3909
|
-
const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);
|
|
4747
|
+
const elements = this.ownerDocument?.querySelectorAll(this.targets);
|
|
3910
4748
|
if (elements.length !== 0) {
|
|
3911
4749
|
return Array.prototype.slice.call(elements);
|
|
3912
4750
|
} else {
|
|
@@ -3916,16 +4754,14 @@ class StreamElement extends HTMLElement {
|
|
|
3916
4754
|
}
|
|
3917
4755
|
|
|
3918
4756
|
class StreamSourceElement extends HTMLElement {
|
|
3919
|
-
|
|
3920
|
-
super(...arguments);
|
|
3921
|
-
this.streamSource = null;
|
|
3922
|
-
}
|
|
4757
|
+
streamSource=null;
|
|
3923
4758
|
connectedCallback() {
|
|
3924
4759
|
this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
|
|
3925
4760
|
connectStreamSource(this.streamSource);
|
|
3926
4761
|
}
|
|
3927
4762
|
disconnectedCallback() {
|
|
3928
4763
|
if (this.streamSource) {
|
|
4764
|
+
this.streamSource.close();
|
|
3929
4765
|
disconnectStreamSource(this.streamSource);
|
|
3930
4766
|
}
|
|
3931
4767
|
}
|
|
@@ -3976,10 +4812,12 @@ start();
|
|
|
3976
4812
|
|
|
3977
4813
|
var turbo_es2017Esm = Object.freeze({
|
|
3978
4814
|
__proto__: null,
|
|
4815
|
+
FetchEnctype: FetchEnctype,
|
|
4816
|
+
FetchMethod: FetchMethod,
|
|
4817
|
+
FetchRequest: FetchRequest,
|
|
4818
|
+
FetchResponse: FetchResponse,
|
|
3979
4819
|
FrameElement: FrameElement,
|
|
3980
|
-
|
|
3981
|
-
return FrameLoadingStyle;
|
|
3982
|
-
},
|
|
4820
|
+
FrameLoadingStyle: FrameLoadingStyle,
|
|
3983
4821
|
FrameRenderer: FrameRenderer,
|
|
3984
4822
|
PageRenderer: PageRenderer,
|
|
3985
4823
|
PageSnapshot: PageSnapshot,
|
|
@@ -3990,6 +4828,10 @@ var turbo_es2017Esm = Object.freeze({
|
|
|
3990
4828
|
clearCache: clearCache,
|
|
3991
4829
|
connectStreamSource: connectStreamSource,
|
|
3992
4830
|
disconnectStreamSource: disconnectStreamSource,
|
|
4831
|
+
fetch: fetch,
|
|
4832
|
+
fetchEnctypeFromString: fetchEnctypeFromString,
|
|
4833
|
+
fetchMethodFromString: fetchMethodFromString,
|
|
4834
|
+
isSafe: isSafe,
|
|
3993
4835
|
navigator: navigator$1,
|
|
3994
4836
|
registerAdapter: registerAdapter,
|
|
3995
4837
|
renderStreamMessage: renderStreamMessage,
|
|
@@ -4004,14 +4846,14 @@ var turbo_es2017Esm = Object.freeze({
|
|
|
4004
4846
|
let consumer;
|
|
4005
4847
|
|
|
4006
4848
|
async function getConsumer() {
|
|
4007
|
-
return consumer || setConsumer(createConsumer().then(setConsumer));
|
|
4849
|
+
return consumer || setConsumer(createConsumer$1().then(setConsumer));
|
|
4008
4850
|
}
|
|
4009
4851
|
|
|
4010
4852
|
function setConsumer(newConsumer) {
|
|
4011
4853
|
return consumer = newConsumer;
|
|
4012
4854
|
}
|
|
4013
4855
|
|
|
4014
|
-
async function createConsumer() {
|
|
4856
|
+
async function createConsumer$1() {
|
|
4015
4857
|
const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
|
|
4016
4858
|
return index;
|
|
4017
4859
|
}));
|
|
@@ -4027,7 +4869,7 @@ var cable = Object.freeze({
|
|
|
4027
4869
|
__proto__: null,
|
|
4028
4870
|
getConsumer: getConsumer,
|
|
4029
4871
|
setConsumer: setConsumer,
|
|
4030
|
-
createConsumer: createConsumer,
|
|
4872
|
+
createConsumer: createConsumer$1,
|
|
4031
4873
|
subscribeTo: subscribeTo
|
|
4032
4874
|
});
|
|
4033
4875
|
|
|
@@ -4048,7 +4890,9 @@ class TurboCableStreamSourceElement extends HTMLElement {
|
|
|
4048
4890
|
async connectedCallback() {
|
|
4049
4891
|
connectStreamSource(this);
|
|
4050
4892
|
this.subscription = await subscribeTo(this.channel, {
|
|
4051
|
-
received: this.dispatchMessageEvent.bind(this)
|
|
4893
|
+
received: this.dispatchMessageEvent.bind(this),
|
|
4894
|
+
connected: this.subscriptionConnected.bind(this),
|
|
4895
|
+
disconnected: this.subscriptionDisconnected.bind(this)
|
|
4052
4896
|
});
|
|
4053
4897
|
}
|
|
4054
4898
|
disconnectedCallback() {
|
|
@@ -4061,6 +4905,12 @@ class TurboCableStreamSourceElement extends HTMLElement {
|
|
|
4061
4905
|
});
|
|
4062
4906
|
return this.dispatchEvent(event);
|
|
4063
4907
|
}
|
|
4908
|
+
subscriptionConnected() {
|
|
4909
|
+
this.setAttribute("connected", "");
|
|
4910
|
+
}
|
|
4911
|
+
subscriptionDisconnected() {
|
|
4912
|
+
this.removeAttribute("connected");
|
|
4913
|
+
}
|
|
4064
4914
|
get channel() {
|
|
4065
4915
|
const channel = this.getAttribute("channel");
|
|
4066
4916
|
const signed_stream_name = this.getAttribute("signed-stream-name");
|
|
@@ -4113,7 +4963,9 @@ function determineFetchMethod(submitter, body, form) {
|
|
|
4113
4963
|
|
|
4114
4964
|
function determineFormMethod(submitter) {
|
|
4115
4965
|
if (submitter instanceof HTMLButtonElement || submitter instanceof HTMLInputElement) {
|
|
4116
|
-
if (submitter.
|
|
4966
|
+
if (submitter.name === "_method") {
|
|
4967
|
+
return submitter.value;
|
|
4968
|
+
} else if (submitter.hasAttribute("formmethod")) {
|
|
4117
4969
|
return submitter.formMethod;
|
|
4118
4970
|
} else {
|
|
4119
4971
|
return null;
|
|
@@ -4243,6 +5095,8 @@ ConnectionMonitor.staleThreshold = 6;
|
|
|
4243
5095
|
|
|
4244
5096
|
ConnectionMonitor.reconnectionBackoffRate = .15;
|
|
4245
5097
|
|
|
5098
|
+
var ConnectionMonitor$1 = ConnectionMonitor;
|
|
5099
|
+
|
|
4246
5100
|
var INTERNAL = {
|
|
4247
5101
|
message_types: {
|
|
4248
5102
|
welcome: "welcome",
|
|
@@ -4254,7 +5108,8 @@ var INTERNAL = {
|
|
|
4254
5108
|
disconnect_reasons: {
|
|
4255
5109
|
unauthorized: "unauthorized",
|
|
4256
5110
|
invalid_request: "invalid_request",
|
|
4257
|
-
server_restart: "server_restart"
|
|
5111
|
+
server_restart: "server_restart",
|
|
5112
|
+
remote: "remote"
|
|
4258
5113
|
},
|
|
4259
5114
|
default_mount_path: "/cable",
|
|
4260
5115
|
protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
|
|
@@ -4271,7 +5126,7 @@ class Connection {
|
|
|
4271
5126
|
this.open = this.open.bind(this);
|
|
4272
5127
|
this.consumer = consumer;
|
|
4273
5128
|
this.subscriptions = this.consumer.subscriptions;
|
|
4274
|
-
this.monitor = new ConnectionMonitor(this);
|
|
5129
|
+
this.monitor = new ConnectionMonitor$1(this);
|
|
4275
5130
|
this.disconnected = true;
|
|
4276
5131
|
}
|
|
4277
5132
|
send(data) {
|
|
@@ -4287,11 +5142,12 @@ class Connection {
|
|
|
4287
5142
|
logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
|
|
4288
5143
|
return false;
|
|
4289
5144
|
} else {
|
|
4290
|
-
|
|
5145
|
+
const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
|
|
5146
|
+
logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
|
|
4291
5147
|
if (this.webSocket) {
|
|
4292
5148
|
this.uninstallEventHandlers();
|
|
4293
5149
|
}
|
|
4294
|
-
this.webSocket = new adapters.WebSocket(this.consumer.url,
|
|
5150
|
+
this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
|
|
4295
5151
|
this.installEventHandlers();
|
|
4296
5152
|
this.monitor.start();
|
|
4297
5153
|
return true;
|
|
@@ -4303,7 +5159,7 @@ class Connection {
|
|
|
4303
5159
|
if (!allowReconnect) {
|
|
4304
5160
|
this.monitor.stop();
|
|
4305
5161
|
}
|
|
4306
|
-
if (this.
|
|
5162
|
+
if (this.isOpen()) {
|
|
4307
5163
|
return this.webSocket.close();
|
|
4308
5164
|
}
|
|
4309
5165
|
}
|
|
@@ -4333,6 +5189,9 @@ class Connection {
|
|
|
4333
5189
|
isActive() {
|
|
4334
5190
|
return this.isState("open", "connecting");
|
|
4335
5191
|
}
|
|
5192
|
+
triedToReconnect() {
|
|
5193
|
+
return this.monitor.reconnectAttempts > 0;
|
|
5194
|
+
}
|
|
4336
5195
|
isProtocolSupported() {
|
|
4337
5196
|
return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
|
|
4338
5197
|
}
|
|
@@ -4372,6 +5231,9 @@ Connection.prototype.events = {
|
|
|
4372
5231
|
const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
|
|
4373
5232
|
switch (type) {
|
|
4374
5233
|
case message_types.welcome:
|
|
5234
|
+
if (this.triedToReconnect()) {
|
|
5235
|
+
this.reconnectAttempted = true;
|
|
5236
|
+
}
|
|
4375
5237
|
this.monitor.recordConnect();
|
|
4376
5238
|
return this.subscriptions.reload();
|
|
4377
5239
|
|
|
@@ -4386,7 +5248,16 @@ Connection.prototype.events = {
|
|
|
4386
5248
|
|
|
4387
5249
|
case message_types.confirmation:
|
|
4388
5250
|
this.subscriptions.confirmSubscription(identifier);
|
|
4389
|
-
|
|
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
|
+
}
|
|
4390
5261
|
|
|
4391
5262
|
case message_types.rejection:
|
|
4392
5263
|
return this.subscriptions.reject(identifier);
|
|
@@ -4421,6 +5292,8 @@ Connection.prototype.events = {
|
|
|
4421
5292
|
}
|
|
4422
5293
|
};
|
|
4423
5294
|
|
|
5295
|
+
var Connection$1 = Connection;
|
|
5296
|
+
|
|
4424
5297
|
const extend = function(object, properties) {
|
|
4425
5298
|
if (properties != null) {
|
|
4426
5299
|
for (let key in properties) {
|
|
@@ -4490,10 +5363,12 @@ class SubscriptionGuarantor {
|
|
|
4490
5363
|
}
|
|
4491
5364
|
}
|
|
4492
5365
|
|
|
5366
|
+
var SubscriptionGuarantor$1 = SubscriptionGuarantor;
|
|
5367
|
+
|
|
4493
5368
|
class Subscriptions {
|
|
4494
5369
|
constructor(consumer) {
|
|
4495
5370
|
this.consumer = consumer;
|
|
4496
|
-
this.guarantor = new SubscriptionGuarantor(this);
|
|
5371
|
+
this.guarantor = new SubscriptionGuarantor$1(this);
|
|
4497
5372
|
this.subscriptions = [];
|
|
4498
5373
|
}
|
|
4499
5374
|
create(channelName, mixin) {
|
|
@@ -4570,7 +5445,8 @@ class Consumer {
|
|
|
4570
5445
|
constructor(url) {
|
|
4571
5446
|
this._url = url;
|
|
4572
5447
|
this.subscriptions = new Subscriptions(this);
|
|
4573
|
-
this.connection = new Connection(this);
|
|
5448
|
+
this.connection = new Connection$1(this);
|
|
5449
|
+
this.subprotocols = [];
|
|
4574
5450
|
}
|
|
4575
5451
|
get url() {
|
|
4576
5452
|
return createWebSocketURL(this._url);
|
|
@@ -4591,6 +5467,9 @@ class Consumer {
|
|
|
4591
5467
|
return this.connection.open();
|
|
4592
5468
|
}
|
|
4593
5469
|
}
|
|
5470
|
+
addSubProtocol(subprotocol) {
|
|
5471
|
+
this.subprotocols = [ ...this.subprotocols, subprotocol ];
|
|
5472
|
+
}
|
|
4594
5473
|
}
|
|
4595
5474
|
|
|
4596
5475
|
function createWebSocketURL(url) {
|
|
@@ -4608,7 +5487,7 @@ function createWebSocketURL(url) {
|
|
|
4608
5487
|
}
|
|
4609
5488
|
}
|
|
4610
5489
|
|
|
4611
|
-
function createConsumer
|
|
5490
|
+
function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
|
|
4612
5491
|
return new Consumer(url);
|
|
4613
5492
|
}
|
|
4614
5493
|
|
|
@@ -4621,17 +5500,17 @@ function getConfig(name) {
|
|
|
4621
5500
|
|
|
4622
5501
|
var index = Object.freeze({
|
|
4623
5502
|
__proto__: null,
|
|
4624
|
-
Connection: Connection,
|
|
4625
|
-
ConnectionMonitor: ConnectionMonitor,
|
|
5503
|
+
Connection: Connection$1,
|
|
5504
|
+
ConnectionMonitor: ConnectionMonitor$1,
|
|
4626
5505
|
Consumer: Consumer,
|
|
4627
5506
|
INTERNAL: INTERNAL,
|
|
4628
5507
|
Subscription: Subscription,
|
|
4629
5508
|
Subscriptions: Subscriptions,
|
|
4630
|
-
SubscriptionGuarantor: SubscriptionGuarantor,
|
|
5509
|
+
SubscriptionGuarantor: SubscriptionGuarantor$1,
|
|
4631
5510
|
adapters: adapters,
|
|
4632
5511
|
createWebSocketURL: createWebSocketURL,
|
|
4633
5512
|
logger: logger,
|
|
4634
|
-
createConsumer: createConsumer
|
|
5513
|
+
createConsumer: createConsumer,
|
|
4635
5514
|
getConfig: getConfig
|
|
4636
5515
|
});
|
|
4637
5516
|
|