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