turbo-rails 1.5.0 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +15 -11
- data/app/assets/javascripts/turbo.js +1902 -742
- data/app/assets/javascripts/turbo.min.js +9 -5
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams/broadcasts.rb +19 -5
- data/app/controllers/concerns/turbo/request_id_tracking.rb +12 -0
- data/app/controllers/turbo/frames/frame_request.rb +2 -2
- data/app/controllers/turbo/native/navigation.rb +6 -3
- data/app/helpers/turbo/drive_helper.rb +72 -14
- data/app/helpers/turbo/frames_helper.rb +8 -8
- data/app/helpers/turbo/streams/action_helper.rb +9 -2
- data/app/helpers/turbo/streams_helper.rb +0 -1
- data/app/javascript/turbo/index.js +2 -0
- data/app/jobs/turbo/streams/action_broadcast_job.rb +2 -2
- data/app/jobs/turbo/streams/broadcast_job.rb +1 -1
- data/app/jobs/turbo/streams/broadcast_stream_job.rb +7 -0
- data/app/models/concerns/turbo/broadcastable.rb +175 -34
- data/app/models/turbo/debouncer.rb +24 -0
- data/app/models/turbo/streams/tag_builder.rb +20 -0
- data/app/models/turbo/thread_debouncer.rb +28 -0
- data/config/routes.rb +0 -1
- data/lib/install/turbo_with_importmap.rb +1 -1
- data/lib/turbo/broadcastable/test_helper.rb +5 -5
- data/lib/turbo/engine.rb +13 -2
- data/lib/turbo/test_assertions/integration_test_assertions.rb +2 -2
- data/lib/turbo/test_assertions.rb +2 -2
- data/lib/turbo/version.rb +1 -1
- data/lib/turbo-rails.rb +10 -0
- metadata +6 -2
@@ -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.4
|
3
|
+
Copyright © 2024 37signals LLC
|
4
|
+
*/
|
17
5
|
(function(prototype) {
|
18
6
|
if (typeof prototype.requestSubmit == "function") return;
|
19
7
|
prototype.requestSubmit = function(submitter) {
|
@@ -44,7 +32,7 @@ const submittersByForm = new WeakMap;
|
|
44
32
|
function findSubmitterFromClickTarget(target) {
|
45
33
|
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
46
34
|
const candidate = element ? element.closest("input, button") : null;
|
47
|
-
return
|
35
|
+
return candidate?.type == "submit" ? candidate : null;
|
48
36
|
}
|
49
37
|
|
50
38
|
function clickCaptured(event) {
|
@@ -57,10 +45,13 @@ function clickCaptured(event) {
|
|
57
45
|
(function() {
|
58
46
|
if ("submitter" in Event.prototype) return;
|
59
47
|
let prototype = window.Event.prototype;
|
60
|
-
if ("SubmitEvent" in window
|
61
|
-
|
62
|
-
|
63
|
-
|
48
|
+
if ("SubmitEvent" in window) {
|
49
|
+
const prototypeOfSubmitEvent = window.SubmitEvent.prototype;
|
50
|
+
if (/Apple Computer/.test(navigator.vendor) && !("submitter" in prototypeOfSubmitEvent)) {
|
51
|
+
prototype = prototypeOfSubmitEvent;
|
52
|
+
} else {
|
53
|
+
return;
|
54
|
+
}
|
64
55
|
}
|
65
56
|
addEventListener("click", clickCaptured, true);
|
66
57
|
Object.defineProperty(prototype, "submitter", {
|
@@ -72,20 +63,19 @@ function clickCaptured(event) {
|
|
72
63
|
});
|
73
64
|
})();
|
74
65
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
FrameLoadingStyle["lazy"] = "lazy";
|
80
|
-
})(FrameLoadingStyle || (FrameLoadingStyle = {}));
|
66
|
+
const FrameLoadingStyle = {
|
67
|
+
eager: "eager",
|
68
|
+
lazy: "lazy"
|
69
|
+
};
|
81
70
|
|
82
71
|
class FrameElement extends HTMLElement {
|
72
|
+
static delegateConstructor=undefined;
|
73
|
+
loaded=Promise.resolve();
|
83
74
|
static get observedAttributes() {
|
84
|
-
return [ "disabled", "
|
75
|
+
return [ "disabled", "loading", "src" ];
|
85
76
|
}
|
86
77
|
constructor() {
|
87
78
|
super();
|
88
|
-
this.loaded = Promise.resolve();
|
89
79
|
this.delegate = new FrameElement.delegateConstructor(this);
|
90
80
|
}
|
91
81
|
connectedCallback() {
|
@@ -100,11 +90,9 @@ class FrameElement extends HTMLElement {
|
|
100
90
|
attributeChangedCallback(name) {
|
101
91
|
if (name == "loading") {
|
102
92
|
this.delegate.loadingStyleChanged();
|
103
|
-
} else if (name == "complete") {
|
104
|
-
this.delegate.completeChanged();
|
105
93
|
} else if (name == "src") {
|
106
94
|
this.delegate.sourceURLChanged();
|
107
|
-
} else {
|
95
|
+
} else if (name == "disabled") {
|
108
96
|
this.delegate.disabledChanged();
|
109
97
|
}
|
110
98
|
}
|
@@ -118,6 +106,16 @@ class FrameElement extends HTMLElement {
|
|
118
106
|
this.removeAttribute("src");
|
119
107
|
}
|
120
108
|
}
|
109
|
+
get refresh() {
|
110
|
+
return this.getAttribute("refresh");
|
111
|
+
}
|
112
|
+
set refresh(value) {
|
113
|
+
if (value) {
|
114
|
+
this.setAttribute("refresh", value);
|
115
|
+
} else {
|
116
|
+
this.removeAttribute("refresh");
|
117
|
+
}
|
118
|
+
}
|
121
119
|
get loading() {
|
122
120
|
return frameLoadingStyleFromString(this.getAttribute("loading") || "");
|
123
121
|
}
|
@@ -155,8 +153,7 @@ class FrameElement extends HTMLElement {
|
|
155
153
|
return this.ownerDocument === document && !this.isPreview;
|
156
154
|
}
|
157
155
|
get isPreview() {
|
158
|
-
|
159
|
-
return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute("data-turbo-preview");
|
156
|
+
return this.ownerDocument?.documentElement?.hasAttribute("data-turbo-preview");
|
160
157
|
}
|
161
158
|
}
|
162
159
|
|
@@ -183,8 +180,8 @@ function getAnchor(url) {
|
|
183
180
|
}
|
184
181
|
}
|
185
182
|
|
186
|
-
function getAction(form, submitter) {
|
187
|
-
const action =
|
183
|
+
function getAction$1(form, submitter) {
|
184
|
+
const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action;
|
188
185
|
return expandURL(action);
|
189
186
|
}
|
190
187
|
|
@@ -323,6 +320,14 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
|
|
323
320
|
return event;
|
324
321
|
}
|
325
322
|
|
323
|
+
function nextRepaint() {
|
324
|
+
if (document.visibilityState === "hidden") {
|
325
|
+
return nextEventLoopTick();
|
326
|
+
} else {
|
327
|
+
return nextAnimationFrame();
|
328
|
+
}
|
329
|
+
}
|
330
|
+
|
326
331
|
function nextAnimationFrame() {
|
327
332
|
return new Promise((resolve => requestAnimationFrame((() => resolve()))));
|
328
333
|
}
|
@@ -370,7 +375,7 @@ function uuid() {
|
|
370
375
|
}
|
371
376
|
|
372
377
|
function getAttribute(attributeName, ...elements) {
|
373
|
-
for (const value of elements.map((element => element
|
378
|
+
for (const value of elements.map((element => element?.getAttribute(attributeName)))) {
|
374
379
|
if (typeof value == "string") return value;
|
375
380
|
}
|
376
381
|
return null;
|
@@ -456,21 +461,83 @@ function setMetaContent(name, content) {
|
|
456
461
|
}
|
457
462
|
|
458
463
|
function findClosestRecursively(element, selector) {
|
459
|
-
var _a;
|
460
464
|
if (element instanceof Element) {
|
461
|
-
return element.closest(selector) || findClosestRecursively(element.assignedSlot ||
|
465
|
+
return element.closest(selector) || findClosestRecursively(element.assignedSlot || element.getRootNode()?.host, selector);
|
466
|
+
}
|
467
|
+
}
|
468
|
+
|
469
|
+
function elementIsFocusable(element) {
|
470
|
+
const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";
|
471
|
+
return !!element && element.closest(inertDisabledOrHidden) == null && typeof element.focus == "function";
|
472
|
+
}
|
473
|
+
|
474
|
+
function queryAutofocusableElement(elementOrDocumentFragment) {
|
475
|
+
return Array.from(elementOrDocumentFragment.querySelectorAll("[autofocus]")).find(elementIsFocusable);
|
476
|
+
}
|
477
|
+
|
478
|
+
async function around(callback, reader) {
|
479
|
+
const before = reader();
|
480
|
+
callback();
|
481
|
+
await nextAnimationFrame();
|
482
|
+
const after = reader();
|
483
|
+
return [ before, after ];
|
484
|
+
}
|
485
|
+
|
486
|
+
function doesNotTargetIFrame(anchor) {
|
487
|
+
if (anchor.hasAttribute("target")) {
|
488
|
+
for (const element of document.getElementsByName(anchor.target)) {
|
489
|
+
if (element instanceof HTMLIFrameElement) return false;
|
490
|
+
}
|
491
|
+
}
|
492
|
+
return true;
|
493
|
+
}
|
494
|
+
|
495
|
+
function findLinkFromClickTarget(target) {
|
496
|
+
return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
|
497
|
+
}
|
498
|
+
|
499
|
+
function getLocationForLink(link) {
|
500
|
+
return expandURL(link.getAttribute("href") || "");
|
501
|
+
}
|
502
|
+
|
503
|
+
function debounce(fn, delay) {
|
504
|
+
let timeoutId = null;
|
505
|
+
return (...args) => {
|
506
|
+
const callback = () => fn.apply(this, args);
|
507
|
+
clearTimeout(timeoutId);
|
508
|
+
timeoutId = setTimeout(callback, delay);
|
509
|
+
};
|
510
|
+
}
|
511
|
+
|
512
|
+
class LimitedSet extends Set {
|
513
|
+
constructor(maxSize) {
|
514
|
+
super();
|
515
|
+
this.maxSize = maxSize;
|
516
|
+
}
|
517
|
+
add(value) {
|
518
|
+
if (this.size >= this.maxSize) {
|
519
|
+
const iterator = this.values();
|
520
|
+
const oldestValue = iterator.next().value;
|
521
|
+
this.delete(oldestValue);
|
522
|
+
}
|
523
|
+
super.add(value);
|
462
524
|
}
|
463
525
|
}
|
464
526
|
|
465
|
-
|
527
|
+
const recentRequests = new LimitedSet(20);
|
528
|
+
|
529
|
+
const nativeFetch = window.fetch;
|
466
530
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
531
|
+
function fetchWithTurboHeaders(url, options = {}) {
|
532
|
+
const modifiedHeaders = new Headers(options.headers || {});
|
533
|
+
const requestUID = uuid();
|
534
|
+
recentRequests.add(requestUID);
|
535
|
+
modifiedHeaders.append("X-Turbo-Request-Id", requestUID);
|
536
|
+
return nativeFetch(url, {
|
537
|
+
...options,
|
538
|
+
headers: modifiedHeaders
|
539
|
+
});
|
540
|
+
}
|
474
541
|
|
475
542
|
function fetchMethodFromString(method) {
|
476
543
|
switch (method.toLowerCase()) {
|
@@ -491,16 +558,81 @@ function fetchMethodFromString(method) {
|
|
491
558
|
}
|
492
559
|
}
|
493
560
|
|
561
|
+
const FetchMethod = {
|
562
|
+
get: "get",
|
563
|
+
post: "post",
|
564
|
+
put: "put",
|
565
|
+
patch: "patch",
|
566
|
+
delete: "delete"
|
567
|
+
};
|
568
|
+
|
569
|
+
function fetchEnctypeFromString(encoding) {
|
570
|
+
switch (encoding.toLowerCase()) {
|
571
|
+
case FetchEnctype.multipart:
|
572
|
+
return FetchEnctype.multipart;
|
573
|
+
|
574
|
+
case FetchEnctype.plain:
|
575
|
+
return FetchEnctype.plain;
|
576
|
+
|
577
|
+
default:
|
578
|
+
return FetchEnctype.urlEncoded;
|
579
|
+
}
|
580
|
+
}
|
581
|
+
|
582
|
+
const FetchEnctype = {
|
583
|
+
urlEncoded: "application/x-www-form-urlencoded",
|
584
|
+
multipart: "multipart/form-data",
|
585
|
+
plain: "text/plain"
|
586
|
+
};
|
587
|
+
|
494
588
|
class FetchRequest {
|
495
|
-
|
496
|
-
|
497
|
-
|
589
|
+
abortController=new AbortController;
|
590
|
+
#resolveRequestPromise=_value => {};
|
591
|
+
constructor(delegate, method, location, requestBody = new URLSearchParams, target = null, enctype = FetchEnctype.urlEncoded) {
|
592
|
+
const [url, body] = buildResourceAndBody(expandURL(location), method, requestBody, enctype);
|
498
593
|
this.delegate = delegate;
|
499
|
-
this.
|
500
|
-
this.headers = this.defaultHeaders;
|
501
|
-
this.body = body;
|
502
|
-
this.url = location;
|
594
|
+
this.url = url;
|
503
595
|
this.target = target;
|
596
|
+
this.fetchOptions = {
|
597
|
+
credentials: "same-origin",
|
598
|
+
redirect: "follow",
|
599
|
+
method: method,
|
600
|
+
headers: {
|
601
|
+
...this.defaultHeaders
|
602
|
+
},
|
603
|
+
body: body,
|
604
|
+
signal: this.abortSignal,
|
605
|
+
referrer: this.delegate.referrer?.href
|
606
|
+
};
|
607
|
+
this.enctype = enctype;
|
608
|
+
}
|
609
|
+
get method() {
|
610
|
+
return this.fetchOptions.method;
|
611
|
+
}
|
612
|
+
set method(value) {
|
613
|
+
const fetchBody = this.isSafe ? this.url.searchParams : this.fetchOptions.body || new FormData;
|
614
|
+
const fetchMethod = fetchMethodFromString(value) || FetchMethod.get;
|
615
|
+
this.url.search = "";
|
616
|
+
const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype);
|
617
|
+
this.url = url;
|
618
|
+
this.fetchOptions.body = body;
|
619
|
+
this.fetchOptions.method = fetchMethod;
|
620
|
+
}
|
621
|
+
get headers() {
|
622
|
+
return this.fetchOptions.headers;
|
623
|
+
}
|
624
|
+
set headers(value) {
|
625
|
+
this.fetchOptions.headers = value;
|
626
|
+
}
|
627
|
+
get body() {
|
628
|
+
if (this.isSafe) {
|
629
|
+
return this.url.searchParams;
|
630
|
+
} else {
|
631
|
+
return this.fetchOptions.body;
|
632
|
+
}
|
633
|
+
}
|
634
|
+
set body(value) {
|
635
|
+
this.fetchOptions.body = value;
|
504
636
|
}
|
505
637
|
get location() {
|
506
638
|
return this.url;
|
@@ -517,14 +649,19 @@ class FetchRequest {
|
|
517
649
|
async perform() {
|
518
650
|
const {fetchOptions: fetchOptions} = this;
|
519
651
|
this.delegate.prepareRequest(this);
|
520
|
-
await this
|
652
|
+
const event = await this.#allowRequestToBeIntercepted(fetchOptions);
|
521
653
|
try {
|
522
654
|
this.delegate.requestStarted(this);
|
523
|
-
|
655
|
+
if (event.detail.fetchRequest) {
|
656
|
+
this.response = event.detail.fetchRequest.response;
|
657
|
+
} else {
|
658
|
+
this.response = fetchWithTurboHeaders(this.url.href, fetchOptions);
|
659
|
+
}
|
660
|
+
const response = await this.response;
|
524
661
|
return await this.receive(response);
|
525
662
|
} catch (error) {
|
526
663
|
if (error.name !== "AbortError") {
|
527
|
-
if (this
|
664
|
+
if (this.#willDelegateErrorHandling(error)) {
|
528
665
|
this.delegate.requestErrored(this, error);
|
529
666
|
}
|
530
667
|
throw error;
|
@@ -551,25 +688,13 @@ class FetchRequest {
|
|
551
688
|
}
|
552
689
|
return fetchResponse;
|
553
690
|
}
|
554
|
-
get fetchOptions() {
|
555
|
-
var _a;
|
556
|
-
return {
|
557
|
-
method: FetchMethod[this.method].toUpperCase(),
|
558
|
-
credentials: "same-origin",
|
559
|
-
headers: this.headers,
|
560
|
-
redirect: "follow",
|
561
|
-
body: this.isSafe ? null : this.body,
|
562
|
-
signal: this.abortSignal,
|
563
|
-
referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
|
564
|
-
};
|
565
|
-
}
|
566
691
|
get defaultHeaders() {
|
567
692
|
return {
|
568
693
|
Accept: "text/html, application/xhtml+xml"
|
569
694
|
};
|
570
695
|
}
|
571
696
|
get isSafe() {
|
572
|
-
return this.method
|
697
|
+
return isSafe(this.method);
|
573
698
|
}
|
574
699
|
get abortSignal() {
|
575
700
|
return this.abortController.signal;
|
@@ -577,20 +702,22 @@ class FetchRequest {
|
|
577
702
|
acceptResponseType(mimeType) {
|
578
703
|
this.headers["Accept"] = [ mimeType, this.headers["Accept"] ].join(", ");
|
579
704
|
}
|
580
|
-
async allowRequestToBeIntercepted(fetchOptions) {
|
581
|
-
const requestInterception = new Promise((resolve => this
|
705
|
+
async #allowRequestToBeIntercepted(fetchOptions) {
|
706
|
+
const requestInterception = new Promise((resolve => this.#resolveRequestPromise = resolve));
|
582
707
|
const event = dispatch("turbo:before-fetch-request", {
|
583
708
|
cancelable: true,
|
584
709
|
detail: {
|
585
710
|
fetchOptions: fetchOptions,
|
586
711
|
url: this.url,
|
587
|
-
resume: this
|
712
|
+
resume: this.#resolveRequestPromise
|
588
713
|
},
|
589
714
|
target: this.target
|
590
715
|
});
|
716
|
+
this.url = event.detail.url;
|
591
717
|
if (event.defaultPrevented) await requestInterception;
|
718
|
+
return event;
|
592
719
|
}
|
593
|
-
willDelegateErrorHandling(error) {
|
720
|
+
#willDelegateErrorHandling(error) {
|
594
721
|
const event = dispatch("turbo:fetch-request-error", {
|
595
722
|
target: this.target,
|
596
723
|
cancelable: true,
|
@@ -603,15 +730,38 @@ class FetchRequest {
|
|
603
730
|
}
|
604
731
|
}
|
605
732
|
|
733
|
+
function isSafe(fetchMethod) {
|
734
|
+
return fetchMethodFromString(fetchMethod) == FetchMethod.get;
|
735
|
+
}
|
736
|
+
|
737
|
+
function buildResourceAndBody(resource, method, requestBody, enctype) {
|
738
|
+
const searchParams = Array.from(requestBody).length > 0 ? new URLSearchParams(entriesExcludingFiles(requestBody)) : resource.searchParams;
|
739
|
+
if (isSafe(method)) {
|
740
|
+
return [ mergeIntoURLSearchParams(resource, searchParams), null ];
|
741
|
+
} else if (enctype == FetchEnctype.urlEncoded) {
|
742
|
+
return [ resource, searchParams ];
|
743
|
+
} else {
|
744
|
+
return [ resource, requestBody ];
|
745
|
+
}
|
746
|
+
}
|
747
|
+
|
748
|
+
function entriesExcludingFiles(requestBody) {
|
749
|
+
const entries = [];
|
750
|
+
for (const [name, value] of requestBody) {
|
751
|
+
if (value instanceof File) continue; else entries.push([ name, value ]);
|
752
|
+
}
|
753
|
+
return entries;
|
754
|
+
}
|
755
|
+
|
756
|
+
function mergeIntoURLSearchParams(url, requestBody) {
|
757
|
+
const searchParams = new URLSearchParams(entriesExcludingFiles(requestBody));
|
758
|
+
url.search = searchParams.toString();
|
759
|
+
return url;
|
760
|
+
}
|
761
|
+
|
606
762
|
class AppearanceObserver {
|
763
|
+
started=false;
|
607
764
|
constructor(delegate, element) {
|
608
|
-
this.started = false;
|
609
|
-
this.intersect = entries => {
|
610
|
-
const lastEntry = entries.slice(-1)[0];
|
611
|
-
if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
|
612
|
-
this.delegate.elementAppearedInViewport(this.element);
|
613
|
-
}
|
614
|
-
};
|
615
765
|
this.delegate = delegate;
|
616
766
|
this.element = element;
|
617
767
|
this.intersectionObserver = new IntersectionObserver(this.intersect);
|
@@ -628,9 +778,16 @@ class AppearanceObserver {
|
|
628
778
|
this.intersectionObserver.unobserve(this.element);
|
629
779
|
}
|
630
780
|
}
|
781
|
+
intersect=entries => {
|
782
|
+
const lastEntry = entries.slice(-1)[0];
|
783
|
+
if (lastEntry?.isIntersecting) {
|
784
|
+
this.delegate.elementAppearedInViewport(this.element);
|
785
|
+
}
|
786
|
+
};
|
631
787
|
}
|
632
788
|
|
633
789
|
class StreamMessage {
|
790
|
+
static contentType="text/vnd.turbo-stream.html";
|
634
791
|
static wrap(message) {
|
635
792
|
if (typeof message == "string") {
|
636
793
|
return new this(createDocumentFragment(message));
|
@@ -643,8 +800,6 @@ class StreamMessage {
|
|
643
800
|
}
|
644
801
|
}
|
645
802
|
|
646
|
-
StreamMessage.contentType = "text/vnd.turbo-stream.html";
|
647
|
-
|
648
803
|
function importStreamElements(fragment) {
|
649
804
|
for (const element of fragment.querySelectorAll("turbo-stream")) {
|
650
805
|
const streamElement = document.importNode(element, true);
|
@@ -656,85 +811,89 @@ function importStreamElements(fragment) {
|
|
656
811
|
return fragment;
|
657
812
|
}
|
658
813
|
|
659
|
-
|
660
|
-
|
661
|
-
(function(FormSubmissionState) {
|
662
|
-
FormSubmissionState[FormSubmissionState["initialized"] = 0] = "initialized";
|
663
|
-
FormSubmissionState[FormSubmissionState["requesting"] = 1] = "requesting";
|
664
|
-
FormSubmissionState[FormSubmissionState["waiting"] = 2] = "waiting";
|
665
|
-
FormSubmissionState[FormSubmissionState["receiving"] = 3] = "receiving";
|
666
|
-
FormSubmissionState[FormSubmissionState["stopping"] = 4] = "stopping";
|
667
|
-
FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
|
668
|
-
})(FormSubmissionState || (FormSubmissionState = {}));
|
814
|
+
const PREFETCH_DELAY = 100;
|
669
815
|
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
}
|
816
|
+
class PrefetchCache {
|
817
|
+
#prefetchTimeout=null;
|
818
|
+
#prefetched=null;
|
819
|
+
get(url) {
|
820
|
+
if (this.#prefetched && this.#prefetched.url === url && this.#prefetched.expire > Date.now()) {
|
821
|
+
return this.#prefetched.request;
|
822
|
+
}
|
823
|
+
}
|
824
|
+
setLater(url, request, ttl) {
|
825
|
+
this.clear();
|
826
|
+
this.#prefetchTimeout = setTimeout((() => {
|
827
|
+
request.perform();
|
828
|
+
this.set(url, request, ttl);
|
829
|
+
this.#prefetchTimeout = null;
|
830
|
+
}), PREFETCH_DELAY);
|
831
|
+
}
|
832
|
+
set(url, request, ttl) {
|
833
|
+
this.#prefetched = {
|
834
|
+
url: url,
|
835
|
+
request: request,
|
836
|
+
expire: new Date((new Date).getTime() + ttl)
|
837
|
+
};
|
838
|
+
}
|
839
|
+
clear() {
|
840
|
+
if (this.#prefetchTimeout) clearTimeout(this.#prefetchTimeout);
|
841
|
+
this.#prefetched = null;
|
842
|
+
}
|
843
|
+
}
|
677
844
|
|
678
|
-
|
679
|
-
switch (encoding.toLowerCase()) {
|
680
|
-
case FormEnctype.multipart:
|
681
|
-
return FormEnctype.multipart;
|
845
|
+
const cacheTtl = 10 * 1e3;
|
682
846
|
|
683
|
-
|
684
|
-
return FormEnctype.plain;
|
847
|
+
const prefetchCache = new PrefetchCache;
|
685
848
|
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
849
|
+
const FormSubmissionState = {
|
850
|
+
initialized: "initialized",
|
851
|
+
requesting: "requesting",
|
852
|
+
waiting: "waiting",
|
853
|
+
receiving: "receiving",
|
854
|
+
stopping: "stopping",
|
855
|
+
stopped: "stopped"
|
856
|
+
};
|
690
857
|
|
691
858
|
class FormSubmission {
|
859
|
+
state=FormSubmissionState.initialized;
|
692
860
|
static confirmMethod(message, _element, _submitter) {
|
693
861
|
return Promise.resolve(confirm(message));
|
694
862
|
}
|
695
863
|
constructor(delegate, formElement, submitter, mustRedirect = false) {
|
696
|
-
|
864
|
+
const method = getMethod(formElement, submitter);
|
865
|
+
const action = getAction(getFormAction(formElement, submitter), method);
|
866
|
+
const body = buildFormData(formElement, submitter);
|
867
|
+
const enctype = getEnctype(formElement, submitter);
|
697
868
|
this.delegate = delegate;
|
698
869
|
this.formElement = formElement;
|
699
870
|
this.submitter = submitter;
|
700
|
-
this.
|
701
|
-
this.location = expandURL(this.action);
|
702
|
-
if (this.method == FetchMethod.get) {
|
703
|
-
mergeFormDataEntries(this.location, [ ...this.body.entries() ]);
|
704
|
-
}
|
705
|
-
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
|
871
|
+
this.fetchRequest = new FetchRequest(this, method, action, body, formElement, enctype);
|
706
872
|
this.mustRedirect = mustRedirect;
|
707
873
|
}
|
708
874
|
get method() {
|
709
|
-
|
710
|
-
|
711
|
-
|
875
|
+
return this.fetchRequest.method;
|
876
|
+
}
|
877
|
+
set method(value) {
|
878
|
+
this.fetchRequest.method = value;
|
712
879
|
}
|
713
880
|
get action() {
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
} else {
|
719
|
-
return this.formElement.getAttribute("action") || formElementAction || "";
|
720
|
-
}
|
881
|
+
return this.fetchRequest.url.toString();
|
882
|
+
}
|
883
|
+
set action(value) {
|
884
|
+
this.fetchRequest.url = expandURL(value);
|
721
885
|
}
|
722
886
|
get body() {
|
723
|
-
|
724
|
-
return new URLSearchParams(this.stringFormData);
|
725
|
-
} else {
|
726
|
-
return this.formData;
|
727
|
-
}
|
887
|
+
return this.fetchRequest.body;
|
728
888
|
}
|
729
889
|
get enctype() {
|
730
|
-
|
731
|
-
return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
|
890
|
+
return this.fetchRequest.enctype;
|
732
891
|
}
|
733
892
|
get isSafe() {
|
734
893
|
return this.fetchRequest.isSafe;
|
735
894
|
}
|
736
|
-
get
|
737
|
-
return
|
895
|
+
get location() {
|
896
|
+
return this.fetchRequest.url;
|
738
897
|
}
|
739
898
|
async start() {
|
740
899
|
const {initialized: initialized, requesting: requesting} = FormSubmissionState;
|
@@ -770,10 +929,10 @@ class FormSubmission {
|
|
770
929
|
}
|
771
930
|
}
|
772
931
|
requestStarted(_request) {
|
773
|
-
var _a;
|
774
932
|
this.state = FormSubmissionState.waiting;
|
775
|
-
|
933
|
+
this.submitter?.setAttribute("disabled", "");
|
776
934
|
this.setSubmitsWith();
|
935
|
+
markAsBusy(this.formElement);
|
777
936
|
dispatch("turbo:submit-start", {
|
778
937
|
target: this.formElement,
|
779
938
|
detail: {
|
@@ -783,6 +942,7 @@ class FormSubmission {
|
|
783
942
|
this.delegate.formSubmissionStarted(this);
|
784
943
|
}
|
785
944
|
requestPreventedHandlingResponse(request, response) {
|
945
|
+
prefetchCache.clear();
|
786
946
|
this.result = {
|
787
947
|
success: response.succeeded,
|
788
948
|
fetchResponse: response
|
@@ -791,7 +951,10 @@ class FormSubmission {
|
|
791
951
|
requestSucceededWithResponse(request, response) {
|
792
952
|
if (response.clientError || response.serverError) {
|
793
953
|
this.delegate.formSubmissionFailedWithResponse(this, response);
|
794
|
-
|
954
|
+
return;
|
955
|
+
}
|
956
|
+
prefetchCache.clear();
|
957
|
+
if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {
|
795
958
|
const error = new Error("Form responses must redirect to another location");
|
796
959
|
this.delegate.formSubmissionErrored(this, error);
|
797
960
|
} else {
|
@@ -818,15 +981,16 @@ class FormSubmission {
|
|
818
981
|
this.delegate.formSubmissionErrored(this, error);
|
819
982
|
}
|
820
983
|
requestFinished(_request) {
|
821
|
-
var _a;
|
822
984
|
this.state = FormSubmissionState.stopped;
|
823
|
-
|
985
|
+
this.submitter?.removeAttribute("disabled");
|
824
986
|
this.resetSubmitterText();
|
987
|
+
clearBusyState(this.formElement);
|
825
988
|
dispatch("turbo:submit-end", {
|
826
989
|
target: this.formElement,
|
827
|
-
detail:
|
828
|
-
formSubmission: this
|
829
|
-
|
990
|
+
detail: {
|
991
|
+
formSubmission: this,
|
992
|
+
...this.result
|
993
|
+
}
|
830
994
|
});
|
831
995
|
this.delegate.formSubmissionFinished(this);
|
832
996
|
}
|
@@ -857,15 +1021,14 @@ class FormSubmission {
|
|
857
1021
|
return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
|
858
1022
|
}
|
859
1023
|
get submitsWith() {
|
860
|
-
|
861
|
-
return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-submits-with");
|
1024
|
+
return this.submitter?.getAttribute("data-turbo-submits-with");
|
862
1025
|
}
|
863
1026
|
}
|
864
1027
|
|
865
1028
|
function buildFormData(formElement, submitter) {
|
866
1029
|
const formData = new FormData(formElement);
|
867
|
-
const name = submitter
|
868
|
-
const value = submitter
|
1030
|
+
const name = submitter?.getAttribute("name");
|
1031
|
+
const value = submitter?.getAttribute("value");
|
869
1032
|
if (name) {
|
870
1033
|
formData.append(name, value || "");
|
871
1034
|
}
|
@@ -887,14 +1050,30 @@ function responseSucceededWithoutRedirect(response) {
|
|
887
1050
|
return response.statusCode == 200 && !response.redirected;
|
888
1051
|
}
|
889
1052
|
|
890
|
-
function
|
891
|
-
const
|
892
|
-
|
893
|
-
|
894
|
-
|
1053
|
+
function getFormAction(formElement, submitter) {
|
1054
|
+
const formElementAction = typeof formElement.action === "string" ? formElement.action : null;
|
1055
|
+
if (submitter?.hasAttribute("formaction")) {
|
1056
|
+
return submitter.getAttribute("formaction") || "";
|
1057
|
+
} else {
|
1058
|
+
return formElement.getAttribute("action") || formElementAction || "";
|
1059
|
+
}
|
1060
|
+
}
|
1061
|
+
|
1062
|
+
function getAction(formAction, fetchMethod) {
|
1063
|
+
const action = expandURL(formAction);
|
1064
|
+
if (isSafe(fetchMethod)) {
|
1065
|
+
action.search = "";
|
895
1066
|
}
|
896
|
-
|
897
|
-
|
1067
|
+
return action;
|
1068
|
+
}
|
1069
|
+
|
1070
|
+
function getMethod(formElement, submitter) {
|
1071
|
+
const method = submitter?.getAttribute("formmethod") || formElement.getAttribute("method") || "";
|
1072
|
+
return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
|
1073
|
+
}
|
1074
|
+
|
1075
|
+
function getEnctype(formElement, submitter) {
|
1076
|
+
return fetchEnctypeFromString(submitter?.getAttribute("formenctype") || formElement.enctype);
|
898
1077
|
}
|
899
1078
|
|
900
1079
|
class Snapshot {
|
@@ -917,11 +1096,7 @@ class Snapshot {
|
|
917
1096
|
return this.element.isConnected;
|
918
1097
|
}
|
919
1098
|
get firstAutofocusableElement() {
|
920
|
-
|
921
|
-
for (const element of this.element.querySelectorAll("[autofocus]")) {
|
922
|
-
if (element.closest(inertDisabledOrHidden) == null) return element; else continue;
|
923
|
-
}
|
924
|
-
return null;
|
1099
|
+
return queryAutofocusableElement(this.element);
|
925
1100
|
}
|
926
1101
|
get permanentElements() {
|
927
1102
|
return queryPermanentElementsAll(this.element);
|
@@ -951,23 +1126,8 @@ function queryPermanentElementsAll(node) {
|
|
951
1126
|
}
|
952
1127
|
|
953
1128
|
class FormSubmitObserver {
|
1129
|
+
started=false;
|
954
1130
|
constructor(delegate, eventTarget) {
|
955
|
-
this.started = false;
|
956
|
-
this.submitCaptured = () => {
|
957
|
-
this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
|
958
|
-
this.eventTarget.addEventListener("submit", this.submitBubbled, false);
|
959
|
-
};
|
960
|
-
this.submitBubbled = event => {
|
961
|
-
if (!event.defaultPrevented) {
|
962
|
-
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
963
|
-
const submitter = event.submitter || undefined;
|
964
|
-
if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
|
965
|
-
event.preventDefault();
|
966
|
-
event.stopImmediatePropagation();
|
967
|
-
this.delegate.formSubmitted(form, submitter);
|
968
|
-
}
|
969
|
-
}
|
970
|
-
};
|
971
1131
|
this.delegate = delegate;
|
972
1132
|
this.eventTarget = eventTarget;
|
973
1133
|
}
|
@@ -983,16 +1143,31 @@ class FormSubmitObserver {
|
|
983
1143
|
this.started = false;
|
984
1144
|
}
|
985
1145
|
}
|
1146
|
+
submitCaptured=() => {
|
1147
|
+
this.eventTarget.removeEventListener("submit", this.submitBubbled, false);
|
1148
|
+
this.eventTarget.addEventListener("submit", this.submitBubbled, false);
|
1149
|
+
};
|
1150
|
+
submitBubbled=event => {
|
1151
|
+
if (!event.defaultPrevented) {
|
1152
|
+
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
1153
|
+
const submitter = event.submitter || undefined;
|
1154
|
+
if (form && submissionDoesNotDismissDialog(form, submitter) && submissionDoesNotTargetIFrame(form, submitter) && this.delegate.willSubmitForm(form, submitter)) {
|
1155
|
+
event.preventDefault();
|
1156
|
+
event.stopImmediatePropagation();
|
1157
|
+
this.delegate.formSubmitted(form, submitter);
|
1158
|
+
}
|
1159
|
+
}
|
1160
|
+
};
|
986
1161
|
}
|
987
1162
|
|
988
1163
|
function submissionDoesNotDismissDialog(form, submitter) {
|
989
|
-
const method =
|
1164
|
+
const method = submitter?.getAttribute("formmethod") || form.getAttribute("method");
|
990
1165
|
return method != "dialog";
|
991
1166
|
}
|
992
1167
|
|
993
1168
|
function submissionDoesNotTargetIFrame(form, submitter) {
|
994
|
-
if (
|
995
|
-
const target =
|
1169
|
+
if (submitter?.hasAttribute("formtarget") || form.hasAttribute("target")) {
|
1170
|
+
const target = submitter?.getAttribute("formtarget") || form.target;
|
996
1171
|
for (const element of document.getElementsByName(target)) {
|
997
1172
|
if (element instanceof HTMLIFrameElement) return false;
|
998
1173
|
}
|
@@ -1003,9 +1178,9 @@ function submissionDoesNotTargetIFrame(form, submitter) {
|
|
1003
1178
|
}
|
1004
1179
|
|
1005
1180
|
class View {
|
1181
|
+
#resolveRenderPromise=_value => {};
|
1182
|
+
#resolveInterceptionPromise=_value => {};
|
1006
1183
|
constructor(delegate, element) {
|
1007
|
-
this.resolveRenderPromise = _value => {};
|
1008
|
-
this.resolveInterceptionPromise = _value => {};
|
1009
1184
|
this.delegate = delegate;
|
1010
1185
|
this.element = element;
|
1011
1186
|
}
|
@@ -1051,29 +1226,31 @@ class View {
|
|
1051
1226
|
return window;
|
1052
1227
|
}
|
1053
1228
|
async render(renderer) {
|
1054
|
-
const {isPreview: isPreview, shouldRender: shouldRender, newSnapshot: snapshot} = renderer;
|
1229
|
+
const {isPreview: isPreview, shouldRender: shouldRender, willRender: willRender, newSnapshot: snapshot} = renderer;
|
1230
|
+
const shouldInvalidate = willRender;
|
1055
1231
|
if (shouldRender) {
|
1056
1232
|
try {
|
1057
|
-
this.renderPromise = new Promise((resolve => this
|
1233
|
+
this.renderPromise = new Promise((resolve => this.#resolveRenderPromise = resolve));
|
1058
1234
|
this.renderer = renderer;
|
1059
1235
|
await this.prepareToRenderSnapshot(renderer);
|
1060
|
-
const renderInterception = new Promise((resolve => this
|
1236
|
+
const renderInterception = new Promise((resolve => this.#resolveInterceptionPromise = resolve));
|
1061
1237
|
const options = {
|
1062
|
-
resume: this
|
1063
|
-
render: this.renderer.renderElement
|
1238
|
+
resume: this.#resolveInterceptionPromise,
|
1239
|
+
render: this.renderer.renderElement,
|
1240
|
+
renderMethod: this.renderer.renderMethod
|
1064
1241
|
};
|
1065
1242
|
const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);
|
1066
1243
|
if (!immediateRender) await renderInterception;
|
1067
1244
|
await this.renderSnapshot(renderer);
|
1068
|
-
this.delegate.viewRenderedSnapshot(snapshot, isPreview);
|
1245
|
+
this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod);
|
1069
1246
|
this.delegate.preloadOnLoadLinksForView(this.element);
|
1070
1247
|
this.finishRenderingSnapshot(renderer);
|
1071
1248
|
} finally {
|
1072
1249
|
delete this.renderer;
|
1073
|
-
this
|
1250
|
+
this.#resolveRenderPromise(undefined);
|
1074
1251
|
delete this.renderPromise;
|
1075
1252
|
}
|
1076
|
-
} else {
|
1253
|
+
} else if (shouldInvalidate) {
|
1077
1254
|
this.invalidate(renderer.reloadReason);
|
1078
1255
|
}
|
1079
1256
|
}
|
@@ -1091,6 +1268,12 @@ class View {
|
|
1091
1268
|
this.element.removeAttribute("data-turbo-preview");
|
1092
1269
|
}
|
1093
1270
|
}
|
1271
|
+
markVisitDirection(direction) {
|
1272
|
+
this.element.setAttribute("data-turbo-visit-direction", direction);
|
1273
|
+
}
|
1274
|
+
unmarkVisitDirection() {
|
1275
|
+
this.element.removeAttribute("data-turbo-visit-direction");
|
1276
|
+
}
|
1094
1277
|
async renderSnapshot(renderer) {
|
1095
1278
|
await renderer.render();
|
1096
1279
|
}
|
@@ -1110,26 +1293,6 @@ class FrameView extends View {
|
|
1110
1293
|
|
1111
1294
|
class LinkInterceptor {
|
1112
1295
|
constructor(delegate, element) {
|
1113
|
-
this.clickBubbled = event => {
|
1114
|
-
if (this.respondsToEventTarget(event.target)) {
|
1115
|
-
this.clickEvent = event;
|
1116
|
-
} else {
|
1117
|
-
delete this.clickEvent;
|
1118
|
-
}
|
1119
|
-
};
|
1120
|
-
this.linkClicked = event => {
|
1121
|
-
if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
|
1122
|
-
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
1123
|
-
this.clickEvent.preventDefault();
|
1124
|
-
event.preventDefault();
|
1125
|
-
this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
|
1126
|
-
}
|
1127
|
-
}
|
1128
|
-
delete this.clickEvent;
|
1129
|
-
};
|
1130
|
-
this.willVisit = _event => {
|
1131
|
-
delete this.clickEvent;
|
1132
|
-
};
|
1133
1296
|
this.delegate = delegate;
|
1134
1297
|
this.element = element;
|
1135
1298
|
}
|
@@ -1143,6 +1306,26 @@ class LinkInterceptor {
|
|
1143
1306
|
document.removeEventListener("turbo:click", this.linkClicked);
|
1144
1307
|
document.removeEventListener("turbo:before-visit", this.willVisit);
|
1145
1308
|
}
|
1309
|
+
clickBubbled=event => {
|
1310
|
+
if (this.respondsToEventTarget(event.target)) {
|
1311
|
+
this.clickEvent = event;
|
1312
|
+
} else {
|
1313
|
+
delete this.clickEvent;
|
1314
|
+
}
|
1315
|
+
};
|
1316
|
+
linkClicked=event => {
|
1317
|
+
if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
|
1318
|
+
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
|
1319
|
+
this.clickEvent.preventDefault();
|
1320
|
+
event.preventDefault();
|
1321
|
+
this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);
|
1322
|
+
}
|
1323
|
+
}
|
1324
|
+
delete this.clickEvent;
|
1325
|
+
};
|
1326
|
+
willVisit=_event => {
|
1327
|
+
delete this.clickEvent;
|
1328
|
+
};
|
1146
1329
|
respondsToEventTarget(target) {
|
1147
1330
|
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
1148
1331
|
return element && element.closest("turbo-frame, html") == this.element;
|
@@ -1150,25 +1333,8 @@ class LinkInterceptor {
|
|
1150
1333
|
}
|
1151
1334
|
|
1152
1335
|
class LinkClickObserver {
|
1336
|
+
started=false;
|
1153
1337
|
constructor(delegate, eventTarget) {
|
1154
|
-
this.started = false;
|
1155
|
-
this.clickCaptured = () => {
|
1156
|
-
this.eventTarget.removeEventListener("click", this.clickBubbled, false);
|
1157
|
-
this.eventTarget.addEventListener("click", this.clickBubbled, false);
|
1158
|
-
};
|
1159
|
-
this.clickBubbled = event => {
|
1160
|
-
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
|
1161
|
-
const target = event.composedPath && event.composedPath()[0] || event.target;
|
1162
|
-
const link = this.findLinkFromClickTarget(target);
|
1163
|
-
if (link && doesNotTargetIFrame(link)) {
|
1164
|
-
const location = this.getLocationForLink(link);
|
1165
|
-
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
1166
|
-
event.preventDefault();
|
1167
|
-
this.delegate.followedLinkToLocation(link, location);
|
1168
|
-
}
|
1169
|
-
}
|
1170
|
-
}
|
1171
|
-
};
|
1172
1338
|
this.delegate = delegate;
|
1173
1339
|
this.eventTarget = eventTarget;
|
1174
1340
|
}
|
@@ -1184,26 +1350,26 @@ class LinkClickObserver {
|
|
1184
1350
|
this.started = false;
|
1185
1351
|
}
|
1186
1352
|
}
|
1353
|
+
clickCaptured=() => {
|
1354
|
+
this.eventTarget.removeEventListener("click", this.clickBubbled, false);
|
1355
|
+
this.eventTarget.addEventListener("click", this.clickBubbled, false);
|
1356
|
+
};
|
1357
|
+
clickBubbled=event => {
|
1358
|
+
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
|
1359
|
+
const target = event.composedPath && event.composedPath()[0] || event.target;
|
1360
|
+
const link = findLinkFromClickTarget(target);
|
1361
|
+
if (link && doesNotTargetIFrame(link)) {
|
1362
|
+
const location = getLocationForLink(link);
|
1363
|
+
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
|
1364
|
+
event.preventDefault();
|
1365
|
+
this.delegate.followedLinkToLocation(link, location);
|
1366
|
+
}
|
1367
|
+
}
|
1368
|
+
}
|
1369
|
+
};
|
1187
1370
|
clickEventIsSignificant(event) {
|
1188
1371
|
return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
|
1189
1372
|
}
|
1190
|
-
findLinkFromClickTarget(target) {
|
1191
|
-
return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])");
|
1192
|
-
}
|
1193
|
-
getLocationForLink(link) {
|
1194
|
-
return expandURL(link.getAttribute("href") || "");
|
1195
|
-
}
|
1196
|
-
}
|
1197
|
-
|
1198
|
-
function doesNotTargetIFrame(anchor) {
|
1199
|
-
if (anchor.hasAttribute("target")) {
|
1200
|
-
for (const element of document.getElementsByName(anchor.target)) {
|
1201
|
-
if (element instanceof HTMLIFrameElement) return false;
|
1202
|
-
}
|
1203
|
-
return true;
|
1204
|
-
} else {
|
1205
|
-
return true;
|
1206
|
-
}
|
1207
1373
|
}
|
1208
1374
|
|
1209
1375
|
class FormLinkClickObserver {
|
@@ -1217,8 +1383,14 @@ class FormLinkClickObserver {
|
|
1217
1383
|
stop() {
|
1218
1384
|
this.linkInterceptor.stop();
|
1219
1385
|
}
|
1386
|
+
canPrefetchRequestToLocation(link, location) {
|
1387
|
+
return false;
|
1388
|
+
}
|
1389
|
+
prefetchAndCacheRequestToLocation(link, location) {
|
1390
|
+
return;
|
1391
|
+
}
|
1220
1392
|
willFollowLinkToLocation(link, location, originalEvent) {
|
1221
|
-
return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && link.hasAttribute("data-turbo-method");
|
1393
|
+
return this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) && (link.hasAttribute("data-turbo-method") || link.hasAttribute("data-turbo-stream"));
|
1222
1394
|
}
|
1223
1395
|
followedLinkToLocation(link, location) {
|
1224
1396
|
const form = document.createElement("form");
|
@@ -1291,7 +1463,7 @@ class Bardo {
|
|
1291
1463
|
}
|
1292
1464
|
replacePlaceholderWithPermanentElement(permanentElement) {
|
1293
1465
|
const placeholder = this.getPlaceholderById(permanentElement.id);
|
1294
|
-
placeholder
|
1466
|
+
placeholder?.replaceWith(permanentElement);
|
1295
1467
|
}
|
1296
1468
|
getPlaceholderById(id) {
|
1297
1469
|
return this.placeholders.find((element => element.content == id));
|
@@ -1309,8 +1481,8 @@ function createPlaceholderForPermanentElement(permanentElement) {
|
|
1309
1481
|
}
|
1310
1482
|
|
1311
1483
|
class Renderer {
|
1484
|
+
#activeElement=null;
|
1312
1485
|
constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {
|
1313
|
-
this.activeElement = null;
|
1314
1486
|
this.currentSnapshot = currentSnapshot;
|
1315
1487
|
this.newSnapshot = newSnapshot;
|
1316
1488
|
this.isPreview = isPreview;
|
@@ -1330,6 +1502,7 @@ class Renderer {
|
|
1330
1502
|
prepareToRender() {
|
1331
1503
|
return;
|
1332
1504
|
}
|
1505
|
+
render() {}
|
1333
1506
|
finishRendering() {
|
1334
1507
|
if (this.resolvingFunctions) {
|
1335
1508
|
this.resolvingFunctions.resolve();
|
@@ -1341,20 +1514,20 @@ class Renderer {
|
|
1341
1514
|
}
|
1342
1515
|
focusFirstAutofocusableElement() {
|
1343
1516
|
const element = this.connectedSnapshot.firstAutofocusableElement;
|
1344
|
-
if (
|
1517
|
+
if (element) {
|
1345
1518
|
element.focus();
|
1346
1519
|
}
|
1347
1520
|
}
|
1348
1521
|
enteringBardo(currentPermanentElement) {
|
1349
|
-
if (this
|
1522
|
+
if (this.#activeElement) return;
|
1350
1523
|
if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {
|
1351
|
-
this
|
1524
|
+
this.#activeElement = this.currentSnapshot.activeElement;
|
1352
1525
|
}
|
1353
1526
|
}
|
1354
1527
|
leavingBardo(currentPermanentElement) {
|
1355
|
-
if (currentPermanentElement.contains(this
|
1356
|
-
this
|
1357
|
-
this
|
1528
|
+
if (currentPermanentElement.contains(this.#activeElement) && this.#activeElement instanceof HTMLElement) {
|
1529
|
+
this.#activeElement.focus();
|
1530
|
+
this.#activeElement = null;
|
1358
1531
|
}
|
1359
1532
|
}
|
1360
1533
|
get connectedSnapshot() {
|
@@ -1369,20 +1542,18 @@ class Renderer {
|
|
1369
1542
|
get permanentElementMap() {
|
1370
1543
|
return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
|
1371
1544
|
}
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
return element && typeof element.focus == "function";
|
1545
|
+
get renderMethod() {
|
1546
|
+
return "replace";
|
1547
|
+
}
|
1376
1548
|
}
|
1377
1549
|
|
1378
1550
|
class FrameRenderer extends Renderer {
|
1379
1551
|
static renderElement(currentElement, newElement) {
|
1380
|
-
var _a;
|
1381
1552
|
const destinationRange = document.createRange();
|
1382
1553
|
destinationRange.selectNodeContents(currentElement);
|
1383
1554
|
destinationRange.deleteContents();
|
1384
1555
|
const frameElement = newElement;
|
1385
|
-
const sourceRange =
|
1556
|
+
const sourceRange = frameElement.ownerDocument?.createRange();
|
1386
1557
|
if (sourceRange) {
|
1387
1558
|
sourceRange.selectNodeContents(frameElement);
|
1388
1559
|
currentElement.appendChild(sourceRange.extractContents());
|
@@ -1396,14 +1567,14 @@ class FrameRenderer extends Renderer {
|
|
1396
1567
|
return true;
|
1397
1568
|
}
|
1398
1569
|
async render() {
|
1399
|
-
await
|
1570
|
+
await nextRepaint();
|
1400
1571
|
this.preservingPermanentElements((() => {
|
1401
1572
|
this.loadFrameElement();
|
1402
1573
|
}));
|
1403
1574
|
this.scrollFrameIntoView();
|
1404
|
-
await
|
1575
|
+
await nextRepaint();
|
1405
1576
|
this.focusFirstAutofocusableElement();
|
1406
|
-
await
|
1577
|
+
await nextRepaint();
|
1407
1578
|
this.activateScriptElements();
|
1408
1579
|
}
|
1409
1580
|
loadFrameElement() {
|
@@ -1453,6 +1624,7 @@ function readScrollBehavior(value, defaultValue) {
|
|
1453
1624
|
}
|
1454
1625
|
|
1455
1626
|
class ProgressBar {
|
1627
|
+
static animationDuration=300;
|
1456
1628
|
static get defaultCSS() {
|
1457
1629
|
return unindent`
|
1458
1630
|
.turbo-progress-bar {
|
@@ -1470,13 +1642,10 @@ class ProgressBar {
|
|
1470
1642
|
}
|
1471
1643
|
`;
|
1472
1644
|
}
|
1645
|
+
hiding=false;
|
1646
|
+
value=0;
|
1647
|
+
visible=false;
|
1473
1648
|
constructor() {
|
1474
|
-
this.hiding = false;
|
1475
|
-
this.value = 0;
|
1476
|
-
this.visible = false;
|
1477
|
-
this.trickle = () => {
|
1478
|
-
this.setValue(this.value + Math.random() / 100);
|
1479
|
-
};
|
1480
1649
|
this.stylesheetElement = this.createStylesheetElement();
|
1481
1650
|
this.progressElement = this.createProgressElement();
|
1482
1651
|
this.installStylesheetElement();
|
@@ -1531,6 +1700,9 @@ class ProgressBar {
|
|
1531
1700
|
window.clearInterval(this.trickleInterval);
|
1532
1701
|
delete this.trickleInterval;
|
1533
1702
|
}
|
1703
|
+
trickle=() => {
|
1704
|
+
this.setValue(this.value + Math.random() / 100);
|
1705
|
+
};
|
1534
1706
|
refresh() {
|
1535
1707
|
requestAnimationFrame((() => {
|
1536
1708
|
this.progressElement.style.width = `${10 + this.value * 90}%`;
|
@@ -1555,25 +1727,22 @@ class ProgressBar {
|
|
1555
1727
|
}
|
1556
1728
|
}
|
1557
1729
|
|
1558
|
-
ProgressBar.animationDuration = 300;
|
1559
|
-
|
1560
1730
|
class HeadSnapshot extends Snapshot {
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
}
|
1731
|
+
detailsByOuterHTML=this.children.filter((element => !elementIsNoscript(element))).map((element => elementWithoutNonce(element))).reduce(((result, element) => {
|
1732
|
+
const {outerHTML: outerHTML} = element;
|
1733
|
+
const details = outerHTML in result ? result[outerHTML] : {
|
1734
|
+
type: elementType(element),
|
1735
|
+
tracked: elementIsTracked(element),
|
1736
|
+
elements: []
|
1737
|
+
};
|
1738
|
+
return {
|
1739
|
+
...result,
|
1740
|
+
[outerHTML]: {
|
1741
|
+
...details,
|
1742
|
+
elements: [ ...details.elements, element ]
|
1743
|
+
}
|
1744
|
+
};
|
1745
|
+
}), {});
|
1577
1746
|
get trackedElementSignature() {
|
1578
1747
|
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
|
1579
1748
|
}
|
@@ -1606,7 +1775,7 @@ class HeadSnapshot extends Snapshot {
|
|
1606
1775
|
return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
|
1607
1776
|
const {elements: [element]} = this.detailsByOuterHTML[outerHTML];
|
1608
1777
|
return elementIsMetaElementWithName(element, name) ? element : result;
|
1609
|
-
}), undefined);
|
1778
|
+
}), undefined | undefined);
|
1610
1779
|
}
|
1611
1780
|
}
|
1612
1781
|
|
@@ -1656,11 +1825,12 @@ class PageSnapshot extends Snapshot {
|
|
1656
1825
|
static fromElement(element) {
|
1657
1826
|
return this.fromDocument(element.ownerDocument);
|
1658
1827
|
}
|
1659
|
-
static fromDocument({
|
1660
|
-
return new this(body, new HeadSnapshot(head));
|
1828
|
+
static fromDocument({documentElement: documentElement, body: body, head: head}) {
|
1829
|
+
return new this(documentElement, body, new HeadSnapshot(head));
|
1661
1830
|
}
|
1662
|
-
constructor(
|
1663
|
-
super(
|
1831
|
+
constructor(documentElement, body, headSnapshot) {
|
1832
|
+
super(body);
|
1833
|
+
this.documentElement = documentElement;
|
1664
1834
|
this.headSnapshot = headSnapshot;
|
1665
1835
|
}
|
1666
1836
|
clone() {
|
@@ -1675,14 +1845,16 @@ class PageSnapshot extends Snapshot {
|
|
1675
1845
|
for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
|
1676
1846
|
clonedPasswordInput.value = "";
|
1677
1847
|
}
|
1678
|
-
return new PageSnapshot(clonedElement, this.headSnapshot);
|
1848
|
+
return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot);
|
1849
|
+
}
|
1850
|
+
get lang() {
|
1851
|
+
return this.documentElement.getAttribute("lang");
|
1679
1852
|
}
|
1680
1853
|
get headElement() {
|
1681
1854
|
return this.headSnapshot.element;
|
1682
1855
|
}
|
1683
1856
|
get rootLocation() {
|
1684
|
-
|
1685
|
-
const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
|
1857
|
+
const root = this.getSetting("root") ?? "/";
|
1686
1858
|
return expandURL(root);
|
1687
1859
|
}
|
1688
1860
|
get cacheControlValue() {
|
@@ -1697,29 +1869,38 @@ class PageSnapshot extends Snapshot {
|
|
1697
1869
|
get isVisitable() {
|
1698
1870
|
return this.getSetting("visit-control") != "reload";
|
1699
1871
|
}
|
1872
|
+
get prefersViewTransitions() {
|
1873
|
+
return this.headSnapshot.getMetaValue("view-transition") === "same-origin";
|
1874
|
+
}
|
1875
|
+
get shouldMorphPage() {
|
1876
|
+
return this.getSetting("refresh-method") === "morph";
|
1877
|
+
}
|
1878
|
+
get shouldPreserveScrollPosition() {
|
1879
|
+
return this.getSetting("refresh-scroll") === "preserve";
|
1880
|
+
}
|
1700
1881
|
getSetting(name) {
|
1701
1882
|
return this.headSnapshot.getMetaValue(`turbo-${name}`);
|
1702
1883
|
}
|
1703
1884
|
}
|
1704
1885
|
|
1705
|
-
|
1706
|
-
|
1707
|
-
(
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
1716
|
-
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
}
|
1886
|
+
class ViewTransitioner {
|
1887
|
+
#viewTransitionStarted=false;
|
1888
|
+
#lastOperation=Promise.resolve();
|
1889
|
+
renderChange(useViewTransition, render) {
|
1890
|
+
if (useViewTransition && this.viewTransitionsAvailable && !this.#viewTransitionStarted) {
|
1891
|
+
this.#viewTransitionStarted = true;
|
1892
|
+
this.#lastOperation = this.#lastOperation.then((async () => {
|
1893
|
+
await document.startViewTransition(render).finished;
|
1894
|
+
}));
|
1895
|
+
} else {
|
1896
|
+
this.#lastOperation = this.#lastOperation.then(render);
|
1897
|
+
}
|
1898
|
+
return this.#lastOperation;
|
1899
|
+
}
|
1900
|
+
get viewTransitionsAvailable() {
|
1901
|
+
return document.startViewTransition;
|
1902
|
+
}
|
1903
|
+
}
|
1723
1904
|
|
1724
1905
|
const defaultOptions = {
|
1725
1906
|
action: "advance",
|
@@ -1731,29 +1912,52 @@ const defaultOptions = {
|
|
1731
1912
|
acceptsStreamResponse: false
|
1732
1913
|
};
|
1733
1914
|
|
1734
|
-
|
1915
|
+
const TimingMetric = {
|
1916
|
+
visitStart: "visitStart",
|
1917
|
+
requestStart: "requestStart",
|
1918
|
+
requestEnd: "requestEnd",
|
1919
|
+
visitEnd: "visitEnd"
|
1920
|
+
};
|
1921
|
+
|
1922
|
+
const VisitState = {
|
1923
|
+
initialized: "initialized",
|
1924
|
+
started: "started",
|
1925
|
+
canceled: "canceled",
|
1926
|
+
failed: "failed",
|
1927
|
+
completed: "completed"
|
1928
|
+
};
|
1929
|
+
|
1930
|
+
const SystemStatusCode = {
|
1931
|
+
networkFailure: 0,
|
1932
|
+
timeoutFailure: -1,
|
1933
|
+
contentTypeMismatch: -2
|
1934
|
+
};
|
1735
1935
|
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
1739
|
-
|
1740
|
-
}
|
1936
|
+
const Direction = {
|
1937
|
+
advance: "forward",
|
1938
|
+
restore: "back",
|
1939
|
+
replace: "none"
|
1940
|
+
};
|
1741
1941
|
|
1742
1942
|
class Visit {
|
1943
|
+
identifier=uuid();
|
1944
|
+
timingMetrics={};
|
1945
|
+
followedRedirect=false;
|
1946
|
+
historyChanged=false;
|
1947
|
+
scrolled=false;
|
1948
|
+
shouldCacheSnapshot=true;
|
1949
|
+
acceptsStreamResponse=false;
|
1950
|
+
snapshotCached=false;
|
1951
|
+
state=VisitState.initialized;
|
1952
|
+
viewTransitioner=new ViewTransitioner;
|
1743
1953
|
constructor(delegate, location, restorationIdentifier, options = {}) {
|
1744
|
-
this.identifier = uuid();
|
1745
|
-
this.timingMetrics = {};
|
1746
|
-
this.followedRedirect = false;
|
1747
|
-
this.historyChanged = false;
|
1748
|
-
this.scrolled = false;
|
1749
|
-
this.shouldCacheSnapshot = true;
|
1750
|
-
this.acceptsStreamResponse = false;
|
1751
|
-
this.snapshotCached = false;
|
1752
|
-
this.state = VisitState.initialized;
|
1753
1954
|
this.delegate = delegate;
|
1754
1955
|
this.location = location;
|
1755
1956
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
1756
|
-
const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} =
|
1957
|
+
const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse, direction: direction} = {
|
1958
|
+
...defaultOptions,
|
1959
|
+
...options
|
1960
|
+
};
|
1757
1961
|
this.action = action;
|
1758
1962
|
this.historyChanged = historyChanged;
|
1759
1963
|
this.referrer = referrer;
|
@@ -1761,12 +1965,14 @@ class Visit {
|
|
1761
1965
|
this.snapshotHTML = snapshotHTML;
|
1762
1966
|
this.response = response;
|
1763
1967
|
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
1968
|
+
this.isPageRefresh = this.view.isPageRefresh(this);
|
1764
1969
|
this.visitCachedSnapshot = visitCachedSnapshot;
|
1765
1970
|
this.willRender = willRender;
|
1766
1971
|
this.updateHistory = updateHistory;
|
1767
1972
|
this.scrolled = !willRender;
|
1768
1973
|
this.shouldCacheSnapshot = shouldCacheSnapshot;
|
1769
1974
|
this.acceptsStreamResponse = acceptsStreamResponse;
|
1975
|
+
this.direction = direction || Direction[action];
|
1770
1976
|
}
|
1771
1977
|
get adapter() {
|
1772
1978
|
return this.delegate.adapter;
|
@@ -1803,10 +2009,10 @@ class Visit {
|
|
1803
2009
|
complete() {
|
1804
2010
|
if (this.state == VisitState.started) {
|
1805
2011
|
this.recordTimingMetric(TimingMetric.visitEnd);
|
2012
|
+
this.adapter.visitCompleted(this);
|
1806
2013
|
this.state = VisitState.completed;
|
1807
2014
|
this.followRedirect();
|
1808
2015
|
if (!this.followedRedirect) {
|
1809
|
-
this.adapter.visitCompleted(this);
|
1810
2016
|
this.delegate.visitCompleted(this);
|
1811
2017
|
}
|
1812
2018
|
}
|
@@ -1815,12 +2021,12 @@ class Visit {
|
|
1815
2021
|
if (this.state == VisitState.started) {
|
1816
2022
|
this.state = VisitState.failed;
|
1817
2023
|
this.adapter.visitFailed(this);
|
2024
|
+
this.delegate.visitCompleted(this);
|
1818
2025
|
}
|
1819
2026
|
}
|
1820
2027
|
changeHistory() {
|
1821
|
-
var _a;
|
1822
2028
|
if (!this.historyChanged && this.updateHistory) {
|
1823
|
-
const actionForHistory = this.location.href ===
|
2029
|
+
const actionForHistory = this.location.href === this.referrer?.href ? "replace" : this.action;
|
1824
2030
|
const method = getHistoryMethodForAction(actionForHistory);
|
1825
2031
|
this.history.update(method, this.location, this.restorationIdentifier);
|
1826
2032
|
this.historyChanged = true;
|
@@ -1867,8 +2073,8 @@ class Visit {
|
|
1867
2073
|
if (this.shouldCacheSnapshot) this.cacheSnapshot();
|
1868
2074
|
if (this.view.renderPromise) await this.view.renderPromise;
|
1869
2075
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
1870
|
-
|
1871
|
-
this.
|
2076
|
+
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
2077
|
+
await this.renderPageSnapshot(snapshot, false);
|
1872
2078
|
this.adapter.visitRendered(this);
|
1873
2079
|
this.complete();
|
1874
2080
|
} else {
|
@@ -1901,12 +2107,11 @@ class Visit {
|
|
1901
2107
|
const isPreview = this.shouldIssueRequest();
|
1902
2108
|
this.render((async () => {
|
1903
2109
|
this.cacheSnapshot();
|
1904
|
-
if (this.isSamePage) {
|
2110
|
+
if (this.isSamePage || this.isPageRefresh) {
|
1905
2111
|
this.adapter.visitRendered(this);
|
1906
2112
|
} else {
|
1907
2113
|
if (this.view.renderPromise) await this.view.renderPromise;
|
1908
|
-
await this.
|
1909
|
-
this.performScroll();
|
2114
|
+
await this.renderPageSnapshot(snapshot, isPreview);
|
1910
2115
|
this.adapter.visitRendered(this);
|
1911
2116
|
if (!isPreview) {
|
1912
2117
|
this.complete();
|
@@ -1916,8 +2121,7 @@ class Visit {
|
|
1916
2121
|
}
|
1917
2122
|
}
|
1918
2123
|
followRedirect() {
|
1919
|
-
|
1920
|
-
if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
|
2124
|
+
if (this.redirectedToLocation && !this.followedRedirect && this.response?.redirected) {
|
1921
2125
|
this.adapter.visitProposedToLocation(this.redirectedToLocation, {
|
1922
2126
|
action: "replace",
|
1923
2127
|
response: this.response,
|
@@ -1989,7 +2193,7 @@ class Visit {
|
|
1989
2193
|
this.finishRequest();
|
1990
2194
|
}
|
1991
2195
|
performScroll() {
|
1992
|
-
if (!this.scrolled && !this.view.forceReloaded) {
|
2196
|
+
if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) {
|
1993
2197
|
if (this.action == "restore") {
|
1994
2198
|
this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
|
1995
2199
|
} else {
|
@@ -2019,7 +2223,9 @@ class Visit {
|
|
2019
2223
|
this.timingMetrics[metric] = (new Date).getTime();
|
2020
2224
|
}
|
2021
2225
|
getTimingMetrics() {
|
2022
|
-
return
|
2226
|
+
return {
|
2227
|
+
...this.timingMetrics
|
2228
|
+
};
|
2023
2229
|
}
|
2024
2230
|
getHistoryMethodForAction(action) {
|
2025
2231
|
switch (action) {
|
@@ -2051,12 +2257,16 @@ class Visit {
|
|
2051
2257
|
}
|
2052
2258
|
async render(callback) {
|
2053
2259
|
this.cancelRender();
|
2054
|
-
await
|
2055
|
-
this.frame = requestAnimationFrame((() => resolve()));
|
2056
|
-
}));
|
2260
|
+
this.frame = await nextRepaint();
|
2057
2261
|
await callback();
|
2058
2262
|
delete this.frame;
|
2059
2263
|
}
|
2264
|
+
async renderPageSnapshot(snapshot, isPreview) {
|
2265
|
+
await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(snapshot), (async () => {
|
2266
|
+
await this.view.renderPage(snapshot, isPreview, this.willRender, this);
|
2267
|
+
this.performScroll();
|
2268
|
+
}));
|
2269
|
+
}
|
2060
2270
|
cancelRender() {
|
2061
2271
|
if (this.frame) {
|
2062
2272
|
cancelAnimationFrame(this.frame);
|
@@ -2070,15 +2280,16 @@ function isSuccessful(statusCode) {
|
|
2070
2280
|
}
|
2071
2281
|
|
2072
2282
|
class BrowserAdapter {
|
2283
|
+
progressBar=new ProgressBar;
|
2073
2284
|
constructor(session) {
|
2074
|
-
this.progressBar = new ProgressBar;
|
2075
|
-
this.showProgressBar = () => {
|
2076
|
-
this.progressBar.show();
|
2077
|
-
};
|
2078
2285
|
this.session = session;
|
2079
2286
|
}
|
2080
2287
|
visitProposedToLocation(location, options) {
|
2081
|
-
|
2288
|
+
if (locationIsVisitable(location, this.navigator.rootLocation)) {
|
2289
|
+
this.navigator.startVisit(location, options?.restorationIdentifier || uuid(), options);
|
2290
|
+
} else {
|
2291
|
+
window.location.href = location.toString();
|
2292
|
+
}
|
2082
2293
|
}
|
2083
2294
|
visitStarted(visit) {
|
2084
2295
|
this.location = visit.location;
|
@@ -2113,17 +2324,20 @@ class BrowserAdapter {
|
|
2113
2324
|
return visit.loadResponse();
|
2114
2325
|
}
|
2115
2326
|
}
|
2116
|
-
visitRequestFinished(_visit) {
|
2327
|
+
visitRequestFinished(_visit) {}
|
2328
|
+
visitCompleted(_visit) {
|
2117
2329
|
this.progressBar.setValue(1);
|
2118
2330
|
this.hideVisitProgressBar();
|
2119
2331
|
}
|
2120
|
-
visitCompleted(_visit) {}
|
2121
2332
|
pageInvalidated(reason) {
|
2122
2333
|
this.reload(reason);
|
2123
2334
|
}
|
2124
|
-
visitFailed(_visit) {
|
2125
|
-
|
2126
|
-
|
2335
|
+
visitFailed(_visit) {
|
2336
|
+
this.progressBar.setValue(1);
|
2337
|
+
this.hideVisitProgressBar();
|
2338
|
+
}
|
2339
|
+
visitRendered(_visit) {}
|
2340
|
+
formSubmissionStarted(_formSubmission) {
|
2127
2341
|
this.progressBar.setValue(0);
|
2128
2342
|
this.showFormProgressBarAfterDelay();
|
2129
2343
|
}
|
@@ -2153,12 +2367,14 @@ class BrowserAdapter {
|
|
2153
2367
|
delete this.formProgressBarTimeout;
|
2154
2368
|
}
|
2155
2369
|
}
|
2370
|
+
showProgressBar=() => {
|
2371
|
+
this.progressBar.show();
|
2372
|
+
};
|
2156
2373
|
reload(reason) {
|
2157
|
-
var _a;
|
2158
2374
|
dispatch("turbo:reload", {
|
2159
2375
|
detail: reason
|
2160
2376
|
});
|
2161
|
-
window.location.href =
|
2377
|
+
window.location.href = this.location?.toString() || window.location.href;
|
2162
2378
|
}
|
2163
2379
|
get navigator() {
|
2164
2380
|
return this.session.navigator;
|
@@ -2166,16 +2382,9 @@ class BrowserAdapter {
|
|
2166
2382
|
}
|
2167
2383
|
|
2168
2384
|
class CacheObserver {
|
2169
|
-
|
2170
|
-
|
2171
|
-
|
2172
|
-
this.started = false;
|
2173
|
-
this.removeTemporaryElements = _event => {
|
2174
|
-
for (const element of this.temporaryElements) {
|
2175
|
-
element.remove();
|
2176
|
-
}
|
2177
|
-
};
|
2178
|
-
}
|
2385
|
+
selector="[data-turbo-temporary]";
|
2386
|
+
deprecatedSelector="[data-turbo-cache=false]";
|
2387
|
+
started=false;
|
2179
2388
|
start() {
|
2180
2389
|
if (!this.started) {
|
2181
2390
|
this.started = true;
|
@@ -2188,6 +2397,11 @@ class CacheObserver {
|
|
2188
2397
|
removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
2189
2398
|
}
|
2190
2399
|
}
|
2400
|
+
removeTemporaryElements=_event => {
|
2401
|
+
for (const element of this.temporaryElements) {
|
2402
|
+
element.remove();
|
2403
|
+
}
|
2404
|
+
};
|
2191
2405
|
get temporaryElements() {
|
2192
2406
|
return [ ...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation ];
|
2193
2407
|
}
|
@@ -2216,41 +2430,40 @@ class FrameRedirector {
|
|
2216
2430
|
this.formSubmitObserver.stop();
|
2217
2431
|
}
|
2218
2432
|
shouldInterceptLinkClick(element, _location, _event) {
|
2219
|
-
return this
|
2433
|
+
return this.#shouldRedirect(element);
|
2220
2434
|
}
|
2221
2435
|
linkClickIntercepted(element, url, event) {
|
2222
|
-
const frame = this
|
2436
|
+
const frame = this.#findFrameElement(element);
|
2223
2437
|
if (frame) {
|
2224
2438
|
frame.delegate.linkClickIntercepted(element, url, event);
|
2225
2439
|
}
|
2226
2440
|
}
|
2227
2441
|
willSubmitForm(element, submitter) {
|
2228
|
-
return element.closest("turbo-frame") == null && this
|
2442
|
+
return element.closest("turbo-frame") == null && this.#shouldSubmit(element, submitter) && this.#shouldRedirect(element, submitter);
|
2229
2443
|
}
|
2230
2444
|
formSubmitted(element, submitter) {
|
2231
|
-
const frame = this
|
2445
|
+
const frame = this.#findFrameElement(element, submitter);
|
2232
2446
|
if (frame) {
|
2233
2447
|
frame.delegate.formSubmitted(element, submitter);
|
2234
2448
|
}
|
2235
2449
|
}
|
2236
|
-
shouldSubmit(form, submitter) {
|
2237
|
-
|
2238
|
-
const action = getAction(form, submitter);
|
2450
|
+
#shouldSubmit(form, submitter) {
|
2451
|
+
const action = getAction$1(form, submitter);
|
2239
2452
|
const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
|
2240
|
-
const rootLocation = expandURL(
|
2241
|
-
return this
|
2453
|
+
const rootLocation = expandURL(meta?.content ?? "/");
|
2454
|
+
return this.#shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
|
2242
2455
|
}
|
2243
|
-
shouldRedirect(element, submitter) {
|
2456
|
+
#shouldRedirect(element, submitter) {
|
2244
2457
|
const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
|
2245
2458
|
if (isNavigatable) {
|
2246
|
-
const frame = this
|
2459
|
+
const frame = this.#findFrameElement(element, submitter);
|
2247
2460
|
return frame ? frame != element.closest("turbo-frame") : false;
|
2248
2461
|
} else {
|
2249
2462
|
return false;
|
2250
2463
|
}
|
2251
2464
|
}
|
2252
|
-
findFrameElement(element, submitter) {
|
2253
|
-
const id =
|
2465
|
+
#findFrameElement(element, submitter) {
|
2466
|
+
const id = submitter?.getAttribute("data-turbo-frame") || element.getAttribute("data-turbo-frame");
|
2254
2467
|
if (id && id != "_top") {
|
2255
2468
|
const frame = this.element.querySelector(`#${id}:not([disabled])`);
|
2256
2469
|
if (frame instanceof FrameElement) {
|
@@ -2261,32 +2474,20 @@ class FrameRedirector {
|
|
2261
2474
|
}
|
2262
2475
|
|
2263
2476
|
class History {
|
2477
|
+
location;
|
2478
|
+
restorationIdentifier=uuid();
|
2479
|
+
restorationData={};
|
2480
|
+
started=false;
|
2481
|
+
pageLoaded=false;
|
2482
|
+
currentIndex=0;
|
2264
2483
|
constructor(delegate) {
|
2265
|
-
this.restorationIdentifier = uuid();
|
2266
|
-
this.restorationData = {};
|
2267
|
-
this.started = false;
|
2268
|
-
this.pageLoaded = false;
|
2269
|
-
this.onPopState = event => {
|
2270
|
-
if (this.shouldHandlePopState()) {
|
2271
|
-
const {turbo: turbo} = event.state || {};
|
2272
|
-
if (turbo) {
|
2273
|
-
this.location = new URL(window.location.href);
|
2274
|
-
const {restorationIdentifier: restorationIdentifier} = turbo;
|
2275
|
-
this.restorationIdentifier = restorationIdentifier;
|
2276
|
-
this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
|
2277
|
-
}
|
2278
|
-
}
|
2279
|
-
};
|
2280
|
-
this.onPageLoad = async _event => {
|
2281
|
-
await nextMicrotask();
|
2282
|
-
this.pageLoaded = true;
|
2283
|
-
};
|
2284
2484
|
this.delegate = delegate;
|
2285
2485
|
}
|
2286
2486
|
start() {
|
2287
2487
|
if (!this.started) {
|
2288
2488
|
addEventListener("popstate", this.onPopState, false);
|
2289
2489
|
addEventListener("load", this.onPageLoad, false);
|
2490
|
+
this.currentIndex = history.state?.turbo?.restorationIndex || 0;
|
2290
2491
|
this.started = true;
|
2291
2492
|
this.replace(new URL(window.location.href));
|
2292
2493
|
}
|
@@ -2305,9 +2506,11 @@ class History {
|
|
2305
2506
|
this.update(history.replaceState, location, restorationIdentifier);
|
2306
2507
|
}
|
2307
2508
|
update(method, location, restorationIdentifier = uuid()) {
|
2509
|
+
if (method === history.pushState) ++this.currentIndex;
|
2308
2510
|
const state = {
|
2309
2511
|
turbo: {
|
2310
|
-
restorationIdentifier: restorationIdentifier
|
2512
|
+
restorationIdentifier: restorationIdentifier,
|
2513
|
+
restorationIndex: this.currentIndex
|
2311
2514
|
}
|
2312
2515
|
};
|
2313
2516
|
method.call(history, state, "", location.href);
|
@@ -2320,12 +2523,14 @@ class History {
|
|
2320
2523
|
updateRestorationData(additionalData) {
|
2321
2524
|
const {restorationIdentifier: restorationIdentifier} = this;
|
2322
2525
|
const restorationData = this.restorationData[restorationIdentifier];
|
2323
|
-
this.restorationData[restorationIdentifier] =
|
2526
|
+
this.restorationData[restorationIdentifier] = {
|
2527
|
+
...restorationData,
|
2528
|
+
...additionalData
|
2529
|
+
};
|
2324
2530
|
}
|
2325
2531
|
assumeControlOfScrollRestoration() {
|
2326
|
-
var _a;
|
2327
2532
|
if (!this.previousScrollRestoration) {
|
2328
|
-
this.previousScrollRestoration =
|
2533
|
+
this.previousScrollRestoration = history.scrollRestoration ?? "auto";
|
2329
2534
|
history.scrollRestoration = "manual";
|
2330
2535
|
}
|
2331
2536
|
}
|
@@ -2335,6 +2540,23 @@ class History {
|
|
2335
2540
|
delete this.previousScrollRestoration;
|
2336
2541
|
}
|
2337
2542
|
}
|
2543
|
+
onPopState=event => {
|
2544
|
+
if (this.shouldHandlePopState()) {
|
2545
|
+
const {turbo: turbo} = event.state || {};
|
2546
|
+
if (turbo) {
|
2547
|
+
this.location = new URL(window.location.href);
|
2548
|
+
const {restorationIdentifier: restorationIdentifier, restorationIndex: restorationIndex} = turbo;
|
2549
|
+
this.restorationIdentifier = restorationIdentifier;
|
2550
|
+
const direction = restorationIndex > this.currentIndex ? "forward" : "back";
|
2551
|
+
this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
|
2552
|
+
this.currentIndex = restorationIndex;
|
2553
|
+
}
|
2554
|
+
}
|
2555
|
+
};
|
2556
|
+
onPageLoad=async _event => {
|
2557
|
+
await nextMicrotask();
|
2558
|
+
this.pageLoaded = true;
|
2559
|
+
};
|
2338
2560
|
shouldHandlePopState() {
|
2339
2561
|
return this.pageIsLoaded();
|
2340
2562
|
}
|
@@ -2343,24 +2565,154 @@ class History {
|
|
2343
2565
|
}
|
2344
2566
|
}
|
2345
2567
|
|
2568
|
+
class LinkPrefetchObserver {
|
2569
|
+
started=false;
|
2570
|
+
#prefetchedLink=null;
|
2571
|
+
constructor(delegate, eventTarget) {
|
2572
|
+
this.delegate = delegate;
|
2573
|
+
this.eventTarget = eventTarget;
|
2574
|
+
}
|
2575
|
+
start() {
|
2576
|
+
if (this.started) return;
|
2577
|
+
if (this.eventTarget.readyState === "loading") {
|
2578
|
+
this.eventTarget.addEventListener("DOMContentLoaded", this.#enable, {
|
2579
|
+
once: true
|
2580
|
+
});
|
2581
|
+
} else {
|
2582
|
+
this.#enable();
|
2583
|
+
}
|
2584
|
+
}
|
2585
|
+
stop() {
|
2586
|
+
if (!this.started) return;
|
2587
|
+
this.eventTarget.removeEventListener("mouseenter", this.#tryToPrefetchRequest, {
|
2588
|
+
capture: true,
|
2589
|
+
passive: true
|
2590
|
+
});
|
2591
|
+
this.eventTarget.removeEventListener("mouseleave", this.#cancelRequestIfObsolete, {
|
2592
|
+
capture: true,
|
2593
|
+
passive: true
|
2594
|
+
});
|
2595
|
+
this.eventTarget.removeEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
|
2596
|
+
this.started = false;
|
2597
|
+
}
|
2598
|
+
#enable=() => {
|
2599
|
+
this.eventTarget.addEventListener("mouseenter", this.#tryToPrefetchRequest, {
|
2600
|
+
capture: true,
|
2601
|
+
passive: true
|
2602
|
+
});
|
2603
|
+
this.eventTarget.addEventListener("mouseleave", this.#cancelRequestIfObsolete, {
|
2604
|
+
capture: true,
|
2605
|
+
passive: true
|
2606
|
+
});
|
2607
|
+
this.eventTarget.addEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
|
2608
|
+
this.started = true;
|
2609
|
+
};
|
2610
|
+
#tryToPrefetchRequest=event => {
|
2611
|
+
if (getMetaContent("turbo-prefetch") === "false") return;
|
2612
|
+
const target = event.target;
|
2613
|
+
const isLink = target.matches && target.matches("a[href]:not([target^=_]):not([download])");
|
2614
|
+
if (isLink && this.#isPrefetchable(target)) {
|
2615
|
+
const link = target;
|
2616
|
+
const location = getLocationForLink(link);
|
2617
|
+
if (this.delegate.canPrefetchRequestToLocation(link, location)) {
|
2618
|
+
this.#prefetchedLink = link;
|
2619
|
+
const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, target);
|
2620
|
+
prefetchCache.setLater(location.toString(), fetchRequest, this.#cacheTtl);
|
2621
|
+
}
|
2622
|
+
}
|
2623
|
+
};
|
2624
|
+
#cancelRequestIfObsolete=event => {
|
2625
|
+
if (event.target === this.#prefetchedLink) this.#cancelPrefetchRequest();
|
2626
|
+
};
|
2627
|
+
#cancelPrefetchRequest=() => {
|
2628
|
+
prefetchCache.clear();
|
2629
|
+
this.#prefetchedLink = null;
|
2630
|
+
};
|
2631
|
+
#tryToUsePrefetchedRequest=event => {
|
2632
|
+
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "get") {
|
2633
|
+
const cached = prefetchCache.get(event.detail.url.toString());
|
2634
|
+
if (cached) {
|
2635
|
+
event.detail.fetchRequest = cached;
|
2636
|
+
}
|
2637
|
+
prefetchCache.clear();
|
2638
|
+
}
|
2639
|
+
};
|
2640
|
+
prepareRequest(request) {
|
2641
|
+
const link = request.target;
|
2642
|
+
request.headers["X-Sec-Purpose"] = "prefetch";
|
2643
|
+
const turboFrame = link.closest("turbo-frame");
|
2644
|
+
const turboFrameTarget = link.getAttribute("data-turbo-frame") || turboFrame?.getAttribute("target") || turboFrame?.id;
|
2645
|
+
if (turboFrameTarget && turboFrameTarget !== "_top") {
|
2646
|
+
request.headers["Turbo-Frame"] = turboFrameTarget;
|
2647
|
+
}
|
2648
|
+
}
|
2649
|
+
requestSucceededWithResponse() {}
|
2650
|
+
requestStarted(fetchRequest) {}
|
2651
|
+
requestErrored(fetchRequest) {}
|
2652
|
+
requestFinished(fetchRequest) {}
|
2653
|
+
requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
|
2654
|
+
requestFailedWithResponse(fetchRequest, fetchResponse) {}
|
2655
|
+
get #cacheTtl() {
|
2656
|
+
return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl;
|
2657
|
+
}
|
2658
|
+
#isPrefetchable(link) {
|
2659
|
+
const href = link.getAttribute("href");
|
2660
|
+
if (!href) return false;
|
2661
|
+
if (unfetchableLink(link)) return false;
|
2662
|
+
if (linkToTheSamePage(link)) return false;
|
2663
|
+
if (linkOptsOut(link)) return false;
|
2664
|
+
if (nonSafeLink(link)) return false;
|
2665
|
+
if (eventPrevented(link)) return false;
|
2666
|
+
return true;
|
2667
|
+
}
|
2668
|
+
}
|
2669
|
+
|
2670
|
+
const unfetchableLink = link => link.origin !== document.location.origin || ![ "http:", "https:" ].includes(link.protocol) || link.hasAttribute("target");
|
2671
|
+
|
2672
|
+
const linkToTheSamePage = link => link.pathname + link.search === document.location.pathname + document.location.search || link.href.startsWith("#");
|
2673
|
+
|
2674
|
+
const linkOptsOut = link => {
|
2675
|
+
if (link.getAttribute("data-turbo-prefetch") === "false") return true;
|
2676
|
+
if (link.getAttribute("data-turbo") === "false") return true;
|
2677
|
+
const turboPrefetchParent = findClosestRecursively(link, "[data-turbo-prefetch]");
|
2678
|
+
if (turboPrefetchParent && turboPrefetchParent.getAttribute("data-turbo-prefetch") === "false") return true;
|
2679
|
+
return false;
|
2680
|
+
};
|
2681
|
+
|
2682
|
+
const nonSafeLink = link => {
|
2683
|
+
const turboMethod = link.getAttribute("data-turbo-method");
|
2684
|
+
if (turboMethod && turboMethod.toLowerCase() !== "get") return true;
|
2685
|
+
if (isUJS(link)) return true;
|
2686
|
+
if (link.hasAttribute("data-turbo-confirm")) return true;
|
2687
|
+
if (link.hasAttribute("data-turbo-stream")) return true;
|
2688
|
+
return false;
|
2689
|
+
};
|
2690
|
+
|
2691
|
+
const isUJS = link => link.hasAttribute("data-remote") || link.hasAttribute("data-behavior") || link.hasAttribute("data-confirm") || link.hasAttribute("data-method");
|
2692
|
+
|
2693
|
+
const eventPrevented = link => {
|
2694
|
+
const event = dispatch("turbo:before-prefetch", {
|
2695
|
+
target: link,
|
2696
|
+
cancelable: true
|
2697
|
+
});
|
2698
|
+
return event.defaultPrevented;
|
2699
|
+
};
|
2700
|
+
|
2346
2701
|
class Navigator {
|
2347
2702
|
constructor(delegate) {
|
2348
2703
|
this.delegate = delegate;
|
2349
2704
|
}
|
2350
2705
|
proposeVisit(location, options = {}) {
|
2351
2706
|
if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
|
2352
|
-
|
2353
|
-
this.delegate.visitProposedToLocation(location, options);
|
2354
|
-
} else {
|
2355
|
-
window.location.href = location.toString();
|
2356
|
-
}
|
2707
|
+
this.delegate.visitProposedToLocation(location, options);
|
2357
2708
|
}
|
2358
2709
|
}
|
2359
2710
|
startVisit(locatable, restorationIdentifier, options = {}) {
|
2360
2711
|
this.stop();
|
2361
|
-
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier,
|
2362
|
-
referrer: this.location
|
2363
|
-
|
2712
|
+
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, {
|
2713
|
+
referrer: this.location,
|
2714
|
+
...options
|
2715
|
+
});
|
2364
2716
|
this.currentVisit.start();
|
2365
2717
|
}
|
2366
2718
|
submitForm(form, submitter) {
|
@@ -2384,6 +2736,9 @@ class Navigator {
|
|
2384
2736
|
get view() {
|
2385
2737
|
return this.delegate.view;
|
2386
2738
|
}
|
2739
|
+
get rootLocation() {
|
2740
|
+
return this.view.snapshot.rootLocation;
|
2741
|
+
}
|
2387
2742
|
get history() {
|
2388
2743
|
return this.delegate.history;
|
2389
2744
|
}
|
@@ -2401,7 +2756,7 @@ class Navigator {
|
|
2401
2756
|
this.view.clearSnapshotCache();
|
2402
2757
|
}
|
2403
2758
|
const {statusCode: statusCode, redirected: redirected} = fetchResponse;
|
2404
|
-
const action = this
|
2759
|
+
const action = this.#getActionForFormSubmission(formSubmission, fetchResponse);
|
2405
2760
|
const visitOptions = {
|
2406
2761
|
action: action,
|
2407
2762
|
shouldCacheSnapshot: shouldCacheSnapshot,
|
@@ -2424,7 +2779,9 @@ class Navigator {
|
|
2424
2779
|
} else {
|
2425
2780
|
await this.view.renderPage(snapshot, false, true, this.currentVisit);
|
2426
2781
|
}
|
2427
|
-
|
2782
|
+
if (!snapshot.shouldPreserveScrollPosition) {
|
2783
|
+
this.view.scrollToTop();
|
2784
|
+
}
|
2428
2785
|
this.view.clearSnapshotCache();
|
2429
2786
|
}
|
2430
2787
|
}
|
@@ -2457,35 +2814,27 @@ class Navigator {
|
|
2457
2814
|
get restorationIdentifier() {
|
2458
2815
|
return this.history.restorationIdentifier;
|
2459
2816
|
}
|
2460
|
-
getActionForFormSubmission(
|
2461
|
-
|
2817
|
+
#getActionForFormSubmission(formSubmission, fetchResponse) {
|
2818
|
+
const {submitter: submitter, formElement: formElement} = formSubmission;
|
2819
|
+
return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse);
|
2820
|
+
}
|
2821
|
+
#getDefaultAction(fetchResponse) {
|
2822
|
+
const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;
|
2823
|
+
return sameLocationRedirect ? "replace" : "advance";
|
2462
2824
|
}
|
2463
2825
|
}
|
2464
2826
|
|
2465
|
-
|
2466
|
-
|
2467
|
-
|
2468
|
-
|
2469
|
-
|
2470
|
-
|
2471
|
-
PageStage[PageStage["complete"] = 3] = "complete";
|
2472
|
-
})(PageStage || (PageStage = {}));
|
2827
|
+
const PageStage = {
|
2828
|
+
initial: 0,
|
2829
|
+
loading: 1,
|
2830
|
+
interactive: 2,
|
2831
|
+
complete: 3
|
2832
|
+
};
|
2473
2833
|
|
2474
2834
|
class PageObserver {
|
2835
|
+
stage=PageStage.initial;
|
2836
|
+
started=false;
|
2475
2837
|
constructor(delegate) {
|
2476
|
-
this.stage = PageStage.initial;
|
2477
|
-
this.started = false;
|
2478
|
-
this.interpretReadyState = () => {
|
2479
|
-
const {readyState: readyState} = this;
|
2480
|
-
if (readyState == "interactive") {
|
2481
|
-
this.pageIsInteractive();
|
2482
|
-
} else if (readyState == "complete") {
|
2483
|
-
this.pageIsComplete();
|
2484
|
-
}
|
2485
|
-
};
|
2486
|
-
this.pageWillUnload = () => {
|
2487
|
-
this.delegate.pageWillUnload();
|
2488
|
-
};
|
2489
2838
|
this.delegate = delegate;
|
2490
2839
|
}
|
2491
2840
|
start() {
|
@@ -2505,6 +2854,14 @@ class PageObserver {
|
|
2505
2854
|
this.started = false;
|
2506
2855
|
}
|
2507
2856
|
}
|
2857
|
+
interpretReadyState=() => {
|
2858
|
+
const {readyState: readyState} = this;
|
2859
|
+
if (readyState == "interactive") {
|
2860
|
+
this.pageIsInteractive();
|
2861
|
+
} else if (readyState == "complete") {
|
2862
|
+
this.pageIsComplete();
|
2863
|
+
}
|
2864
|
+
};
|
2508
2865
|
pageIsInteractive() {
|
2509
2866
|
if (this.stage == PageStage.loading) {
|
2510
2867
|
this.stage = PageStage.interactive;
|
@@ -2518,20 +2875,17 @@ class PageObserver {
|
|
2518
2875
|
this.delegate.pageLoaded();
|
2519
2876
|
}
|
2520
2877
|
}
|
2878
|
+
pageWillUnload=() => {
|
2879
|
+
this.delegate.pageWillUnload();
|
2880
|
+
};
|
2521
2881
|
get readyState() {
|
2522
2882
|
return document.readyState;
|
2523
2883
|
}
|
2524
2884
|
}
|
2525
2885
|
|
2526
2886
|
class ScrollObserver {
|
2887
|
+
started=false;
|
2527
2888
|
constructor(delegate) {
|
2528
|
-
this.started = false;
|
2529
|
-
this.onScroll = () => {
|
2530
|
-
this.updatePosition({
|
2531
|
-
x: window.pageXOffset,
|
2532
|
-
y: window.pageYOffset
|
2533
|
-
});
|
2534
|
-
};
|
2535
2889
|
this.delegate = delegate;
|
2536
2890
|
}
|
2537
2891
|
start() {
|
@@ -2547,6 +2901,12 @@ class ScrollObserver {
|
|
2547
2901
|
this.started = false;
|
2548
2902
|
}
|
2549
2903
|
}
|
2904
|
+
onScroll=() => {
|
2905
|
+
this.updatePosition({
|
2906
|
+
x: window.pageXOffset,
|
2907
|
+
y: window.pageYOffset
|
2908
|
+
});
|
2909
|
+
};
|
2550
2910
|
updatePosition(position) {
|
2551
2911
|
this.delegate.scrollPositionChanged(position);
|
2552
2912
|
}
|
@@ -2554,7 +2914,13 @@ class ScrollObserver {
|
|
2554
2914
|
|
2555
2915
|
class StreamMessageRenderer {
|
2556
2916
|
render({fragment: fragment}) {
|
2557
|
-
Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() =>
|
2917
|
+
Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => {
|
2918
|
+
withAutofocusFromFragment(fragment, (() => {
|
2919
|
+
withPreservedFocus((() => {
|
2920
|
+
document.documentElement.appendChild(fragment);
|
2921
|
+
}));
|
2922
|
+
}));
|
2923
|
+
}));
|
2558
2924
|
}
|
2559
2925
|
enteringBardo(currentPermanentElement, newPermanentElement) {
|
2560
2926
|
newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
|
@@ -2577,33 +2943,67 @@ function getPermanentElementMapForFragment(fragment) {
|
|
2577
2943
|
return permanentElementMap;
|
2578
2944
|
}
|
2579
2945
|
|
2946
|
+
async function withAutofocusFromFragment(fragment, callback) {
|
2947
|
+
const generatedID = `turbo-stream-autofocus-${uuid()}`;
|
2948
|
+
const turboStreams = fragment.querySelectorAll("turbo-stream");
|
2949
|
+
const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
|
2950
|
+
let willAutofocusId = null;
|
2951
|
+
if (elementWithAutofocus) {
|
2952
|
+
if (elementWithAutofocus.id) {
|
2953
|
+
willAutofocusId = elementWithAutofocus.id;
|
2954
|
+
} else {
|
2955
|
+
willAutofocusId = generatedID;
|
2956
|
+
}
|
2957
|
+
elementWithAutofocus.id = willAutofocusId;
|
2958
|
+
}
|
2959
|
+
callback();
|
2960
|
+
await nextRepaint();
|
2961
|
+
const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
|
2962
|
+
if (hasNoActiveElement && willAutofocusId) {
|
2963
|
+
const elementToAutofocus = document.getElementById(willAutofocusId);
|
2964
|
+
if (elementIsFocusable(elementToAutofocus)) {
|
2965
|
+
elementToAutofocus.focus();
|
2966
|
+
}
|
2967
|
+
if (elementToAutofocus && elementToAutofocus.id == generatedID) {
|
2968
|
+
elementToAutofocus.removeAttribute("id");
|
2969
|
+
}
|
2970
|
+
}
|
2971
|
+
}
|
2972
|
+
|
2973
|
+
async function withPreservedFocus(callback) {
|
2974
|
+
const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, (() => document.activeElement));
|
2975
|
+
const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;
|
2976
|
+
if (restoreFocusTo) {
|
2977
|
+
const elementToFocus = document.getElementById(restoreFocusTo);
|
2978
|
+
if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
|
2979
|
+
elementToFocus.focus();
|
2980
|
+
}
|
2981
|
+
}
|
2982
|
+
}
|
2983
|
+
|
2984
|
+
function firstAutofocusableElementInStreams(nodeListOfStreamElements) {
|
2985
|
+
for (const streamElement of nodeListOfStreamElements) {
|
2986
|
+
const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);
|
2987
|
+
if (elementWithAutofocus) return elementWithAutofocus;
|
2988
|
+
}
|
2989
|
+
return null;
|
2990
|
+
}
|
2991
|
+
|
2580
2992
|
class StreamObserver {
|
2993
|
+
sources=new Set;
|
2994
|
+
#started=false;
|
2581
2995
|
constructor(delegate) {
|
2582
|
-
this.sources = new Set;
|
2583
|
-
this.started = false;
|
2584
|
-
this.inspectFetchResponse = event => {
|
2585
|
-
const response = fetchResponseFromEvent(event);
|
2586
|
-
if (response && fetchResponseIsStream(response)) {
|
2587
|
-
event.preventDefault();
|
2588
|
-
this.receiveMessageResponse(response);
|
2589
|
-
}
|
2590
|
-
};
|
2591
|
-
this.receiveMessageEvent = event => {
|
2592
|
-
if (this.started && typeof event.data == "string") {
|
2593
|
-
this.receiveMessageHTML(event.data);
|
2594
|
-
}
|
2595
|
-
};
|
2596
2996
|
this.delegate = delegate;
|
2597
2997
|
}
|
2598
2998
|
start() {
|
2599
|
-
if (!this
|
2600
|
-
this
|
2999
|
+
if (!this.#started) {
|
3000
|
+
this.#started = true;
|
2601
3001
|
addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
2602
3002
|
}
|
2603
3003
|
}
|
2604
3004
|
stop() {
|
2605
|
-
if (this
|
2606
|
-
this
|
3005
|
+
if (this.#started) {
|
3006
|
+
this.#started = false;
|
2607
3007
|
removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
2608
3008
|
}
|
2609
3009
|
}
|
@@ -2622,6 +3022,18 @@ class StreamObserver {
|
|
2622
3022
|
streamSourceIsConnected(source) {
|
2623
3023
|
return this.sources.has(source);
|
2624
3024
|
}
|
3025
|
+
inspectFetchResponse=event => {
|
3026
|
+
const response = fetchResponseFromEvent(event);
|
3027
|
+
if (response && fetchResponseIsStream(response)) {
|
3028
|
+
event.preventDefault();
|
3029
|
+
this.receiveMessageResponse(response);
|
3030
|
+
}
|
3031
|
+
};
|
3032
|
+
receiveMessageEvent=event => {
|
3033
|
+
if (this.#started && typeof event.data == "string") {
|
3034
|
+
this.receiveMessageHTML(event.data);
|
3035
|
+
}
|
3036
|
+
};
|
2625
3037
|
async receiveMessageResponse(response) {
|
2626
3038
|
const html = await response.responseHTML;
|
2627
3039
|
if (html) {
|
@@ -2634,16 +3046,14 @@ class StreamObserver {
|
|
2634
3046
|
}
|
2635
3047
|
|
2636
3048
|
function fetchResponseFromEvent(event) {
|
2637
|
-
|
2638
|
-
const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse;
|
3049
|
+
const fetchResponse = event.detail?.fetchResponse;
|
2639
3050
|
if (fetchResponse instanceof FetchResponse) {
|
2640
3051
|
return fetchResponse;
|
2641
3052
|
}
|
2642
3053
|
}
|
2643
3054
|
|
2644
3055
|
function fetchResponseIsStream(response) {
|
2645
|
-
|
2646
|
-
const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
|
3056
|
+
const contentType = response.contentType ?? "";
|
2647
3057
|
return contentType.startsWith(StreamMessage.contentType);
|
2648
3058
|
}
|
2649
3059
|
|
@@ -2678,6 +3088,546 @@ class ErrorRenderer extends Renderer {
|
|
2678
3088
|
}
|
2679
3089
|
}
|
2680
3090
|
|
3091
|
+
var Idiomorph = function() {
|
3092
|
+
let EMPTY_SET = new Set;
|
3093
|
+
let defaults = {
|
3094
|
+
morphStyle: "outerHTML",
|
3095
|
+
callbacks: {
|
3096
|
+
beforeNodeAdded: noOp,
|
3097
|
+
afterNodeAdded: noOp,
|
3098
|
+
beforeNodeMorphed: noOp,
|
3099
|
+
afterNodeMorphed: noOp,
|
3100
|
+
beforeNodeRemoved: noOp,
|
3101
|
+
afterNodeRemoved: noOp,
|
3102
|
+
beforeAttributeUpdated: noOp
|
3103
|
+
},
|
3104
|
+
head: {
|
3105
|
+
style: "merge",
|
3106
|
+
shouldPreserve: function(elt) {
|
3107
|
+
return elt.getAttribute("im-preserve") === "true";
|
3108
|
+
},
|
3109
|
+
shouldReAppend: function(elt) {
|
3110
|
+
return elt.getAttribute("im-re-append") === "true";
|
3111
|
+
},
|
3112
|
+
shouldRemove: noOp,
|
3113
|
+
afterHeadMorphed: noOp
|
3114
|
+
}
|
3115
|
+
};
|
3116
|
+
function morph(oldNode, newContent, config = {}) {
|
3117
|
+
if (oldNode instanceof Document) {
|
3118
|
+
oldNode = oldNode.documentElement;
|
3119
|
+
}
|
3120
|
+
if (typeof newContent === "string") {
|
3121
|
+
newContent = parseContent(newContent);
|
3122
|
+
}
|
3123
|
+
let normalizedContent = normalizeContent(newContent);
|
3124
|
+
let ctx = createMorphContext(oldNode, normalizedContent, config);
|
3125
|
+
return morphNormalizedContent(oldNode, normalizedContent, ctx);
|
3126
|
+
}
|
3127
|
+
function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
|
3128
|
+
if (ctx.head.block) {
|
3129
|
+
let oldHead = oldNode.querySelector("head");
|
3130
|
+
let newHead = normalizedNewContent.querySelector("head");
|
3131
|
+
if (oldHead && newHead) {
|
3132
|
+
let promises = handleHeadElement(newHead, oldHead, ctx);
|
3133
|
+
Promise.all(promises).then((function() {
|
3134
|
+
morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
|
3135
|
+
head: {
|
3136
|
+
block: false,
|
3137
|
+
ignore: true
|
3138
|
+
}
|
3139
|
+
}));
|
3140
|
+
}));
|
3141
|
+
return;
|
3142
|
+
}
|
3143
|
+
}
|
3144
|
+
if (ctx.morphStyle === "innerHTML") {
|
3145
|
+
morphChildren(normalizedNewContent, oldNode, ctx);
|
3146
|
+
return oldNode.children;
|
3147
|
+
} else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
|
3148
|
+
let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
|
3149
|
+
let previousSibling = bestMatch?.previousSibling;
|
3150
|
+
let nextSibling = bestMatch?.nextSibling;
|
3151
|
+
let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
|
3152
|
+
if (bestMatch) {
|
3153
|
+
return insertSiblings(previousSibling, morphedNode, nextSibling);
|
3154
|
+
} else {
|
3155
|
+
return [];
|
3156
|
+
}
|
3157
|
+
} else {
|
3158
|
+
throw "Do not understand how to morph style " + ctx.morphStyle;
|
3159
|
+
}
|
3160
|
+
}
|
3161
|
+
function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
|
3162
|
+
return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
|
3163
|
+
}
|
3164
|
+
function morphOldNodeTo(oldNode, newContent, ctx) {
|
3165
|
+
if (ctx.ignoreActive && oldNode === document.activeElement) ; else if (newContent == null) {
|
3166
|
+
if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
|
3167
|
+
oldNode.remove();
|
3168
|
+
ctx.callbacks.afterNodeRemoved(oldNode);
|
3169
|
+
return null;
|
3170
|
+
} else if (!isSoftMatch(oldNode, newContent)) {
|
3171
|
+
if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
|
3172
|
+
if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;
|
3173
|
+
oldNode.parentElement.replaceChild(newContent, oldNode);
|
3174
|
+
ctx.callbacks.afterNodeAdded(newContent);
|
3175
|
+
ctx.callbacks.afterNodeRemoved(oldNode);
|
3176
|
+
return newContent;
|
3177
|
+
} else {
|
3178
|
+
if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode;
|
3179
|
+
if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
|
3180
|
+
handleHeadElement(newContent, oldNode, ctx);
|
3181
|
+
} else {
|
3182
|
+
syncNodeFrom(newContent, oldNode, ctx);
|
3183
|
+
if (!ignoreValueOfActiveElement(oldNode, ctx)) {
|
3184
|
+
morphChildren(newContent, oldNode, ctx);
|
3185
|
+
}
|
3186
|
+
}
|
3187
|
+
ctx.callbacks.afterNodeMorphed(oldNode, newContent);
|
3188
|
+
return oldNode;
|
3189
|
+
}
|
3190
|
+
}
|
3191
|
+
function morphChildren(newParent, oldParent, ctx) {
|
3192
|
+
let nextNewChild = newParent.firstChild;
|
3193
|
+
let insertionPoint = oldParent.firstChild;
|
3194
|
+
let newChild;
|
3195
|
+
while (nextNewChild) {
|
3196
|
+
newChild = nextNewChild;
|
3197
|
+
nextNewChild = newChild.nextSibling;
|
3198
|
+
if (insertionPoint == null) {
|
3199
|
+
if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
|
3200
|
+
oldParent.appendChild(newChild);
|
3201
|
+
ctx.callbacks.afterNodeAdded(newChild);
|
3202
|
+
removeIdsFromConsideration(ctx, newChild);
|
3203
|
+
continue;
|
3204
|
+
}
|
3205
|
+
if (isIdSetMatch(newChild, insertionPoint, ctx)) {
|
3206
|
+
morphOldNodeTo(insertionPoint, newChild, ctx);
|
3207
|
+
insertionPoint = insertionPoint.nextSibling;
|
3208
|
+
removeIdsFromConsideration(ctx, newChild);
|
3209
|
+
continue;
|
3210
|
+
}
|
3211
|
+
let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
|
3212
|
+
if (idSetMatch) {
|
3213
|
+
insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
|
3214
|
+
morphOldNodeTo(idSetMatch, newChild, ctx);
|
3215
|
+
removeIdsFromConsideration(ctx, newChild);
|
3216
|
+
continue;
|
3217
|
+
}
|
3218
|
+
let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
|
3219
|
+
if (softMatch) {
|
3220
|
+
insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
|
3221
|
+
morphOldNodeTo(softMatch, newChild, ctx);
|
3222
|
+
removeIdsFromConsideration(ctx, newChild);
|
3223
|
+
continue;
|
3224
|
+
}
|
3225
|
+
if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
|
3226
|
+
oldParent.insertBefore(newChild, insertionPoint);
|
3227
|
+
ctx.callbacks.afterNodeAdded(newChild);
|
3228
|
+
removeIdsFromConsideration(ctx, newChild);
|
3229
|
+
}
|
3230
|
+
while (insertionPoint !== null) {
|
3231
|
+
let tempNode = insertionPoint;
|
3232
|
+
insertionPoint = insertionPoint.nextSibling;
|
3233
|
+
removeNode(tempNode, ctx);
|
3234
|
+
}
|
3235
|
+
}
|
3236
|
+
function ignoreAttribute(attr, to, updateType, ctx) {
|
3237
|
+
if (attr === "value" && ctx.ignoreActiveValue && to === document.activeElement) {
|
3238
|
+
return true;
|
3239
|
+
}
|
3240
|
+
return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
|
3241
|
+
}
|
3242
|
+
function syncNodeFrom(from, to, ctx) {
|
3243
|
+
let type = from.nodeType;
|
3244
|
+
if (type === 1) {
|
3245
|
+
const fromAttributes = from.attributes;
|
3246
|
+
const toAttributes = to.attributes;
|
3247
|
+
for (const fromAttribute of fromAttributes) {
|
3248
|
+
if (ignoreAttribute(fromAttribute.name, to, "update", ctx)) {
|
3249
|
+
continue;
|
3250
|
+
}
|
3251
|
+
if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
|
3252
|
+
to.setAttribute(fromAttribute.name, fromAttribute.value);
|
3253
|
+
}
|
3254
|
+
}
|
3255
|
+
for (let i = toAttributes.length - 1; 0 <= i; i--) {
|
3256
|
+
const toAttribute = toAttributes[i];
|
3257
|
+
if (ignoreAttribute(toAttribute.name, to, "remove", ctx)) {
|
3258
|
+
continue;
|
3259
|
+
}
|
3260
|
+
if (!from.hasAttribute(toAttribute.name)) {
|
3261
|
+
to.removeAttribute(toAttribute.name);
|
3262
|
+
}
|
3263
|
+
}
|
3264
|
+
}
|
3265
|
+
if (type === 8 || type === 3) {
|
3266
|
+
if (to.nodeValue !== from.nodeValue) {
|
3267
|
+
to.nodeValue = from.nodeValue;
|
3268
|
+
}
|
3269
|
+
}
|
3270
|
+
if (!ignoreValueOfActiveElement(to, ctx)) {
|
3271
|
+
syncInputValue(from, to, ctx);
|
3272
|
+
}
|
3273
|
+
}
|
3274
|
+
function syncBooleanAttribute(from, to, attributeName, ctx) {
|
3275
|
+
if (from[attributeName] !== to[attributeName]) {
|
3276
|
+
let ignoreUpdate = ignoreAttribute(attributeName, to, "update", ctx);
|
3277
|
+
if (!ignoreUpdate) {
|
3278
|
+
to[attributeName] = from[attributeName];
|
3279
|
+
}
|
3280
|
+
if (from[attributeName]) {
|
3281
|
+
if (!ignoreUpdate) {
|
3282
|
+
to.setAttribute(attributeName, from[attributeName]);
|
3283
|
+
}
|
3284
|
+
} else {
|
3285
|
+
if (!ignoreAttribute(attributeName, to, "remove", ctx)) {
|
3286
|
+
to.removeAttribute(attributeName);
|
3287
|
+
}
|
3288
|
+
}
|
3289
|
+
}
|
3290
|
+
}
|
3291
|
+
function syncInputValue(from, to, ctx) {
|
3292
|
+
if (from instanceof HTMLInputElement && to instanceof HTMLInputElement && from.type !== "file") {
|
3293
|
+
let fromValue = from.value;
|
3294
|
+
let toValue = to.value;
|
3295
|
+
syncBooleanAttribute(from, to, "checked", ctx);
|
3296
|
+
syncBooleanAttribute(from, to, "disabled", ctx);
|
3297
|
+
if (!from.hasAttribute("value")) {
|
3298
|
+
if (!ignoreAttribute("value", to, "remove", ctx)) {
|
3299
|
+
to.value = "";
|
3300
|
+
to.removeAttribute("value");
|
3301
|
+
}
|
3302
|
+
} else if (fromValue !== toValue) {
|
3303
|
+
if (!ignoreAttribute("value", to, "update", ctx)) {
|
3304
|
+
to.setAttribute("value", fromValue);
|
3305
|
+
to.value = fromValue;
|
3306
|
+
}
|
3307
|
+
}
|
3308
|
+
} else if (from instanceof HTMLOptionElement) {
|
3309
|
+
syncBooleanAttribute(from, to, "selected", ctx);
|
3310
|
+
} else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
|
3311
|
+
let fromValue = from.value;
|
3312
|
+
let toValue = to.value;
|
3313
|
+
if (ignoreAttribute("value", to, "update", ctx)) {
|
3314
|
+
return;
|
3315
|
+
}
|
3316
|
+
if (fromValue !== toValue) {
|
3317
|
+
to.value = fromValue;
|
3318
|
+
}
|
3319
|
+
if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
|
3320
|
+
to.firstChild.nodeValue = fromValue;
|
3321
|
+
}
|
3322
|
+
}
|
3323
|
+
}
|
3324
|
+
function handleHeadElement(newHeadTag, currentHead, ctx) {
|
3325
|
+
let added = [];
|
3326
|
+
let removed = [];
|
3327
|
+
let preserved = [];
|
3328
|
+
let nodesToAppend = [];
|
3329
|
+
let headMergeStyle = ctx.head.style;
|
3330
|
+
let srcToNewHeadNodes = new Map;
|
3331
|
+
for (const newHeadChild of newHeadTag.children) {
|
3332
|
+
srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
|
3333
|
+
}
|
3334
|
+
for (const currentHeadElt of currentHead.children) {
|
3335
|
+
let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
|
3336
|
+
let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
|
3337
|
+
let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
|
3338
|
+
if (inNewContent || isPreserved) {
|
3339
|
+
if (isReAppended) {
|
3340
|
+
removed.push(currentHeadElt);
|
3341
|
+
} else {
|
3342
|
+
srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
|
3343
|
+
preserved.push(currentHeadElt);
|
3344
|
+
}
|
3345
|
+
} else {
|
3346
|
+
if (headMergeStyle === "append") {
|
3347
|
+
if (isReAppended) {
|
3348
|
+
removed.push(currentHeadElt);
|
3349
|
+
nodesToAppend.push(currentHeadElt);
|
3350
|
+
}
|
3351
|
+
} else {
|
3352
|
+
if (ctx.head.shouldRemove(currentHeadElt) !== false) {
|
3353
|
+
removed.push(currentHeadElt);
|
3354
|
+
}
|
3355
|
+
}
|
3356
|
+
}
|
3357
|
+
}
|
3358
|
+
nodesToAppend.push(...srcToNewHeadNodes.values());
|
3359
|
+
let promises = [];
|
3360
|
+
for (const newNode of nodesToAppend) {
|
3361
|
+
let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
|
3362
|
+
if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
|
3363
|
+
if (newElt.href || newElt.src) {
|
3364
|
+
let resolve = null;
|
3365
|
+
let promise = new Promise((function(_resolve) {
|
3366
|
+
resolve = _resolve;
|
3367
|
+
}));
|
3368
|
+
newElt.addEventListener("load", (function() {
|
3369
|
+
resolve();
|
3370
|
+
}));
|
3371
|
+
promises.push(promise);
|
3372
|
+
}
|
3373
|
+
currentHead.appendChild(newElt);
|
3374
|
+
ctx.callbacks.afterNodeAdded(newElt);
|
3375
|
+
added.push(newElt);
|
3376
|
+
}
|
3377
|
+
}
|
3378
|
+
for (const removedElement of removed) {
|
3379
|
+
if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
|
3380
|
+
currentHead.removeChild(removedElement);
|
3381
|
+
ctx.callbacks.afterNodeRemoved(removedElement);
|
3382
|
+
}
|
3383
|
+
}
|
3384
|
+
ctx.head.afterHeadMorphed(currentHead, {
|
3385
|
+
added: added,
|
3386
|
+
kept: preserved,
|
3387
|
+
removed: removed
|
3388
|
+
});
|
3389
|
+
return promises;
|
3390
|
+
}
|
3391
|
+
function noOp() {}
|
3392
|
+
function mergeDefaults(config) {
|
3393
|
+
let finalConfig = {};
|
3394
|
+
Object.assign(finalConfig, defaults);
|
3395
|
+
Object.assign(finalConfig, config);
|
3396
|
+
finalConfig.callbacks = {};
|
3397
|
+
Object.assign(finalConfig.callbacks, defaults.callbacks);
|
3398
|
+
Object.assign(finalConfig.callbacks, config.callbacks);
|
3399
|
+
finalConfig.head = {};
|
3400
|
+
Object.assign(finalConfig.head, defaults.head);
|
3401
|
+
Object.assign(finalConfig.head, config.head);
|
3402
|
+
return finalConfig;
|
3403
|
+
}
|
3404
|
+
function createMorphContext(oldNode, newContent, config) {
|
3405
|
+
config = mergeDefaults(config);
|
3406
|
+
return {
|
3407
|
+
target: oldNode,
|
3408
|
+
newContent: newContent,
|
3409
|
+
config: config,
|
3410
|
+
morphStyle: config.morphStyle,
|
3411
|
+
ignoreActive: config.ignoreActive,
|
3412
|
+
ignoreActiveValue: config.ignoreActiveValue,
|
3413
|
+
idMap: createIdMap(oldNode, newContent),
|
3414
|
+
deadIds: new Set,
|
3415
|
+
callbacks: config.callbacks,
|
3416
|
+
head: config.head
|
3417
|
+
};
|
3418
|
+
}
|
3419
|
+
function isIdSetMatch(node1, node2, ctx) {
|
3420
|
+
if (node1 == null || node2 == null) {
|
3421
|
+
return false;
|
3422
|
+
}
|
3423
|
+
if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
|
3424
|
+
if (node1.id !== "" && node1.id === node2.id) {
|
3425
|
+
return true;
|
3426
|
+
} else {
|
3427
|
+
return getIdIntersectionCount(ctx, node1, node2) > 0;
|
3428
|
+
}
|
3429
|
+
}
|
3430
|
+
return false;
|
3431
|
+
}
|
3432
|
+
function isSoftMatch(node1, node2) {
|
3433
|
+
if (node1 == null || node2 == null) {
|
3434
|
+
return false;
|
3435
|
+
}
|
3436
|
+
return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName;
|
3437
|
+
}
|
3438
|
+
function removeNodesBetween(startInclusive, endExclusive, ctx) {
|
3439
|
+
while (startInclusive !== endExclusive) {
|
3440
|
+
let tempNode = startInclusive;
|
3441
|
+
startInclusive = startInclusive.nextSibling;
|
3442
|
+
removeNode(tempNode, ctx);
|
3443
|
+
}
|
3444
|
+
removeIdsFromConsideration(ctx, endExclusive);
|
3445
|
+
return endExclusive.nextSibling;
|
3446
|
+
}
|
3447
|
+
function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
|
3448
|
+
let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
|
3449
|
+
let potentialMatch = null;
|
3450
|
+
if (newChildPotentialIdCount > 0) {
|
3451
|
+
let potentialMatch = insertionPoint;
|
3452
|
+
let otherMatchCount = 0;
|
3453
|
+
while (potentialMatch != null) {
|
3454
|
+
if (isIdSetMatch(newChild, potentialMatch, ctx)) {
|
3455
|
+
return potentialMatch;
|
3456
|
+
}
|
3457
|
+
otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
|
3458
|
+
if (otherMatchCount > newChildPotentialIdCount) {
|
3459
|
+
return null;
|
3460
|
+
}
|
3461
|
+
potentialMatch = potentialMatch.nextSibling;
|
3462
|
+
}
|
3463
|
+
}
|
3464
|
+
return potentialMatch;
|
3465
|
+
}
|
3466
|
+
function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
|
3467
|
+
let potentialSoftMatch = insertionPoint;
|
3468
|
+
let nextSibling = newChild.nextSibling;
|
3469
|
+
let siblingSoftMatchCount = 0;
|
3470
|
+
while (potentialSoftMatch != null) {
|
3471
|
+
if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
|
3472
|
+
return null;
|
3473
|
+
}
|
3474
|
+
if (isSoftMatch(newChild, potentialSoftMatch)) {
|
3475
|
+
return potentialSoftMatch;
|
3476
|
+
}
|
3477
|
+
if (isSoftMatch(nextSibling, potentialSoftMatch)) {
|
3478
|
+
siblingSoftMatchCount++;
|
3479
|
+
nextSibling = nextSibling.nextSibling;
|
3480
|
+
if (siblingSoftMatchCount >= 2) {
|
3481
|
+
return null;
|
3482
|
+
}
|
3483
|
+
}
|
3484
|
+
potentialSoftMatch = potentialSoftMatch.nextSibling;
|
3485
|
+
}
|
3486
|
+
return potentialSoftMatch;
|
3487
|
+
}
|
3488
|
+
function parseContent(newContent) {
|
3489
|
+
let parser = new DOMParser;
|
3490
|
+
let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
|
3491
|
+
if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
|
3492
|
+
let content = parser.parseFromString(newContent, "text/html");
|
3493
|
+
if (contentWithSvgsRemoved.match(/<\/html>/)) {
|
3494
|
+
content.generatedByIdiomorph = true;
|
3495
|
+
return content;
|
3496
|
+
} else {
|
3497
|
+
let htmlElement = content.firstChild;
|
3498
|
+
if (htmlElement) {
|
3499
|
+
htmlElement.generatedByIdiomorph = true;
|
3500
|
+
return htmlElement;
|
3501
|
+
} else {
|
3502
|
+
return null;
|
3503
|
+
}
|
3504
|
+
}
|
3505
|
+
} else {
|
3506
|
+
let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
|
3507
|
+
let content = responseDoc.body.querySelector("template").content;
|
3508
|
+
content.generatedByIdiomorph = true;
|
3509
|
+
return content;
|
3510
|
+
}
|
3511
|
+
}
|
3512
|
+
function normalizeContent(newContent) {
|
3513
|
+
if (newContent == null) {
|
3514
|
+
const dummyParent = document.createElement("div");
|
3515
|
+
return dummyParent;
|
3516
|
+
} else if (newContent.generatedByIdiomorph) {
|
3517
|
+
return newContent;
|
3518
|
+
} else if (newContent instanceof Node) {
|
3519
|
+
const dummyParent = document.createElement("div");
|
3520
|
+
dummyParent.append(newContent);
|
3521
|
+
return dummyParent;
|
3522
|
+
} else {
|
3523
|
+
const dummyParent = document.createElement("div");
|
3524
|
+
for (const elt of [ ...newContent ]) {
|
3525
|
+
dummyParent.append(elt);
|
3526
|
+
}
|
3527
|
+
return dummyParent;
|
3528
|
+
}
|
3529
|
+
}
|
3530
|
+
function insertSiblings(previousSibling, morphedNode, nextSibling) {
|
3531
|
+
let stack = [];
|
3532
|
+
let added = [];
|
3533
|
+
while (previousSibling != null) {
|
3534
|
+
stack.push(previousSibling);
|
3535
|
+
previousSibling = previousSibling.previousSibling;
|
3536
|
+
}
|
3537
|
+
while (stack.length > 0) {
|
3538
|
+
let node = stack.pop();
|
3539
|
+
added.push(node);
|
3540
|
+
morphedNode.parentElement.insertBefore(node, morphedNode);
|
3541
|
+
}
|
3542
|
+
added.push(morphedNode);
|
3543
|
+
while (nextSibling != null) {
|
3544
|
+
stack.push(nextSibling);
|
3545
|
+
added.push(nextSibling);
|
3546
|
+
nextSibling = nextSibling.nextSibling;
|
3547
|
+
}
|
3548
|
+
while (stack.length > 0) {
|
3549
|
+
morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
|
3550
|
+
}
|
3551
|
+
return added;
|
3552
|
+
}
|
3553
|
+
function findBestNodeMatch(newContent, oldNode, ctx) {
|
3554
|
+
let currentElement;
|
3555
|
+
currentElement = newContent.firstChild;
|
3556
|
+
let bestElement = currentElement;
|
3557
|
+
let score = 0;
|
3558
|
+
while (currentElement) {
|
3559
|
+
let newScore = scoreElement(currentElement, oldNode, ctx);
|
3560
|
+
if (newScore > score) {
|
3561
|
+
bestElement = currentElement;
|
3562
|
+
score = newScore;
|
3563
|
+
}
|
3564
|
+
currentElement = currentElement.nextSibling;
|
3565
|
+
}
|
3566
|
+
return bestElement;
|
3567
|
+
}
|
3568
|
+
function scoreElement(node1, node2, ctx) {
|
3569
|
+
if (isSoftMatch(node1, node2)) {
|
3570
|
+
return .5 + getIdIntersectionCount(ctx, node1, node2);
|
3571
|
+
}
|
3572
|
+
return 0;
|
3573
|
+
}
|
3574
|
+
function removeNode(tempNode, ctx) {
|
3575
|
+
removeIdsFromConsideration(ctx, tempNode);
|
3576
|
+
if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
|
3577
|
+
tempNode.remove();
|
3578
|
+
ctx.callbacks.afterNodeRemoved(tempNode);
|
3579
|
+
}
|
3580
|
+
function isIdInConsideration(ctx, id) {
|
3581
|
+
return !ctx.deadIds.has(id);
|
3582
|
+
}
|
3583
|
+
function idIsWithinNode(ctx, id, targetNode) {
|
3584
|
+
let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
|
3585
|
+
return idSet.has(id);
|
3586
|
+
}
|
3587
|
+
function removeIdsFromConsideration(ctx, node) {
|
3588
|
+
let idSet = ctx.idMap.get(node) || EMPTY_SET;
|
3589
|
+
for (const id of idSet) {
|
3590
|
+
ctx.deadIds.add(id);
|
3591
|
+
}
|
3592
|
+
}
|
3593
|
+
function getIdIntersectionCount(ctx, node1, node2) {
|
3594
|
+
let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
|
3595
|
+
let matchCount = 0;
|
3596
|
+
for (const id of sourceSet) {
|
3597
|
+
if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
|
3598
|
+
++matchCount;
|
3599
|
+
}
|
3600
|
+
}
|
3601
|
+
return matchCount;
|
3602
|
+
}
|
3603
|
+
function populateIdMapForNode(node, idMap) {
|
3604
|
+
let nodeParent = node.parentElement;
|
3605
|
+
let idElements = node.querySelectorAll("[id]");
|
3606
|
+
for (const elt of idElements) {
|
3607
|
+
let current = elt;
|
3608
|
+
while (current !== nodeParent && current != null) {
|
3609
|
+
let idSet = idMap.get(current);
|
3610
|
+
if (idSet == null) {
|
3611
|
+
idSet = new Set;
|
3612
|
+
idMap.set(current, idSet);
|
3613
|
+
}
|
3614
|
+
idSet.add(elt.id);
|
3615
|
+
current = current.parentElement;
|
3616
|
+
}
|
3617
|
+
}
|
3618
|
+
}
|
3619
|
+
function createIdMap(oldContent, newContent) {
|
3620
|
+
let idMap = new Map;
|
3621
|
+
populateIdMapForNode(oldContent, idMap);
|
3622
|
+
populateIdMapForNode(newContent, idMap);
|
3623
|
+
return idMap;
|
3624
|
+
}
|
3625
|
+
return {
|
3626
|
+
morph: morph,
|
3627
|
+
defaults: defaults
|
3628
|
+
};
|
3629
|
+
}();
|
3630
|
+
|
2681
3631
|
class PageRenderer extends Renderer {
|
2682
3632
|
static renderElement(currentElement, newElement) {
|
2683
3633
|
if (document.body && newElement instanceof HTMLBodyElement) {
|
@@ -2702,6 +3652,7 @@ class PageRenderer extends Renderer {
|
|
2702
3652
|
}
|
2703
3653
|
}
|
2704
3654
|
async prepareToRender() {
|
3655
|
+
this.#setLanguage();
|
2705
3656
|
await this.mergeHead();
|
2706
3657
|
}
|
2707
3658
|
async render() {
|
@@ -2724,12 +3675,24 @@ class PageRenderer extends Renderer {
|
|
2724
3675
|
get newElement() {
|
2725
3676
|
return this.newSnapshot.element;
|
2726
3677
|
}
|
3678
|
+
#setLanguage() {
|
3679
|
+
const {documentElement: documentElement} = this.currentSnapshot;
|
3680
|
+
const {lang: lang} = this.newSnapshot;
|
3681
|
+
if (lang) {
|
3682
|
+
documentElement.setAttribute("lang", lang);
|
3683
|
+
} else {
|
3684
|
+
documentElement.removeAttribute("lang");
|
3685
|
+
}
|
3686
|
+
}
|
2727
3687
|
async mergeHead() {
|
2728
3688
|
const mergedHeadElements = this.mergeProvisionalElements();
|
2729
3689
|
const newStylesheetElements = this.copyNewHeadStylesheetElements();
|
2730
3690
|
this.copyNewHeadScriptElements();
|
2731
3691
|
await mergedHeadElements;
|
2732
3692
|
await newStylesheetElements;
|
3693
|
+
if (this.willRender) {
|
3694
|
+
this.removeUnusedDynamicStylesheetElements();
|
3695
|
+
}
|
2733
3696
|
}
|
2734
3697
|
async replaceBody() {
|
2735
3698
|
await this.preservingPermanentElements((async () => {
|
@@ -2753,6 +3716,11 @@ class PageRenderer extends Renderer {
|
|
2753
3716
|
document.head.appendChild(activateScriptElement(element));
|
2754
3717
|
}
|
2755
3718
|
}
|
3719
|
+
removeUnusedDynamicStylesheetElements() {
|
3720
|
+
for (const element of this.unusedDynamicStylesheetElements) {
|
3721
|
+
document.head.removeChild(element);
|
3722
|
+
}
|
3723
|
+
}
|
2756
3724
|
async mergeProvisionalElements() {
|
2757
3725
|
const newHeadElements = [ ...this.newHeadProvisionalElements ];
|
2758
3726
|
for (const element of this.currentHeadProvisionalElements) {
|
@@ -2805,6 +3773,12 @@ class PageRenderer extends Renderer {
|
|
2805
3773
|
async assignNewBody() {
|
2806
3774
|
await this.renderElement(this.currentElement, this.newElement);
|
2807
3775
|
}
|
3776
|
+
get unusedDynamicStylesheetElements() {
|
3777
|
+
return this.oldHeadStylesheetElements.filter((element => element.getAttribute("data-turbo-track") === "dynamic"));
|
3778
|
+
}
|
3779
|
+
get oldHeadStylesheetElements() {
|
3780
|
+
return this.currentHeadSnapshot.getStylesheetElementsNotInSnapshot(this.newHeadSnapshot);
|
3781
|
+
}
|
2808
3782
|
get newHeadStylesheetElements() {
|
2809
3783
|
return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
|
2810
3784
|
}
|
@@ -2822,10 +3796,112 @@ class PageRenderer extends Renderer {
|
|
2822
3796
|
}
|
2823
3797
|
}
|
2824
3798
|
|
3799
|
+
class MorphRenderer extends PageRenderer {
|
3800
|
+
async render() {
|
3801
|
+
if (this.willRender) await this.#morphBody();
|
3802
|
+
}
|
3803
|
+
get renderMethod() {
|
3804
|
+
return "morph";
|
3805
|
+
}
|
3806
|
+
async #morphBody() {
|
3807
|
+
this.#morphElements(this.currentElement, this.newElement);
|
3808
|
+
this.#reloadRemoteFrames();
|
3809
|
+
dispatch("turbo:morph", {
|
3810
|
+
detail: {
|
3811
|
+
currentElement: this.currentElement,
|
3812
|
+
newElement: this.newElement
|
3813
|
+
}
|
3814
|
+
});
|
3815
|
+
}
|
3816
|
+
#morphElements(currentElement, newElement, morphStyle = "outerHTML") {
|
3817
|
+
this.isMorphingTurboFrame = this.#isFrameReloadedWithMorph(currentElement);
|
3818
|
+
Idiomorph.morph(currentElement, newElement, {
|
3819
|
+
morphStyle: morphStyle,
|
3820
|
+
callbacks: {
|
3821
|
+
beforeNodeAdded: this.#shouldAddElement,
|
3822
|
+
beforeNodeMorphed: this.#shouldMorphElement,
|
3823
|
+
beforeAttributeUpdated: this.#shouldUpdateAttribute,
|
3824
|
+
beforeNodeRemoved: this.#shouldRemoveElement,
|
3825
|
+
afterNodeMorphed: this.#didMorphElement
|
3826
|
+
}
|
3827
|
+
});
|
3828
|
+
}
|
3829
|
+
#shouldAddElement=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
|
3830
|
+
#shouldMorphElement=(oldNode, newNode) => {
|
3831
|
+
if (oldNode instanceof HTMLElement) {
|
3832
|
+
if (!oldNode.hasAttribute("data-turbo-permanent") && (this.isMorphingTurboFrame || !this.#isFrameReloadedWithMorph(oldNode))) {
|
3833
|
+
const event = dispatch("turbo:before-morph-element", {
|
3834
|
+
cancelable: true,
|
3835
|
+
target: oldNode,
|
3836
|
+
detail: {
|
3837
|
+
newElement: newNode
|
3838
|
+
}
|
3839
|
+
});
|
3840
|
+
return !event.defaultPrevented;
|
3841
|
+
} else {
|
3842
|
+
return false;
|
3843
|
+
}
|
3844
|
+
}
|
3845
|
+
};
|
3846
|
+
#shouldUpdateAttribute=(attributeName, target, mutationType) => {
|
3847
|
+
const event = dispatch("turbo:before-morph-attribute", {
|
3848
|
+
cancelable: true,
|
3849
|
+
target: target,
|
3850
|
+
detail: {
|
3851
|
+
attributeName: attributeName,
|
3852
|
+
mutationType: mutationType
|
3853
|
+
}
|
3854
|
+
});
|
3855
|
+
return !event.defaultPrevented;
|
3856
|
+
};
|
3857
|
+
#didMorphElement=(oldNode, newNode) => {
|
3858
|
+
if (newNode instanceof HTMLElement) {
|
3859
|
+
dispatch("turbo:morph-element", {
|
3860
|
+
target: oldNode,
|
3861
|
+
detail: {
|
3862
|
+
newElement: newNode
|
3863
|
+
}
|
3864
|
+
});
|
3865
|
+
}
|
3866
|
+
};
|
3867
|
+
#shouldRemoveElement=node => this.#shouldMorphElement(node);
|
3868
|
+
#reloadRemoteFrames() {
|
3869
|
+
this.#remoteFrames().forEach((frame => {
|
3870
|
+
if (this.#isFrameReloadedWithMorph(frame)) {
|
3871
|
+
this.#renderFrameWithMorph(frame);
|
3872
|
+
frame.reload();
|
3873
|
+
}
|
3874
|
+
}));
|
3875
|
+
}
|
3876
|
+
#renderFrameWithMorph(frame) {
|
3877
|
+
frame.addEventListener("turbo:before-frame-render", (event => {
|
3878
|
+
event.detail.render = this.#morphFrameUpdate;
|
3879
|
+
}), {
|
3880
|
+
once: true
|
3881
|
+
});
|
3882
|
+
}
|
3883
|
+
#morphFrameUpdate=(currentElement, newElement) => {
|
3884
|
+
dispatch("turbo:before-frame-morph", {
|
3885
|
+
target: currentElement,
|
3886
|
+
detail: {
|
3887
|
+
currentElement: currentElement,
|
3888
|
+
newElement: newElement
|
3889
|
+
}
|
3890
|
+
});
|
3891
|
+
this.#morphElements(currentElement, newElement.children, "innerHTML");
|
3892
|
+
};
|
3893
|
+
#isFrameReloadedWithMorph(element) {
|
3894
|
+
return element.src && element.refresh === "morph";
|
3895
|
+
}
|
3896
|
+
#remoteFrames() {
|
3897
|
+
return Array.from(document.querySelectorAll("turbo-frame[src]")).filter((frame => !frame.closest("[data-turbo-permanent]")));
|
3898
|
+
}
|
3899
|
+
}
|
3900
|
+
|
2825
3901
|
class SnapshotCache {
|
3902
|
+
keys=[];
|
3903
|
+
snapshots={};
|
2826
3904
|
constructor(size) {
|
2827
|
-
this.keys = [];
|
2828
|
-
this.snapshots = {};
|
2829
3905
|
this.size = size;
|
2830
3906
|
}
|
2831
3907
|
has(location) {
|
@@ -2867,23 +3943,25 @@ class SnapshotCache {
|
|
2867
3943
|
}
|
2868
3944
|
|
2869
3945
|
class PageView extends View {
|
2870
|
-
|
2871
|
-
|
2872
|
-
|
2873
|
-
|
2874
|
-
this.
|
3946
|
+
snapshotCache=new SnapshotCache(10);
|
3947
|
+
lastRenderedLocation=new URL(location.href);
|
3948
|
+
forceReloaded=false;
|
3949
|
+
shouldTransitionTo(newSnapshot) {
|
3950
|
+
return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions;
|
2875
3951
|
}
|
2876
3952
|
renderPage(snapshot, isPreview = false, willRender = true, visit) {
|
2877
|
-
const
|
3953
|
+
const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;
|
3954
|
+
const rendererClass = shouldMorphPage ? MorphRenderer : PageRenderer;
|
3955
|
+
const renderer = new rendererClass(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);
|
2878
3956
|
if (!renderer.shouldRender) {
|
2879
3957
|
this.forceReloaded = true;
|
2880
3958
|
} else {
|
2881
|
-
visit
|
3959
|
+
visit?.changeHistory();
|
2882
3960
|
}
|
2883
3961
|
return this.render(renderer);
|
2884
3962
|
}
|
2885
3963
|
renderError(snapshot, visit) {
|
2886
|
-
visit
|
3964
|
+
visit?.changeHistory();
|
2887
3965
|
const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);
|
2888
3966
|
return this.render(renderer);
|
2889
3967
|
}
|
@@ -2903,31 +3981,38 @@ class PageView extends View {
|
|
2903
3981
|
getCachedSnapshotForLocation(location) {
|
2904
3982
|
return this.snapshotCache.get(location);
|
2905
3983
|
}
|
3984
|
+
isPageRefresh(visit) {
|
3985
|
+
return !visit || this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === "replace";
|
3986
|
+
}
|
3987
|
+
shouldPreserveScrollPosition(visit) {
|
3988
|
+
return this.isPageRefresh(visit) && this.snapshot.shouldPreserveScrollPosition;
|
3989
|
+
}
|
2906
3990
|
get snapshot() {
|
2907
3991
|
return PageSnapshot.fromElement(this.element);
|
2908
3992
|
}
|
2909
3993
|
}
|
2910
3994
|
|
2911
3995
|
class Preloader {
|
2912
|
-
|
2913
|
-
|
3996
|
+
selector="a[data-turbo-preload]";
|
3997
|
+
constructor(delegate, snapshotCache) {
|
2914
3998
|
this.delegate = delegate;
|
2915
|
-
|
2916
|
-
get snapshotCache() {
|
2917
|
-
return this.delegate.navigator.view.snapshotCache;
|
3999
|
+
this.snapshotCache = snapshotCache;
|
2918
4000
|
}
|
2919
4001
|
start() {
|
2920
4002
|
if (document.readyState === "loading") {
|
2921
|
-
|
2922
|
-
this.preloadOnLoadLinksForView(document.body);
|
2923
|
-
}));
|
4003
|
+
document.addEventListener("DOMContentLoaded", this.#preloadAll);
|
2924
4004
|
} else {
|
2925
4005
|
this.preloadOnLoadLinksForView(document.body);
|
2926
4006
|
}
|
2927
4007
|
}
|
4008
|
+
stop() {
|
4009
|
+
document.removeEventListener("DOMContentLoaded", this.#preloadAll);
|
4010
|
+
}
|
2928
4011
|
preloadOnLoadLinksForView(element) {
|
2929
4012
|
for (const link of element.querySelectorAll(this.selector)) {
|
2930
|
-
this.
|
4013
|
+
if (this.delegate.shouldPreloadLink(link)) {
|
4014
|
+
this.preloadURL(link);
|
4015
|
+
}
|
2931
4016
|
}
|
2932
4017
|
}
|
2933
4018
|
async preloadURL(link) {
|
@@ -2935,46 +4020,83 @@ class Preloader {
|
|
2935
4020
|
if (this.snapshotCache.has(location)) {
|
2936
4021
|
return;
|
2937
4022
|
}
|
4023
|
+
const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, link);
|
4024
|
+
await fetchRequest.perform();
|
4025
|
+
}
|
4026
|
+
prepareRequest(fetchRequest) {
|
4027
|
+
fetchRequest.headers["X-Sec-Purpose"] = "prefetch";
|
4028
|
+
}
|
4029
|
+
async requestSucceededWithResponse(fetchRequest, fetchResponse) {
|
2938
4030
|
try {
|
2939
|
-
const
|
2940
|
-
|
2941
|
-
|
2942
|
-
Accept: "text/html"
|
2943
|
-
}
|
2944
|
-
});
|
2945
|
-
const responseText = await response.text();
|
2946
|
-
const snapshot = PageSnapshot.fromHTMLString(responseText);
|
2947
|
-
this.snapshotCache.put(location, snapshot);
|
4031
|
+
const responseHTML = await fetchResponse.responseHTML;
|
4032
|
+
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
4033
|
+
this.snapshotCache.put(fetchRequest.url, snapshot);
|
2948
4034
|
} catch (_) {}
|
2949
4035
|
}
|
4036
|
+
requestStarted(fetchRequest) {}
|
4037
|
+
requestErrored(fetchRequest) {}
|
4038
|
+
requestFinished(fetchRequest) {}
|
4039
|
+
requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
|
4040
|
+
requestFailedWithResponse(fetchRequest, fetchResponse) {}
|
4041
|
+
#preloadAll=() => {
|
4042
|
+
this.preloadOnLoadLinksForView(document.body);
|
4043
|
+
};
|
4044
|
+
}
|
4045
|
+
|
4046
|
+
class Cache {
|
4047
|
+
constructor(session) {
|
4048
|
+
this.session = session;
|
4049
|
+
}
|
4050
|
+
clear() {
|
4051
|
+
this.session.clearCache();
|
4052
|
+
}
|
4053
|
+
resetCacheControl() {
|
4054
|
+
this.#setCacheControl("");
|
4055
|
+
}
|
4056
|
+
exemptPageFromCache() {
|
4057
|
+
this.#setCacheControl("no-cache");
|
4058
|
+
}
|
4059
|
+
exemptPageFromPreview() {
|
4060
|
+
this.#setCacheControl("no-preview");
|
4061
|
+
}
|
4062
|
+
#setCacheControl(value) {
|
4063
|
+
setMetaContent("turbo-cache-control", value);
|
4064
|
+
}
|
2950
4065
|
}
|
2951
4066
|
|
2952
4067
|
class Session {
|
2953
|
-
|
2954
|
-
|
2955
|
-
|
2956
|
-
|
2957
|
-
|
2958
|
-
|
2959
|
-
|
2960
|
-
|
2961
|
-
|
2962
|
-
|
2963
|
-
|
2964
|
-
|
2965
|
-
|
2966
|
-
|
2967
|
-
|
2968
|
-
|
2969
|
-
|
2970
|
-
|
2971
|
-
|
2972
|
-
|
4068
|
+
navigator=new Navigator(this);
|
4069
|
+
history=new History(this);
|
4070
|
+
view=new PageView(this, document.documentElement);
|
4071
|
+
adapter=new BrowserAdapter(this);
|
4072
|
+
pageObserver=new PageObserver(this);
|
4073
|
+
cacheObserver=new CacheObserver;
|
4074
|
+
linkPrefetchObserver=new LinkPrefetchObserver(this, document);
|
4075
|
+
linkClickObserver=new LinkClickObserver(this, window);
|
4076
|
+
formSubmitObserver=new FormSubmitObserver(this, document);
|
4077
|
+
scrollObserver=new ScrollObserver(this);
|
4078
|
+
streamObserver=new StreamObserver(this);
|
4079
|
+
formLinkClickObserver=new FormLinkClickObserver(this, document.documentElement);
|
4080
|
+
frameRedirector=new FrameRedirector(this, document.documentElement);
|
4081
|
+
streamMessageRenderer=new StreamMessageRenderer;
|
4082
|
+
cache=new Cache(this);
|
4083
|
+
drive=true;
|
4084
|
+
enabled=true;
|
4085
|
+
progressBarDelay=500;
|
4086
|
+
started=false;
|
4087
|
+
formMode="on";
|
4088
|
+
#pageRefreshDebouncePeriod=150;
|
4089
|
+
constructor(recentRequests) {
|
4090
|
+
this.recentRequests = recentRequests;
|
4091
|
+
this.preloader = new Preloader(this, this.view.snapshotCache);
|
4092
|
+
this.debouncedRefresh = this.refresh;
|
4093
|
+
this.pageRefreshDebouncePeriod = this.pageRefreshDebouncePeriod;
|
2973
4094
|
}
|
2974
4095
|
start() {
|
2975
4096
|
if (!this.started) {
|
2976
4097
|
this.pageObserver.start();
|
2977
4098
|
this.cacheObserver.start();
|
4099
|
+
this.linkPrefetchObserver.start();
|
2978
4100
|
this.formLinkClickObserver.start();
|
2979
4101
|
this.linkClickObserver.start();
|
2980
4102
|
this.formSubmitObserver.start();
|
@@ -2994,6 +4116,7 @@ class Session {
|
|
2994
4116
|
if (this.started) {
|
2995
4117
|
this.pageObserver.stop();
|
2996
4118
|
this.cacheObserver.stop();
|
4119
|
+
this.linkPrefetchObserver.stop();
|
2997
4120
|
this.formLinkClickObserver.stop();
|
2998
4121
|
this.linkClickObserver.stop();
|
2999
4122
|
this.formSubmitObserver.stop();
|
@@ -3001,6 +4124,7 @@ class Session {
|
|
3001
4124
|
this.streamObserver.stop();
|
3002
4125
|
this.frameRedirector.stop();
|
3003
4126
|
this.history.stop();
|
4127
|
+
this.preloader.stop();
|
3004
4128
|
this.started = false;
|
3005
4129
|
}
|
3006
4130
|
}
|
@@ -3010,12 +4134,22 @@ class Session {
|
|
3010
4134
|
visit(location, options = {}) {
|
3011
4135
|
const frameElement = options.frame ? document.getElementById(options.frame) : null;
|
3012
4136
|
if (frameElement instanceof FrameElement) {
|
4137
|
+
const action = options.action || getVisitAction(frameElement);
|
4138
|
+
frameElement.delegate.proposeVisitIfNavigatedWithAction(frameElement, action);
|
3013
4139
|
frameElement.src = location.toString();
|
3014
|
-
frameElement.loaded;
|
3015
4140
|
} else {
|
3016
4141
|
this.navigator.proposeVisit(expandURL(location), options);
|
3017
4142
|
}
|
3018
4143
|
}
|
4144
|
+
refresh(url, requestId) {
|
4145
|
+
const isRecentRequest = requestId && this.recentRequests.has(requestId);
|
4146
|
+
if (!isRecentRequest) {
|
4147
|
+
this.visit(url, {
|
4148
|
+
action: "replace",
|
4149
|
+
shouldCacheSnapshot: false
|
4150
|
+
});
|
4151
|
+
}
|
4152
|
+
}
|
3019
4153
|
connectStreamSource(source) {
|
3020
4154
|
this.streamObserver.connectStreamSource(source);
|
3021
4155
|
}
|
@@ -3040,11 +4174,31 @@ class Session {
|
|
3040
4174
|
get restorationIdentifier() {
|
3041
4175
|
return this.history.restorationIdentifier;
|
3042
4176
|
}
|
3043
|
-
|
4177
|
+
get pageRefreshDebouncePeriod() {
|
4178
|
+
return this.#pageRefreshDebouncePeriod;
|
4179
|
+
}
|
4180
|
+
set pageRefreshDebouncePeriod(value) {
|
4181
|
+
this.refresh = debounce(this.debouncedRefresh.bind(this), value);
|
4182
|
+
this.#pageRefreshDebouncePeriod = value;
|
4183
|
+
}
|
4184
|
+
shouldPreloadLink(element) {
|
4185
|
+
const isUnsafe = element.hasAttribute("data-turbo-method");
|
4186
|
+
const isStream = element.hasAttribute("data-turbo-stream");
|
4187
|
+
const frameTarget = element.getAttribute("data-turbo-frame");
|
4188
|
+
const frame = frameTarget == "_top" ? null : document.getElementById(frameTarget) || findClosestRecursively(element, "turbo-frame:not([disabled])");
|
4189
|
+
if (isUnsafe || isStream || frame instanceof FrameElement) {
|
4190
|
+
return false;
|
4191
|
+
} else {
|
4192
|
+
const location = new URL(element.href);
|
4193
|
+
return this.elementIsNavigatable(element) && locationIsVisitable(location, this.snapshot.rootLocation);
|
4194
|
+
}
|
4195
|
+
}
|
4196
|
+
historyPoppedToLocationWithRestorationIdentifierAndDirection(location, restorationIdentifier, direction) {
|
3044
4197
|
if (this.enabled) {
|
3045
4198
|
this.navigator.startVisit(location, restorationIdentifier, {
|
3046
4199
|
action: "restore",
|
3047
|
-
historyChanged: true
|
4200
|
+
historyChanged: true,
|
4201
|
+
direction: direction
|
3048
4202
|
});
|
3049
4203
|
} else {
|
3050
4204
|
this.adapter.pageInvalidated({
|
@@ -3061,6 +4215,9 @@ class Session {
|
|
3061
4215
|
return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
|
3062
4216
|
}
|
3063
4217
|
submittedFormLinkToLocation() {}
|
4218
|
+
canPrefetchRequestToLocation(link, location) {
|
4219
|
+
return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);
|
4220
|
+
}
|
3064
4221
|
willFollowLinkToLocation(link, location, event) {
|
3065
4222
|
return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location, event);
|
3066
4223
|
}
|
@@ -3082,6 +4239,7 @@ class Session {
|
|
3082
4239
|
visitStarted(visit) {
|
3083
4240
|
if (!visit.acceptsStreamResponse) {
|
3084
4241
|
markAsBusy(document.documentElement);
|
4242
|
+
this.view.markVisitDirection(visit.direction);
|
3085
4243
|
}
|
3086
4244
|
extendURLWithDeprecatedProperties(visit.location);
|
3087
4245
|
if (!visit.silent) {
|
@@ -3089,6 +4247,7 @@ class Session {
|
|
3089
4247
|
}
|
3090
4248
|
}
|
3091
4249
|
visitCompleted(visit) {
|
4250
|
+
this.view.unmarkVisitDirection();
|
3092
4251
|
clearBusyState(document.documentElement);
|
3093
4252
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
3094
4253
|
}
|
@@ -3099,7 +4258,7 @@ class Session {
|
|
3099
4258
|
this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
|
3100
4259
|
}
|
3101
4260
|
willSubmitForm(form, submitter) {
|
3102
|
-
const action = getAction(form, submitter);
|
4261
|
+
const action = getAction$1(form, submitter);
|
3103
4262
|
return this.submissionIsNavigatable(form, submitter) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
|
3104
4263
|
}
|
3105
4264
|
formSubmitted(form, submitter) {
|
@@ -3119,8 +4278,7 @@ class Session {
|
|
3119
4278
|
this.renderStreamMessage(message);
|
3120
4279
|
}
|
3121
4280
|
viewWillCacheSnapshot() {
|
3122
|
-
|
3123
|
-
if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) {
|
4281
|
+
if (!this.navigator.currentVisit?.silent) {
|
3124
4282
|
this.notifyApplicationBeforeCachingSnapshot();
|
3125
4283
|
}
|
3126
4284
|
}
|
@@ -3132,9 +4290,9 @@ class Session {
|
|
3132
4290
|
}
|
3133
4291
|
return !defaultPrevented;
|
3134
4292
|
}
|
3135
|
-
viewRenderedSnapshot(_snapshot, _isPreview) {
|
4293
|
+
viewRenderedSnapshot(_snapshot, _isPreview, renderMethod) {
|
3136
4294
|
this.view.lastRenderedLocation = this.history.location;
|
3137
|
-
this.notifyApplicationAfterRender();
|
4295
|
+
this.notifyApplicationAfterRender(renderMethod);
|
3138
4296
|
}
|
3139
4297
|
preloadOnLoadLinksForView(element) {
|
3140
4298
|
this.preloader.preloadOnLoadLinksForView(element);
|
@@ -3187,14 +4345,19 @@ class Session {
|
|
3187
4345
|
}
|
3188
4346
|
notifyApplicationBeforeRender(newBody, options) {
|
3189
4347
|
return dispatch("turbo:before-render", {
|
3190
|
-
detail:
|
3191
|
-
newBody: newBody
|
3192
|
-
|
4348
|
+
detail: {
|
4349
|
+
newBody: newBody,
|
4350
|
+
...options
|
4351
|
+
},
|
3193
4352
|
cancelable: true
|
3194
4353
|
});
|
3195
4354
|
}
|
3196
|
-
notifyApplicationAfterRender() {
|
3197
|
-
return dispatch("turbo:render"
|
4355
|
+
notifyApplicationAfterRender(renderMethod) {
|
4356
|
+
return dispatch("turbo:render", {
|
4357
|
+
detail: {
|
4358
|
+
renderMethod: renderMethod
|
4359
|
+
}
|
4360
|
+
});
|
3198
4361
|
}
|
3199
4362
|
notifyApplicationAfterPageLoad(timing = {}) {
|
3200
4363
|
return dispatch("turbo:load", {
|
@@ -3273,67 +4436,9 @@ const deprecatedLocationPropertyDescriptors = {
|
|
3273
4436
|
}
|
3274
4437
|
};
|
3275
4438
|
|
3276
|
-
|
3277
|
-
constructor(session) {
|
3278
|
-
this.session = session;
|
3279
|
-
}
|
3280
|
-
clear() {
|
3281
|
-
this.session.clearCache();
|
3282
|
-
}
|
3283
|
-
resetCacheControl() {
|
3284
|
-
this.setCacheControl("");
|
3285
|
-
}
|
3286
|
-
exemptPageFromCache() {
|
3287
|
-
this.setCacheControl("no-cache");
|
3288
|
-
}
|
3289
|
-
exemptPageFromPreview() {
|
3290
|
-
this.setCacheControl("no-preview");
|
3291
|
-
}
|
3292
|
-
setCacheControl(value) {
|
3293
|
-
setMetaContent("turbo-cache-control", value);
|
3294
|
-
}
|
3295
|
-
}
|
3296
|
-
|
3297
|
-
const StreamActions = {
|
3298
|
-
after() {
|
3299
|
-
this.targetElements.forEach((e => {
|
3300
|
-
var _a;
|
3301
|
-
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling);
|
3302
|
-
}));
|
3303
|
-
},
|
3304
|
-
append() {
|
3305
|
-
this.removeDuplicateTargetChildren();
|
3306
|
-
this.targetElements.forEach((e => e.append(this.templateContent)));
|
3307
|
-
},
|
3308
|
-
before() {
|
3309
|
-
this.targetElements.forEach((e => {
|
3310
|
-
var _a;
|
3311
|
-
return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e);
|
3312
|
-
}));
|
3313
|
-
},
|
3314
|
-
prepend() {
|
3315
|
-
this.removeDuplicateTargetChildren();
|
3316
|
-
this.targetElements.forEach((e => e.prepend(this.templateContent)));
|
3317
|
-
},
|
3318
|
-
remove() {
|
3319
|
-
this.targetElements.forEach((e => e.remove()));
|
3320
|
-
},
|
3321
|
-
replace() {
|
3322
|
-
this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
|
3323
|
-
},
|
3324
|
-
update() {
|
3325
|
-
this.targetElements.forEach((targetElement => {
|
3326
|
-
targetElement.innerHTML = "";
|
3327
|
-
targetElement.append(this.templateContent);
|
3328
|
-
}));
|
3329
|
-
}
|
3330
|
-
};
|
3331
|
-
|
3332
|
-
const session = new Session;
|
3333
|
-
|
3334
|
-
const cache = new Cache(session);
|
4439
|
+
const session = new Session(recentRequests);
|
3335
4440
|
|
3336
|
-
const {navigator: navigator$1} = session;
|
4441
|
+
const {cache: cache, navigator: navigator$1} = session;
|
3337
4442
|
|
3338
4443
|
function start() {
|
3339
4444
|
session.start();
|
@@ -3384,6 +4489,7 @@ var Turbo = Object.freeze({
|
|
3384
4489
|
PageRenderer: PageRenderer,
|
3385
4490
|
PageSnapshot: PageSnapshot,
|
3386
4491
|
FrameRenderer: FrameRenderer,
|
4492
|
+
fetch: fetchWithTurboHeaders,
|
3387
4493
|
start: start,
|
3388
4494
|
registerAdapter: registerAdapter,
|
3389
4495
|
visit: visit,
|
@@ -3393,28 +4499,20 @@ var Turbo = Object.freeze({
|
|
3393
4499
|
clearCache: clearCache,
|
3394
4500
|
setProgressBarDelay: setProgressBarDelay,
|
3395
4501
|
setConfirmMethod: setConfirmMethod,
|
3396
|
-
setFormMode: setFormMode
|
3397
|
-
StreamActions: StreamActions
|
4502
|
+
setFormMode: setFormMode
|
3398
4503
|
});
|
3399
4504
|
|
3400
4505
|
class TurboFrameMissingError extends Error {}
|
3401
4506
|
|
3402
4507
|
class FrameController {
|
4508
|
+
fetchResponseLoaded=_fetchResponse => Promise.resolve();
|
4509
|
+
#currentFetchRequest=null;
|
4510
|
+
#resolveVisitPromise=() => {};
|
4511
|
+
#connected=false;
|
4512
|
+
#hasBeenLoaded=false;
|
4513
|
+
#ignoredAttributes=new Set;
|
4514
|
+
action=null;
|
3403
4515
|
constructor(element) {
|
3404
|
-
this.fetchResponseLoaded = _fetchResponse => {};
|
3405
|
-
this.currentFetchRequest = null;
|
3406
|
-
this.resolveVisitPromise = () => {};
|
3407
|
-
this.connected = false;
|
3408
|
-
this.hasBeenLoaded = false;
|
3409
|
-
this.ignoredAttributes = new Set;
|
3410
|
-
this.action = null;
|
3411
|
-
this.visitCachedSnapshot = ({element: element}) => {
|
3412
|
-
const frame = element.querySelector("#" + this.element.id);
|
3413
|
-
if (frame && this.previousFrameElement) {
|
3414
|
-
frame.replaceChildren(...this.previousFrameElement.children);
|
3415
|
-
}
|
3416
|
-
delete this.previousFrameElement;
|
3417
|
-
};
|
3418
4516
|
this.element = element;
|
3419
4517
|
this.view = new FrameView(this, this.element);
|
3420
4518
|
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
@@ -3424,12 +4522,12 @@ class FrameController {
|
|
3424
4522
|
this.formSubmitObserver = new FormSubmitObserver(this, this.element);
|
3425
4523
|
}
|
3426
4524
|
connect() {
|
3427
|
-
if (!this
|
3428
|
-
this
|
4525
|
+
if (!this.#connected) {
|
4526
|
+
this.#connected = true;
|
3429
4527
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
3430
4528
|
this.appearanceObserver.start();
|
3431
4529
|
} else {
|
3432
|
-
this
|
4530
|
+
this.#loadSourceURL();
|
3433
4531
|
}
|
3434
4532
|
this.formLinkClickObserver.start();
|
3435
4533
|
this.linkInterceptor.start();
|
@@ -3437,8 +4535,8 @@ class FrameController {
|
|
3437
4535
|
}
|
3438
4536
|
}
|
3439
4537
|
disconnect() {
|
3440
|
-
if (this
|
3441
|
-
this
|
4538
|
+
if (this.#connected) {
|
4539
|
+
this.#connected = false;
|
3442
4540
|
this.appearanceObserver.stop();
|
3443
4541
|
this.formLinkClickObserver.stop();
|
3444
4542
|
this.linkInterceptor.stop();
|
@@ -3447,45 +4545,39 @@ class FrameController {
|
|
3447
4545
|
}
|
3448
4546
|
disabledChanged() {
|
3449
4547
|
if (this.loadingStyle == FrameLoadingStyle.eager) {
|
3450
|
-
this
|
4548
|
+
this.#loadSourceURL();
|
3451
4549
|
}
|
3452
4550
|
}
|
3453
4551
|
sourceURLChanged() {
|
3454
|
-
if (this
|
4552
|
+
if (this.#isIgnoringChangesTo("src")) return;
|
3455
4553
|
if (this.element.isConnected) {
|
3456
4554
|
this.complete = false;
|
3457
4555
|
}
|
3458
|
-
if (this.loadingStyle == FrameLoadingStyle.eager || this
|
3459
|
-
this
|
4556
|
+
if (this.loadingStyle == FrameLoadingStyle.eager || this.#hasBeenLoaded) {
|
4557
|
+
this.#loadSourceURL();
|
3460
4558
|
}
|
3461
4559
|
}
|
3462
4560
|
sourceURLReloaded() {
|
3463
4561
|
const {src: src} = this.element;
|
3464
|
-
this.
|
3465
|
-
this.element.removeAttribute("complete");
|
3466
|
-
}));
|
4562
|
+
this.element.removeAttribute("complete");
|
3467
4563
|
this.element.src = null;
|
3468
4564
|
this.element.src = src;
|
3469
4565
|
return this.element.loaded;
|
3470
4566
|
}
|
3471
|
-
completeChanged() {
|
3472
|
-
if (this.isIgnoringChangesTo("complete")) return;
|
3473
|
-
this.loadSourceURL();
|
3474
|
-
}
|
3475
4567
|
loadingStyleChanged() {
|
3476
4568
|
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
3477
4569
|
this.appearanceObserver.start();
|
3478
4570
|
} else {
|
3479
4571
|
this.appearanceObserver.stop();
|
3480
|
-
this
|
4572
|
+
this.#loadSourceURL();
|
3481
4573
|
}
|
3482
4574
|
}
|
3483
|
-
async loadSourceURL() {
|
4575
|
+
async #loadSourceURL() {
|
3484
4576
|
if (this.enabled && this.isActive && !this.complete && this.sourceURL) {
|
3485
|
-
this.element.loaded = this
|
4577
|
+
this.element.loaded = this.#visit(expandURL(this.sourceURL));
|
3486
4578
|
this.appearanceObserver.stop();
|
3487
4579
|
await this.element.loaded;
|
3488
|
-
this
|
4580
|
+
this.#hasBeenLoaded = true;
|
3489
4581
|
}
|
3490
4582
|
}
|
3491
4583
|
async loadResponse(fetchResponse) {
|
@@ -3498,34 +4590,34 @@ class FrameController {
|
|
3498
4590
|
const document = parseHTMLDocument(html);
|
3499
4591
|
const pageSnapshot = PageSnapshot.fromDocument(document);
|
3500
4592
|
if (pageSnapshot.isVisitable) {
|
3501
|
-
await this
|
4593
|
+
await this.#loadFrameResponse(fetchResponse, document);
|
3502
4594
|
} else {
|
3503
|
-
await this
|
4595
|
+
await this.#handleUnvisitableFrameResponse(fetchResponse);
|
3504
4596
|
}
|
3505
4597
|
}
|
3506
4598
|
} finally {
|
3507
|
-
this.fetchResponseLoaded = () =>
|
4599
|
+
this.fetchResponseLoaded = () => Promise.resolve();
|
3508
4600
|
}
|
3509
4601
|
}
|
3510
4602
|
elementAppearedInViewport(element) {
|
3511
|
-
this.proposeVisitIfNavigatedWithAction(element, element);
|
3512
|
-
this
|
4603
|
+
this.proposeVisitIfNavigatedWithAction(element, getVisitAction(element));
|
4604
|
+
this.#loadSourceURL();
|
3513
4605
|
}
|
3514
4606
|
willSubmitFormLinkToLocation(link) {
|
3515
|
-
return this
|
4607
|
+
return this.#shouldInterceptNavigation(link);
|
3516
4608
|
}
|
3517
4609
|
submittedFormLinkToLocation(link, _location, form) {
|
3518
|
-
const frame = this
|
4610
|
+
const frame = this.#findFrameElement(link);
|
3519
4611
|
if (frame) form.setAttribute("data-turbo-frame", frame.id);
|
3520
4612
|
}
|
3521
4613
|
shouldInterceptLinkClick(element, _location, _event) {
|
3522
|
-
return this
|
4614
|
+
return this.#shouldInterceptNavigation(element);
|
3523
4615
|
}
|
3524
4616
|
linkClickIntercepted(element, location) {
|
3525
|
-
this
|
4617
|
+
this.#navigateFrame(element, location);
|
3526
4618
|
}
|
3527
4619
|
willSubmitForm(element, submitter) {
|
3528
|
-
return element.closest("turbo-frame") == this.element && this
|
4620
|
+
return element.closest("turbo-frame") == this.element && this.#shouldInterceptNavigation(element, submitter);
|
3529
4621
|
}
|
3530
4622
|
formSubmitted(element, submitter) {
|
3531
4623
|
if (this.formSubmission) {
|
@@ -3537,9 +4629,8 @@ class FrameController {
|
|
3537
4629
|
this.formSubmission.start();
|
3538
4630
|
}
|
3539
4631
|
prepareRequest(request) {
|
3540
|
-
var _a;
|
3541
4632
|
request.headers["Turbo-Frame"] = this.id;
|
3542
|
-
if (
|
4633
|
+
if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
|
3543
4634
|
request.acceptResponseType(StreamMessage.contentType);
|
3544
4635
|
}
|
3545
4636
|
}
|
@@ -3547,29 +4638,29 @@ class FrameController {
|
|
3547
4638
|
markAsBusy(this.element);
|
3548
4639
|
}
|
3549
4640
|
requestPreventedHandlingResponse(_request, _response) {
|
3550
|
-
this
|
4641
|
+
this.#resolveVisitPromise();
|
3551
4642
|
}
|
3552
4643
|
async requestSucceededWithResponse(request, response) {
|
3553
4644
|
await this.loadResponse(response);
|
3554
|
-
this
|
4645
|
+
this.#resolveVisitPromise();
|
3555
4646
|
}
|
3556
4647
|
async requestFailedWithResponse(request, response) {
|
3557
4648
|
await this.loadResponse(response);
|
3558
|
-
this
|
4649
|
+
this.#resolveVisitPromise();
|
3559
4650
|
}
|
3560
4651
|
requestErrored(request, error) {
|
3561
4652
|
console.error(error);
|
3562
|
-
this
|
4653
|
+
this.#resolveVisitPromise();
|
3563
4654
|
}
|
3564
4655
|
requestFinished(_request) {
|
3565
4656
|
clearBusyState(this.element);
|
3566
4657
|
}
|
3567
4658
|
formSubmissionStarted({formElement: formElement}) {
|
3568
|
-
markAsBusy(formElement, this
|
4659
|
+
markAsBusy(formElement, this.#findFrameElement(formElement));
|
3569
4660
|
}
|
3570
4661
|
formSubmissionSucceededWithResponse(formSubmission, response) {
|
3571
|
-
const frame = this
|
3572
|
-
frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.
|
4662
|
+
const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter);
|
4663
|
+
frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame));
|
3573
4664
|
frame.delegate.loadResponse(response);
|
3574
4665
|
if (!formSubmission.isSafe) {
|
3575
4666
|
session.clearCache();
|
@@ -3583,14 +4674,15 @@ class FrameController {
|
|
3583
4674
|
console.error(error);
|
3584
4675
|
}
|
3585
4676
|
formSubmissionFinished({formElement: formElement}) {
|
3586
|
-
clearBusyState(formElement, this
|
4677
|
+
clearBusyState(formElement, this.#findFrameElement(formElement));
|
3587
4678
|
}
|
3588
4679
|
allowsImmediateRender({element: newFrame}, options) {
|
3589
4680
|
const event = dispatch("turbo:before-frame-render", {
|
3590
4681
|
target: this.element,
|
3591
|
-
detail:
|
3592
|
-
newFrame: newFrame
|
3593
|
-
|
4682
|
+
detail: {
|
4683
|
+
newFrame: newFrame,
|
4684
|
+
...options
|
4685
|
+
},
|
3594
4686
|
cancelable: true
|
3595
4687
|
});
|
3596
4688
|
const {defaultPrevented: defaultPrevented, detail: {render: render}} = event;
|
@@ -3599,7 +4691,7 @@ class FrameController {
|
|
3599
4691
|
}
|
3600
4692
|
return !defaultPrevented;
|
3601
4693
|
}
|
3602
|
-
viewRenderedSnapshot(_snapshot, _isPreview) {}
|
4694
|
+
viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {}
|
3603
4695
|
preloadOnLoadLinksForView(element) {
|
3604
4696
|
session.preloadOnLoadLinksForView(element);
|
3605
4697
|
}
|
@@ -3607,7 +4699,14 @@ class FrameController {
|
|
3607
4699
|
willRenderFrame(currentElement, _newElement) {
|
3608
4700
|
this.previousFrameElement = currentElement.cloneNode(true);
|
3609
4701
|
}
|
3610
|
-
|
4702
|
+
visitCachedSnapshot=({element: element}) => {
|
4703
|
+
const frame = element.querySelector("#" + this.element.id);
|
4704
|
+
if (frame && this.previousFrameElement) {
|
4705
|
+
frame.replaceChildren(...this.previousFrameElement.children);
|
4706
|
+
}
|
4707
|
+
delete this.previousFrameElement;
|
4708
|
+
};
|
4709
|
+
async #loadFrameResponse(fetchResponse, document) {
|
3611
4710
|
const newFrameElement = await this.extractForeignFrameElement(document.body);
|
3612
4711
|
if (newFrameElement) {
|
3613
4712
|
const snapshot = new Snapshot(newFrameElement);
|
@@ -3618,41 +4717,40 @@ class FrameController {
|
|
3618
4717
|
this.complete = true;
|
3619
4718
|
session.frameRendered(fetchResponse, this.element);
|
3620
4719
|
session.frameLoaded(this.element);
|
3621
|
-
this.fetchResponseLoaded(fetchResponse);
|
3622
|
-
} else if (this
|
3623
|
-
this
|
4720
|
+
await this.fetchResponseLoaded(fetchResponse);
|
4721
|
+
} else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) {
|
4722
|
+
this.#handleFrameMissingFromResponse(fetchResponse);
|
3624
4723
|
}
|
3625
4724
|
}
|
3626
|
-
async visit(url) {
|
3627
|
-
var _a;
|
4725
|
+
async #visit(url) {
|
3628
4726
|
const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
|
3629
|
-
|
3630
|
-
this
|
4727
|
+
this.#currentFetchRequest?.cancel();
|
4728
|
+
this.#currentFetchRequest = request;
|
3631
4729
|
return new Promise((resolve => {
|
3632
|
-
this
|
3633
|
-
this
|
3634
|
-
this
|
4730
|
+
this.#resolveVisitPromise = () => {
|
4731
|
+
this.#resolveVisitPromise = () => {};
|
4732
|
+
this.#currentFetchRequest = null;
|
3635
4733
|
resolve();
|
3636
4734
|
};
|
3637
4735
|
request.perform();
|
3638
4736
|
}));
|
3639
4737
|
}
|
3640
|
-
navigateFrame(element, url, submitter) {
|
3641
|
-
const frame = this
|
3642
|
-
frame.delegate.proposeVisitIfNavigatedWithAction(frame, element,
|
3643
|
-
this
|
4738
|
+
#navigateFrame(element, url, submitter) {
|
4739
|
+
const frame = this.#findFrameElement(element, submitter);
|
4740
|
+
frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(submitter, element, frame));
|
4741
|
+
this.#withCurrentNavigationElement(element, (() => {
|
3644
4742
|
frame.src = url;
|
3645
4743
|
}));
|
3646
4744
|
}
|
3647
|
-
proposeVisitIfNavigatedWithAction(frame,
|
3648
|
-
this.action =
|
4745
|
+
proposeVisitIfNavigatedWithAction(frame, action = null) {
|
4746
|
+
this.action = action;
|
3649
4747
|
if (this.action) {
|
3650
4748
|
const pageSnapshot = PageSnapshot.fromElement(frame).clone();
|
3651
4749
|
const {visitCachedSnapshot: visitCachedSnapshot} = frame.delegate;
|
3652
|
-
frame.delegate.fetchResponseLoaded = fetchResponse => {
|
4750
|
+
frame.delegate.fetchResponseLoaded = async fetchResponse => {
|
3653
4751
|
if (frame.src) {
|
3654
4752
|
const {statusCode: statusCode, redirected: redirected} = fetchResponse;
|
3655
|
-
const responseHTML =
|
4753
|
+
const responseHTML = await fetchResponse.responseHTML;
|
3656
4754
|
const response = {
|
3657
4755
|
statusCode: statusCode,
|
3658
4756
|
redirected: redirected,
|
@@ -3678,16 +4776,16 @@ class FrameController {
|
|
3678
4776
|
session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier);
|
3679
4777
|
}
|
3680
4778
|
}
|
3681
|
-
async handleUnvisitableFrameResponse(fetchResponse) {
|
4779
|
+
async #handleUnvisitableFrameResponse(fetchResponse) {
|
3682
4780
|
console.warn(`The response (${fetchResponse.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.`);
|
3683
|
-
await this
|
4781
|
+
await this.#visitResponse(fetchResponse.response);
|
3684
4782
|
}
|
3685
|
-
willHandleFrameMissingFromResponse(fetchResponse) {
|
4783
|
+
#willHandleFrameMissingFromResponse(fetchResponse) {
|
3686
4784
|
this.element.setAttribute("complete", "");
|
3687
4785
|
const response = fetchResponse.response;
|
3688
|
-
const visit = async (url, options
|
4786
|
+
const visit = async (url, options) => {
|
3689
4787
|
if (url instanceof Response) {
|
3690
|
-
this
|
4788
|
+
this.#visitResponse(url);
|
3691
4789
|
} else {
|
3692
4790
|
session.visit(url, options);
|
3693
4791
|
}
|
@@ -3702,15 +4800,15 @@ class FrameController {
|
|
3702
4800
|
});
|
3703
4801
|
return !event.defaultPrevented;
|
3704
4802
|
}
|
3705
|
-
handleFrameMissingFromResponse(fetchResponse) {
|
4803
|
+
#handleFrameMissingFromResponse(fetchResponse) {
|
3706
4804
|
this.view.missing();
|
3707
|
-
this
|
4805
|
+
this.#throwFrameMissingError(fetchResponse);
|
3708
4806
|
}
|
3709
|
-
throwFrameMissingError(fetchResponse) {
|
4807
|
+
#throwFrameMissingError(fetchResponse) {
|
3710
4808
|
const message = `The response (${fetchResponse.statusCode}) did not contain the expected <turbo-frame id="${this.element.id}"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;
|
3711
4809
|
throw new TurboFrameMissingError(message);
|
3712
4810
|
}
|
3713
|
-
async visitResponse(response) {
|
4811
|
+
async #visitResponse(response) {
|
3714
4812
|
const wrapped = new FetchResponse(response);
|
3715
4813
|
const responseHTML = await wrapped.responseHTML;
|
3716
4814
|
const {location: location, redirected: redirected, statusCode: statusCode} = wrapped;
|
@@ -3722,10 +4820,9 @@ class FrameController {
|
|
3722
4820
|
}
|
3723
4821
|
});
|
3724
4822
|
}
|
3725
|
-
findFrameElement(element, submitter) {
|
3726
|
-
var _a;
|
4823
|
+
#findFrameElement(element, submitter) {
|
3727
4824
|
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
3728
|
-
return
|
4825
|
+
return getFrameElementById(id) ?? this.element;
|
3729
4826
|
}
|
3730
4827
|
async extractForeignFrameElement(container) {
|
3731
4828
|
let element;
|
@@ -3746,13 +4843,13 @@ class FrameController {
|
|
3746
4843
|
}
|
3747
4844
|
return null;
|
3748
4845
|
}
|
3749
|
-
formActionIsVisitable(form, submitter) {
|
3750
|
-
const action = getAction(form, submitter);
|
4846
|
+
#formActionIsVisitable(form, submitter) {
|
4847
|
+
const action = getAction$1(form, submitter);
|
3751
4848
|
return locationIsVisitable(expandURL(action), this.rootLocation);
|
3752
4849
|
}
|
3753
|
-
shouldInterceptNavigation(element, submitter) {
|
4850
|
+
#shouldInterceptNavigation(element, submitter) {
|
3754
4851
|
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
3755
|
-
if (element instanceof HTMLFormElement && !this
|
4852
|
+
if (element instanceof HTMLFormElement && !this.#formActionIsVisitable(element, submitter)) {
|
3756
4853
|
return false;
|
3757
4854
|
}
|
3758
4855
|
if (!this.enabled || id == "_top") {
|
@@ -3784,46 +4881,43 @@ class FrameController {
|
|
3784
4881
|
}
|
3785
4882
|
}
|
3786
4883
|
set sourceURL(sourceURL) {
|
3787
|
-
this
|
3788
|
-
this.element.src = sourceURL
|
4884
|
+
this.#ignoringChangesToAttribute("src", (() => {
|
4885
|
+
this.element.src = sourceURL ?? null;
|
3789
4886
|
}));
|
3790
4887
|
}
|
3791
4888
|
get loadingStyle() {
|
3792
4889
|
return this.element.loading;
|
3793
4890
|
}
|
3794
4891
|
get isLoading() {
|
3795
|
-
return this.formSubmission !== undefined || this
|
4892
|
+
return this.formSubmission !== undefined || this.#resolveVisitPromise() !== undefined;
|
3796
4893
|
}
|
3797
4894
|
get complete() {
|
3798
4895
|
return this.element.hasAttribute("complete");
|
3799
4896
|
}
|
3800
4897
|
set complete(value) {
|
3801
|
-
|
3802
|
-
|
3803
|
-
|
3804
|
-
|
3805
|
-
|
3806
|
-
}
|
3807
|
-
}));
|
4898
|
+
if (value) {
|
4899
|
+
this.element.setAttribute("complete", "");
|
4900
|
+
} else {
|
4901
|
+
this.element.removeAttribute("complete");
|
4902
|
+
}
|
3808
4903
|
}
|
3809
4904
|
get isActive() {
|
3810
|
-
return this.element.isActive && this
|
4905
|
+
return this.element.isActive && this.#connected;
|
3811
4906
|
}
|
3812
4907
|
get rootLocation() {
|
3813
|
-
var _a;
|
3814
4908
|
const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
|
3815
|
-
const root =
|
4909
|
+
const root = meta?.content ?? "/";
|
3816
4910
|
return expandURL(root);
|
3817
4911
|
}
|
3818
|
-
isIgnoringChangesTo(attributeName) {
|
3819
|
-
return this
|
4912
|
+
#isIgnoringChangesTo(attributeName) {
|
4913
|
+
return this.#ignoredAttributes.has(attributeName);
|
3820
4914
|
}
|
3821
|
-
ignoringChangesToAttribute(attributeName, callback) {
|
3822
|
-
this
|
4915
|
+
#ignoringChangesToAttribute(attributeName, callback) {
|
4916
|
+
this.#ignoredAttributes.add(attributeName);
|
3823
4917
|
callback();
|
3824
|
-
this
|
4918
|
+
this.#ignoredAttributes.delete(attributeName);
|
3825
4919
|
}
|
3826
|
-
withCurrentNavigationElement(element, callback) {
|
4920
|
+
#withCurrentNavigationElement(element, callback) {
|
3827
4921
|
this.currentNavigationElement = element;
|
3828
4922
|
callback();
|
3829
4923
|
delete this.currentNavigationElement;
|
@@ -3856,6 +4950,38 @@ function activateElement(element, currentURL) {
|
|
3856
4950
|
}
|
3857
4951
|
}
|
3858
4952
|
|
4953
|
+
const StreamActions = {
|
4954
|
+
after() {
|
4955
|
+
this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e.nextSibling)));
|
4956
|
+
},
|
4957
|
+
append() {
|
4958
|
+
this.removeDuplicateTargetChildren();
|
4959
|
+
this.targetElements.forEach((e => e.append(this.templateContent)));
|
4960
|
+
},
|
4961
|
+
before() {
|
4962
|
+
this.targetElements.forEach((e => e.parentElement?.insertBefore(this.templateContent, e)));
|
4963
|
+
},
|
4964
|
+
prepend() {
|
4965
|
+
this.removeDuplicateTargetChildren();
|
4966
|
+
this.targetElements.forEach((e => e.prepend(this.templateContent)));
|
4967
|
+
},
|
4968
|
+
remove() {
|
4969
|
+
this.targetElements.forEach((e => e.remove()));
|
4970
|
+
},
|
4971
|
+
replace() {
|
4972
|
+
this.targetElements.forEach((e => e.replaceWith(this.templateContent)));
|
4973
|
+
},
|
4974
|
+
update() {
|
4975
|
+
this.targetElements.forEach((targetElement => {
|
4976
|
+
targetElement.innerHTML = "";
|
4977
|
+
targetElement.append(this.templateContent);
|
4978
|
+
}));
|
4979
|
+
},
|
4980
|
+
refresh() {
|
4981
|
+
session.refresh(this.baseURI, this.requestId);
|
4982
|
+
}
|
4983
|
+
};
|
4984
|
+
|
3859
4985
|
class StreamElement extends HTMLElement {
|
3860
4986
|
static async renderElement(newElement) {
|
3861
4987
|
await newElement.performAction();
|
@@ -3870,11 +4996,10 @@ class StreamElement extends HTMLElement {
|
|
3870
4996
|
}
|
3871
4997
|
}
|
3872
4998
|
async render() {
|
3873
|
-
|
3874
|
-
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
|
4999
|
+
return this.renderPromise ??= (async () => {
|
3875
5000
|
const event = this.beforeRenderEvent;
|
3876
5001
|
if (this.dispatchEvent(event)) {
|
3877
|
-
await
|
5002
|
+
await nextRepaint();
|
3878
5003
|
await event.detail.render(this);
|
3879
5004
|
}
|
3880
5005
|
})();
|
@@ -3882,15 +5007,14 @@ class StreamElement extends HTMLElement {
|
|
3882
5007
|
disconnect() {
|
3883
5008
|
try {
|
3884
5009
|
this.remove();
|
3885
|
-
} catch
|
5010
|
+
} catch {}
|
3886
5011
|
}
|
3887
5012
|
removeDuplicateTargetChildren() {
|
3888
5013
|
this.duplicateChildren.forEach((c => c.remove()));
|
3889
5014
|
}
|
3890
5015
|
get duplicateChildren() {
|
3891
|
-
var _a;
|
3892
5016
|
const existingChildren = this.targetElements.flatMap((e => [ ...e.children ])).filter((c => !!c.id));
|
3893
|
-
const newChildrenIds = [ ...
|
5017
|
+
const newChildrenIds = [ ...this.templateContent?.children || [] ].filter((c => !!c.id)).map((c => c.id));
|
3894
5018
|
return existingChildren.filter((c => newChildrenIds.includes(c.id)));
|
3895
5019
|
}
|
3896
5020
|
get performAction() {
|
@@ -3899,9 +5023,9 @@ class StreamElement extends HTMLElement {
|
|
3899
5023
|
if (actionFunction) {
|
3900
5024
|
return actionFunction;
|
3901
5025
|
}
|
3902
|
-
this
|
5026
|
+
this.#raise("unknown action");
|
3903
5027
|
}
|
3904
|
-
this
|
5028
|
+
this.#raise("action attribute is missing");
|
3905
5029
|
}
|
3906
5030
|
get targetElements() {
|
3907
5031
|
if (this.target) {
|
@@ -3909,7 +5033,7 @@ class StreamElement extends HTMLElement {
|
|
3909
5033
|
} else if (this.targets) {
|
3910
5034
|
return this.targetElementsByQuery;
|
3911
5035
|
} else {
|
3912
|
-
this
|
5036
|
+
this.#raise("target or targets attribute is missing");
|
3913
5037
|
}
|
3914
5038
|
}
|
3915
5039
|
get templateContent() {
|
@@ -3923,7 +5047,7 @@ class StreamElement extends HTMLElement {
|
|
3923
5047
|
} else if (this.firstElementChild instanceof HTMLTemplateElement) {
|
3924
5048
|
return this.firstElementChild;
|
3925
5049
|
}
|
3926
|
-
this
|
5050
|
+
this.#raise("first child element must be a <template> element");
|
3927
5051
|
}
|
3928
5052
|
get action() {
|
3929
5053
|
return this.getAttribute("action");
|
@@ -3934,12 +5058,14 @@ class StreamElement extends HTMLElement {
|
|
3934
5058
|
get targets() {
|
3935
5059
|
return this.getAttribute("targets");
|
3936
5060
|
}
|
3937
|
-
|
5061
|
+
get requestId() {
|
5062
|
+
return this.getAttribute("request-id");
|
5063
|
+
}
|
5064
|
+
#raise(message) {
|
3938
5065
|
throw new Error(`${this.description}: ${message}`);
|
3939
5066
|
}
|
3940
5067
|
get description() {
|
3941
|
-
|
3942
|
-
return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
|
5068
|
+
return (this.outerHTML.match(/<[^>]+>/) ?? [])[0] ?? "<turbo-stream>";
|
3943
5069
|
}
|
3944
5070
|
get beforeRenderEvent() {
|
3945
5071
|
return new CustomEvent("turbo:before-stream-render", {
|
@@ -3952,8 +5078,7 @@ class StreamElement extends HTMLElement {
|
|
3952
5078
|
});
|
3953
5079
|
}
|
3954
5080
|
get targetElementsById() {
|
3955
|
-
|
3956
|
-
const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
|
5081
|
+
const element = this.ownerDocument?.getElementById(this.target);
|
3957
5082
|
if (element !== null) {
|
3958
5083
|
return [ element ];
|
3959
5084
|
} else {
|
@@ -3961,8 +5086,7 @@ class StreamElement extends HTMLElement {
|
|
3961
5086
|
}
|
3962
5087
|
}
|
3963
5088
|
get targetElementsByQuery() {
|
3964
|
-
|
3965
|
-
const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);
|
5089
|
+
const elements = this.ownerDocument?.querySelectorAll(this.targets);
|
3966
5090
|
if (elements.length !== 0) {
|
3967
5091
|
return Array.prototype.slice.call(elements);
|
3968
5092
|
} else {
|
@@ -3972,16 +5096,14 @@ class StreamElement extends HTMLElement {
|
|
3972
5096
|
}
|
3973
5097
|
|
3974
5098
|
class StreamSourceElement extends HTMLElement {
|
3975
|
-
|
3976
|
-
super(...arguments);
|
3977
|
-
this.streamSource = null;
|
3978
|
-
}
|
5099
|
+
streamSource=null;
|
3979
5100
|
connectedCallback() {
|
3980
5101
|
this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);
|
3981
5102
|
connectStreamSource(this.streamSource);
|
3982
5103
|
}
|
3983
5104
|
disconnectedCallback() {
|
3984
5105
|
if (this.streamSource) {
|
5106
|
+
this.streamSource.close();
|
3985
5107
|
disconnectStreamSource(this.streamSource);
|
3986
5108
|
}
|
3987
5109
|
}
|
@@ -4026,16 +5148,21 @@ if (customElements.get("turbo-stream-source") === undefined) {
|
|
4026
5148
|
}
|
4027
5149
|
})();
|
4028
5150
|
|
4029
|
-
window.Turbo =
|
5151
|
+
window.Turbo = {
|
5152
|
+
...Turbo,
|
5153
|
+
StreamActions: StreamActions
|
5154
|
+
};
|
4030
5155
|
|
4031
5156
|
start();
|
4032
5157
|
|
4033
|
-
var
|
5158
|
+
var Turbo$1 = Object.freeze({
|
4034
5159
|
__proto__: null,
|
5160
|
+
FetchEnctype: FetchEnctype,
|
5161
|
+
FetchMethod: FetchMethod,
|
5162
|
+
FetchRequest: FetchRequest,
|
5163
|
+
FetchResponse: FetchResponse,
|
4035
5164
|
FrameElement: FrameElement,
|
4036
|
-
|
4037
|
-
return FrameLoadingStyle;
|
4038
|
-
},
|
5165
|
+
FrameLoadingStyle: FrameLoadingStyle,
|
4039
5166
|
FrameRenderer: FrameRenderer,
|
4040
5167
|
PageRenderer: PageRenderer,
|
4041
5168
|
PageSnapshot: PageSnapshot,
|
@@ -4046,6 +5173,10 @@ var turbo_es2017Esm = Object.freeze({
|
|
4046
5173
|
clearCache: clearCache,
|
4047
5174
|
connectStreamSource: connectStreamSource,
|
4048
5175
|
disconnectStreamSource: disconnectStreamSource,
|
5176
|
+
fetch: fetchWithTurboHeaders,
|
5177
|
+
fetchEnctypeFromString: fetchEnctypeFromString,
|
5178
|
+
fetchMethodFromString: fetchMethodFromString,
|
5179
|
+
isSafe: isSafe,
|
4049
5180
|
navigator: navigator$1,
|
4050
5181
|
registerAdapter: registerAdapter,
|
4051
5182
|
renderStreamMessage: renderStreamMessage,
|
@@ -4060,14 +5191,14 @@ var turbo_es2017Esm = Object.freeze({
|
|
4060
5191
|
let consumer;
|
4061
5192
|
|
4062
5193
|
async function getConsumer() {
|
4063
|
-
return consumer || setConsumer(createConsumer().then(setConsumer));
|
5194
|
+
return consumer || setConsumer(createConsumer$1().then(setConsumer));
|
4064
5195
|
}
|
4065
5196
|
|
4066
5197
|
function setConsumer(newConsumer) {
|
4067
5198
|
return consumer = newConsumer;
|
4068
5199
|
}
|
4069
5200
|
|
4070
|
-
async function createConsumer() {
|
5201
|
+
async function createConsumer$1() {
|
4071
5202
|
const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
|
4072
5203
|
return index;
|
4073
5204
|
}));
|
@@ -4083,7 +5214,7 @@ var cable = Object.freeze({
|
|
4083
5214
|
__proto__: null,
|
4084
5215
|
getConsumer: getConsumer,
|
4085
5216
|
setConsumer: setConsumer,
|
4086
|
-
createConsumer: createConsumer,
|
5217
|
+
createConsumer: createConsumer$1,
|
4087
5218
|
subscribeTo: subscribeTo
|
4088
5219
|
});
|
4089
5220
|
|
@@ -4193,6 +5324,8 @@ function isBodyInit(body) {
|
|
4193
5324
|
return body instanceof FormData || body instanceof URLSearchParams;
|
4194
5325
|
}
|
4195
5326
|
|
5327
|
+
window.Turbo = Turbo$1;
|
5328
|
+
|
4196
5329
|
addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
|
4197
5330
|
|
4198
5331
|
var adapters = {
|
@@ -4309,6 +5442,8 @@ ConnectionMonitor.staleThreshold = 6;
|
|
4309
5442
|
|
4310
5443
|
ConnectionMonitor.reconnectionBackoffRate = .15;
|
4311
5444
|
|
5445
|
+
var ConnectionMonitor$1 = ConnectionMonitor;
|
5446
|
+
|
4312
5447
|
var INTERNAL = {
|
4313
5448
|
message_types: {
|
4314
5449
|
welcome: "welcome",
|
@@ -4320,7 +5455,8 @@ var INTERNAL = {
|
|
4320
5455
|
disconnect_reasons: {
|
4321
5456
|
unauthorized: "unauthorized",
|
4322
5457
|
invalid_request: "invalid_request",
|
4323
|
-
server_restart: "server_restart"
|
5458
|
+
server_restart: "server_restart",
|
5459
|
+
remote: "remote"
|
4324
5460
|
},
|
4325
5461
|
default_mount_path: "/cable",
|
4326
5462
|
protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
|
@@ -4337,7 +5473,7 @@ class Connection {
|
|
4337
5473
|
this.open = this.open.bind(this);
|
4338
5474
|
this.consumer = consumer;
|
4339
5475
|
this.subscriptions = this.consumer.subscriptions;
|
4340
|
-
this.monitor = new ConnectionMonitor(this);
|
5476
|
+
this.monitor = new ConnectionMonitor$1(this);
|
4341
5477
|
this.disconnected = true;
|
4342
5478
|
}
|
4343
5479
|
send(data) {
|
@@ -4353,11 +5489,12 @@ class Connection {
|
|
4353
5489
|
logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
|
4354
5490
|
return false;
|
4355
5491
|
} else {
|
4356
|
-
|
5492
|
+
const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
|
5493
|
+
logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
|
4357
5494
|
if (this.webSocket) {
|
4358
5495
|
this.uninstallEventHandlers();
|
4359
5496
|
}
|
4360
|
-
this.webSocket = new adapters.WebSocket(this.consumer.url,
|
5497
|
+
this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
|
4361
5498
|
this.installEventHandlers();
|
4362
5499
|
this.monitor.start();
|
4363
5500
|
return true;
|
@@ -4369,7 +5506,7 @@ class Connection {
|
|
4369
5506
|
if (!allowReconnect) {
|
4370
5507
|
this.monitor.stop();
|
4371
5508
|
}
|
4372
|
-
if (this.
|
5509
|
+
if (this.isOpen()) {
|
4373
5510
|
return this.webSocket.close();
|
4374
5511
|
}
|
4375
5512
|
}
|
@@ -4399,6 +5536,9 @@ class Connection {
|
|
4399
5536
|
isActive() {
|
4400
5537
|
return this.isState("open", "connecting");
|
4401
5538
|
}
|
5539
|
+
triedToReconnect() {
|
5540
|
+
return this.monitor.reconnectAttempts > 0;
|
5541
|
+
}
|
4402
5542
|
isProtocolSupported() {
|
4403
5543
|
return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
|
4404
5544
|
}
|
@@ -4438,6 +5578,9 @@ Connection.prototype.events = {
|
|
4438
5578
|
const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
|
4439
5579
|
switch (type) {
|
4440
5580
|
case message_types.welcome:
|
5581
|
+
if (this.triedToReconnect()) {
|
5582
|
+
this.reconnectAttempted = true;
|
5583
|
+
}
|
4441
5584
|
this.monitor.recordConnect();
|
4442
5585
|
return this.subscriptions.reload();
|
4443
5586
|
|
@@ -4452,7 +5595,16 @@ Connection.prototype.events = {
|
|
4452
5595
|
|
4453
5596
|
case message_types.confirmation:
|
4454
5597
|
this.subscriptions.confirmSubscription(identifier);
|
4455
|
-
|
5598
|
+
if (this.reconnectAttempted) {
|
5599
|
+
this.reconnectAttempted = false;
|
5600
|
+
return this.subscriptions.notify(identifier, "connected", {
|
5601
|
+
reconnected: true
|
5602
|
+
});
|
5603
|
+
} else {
|
5604
|
+
return this.subscriptions.notify(identifier, "connected", {
|
5605
|
+
reconnected: false
|
5606
|
+
});
|
5607
|
+
}
|
4456
5608
|
|
4457
5609
|
case message_types.rejection:
|
4458
5610
|
return this.subscriptions.reject(identifier);
|
@@ -4487,6 +5639,8 @@ Connection.prototype.events = {
|
|
4487
5639
|
}
|
4488
5640
|
};
|
4489
5641
|
|
5642
|
+
var Connection$1 = Connection;
|
5643
|
+
|
4490
5644
|
const extend = function(object, properties) {
|
4491
5645
|
if (properties != null) {
|
4492
5646
|
for (let key in properties) {
|
@@ -4556,10 +5710,12 @@ class SubscriptionGuarantor {
|
|
4556
5710
|
}
|
4557
5711
|
}
|
4558
5712
|
|
5713
|
+
var SubscriptionGuarantor$1 = SubscriptionGuarantor;
|
5714
|
+
|
4559
5715
|
class Subscriptions {
|
4560
5716
|
constructor(consumer) {
|
4561
5717
|
this.consumer = consumer;
|
4562
|
-
this.guarantor = new SubscriptionGuarantor(this);
|
5718
|
+
this.guarantor = new SubscriptionGuarantor$1(this);
|
4563
5719
|
this.subscriptions = [];
|
4564
5720
|
}
|
4565
5721
|
create(channelName, mixin) {
|
@@ -4636,7 +5792,8 @@ class Consumer {
|
|
4636
5792
|
constructor(url) {
|
4637
5793
|
this._url = url;
|
4638
5794
|
this.subscriptions = new Subscriptions(this);
|
4639
|
-
this.connection = new Connection(this);
|
5795
|
+
this.connection = new Connection$1(this);
|
5796
|
+
this.subprotocols = [];
|
4640
5797
|
}
|
4641
5798
|
get url() {
|
4642
5799
|
return createWebSocketURL(this._url);
|
@@ -4657,6 +5814,9 @@ class Consumer {
|
|
4657
5814
|
return this.connection.open();
|
4658
5815
|
}
|
4659
5816
|
}
|
5817
|
+
addSubProtocol(subprotocol) {
|
5818
|
+
this.subprotocols = [ ...this.subprotocols, subprotocol ];
|
5819
|
+
}
|
4660
5820
|
}
|
4661
5821
|
|
4662
5822
|
function createWebSocketURL(url) {
|
@@ -4674,7 +5834,7 @@ function createWebSocketURL(url) {
|
|
4674
5834
|
}
|
4675
5835
|
}
|
4676
5836
|
|
4677
|
-
function createConsumer
|
5837
|
+
function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
|
4678
5838
|
return new Consumer(url);
|
4679
5839
|
}
|
4680
5840
|
|
@@ -4687,18 +5847,18 @@ function getConfig(name) {
|
|
4687
5847
|
|
4688
5848
|
var index = Object.freeze({
|
4689
5849
|
__proto__: null,
|
4690
|
-
Connection: Connection,
|
4691
|
-
ConnectionMonitor: ConnectionMonitor,
|
5850
|
+
Connection: Connection$1,
|
5851
|
+
ConnectionMonitor: ConnectionMonitor$1,
|
4692
5852
|
Consumer: Consumer,
|
4693
5853
|
INTERNAL: INTERNAL,
|
4694
5854
|
Subscription: Subscription,
|
4695
5855
|
Subscriptions: Subscriptions,
|
4696
|
-
SubscriptionGuarantor: SubscriptionGuarantor,
|
5856
|
+
SubscriptionGuarantor: SubscriptionGuarantor$1,
|
4697
5857
|
adapters: adapters,
|
4698
5858
|
createWebSocketURL: createWebSocketURL,
|
4699
5859
|
logger: logger,
|
4700
|
-
createConsumer: createConsumer
|
5860
|
+
createConsumer: createConsumer,
|
4701
5861
|
getConfig: getConfig
|
4702
5862
|
});
|
4703
5863
|
|
4704
|
-
export {
|
5864
|
+
export { Turbo$1 as Turbo, cable };
|