@hotwired/turbo 7.0.0-rc.5 → 7.1.0-rc.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.
- package/CHANGELOG.md +3 -0
- package/README.md +0 -2
- package/dist/turbo.es2017-esm.js +272 -70
- package/dist/turbo.es2017-umd.js +273 -70
- package/dist/types/core/drive/form_submission.d.ts +3 -0
- package/dist/types/core/drive/page_view.d.ts +2 -2
- package/dist/types/core/drive/visit.d.ts +6 -0
- package/dist/types/core/frames/frame_controller.d.ts +8 -3
- package/dist/types/core/frames/frame_redirector.d.ts +1 -0
- package/dist/types/core/index.d.ts +1 -0
- package/dist/types/core/renderer.d.ts +2 -1
- package/dist/types/core/session.d.ts +1 -1
- package/dist/types/core/url.d.ts +2 -0
- package/dist/types/elements/frame_element.d.ts +2 -0
- package/dist/types/polyfills/index.d.ts +1 -0
- package/dist/types/tests/functional/drive_disabled_tests.d.ts +2 -0
- package/dist/types/tests/functional/drive_tests.d.ts +1 -0
- package/dist/types/tests/functional/form_submission_tests.d.ts +29 -3
- package/dist/types/tests/functional/frame_tests.d.ts +23 -1
- package/dist/types/tests/functional/navigation_tests.d.ts +4 -2
- package/dist/types/util.d.ts +3 -0
- package/package.json +7 -2
- package/dist/turbo.es2017-esm.js.map +0 -1
- package/dist/turbo.es2017-umd.js.map +0 -1
- package/dist/turbo.es5-umd.js +0 -3954
- package/dist/turbo.es5-umd.js.map +0 -1
package/dist/turbo.es2017-esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Turbo 7.
|
|
2
|
+
Turbo 7.1.0-rc.1
|
|
3
3
|
Copyright © 2021 Basecamp, LLC
|
|
4
4
|
*/
|
|
5
5
|
(function () {
|
|
@@ -20,6 +20,58 @@ Copyright © 2021 Basecamp, LLC
|
|
|
20
20
|
Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);
|
|
21
21
|
})();
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* The MIT License (MIT)
|
|
25
|
+
*
|
|
26
|
+
* Copyright (c) 2019 Javan Makhmali
|
|
27
|
+
*
|
|
28
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
29
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
30
|
+
* in the Software without restriction, including without limitation the rights
|
|
31
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
32
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
33
|
+
* furnished to do so, subject to the following conditions:
|
|
34
|
+
*
|
|
35
|
+
* The above copyright notice and this permission notice shall be included in
|
|
36
|
+
* all copies or substantial portions of the Software.
|
|
37
|
+
*
|
|
38
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
39
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
40
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
41
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
42
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
43
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
44
|
+
* THE SOFTWARE.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
(function(prototype) {
|
|
48
|
+
if (typeof prototype.requestSubmit == "function") return
|
|
49
|
+
|
|
50
|
+
prototype.requestSubmit = function(submitter) {
|
|
51
|
+
if (submitter) {
|
|
52
|
+
validateSubmitter(submitter, this);
|
|
53
|
+
submitter.click();
|
|
54
|
+
} else {
|
|
55
|
+
submitter = document.createElement("input");
|
|
56
|
+
submitter.type = "submit";
|
|
57
|
+
submitter.hidden = true;
|
|
58
|
+
this.appendChild(submitter);
|
|
59
|
+
submitter.click();
|
|
60
|
+
this.removeChild(submitter);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function validateSubmitter(submitter, form) {
|
|
65
|
+
submitter instanceof HTMLElement || raise(TypeError, "parameter 1 is not of type 'HTMLElement'");
|
|
66
|
+
submitter.type == "submit" || raise(TypeError, "The specified element is not a submit button");
|
|
67
|
+
submitter.form == form || raise(DOMException, "The specified element is not owned by this form element", "NotFoundError");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function raise(errorConstructor, message, name) {
|
|
71
|
+
throw new errorConstructor("Failed to execute 'requestSubmit' on 'HTMLFormElement': " + message + ".", name)
|
|
72
|
+
}
|
|
73
|
+
})(HTMLFormElement.prototype);
|
|
74
|
+
|
|
23
75
|
const submittersByForm = new WeakMap;
|
|
24
76
|
function findSubmitterFromClickTarget(target) {
|
|
25
77
|
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
|
@@ -33,12 +85,20 @@ function clickCaptured(event) {
|
|
|
33
85
|
}
|
|
34
86
|
}
|
|
35
87
|
(function () {
|
|
36
|
-
if ("SubmitEvent" in window)
|
|
37
|
-
return;
|
|
38
88
|
if ("submitter" in Event.prototype)
|
|
39
89
|
return;
|
|
90
|
+
let prototype;
|
|
91
|
+
if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
|
|
92
|
+
prototype = window.SubmitEvent.prototype;
|
|
93
|
+
}
|
|
94
|
+
else if ("SubmitEvent" in window) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
prototype = window.Event.prototype;
|
|
99
|
+
}
|
|
40
100
|
addEventListener("click", clickCaptured, true);
|
|
41
|
-
Object.defineProperty(
|
|
101
|
+
Object.defineProperty(prototype, "submitter", {
|
|
42
102
|
get() {
|
|
43
103
|
if (this.type == "submit" && this.target instanceof HTMLFormElement) {
|
|
44
104
|
return submittersByForm.get(this.target);
|
|
@@ -157,6 +217,10 @@ function getAnchor(url) {
|
|
|
157
217
|
return anchorMatch[1];
|
|
158
218
|
}
|
|
159
219
|
}
|
|
220
|
+
function getAction(form, submitter) {
|
|
221
|
+
const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formaction")) || form.getAttribute("action") || form.action;
|
|
222
|
+
return expandURL(action);
|
|
223
|
+
}
|
|
160
224
|
function getExtension(url) {
|
|
161
225
|
return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
|
|
162
226
|
}
|
|
@@ -167,6 +231,9 @@ function isPrefixedBy(baseURL, url) {
|
|
|
167
231
|
const prefix = getPrefix(url);
|
|
168
232
|
return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
|
|
169
233
|
}
|
|
234
|
+
function locationIsVisitable(location, rootLocation) {
|
|
235
|
+
return isPrefixedBy(location, rootLocation) && isHTML(location);
|
|
236
|
+
}
|
|
170
237
|
function getRequestURL(url) {
|
|
171
238
|
const anchor = getAnchor(url);
|
|
172
239
|
return anchor != null
|
|
@@ -289,6 +356,29 @@ function uuid() {
|
|
|
289
356
|
}
|
|
290
357
|
}).join("");
|
|
291
358
|
}
|
|
359
|
+
function getAttribute(attributeName, ...elements) {
|
|
360
|
+
for (const value of elements.map(element => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {
|
|
361
|
+
if (typeof value == "string")
|
|
362
|
+
return value;
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
function markAsBusy(...elements) {
|
|
367
|
+
for (const element of elements) {
|
|
368
|
+
if (element.localName == "turbo-frame") {
|
|
369
|
+
element.setAttribute("busy", "");
|
|
370
|
+
}
|
|
371
|
+
element.setAttribute("aria-busy", "true");
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function clearBusyState(...elements) {
|
|
375
|
+
for (const element of elements) {
|
|
376
|
+
if (element.localName == "turbo-frame") {
|
|
377
|
+
element.removeAttribute("busy");
|
|
378
|
+
}
|
|
379
|
+
element.removeAttribute("aria-busy");
|
|
380
|
+
}
|
|
381
|
+
}
|
|
292
382
|
|
|
293
383
|
var FetchMethod;
|
|
294
384
|
(function (FetchMethod) {
|
|
@@ -518,6 +608,9 @@ class FormSubmission {
|
|
|
518
608
|
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
|
|
519
609
|
this.mustRedirect = mustRedirect;
|
|
520
610
|
}
|
|
611
|
+
static confirmMethod(message, element) {
|
|
612
|
+
return confirm(message);
|
|
613
|
+
}
|
|
521
614
|
get method() {
|
|
522
615
|
var _a;
|
|
523
616
|
const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
|
|
@@ -551,8 +644,20 @@ class FormSubmission {
|
|
|
551
644
|
return entries.concat(typeof value == "string" ? [[name, value]] : []);
|
|
552
645
|
}, []);
|
|
553
646
|
}
|
|
647
|
+
get confirmationMessage() {
|
|
648
|
+
return this.formElement.getAttribute("data-turbo-confirm");
|
|
649
|
+
}
|
|
650
|
+
get needsConfirmation() {
|
|
651
|
+
return this.confirmationMessage !== null;
|
|
652
|
+
}
|
|
554
653
|
async start() {
|
|
555
654
|
const { initialized, requesting } = FormSubmissionState;
|
|
655
|
+
if (this.needsConfirmation) {
|
|
656
|
+
const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
|
|
657
|
+
if (!answer) {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
556
661
|
if (this.state == initialized) {
|
|
557
662
|
this.state = requesting;
|
|
558
663
|
return this.fetchRequest.perform();
|
|
@@ -576,7 +681,9 @@ class FormSubmission {
|
|
|
576
681
|
}
|
|
577
682
|
}
|
|
578
683
|
requestStarted(request) {
|
|
684
|
+
var _a;
|
|
579
685
|
this.state = FormSubmissionState.waiting;
|
|
686
|
+
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
|
|
580
687
|
dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } });
|
|
581
688
|
this.delegate.formSubmissionStarted(this);
|
|
582
689
|
}
|
|
@@ -606,7 +713,9 @@ class FormSubmission {
|
|
|
606
713
|
this.delegate.formSubmissionErrored(this, error);
|
|
607
714
|
}
|
|
608
715
|
requestFinished(request) {
|
|
716
|
+
var _a;
|
|
609
717
|
this.state = FormSubmissionState.stopped;
|
|
718
|
+
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
|
|
610
719
|
dispatch("turbo:submit-end", { target: this.formElement, detail: Object.assign({ formSubmission: this }, this.result) });
|
|
611
720
|
this.delegate.formSubmissionFinished(this);
|
|
612
721
|
}
|
|
@@ -682,10 +791,11 @@ class Snapshot {
|
|
|
682
791
|
class FormInterceptor {
|
|
683
792
|
constructor(delegate, element) {
|
|
684
793
|
this.submitBubbled = ((event) => {
|
|
685
|
-
|
|
686
|
-
|
|
794
|
+
const form = event.target;
|
|
795
|
+
if (form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
|
|
687
796
|
const submitter = event.submitter || undefined;
|
|
688
|
-
|
|
797
|
+
const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
|
|
798
|
+
if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
|
|
689
799
|
event.preventDefault();
|
|
690
800
|
event.stopImmediatePropagation();
|
|
691
801
|
this.delegate.formSubmissionIntercepted(form, submitter);
|
|
@@ -900,10 +1010,11 @@ function createPlaceholderForPermanentElement(permanentElement) {
|
|
|
900
1010
|
}
|
|
901
1011
|
|
|
902
1012
|
class Renderer {
|
|
903
|
-
constructor(currentSnapshot, newSnapshot, isPreview) {
|
|
1013
|
+
constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
|
|
904
1014
|
this.currentSnapshot = currentSnapshot;
|
|
905
1015
|
this.newSnapshot = newSnapshot;
|
|
906
1016
|
this.isPreview = isPreview;
|
|
1017
|
+
this.willRender = willRender;
|
|
907
1018
|
this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
|
|
908
1019
|
}
|
|
909
1020
|
get shouldRender() {
|
|
@@ -1279,7 +1390,9 @@ var VisitState;
|
|
|
1279
1390
|
})(VisitState || (VisitState = {}));
|
|
1280
1391
|
const defaultOptions = {
|
|
1281
1392
|
action: "advance",
|
|
1282
|
-
historyChanged: false
|
|
1393
|
+
historyChanged: false,
|
|
1394
|
+
visitCachedSnapshot: () => { },
|
|
1395
|
+
willRender: true,
|
|
1283
1396
|
};
|
|
1284
1397
|
var SystemStatusCode;
|
|
1285
1398
|
(function (SystemStatusCode) {
|
|
@@ -1299,13 +1412,16 @@ class Visit {
|
|
|
1299
1412
|
this.delegate = delegate;
|
|
1300
1413
|
this.location = location;
|
|
1301
1414
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
|
1302
|
-
const { action, historyChanged, referrer, snapshotHTML, response } = Object.assign(Object.assign({}, defaultOptions), options);
|
|
1415
|
+
const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender } = Object.assign(Object.assign({}, defaultOptions), options);
|
|
1303
1416
|
this.action = action;
|
|
1304
1417
|
this.historyChanged = historyChanged;
|
|
1305
1418
|
this.referrer = referrer;
|
|
1306
1419
|
this.snapshotHTML = snapshotHTML;
|
|
1307
1420
|
this.response = response;
|
|
1308
1421
|
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
|
1422
|
+
this.visitCachedSnapshot = visitCachedSnapshot;
|
|
1423
|
+
this.willRender = willRender;
|
|
1424
|
+
this.scrolled = !willRender;
|
|
1309
1425
|
}
|
|
1310
1426
|
get adapter() {
|
|
1311
1427
|
return this.delegate.adapter;
|
|
@@ -1407,7 +1523,7 @@ class Visit {
|
|
|
1407
1523
|
if (this.view.renderPromise)
|
|
1408
1524
|
await this.view.renderPromise;
|
|
1409
1525
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
|
1410
|
-
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML));
|
|
1526
|
+
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
|
|
1411
1527
|
this.adapter.visitRendered(this);
|
|
1412
1528
|
this.complete();
|
|
1413
1529
|
}
|
|
@@ -1447,7 +1563,7 @@ class Visit {
|
|
|
1447
1563
|
else {
|
|
1448
1564
|
if (this.view.renderPromise)
|
|
1449
1565
|
await this.view.renderPromise;
|
|
1450
|
-
await this.view.renderPage(snapshot, isPreview);
|
|
1566
|
+
await this.view.renderPage(snapshot, isPreview, this.willRender);
|
|
1451
1567
|
this.adapter.visitRendered(this);
|
|
1452
1568
|
if (!isPreview) {
|
|
1453
1569
|
this.complete();
|
|
@@ -1457,7 +1573,8 @@ class Visit {
|
|
|
1457
1573
|
}
|
|
1458
1574
|
}
|
|
1459
1575
|
followRedirect() {
|
|
1460
|
-
|
|
1576
|
+
var _a;
|
|
1577
|
+
if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
|
|
1461
1578
|
this.adapter.visitProposedToLocation(this.redirectedToLocation, {
|
|
1462
1579
|
action: 'replace',
|
|
1463
1580
|
response: this.response
|
|
@@ -1480,25 +1597,27 @@ class Visit {
|
|
|
1480
1597
|
}
|
|
1481
1598
|
async requestSucceededWithResponse(request, response) {
|
|
1482
1599
|
const responseHTML = await response.responseHTML;
|
|
1600
|
+
const { redirected, statusCode } = response;
|
|
1483
1601
|
if (responseHTML == undefined) {
|
|
1484
|
-
this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch });
|
|
1602
|
+
this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
|
|
1485
1603
|
}
|
|
1486
1604
|
else {
|
|
1487
1605
|
this.redirectedToLocation = response.redirected ? response.location : undefined;
|
|
1488
|
-
this.recordResponse({ statusCode:
|
|
1606
|
+
this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
|
|
1489
1607
|
}
|
|
1490
1608
|
}
|
|
1491
1609
|
async requestFailedWithResponse(request, response) {
|
|
1492
1610
|
const responseHTML = await response.responseHTML;
|
|
1611
|
+
const { redirected, statusCode } = response;
|
|
1493
1612
|
if (responseHTML == undefined) {
|
|
1494
|
-
this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch });
|
|
1613
|
+
this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
|
|
1495
1614
|
}
|
|
1496
1615
|
else {
|
|
1497
|
-
this.recordResponse({ statusCode:
|
|
1616
|
+
this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
|
|
1498
1617
|
}
|
|
1499
1618
|
}
|
|
1500
1619
|
requestErrored(request, error) {
|
|
1501
|
-
this.recordResponse({ statusCode: SystemStatusCode.networkFailure });
|
|
1620
|
+
this.recordResponse({ statusCode: SystemStatusCode.networkFailure, redirected: false });
|
|
1502
1621
|
}
|
|
1503
1622
|
requestFinished() {
|
|
1504
1623
|
this.finishRequest();
|
|
@@ -1555,12 +1674,12 @@ class Visit {
|
|
|
1555
1674
|
return !this.hasCachedSnapshot();
|
|
1556
1675
|
}
|
|
1557
1676
|
else {
|
|
1558
|
-
return
|
|
1677
|
+
return this.willRender;
|
|
1559
1678
|
}
|
|
1560
1679
|
}
|
|
1561
1680
|
cacheSnapshot() {
|
|
1562
1681
|
if (!this.snapshotCached) {
|
|
1563
|
-
this.view.cacheSnapshot();
|
|
1682
|
+
this.view.cacheSnapshot().then(snapshot => snapshot && this.visitCachedSnapshot(snapshot));
|
|
1564
1683
|
this.snapshotCached = true;
|
|
1565
1684
|
}
|
|
1566
1685
|
}
|
|
@@ -1710,7 +1829,7 @@ class FormSubmitObserver {
|
|
|
1710
1829
|
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
|
1711
1830
|
const submitter = event.submitter || undefined;
|
|
1712
1831
|
if (form) {
|
|
1713
|
-
const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
|
|
1832
|
+
const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
|
|
1714
1833
|
if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
|
|
1715
1834
|
event.preventDefault();
|
|
1716
1835
|
this.delegate.formSubmitted(form, submitter);
|
|
@@ -1754,12 +1873,11 @@ class FrameRedirector {
|
|
|
1754
1873
|
linkClickIntercepted(element, url) {
|
|
1755
1874
|
const frame = this.findFrameElement(element);
|
|
1756
1875
|
if (frame) {
|
|
1757
|
-
frame.
|
|
1758
|
-
frame.src = url;
|
|
1876
|
+
frame.delegate.linkClickIntercepted(element, url);
|
|
1759
1877
|
}
|
|
1760
1878
|
}
|
|
1761
1879
|
shouldInterceptFormSubmission(element, submitter) {
|
|
1762
|
-
return this.
|
|
1880
|
+
return this.shouldSubmit(element, submitter);
|
|
1763
1881
|
}
|
|
1764
1882
|
formSubmissionIntercepted(element, submitter) {
|
|
1765
1883
|
const frame = this.findFrameElement(element, submitter);
|
|
@@ -1768,6 +1886,13 @@ class FrameRedirector {
|
|
|
1768
1886
|
frame.delegate.formSubmissionIntercepted(element, submitter);
|
|
1769
1887
|
}
|
|
1770
1888
|
}
|
|
1889
|
+
shouldSubmit(form, submitter) {
|
|
1890
|
+
var _a;
|
|
1891
|
+
const action = getAction(form, submitter);
|
|
1892
|
+
const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
|
|
1893
|
+
const rootLocation = expandURL((_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/");
|
|
1894
|
+
return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
|
|
1895
|
+
}
|
|
1771
1896
|
shouldRedirect(element, submitter) {
|
|
1772
1897
|
const frame = this.findFrameElement(element, submitter);
|
|
1773
1898
|
return frame ? frame != element.closest("turbo-frame") : false;
|
|
@@ -1925,7 +2050,12 @@ class Navigator {
|
|
|
1925
2050
|
}
|
|
1926
2051
|
proposeVisit(location, options = {}) {
|
|
1927
2052
|
if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
|
|
1928
|
-
|
|
2053
|
+
if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
|
|
2054
|
+
this.delegate.visitProposedToLocation(location, options);
|
|
2055
|
+
}
|
|
2056
|
+
else {
|
|
2057
|
+
window.location.href = location.toString();
|
|
2058
|
+
}
|
|
1929
2059
|
}
|
|
1930
2060
|
}
|
|
1931
2061
|
startVisit(locatable, restorationIdentifier, options = {}) {
|
|
@@ -1936,12 +2066,7 @@ class Navigator {
|
|
|
1936
2066
|
submitForm(form, submitter) {
|
|
1937
2067
|
this.stop();
|
|
1938
2068
|
this.formSubmission = new FormSubmission(this, form, submitter, true);
|
|
1939
|
-
|
|
1940
|
-
this.proposeVisit(this.formSubmission.fetchRequest.url, { action: this.getActionForFormSubmission(this.formSubmission) });
|
|
1941
|
-
}
|
|
1942
|
-
else {
|
|
1943
|
-
this.formSubmission.start();
|
|
1944
|
-
}
|
|
2069
|
+
this.formSubmission.start();
|
|
1945
2070
|
}
|
|
1946
2071
|
stop() {
|
|
1947
2072
|
if (this.formSubmission) {
|
|
@@ -1974,8 +2099,9 @@ class Navigator {
|
|
|
1974
2099
|
if (formSubmission.method != FetchMethod.get) {
|
|
1975
2100
|
this.view.clearSnapshotCache();
|
|
1976
2101
|
}
|
|
1977
|
-
const { statusCode } = fetchResponse;
|
|
1978
|
-
const
|
|
2102
|
+
const { statusCode, redirected } = fetchResponse;
|
|
2103
|
+
const action = this.getActionForFormSubmission(formSubmission);
|
|
2104
|
+
const visitOptions = { action, response: { statusCode, responseHTML, redirected } };
|
|
1979
2105
|
this.proposeVisit(fetchResponse.location, visitOptions);
|
|
1980
2106
|
}
|
|
1981
2107
|
}
|
|
@@ -2027,7 +2153,7 @@ class Navigator {
|
|
|
2027
2153
|
}
|
|
2028
2154
|
getActionForFormSubmission(formSubmission) {
|
|
2029
2155
|
const { formElement, submitter } = formSubmission;
|
|
2030
|
-
const action =
|
|
2156
|
+
const action = getAttribute("data-turbo-action", submitter, formElement);
|
|
2031
2157
|
return isAction(action) ? action : "advance";
|
|
2032
2158
|
}
|
|
2033
2159
|
}
|
|
@@ -2221,7 +2347,9 @@ class PageRenderer extends Renderer {
|
|
|
2221
2347
|
this.mergeHead();
|
|
2222
2348
|
}
|
|
2223
2349
|
async render() {
|
|
2224
|
-
this.
|
|
2350
|
+
if (this.willRender) {
|
|
2351
|
+
this.replaceBody();
|
|
2352
|
+
}
|
|
2225
2353
|
}
|
|
2226
2354
|
finishRendering() {
|
|
2227
2355
|
super.finishRendering();
|
|
@@ -2359,8 +2487,8 @@ class PageView extends View {
|
|
|
2359
2487
|
this.snapshotCache = new SnapshotCache(10);
|
|
2360
2488
|
this.lastRenderedLocation = new URL(location.href);
|
|
2361
2489
|
}
|
|
2362
|
-
renderPage(snapshot, isPreview = false) {
|
|
2363
|
-
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
|
|
2490
|
+
renderPage(snapshot, isPreview = false, willRender = true) {
|
|
2491
|
+
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
|
|
2364
2492
|
return this.render(renderer);
|
|
2365
2493
|
}
|
|
2366
2494
|
renderError(snapshot) {
|
|
@@ -2375,7 +2503,9 @@ class PageView extends View {
|
|
|
2375
2503
|
this.delegate.viewWillCacheSnapshot();
|
|
2376
2504
|
const { snapshot, lastRenderedLocation: location } = this;
|
|
2377
2505
|
await nextEventLoopTick();
|
|
2378
|
-
|
|
2506
|
+
const cachedSnapshot = snapshot.clone();
|
|
2507
|
+
this.snapshotCache.put(location, cachedSnapshot);
|
|
2508
|
+
return cachedSnapshot;
|
|
2379
2509
|
}
|
|
2380
2510
|
}
|
|
2381
2511
|
getCachedSnapshotForLocation(location) {
|
|
@@ -2477,7 +2607,7 @@ class Session {
|
|
|
2477
2607
|
}
|
|
2478
2608
|
willFollowLinkToLocation(link, location) {
|
|
2479
2609
|
return this.elementDriveEnabled(link)
|
|
2480
|
-
&&
|
|
2610
|
+
&& locationIsVisitable(location, this.snapshot.rootLocation)
|
|
2481
2611
|
&& this.applicationAllowsFollowingLinkToLocation(link, location);
|
|
2482
2612
|
}
|
|
2483
2613
|
followedLinkToLocation(link, location) {
|
|
@@ -2485,14 +2615,24 @@ class Session {
|
|
|
2485
2615
|
this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, { action });
|
|
2486
2616
|
}
|
|
2487
2617
|
convertLinkWithMethodClickToFormSubmission(link) {
|
|
2488
|
-
var _a;
|
|
2489
2618
|
const linkMethod = link.getAttribute("data-turbo-method");
|
|
2490
2619
|
if (linkMethod) {
|
|
2491
2620
|
const form = document.createElement("form");
|
|
2492
2621
|
form.method = linkMethod;
|
|
2493
2622
|
form.action = link.getAttribute("href") || "undefined";
|
|
2494
2623
|
form.hidden = true;
|
|
2495
|
-
(
|
|
2624
|
+
if (link.hasAttribute("data-turbo-confirm")) {
|
|
2625
|
+
form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
|
|
2626
|
+
}
|
|
2627
|
+
const frame = this.getTargetFrameForLink(link);
|
|
2628
|
+
if (frame) {
|
|
2629
|
+
form.setAttribute("data-turbo-frame", frame);
|
|
2630
|
+
form.addEventListener("turbo:submit-start", () => form.remove());
|
|
2631
|
+
}
|
|
2632
|
+
else {
|
|
2633
|
+
form.addEventListener("submit", () => form.remove());
|
|
2634
|
+
}
|
|
2635
|
+
document.body.appendChild(form);
|
|
2496
2636
|
return dispatch("submit", { cancelable: true, target: form });
|
|
2497
2637
|
}
|
|
2498
2638
|
else {
|
|
@@ -2522,7 +2662,10 @@ class Session {
|
|
|
2522
2662
|
this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
|
|
2523
2663
|
}
|
|
2524
2664
|
willSubmitForm(form, submitter) {
|
|
2525
|
-
|
|
2665
|
+
const action = getAction(form, submitter);
|
|
2666
|
+
return this.elementDriveEnabled(form)
|
|
2667
|
+
&& (!submitter || this.elementDriveEnabled(submitter))
|
|
2668
|
+
&& locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
|
|
2526
2669
|
}
|
|
2527
2670
|
formSubmitted(form, submitter) {
|
|
2528
2671
|
this.navigator.submitForm(form, submitter);
|
|
@@ -2578,6 +2721,7 @@ class Session {
|
|
|
2578
2721
|
return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
|
|
2579
2722
|
}
|
|
2580
2723
|
notifyApplicationAfterVisitingLocation(location, action) {
|
|
2724
|
+
markAsBusy(document.documentElement);
|
|
2581
2725
|
return dispatch("turbo:visit", { detail: { url: location.href, action } });
|
|
2582
2726
|
}
|
|
2583
2727
|
notifyApplicationBeforeCachingSnapshot() {
|
|
@@ -2590,6 +2734,7 @@ class Session {
|
|
|
2590
2734
|
return dispatch("turbo:render");
|
|
2591
2735
|
}
|
|
2592
2736
|
notifyApplicationAfterPageLoad(timing = {}) {
|
|
2737
|
+
clearBusyState(document.documentElement);
|
|
2593
2738
|
return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
|
|
2594
2739
|
}
|
|
2595
2740
|
notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
|
|
@@ -2624,8 +2769,17 @@ class Session {
|
|
|
2624
2769
|
const action = link.getAttribute("data-turbo-action");
|
|
2625
2770
|
return isAction(action) ? action : "advance";
|
|
2626
2771
|
}
|
|
2627
|
-
|
|
2628
|
-
|
|
2772
|
+
getTargetFrameForLink(link) {
|
|
2773
|
+
const frame = link.getAttribute("data-turbo-frame");
|
|
2774
|
+
if (frame) {
|
|
2775
|
+
return frame;
|
|
2776
|
+
}
|
|
2777
|
+
else {
|
|
2778
|
+
const container = link.closest("turbo-frame");
|
|
2779
|
+
if (container) {
|
|
2780
|
+
return container.id;
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2629
2783
|
}
|
|
2630
2784
|
get snapshot() {
|
|
2631
2785
|
return this.view.snapshot;
|
|
@@ -2643,7 +2797,7 @@ const deprecatedLocationPropertyDescriptors = {
|
|
|
2643
2797
|
};
|
|
2644
2798
|
|
|
2645
2799
|
const session = new Session;
|
|
2646
|
-
const { navigator } = session;
|
|
2800
|
+
const { navigator: navigator$1 } = session;
|
|
2647
2801
|
function start() {
|
|
2648
2802
|
session.start();
|
|
2649
2803
|
}
|
|
@@ -2668,10 +2822,13 @@ function clearCache() {
|
|
|
2668
2822
|
function setProgressBarDelay(delay) {
|
|
2669
2823
|
session.setProgressBarDelay(delay);
|
|
2670
2824
|
}
|
|
2825
|
+
function setConfirmMethod(confirmMethod) {
|
|
2826
|
+
FormSubmission.confirmMethod = confirmMethod;
|
|
2827
|
+
}
|
|
2671
2828
|
|
|
2672
2829
|
var Turbo = /*#__PURE__*/Object.freeze({
|
|
2673
2830
|
__proto__: null,
|
|
2674
|
-
navigator: navigator,
|
|
2831
|
+
navigator: navigator$1,
|
|
2675
2832
|
session: session,
|
|
2676
2833
|
PageRenderer: PageRenderer,
|
|
2677
2834
|
PageSnapshot: PageSnapshot,
|
|
@@ -2682,11 +2839,14 @@ var Turbo = /*#__PURE__*/Object.freeze({
|
|
|
2682
2839
|
disconnectStreamSource: disconnectStreamSource,
|
|
2683
2840
|
renderStreamMessage: renderStreamMessage,
|
|
2684
2841
|
clearCache: clearCache,
|
|
2685
|
-
setProgressBarDelay: setProgressBarDelay
|
|
2842
|
+
setProgressBarDelay: setProgressBarDelay,
|
|
2843
|
+
setConfirmMethod: setConfirmMethod
|
|
2686
2844
|
});
|
|
2687
2845
|
|
|
2688
2846
|
class FrameController {
|
|
2689
2847
|
constructor(element) {
|
|
2848
|
+
this.fetchResponseLoaded = (fetchResponse) => { };
|
|
2849
|
+
this.currentFetchRequest = null;
|
|
2690
2850
|
this.resolveVisitPromise = () => { };
|
|
2691
2851
|
this.connected = false;
|
|
2692
2852
|
this.hasBeenLoaded = false;
|
|
@@ -2746,7 +2906,6 @@ class FrameController {
|
|
|
2746
2906
|
this.appearanceObserver.stop();
|
|
2747
2907
|
await this.element.loaded;
|
|
2748
2908
|
this.hasBeenLoaded = true;
|
|
2749
|
-
session.frameLoaded(this.element);
|
|
2750
2909
|
}
|
|
2751
2910
|
catch (error) {
|
|
2752
2911
|
this.currentURL = previousURL;
|
|
@@ -2756,7 +2915,7 @@ class FrameController {
|
|
|
2756
2915
|
}
|
|
2757
2916
|
}
|
|
2758
2917
|
async loadResponse(fetchResponse) {
|
|
2759
|
-
if (fetchResponse.redirected) {
|
|
2918
|
+
if (fetchResponse.redirected || (fetchResponse.succeeded && fetchResponse.isHTML)) {
|
|
2760
2919
|
this.sourceURL = fetchResponse.response.url;
|
|
2761
2920
|
}
|
|
2762
2921
|
try {
|
|
@@ -2764,17 +2923,22 @@ class FrameController {
|
|
|
2764
2923
|
if (html) {
|
|
2765
2924
|
const { body } = parseHTMLDocument(html);
|
|
2766
2925
|
const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
|
|
2767
|
-
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
|
|
2926
|
+
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
|
|
2768
2927
|
if (this.view.renderPromise)
|
|
2769
2928
|
await this.view.renderPromise;
|
|
2770
2929
|
await this.view.render(renderer);
|
|
2771
2930
|
session.frameRendered(fetchResponse, this.element);
|
|
2931
|
+
session.frameLoaded(this.element);
|
|
2932
|
+
this.fetchResponseLoaded(fetchResponse);
|
|
2772
2933
|
}
|
|
2773
2934
|
}
|
|
2774
2935
|
catch (error) {
|
|
2775
2936
|
console.error(error);
|
|
2776
2937
|
this.view.invalidate();
|
|
2777
2938
|
}
|
|
2939
|
+
finally {
|
|
2940
|
+
this.fetchResponseLoaded = () => { };
|
|
2941
|
+
}
|
|
2778
2942
|
}
|
|
2779
2943
|
elementAppearedInViewport(element) {
|
|
2780
2944
|
this.loadSourceURL();
|
|
@@ -2800,20 +2964,15 @@ class FrameController {
|
|
|
2800
2964
|
}
|
|
2801
2965
|
this.reloadable = false;
|
|
2802
2966
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
else {
|
|
2807
|
-
const { fetchRequest } = this.formSubmission;
|
|
2808
|
-
this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
|
|
2809
|
-
this.formSubmission.start();
|
|
2810
|
-
}
|
|
2967
|
+
const { fetchRequest } = this.formSubmission;
|
|
2968
|
+
this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
|
|
2969
|
+
this.formSubmission.start();
|
|
2811
2970
|
}
|
|
2812
2971
|
prepareHeadersForRequest(headers, request) {
|
|
2813
2972
|
headers["Turbo-Frame"] = this.id;
|
|
2814
2973
|
}
|
|
2815
2974
|
requestStarted(request) {
|
|
2816
|
-
this.element
|
|
2975
|
+
markAsBusy(this.element);
|
|
2817
2976
|
}
|
|
2818
2977
|
requestPreventedHandlingResponse(request, response) {
|
|
2819
2978
|
this.resolveVisitPromise();
|
|
@@ -2831,14 +2990,14 @@ class FrameController {
|
|
|
2831
2990
|
this.resolveVisitPromise();
|
|
2832
2991
|
}
|
|
2833
2992
|
requestFinished(request) {
|
|
2834
|
-
this.element
|
|
2993
|
+
clearBusyState(this.element);
|
|
2835
2994
|
}
|
|
2836
|
-
formSubmissionStarted(
|
|
2837
|
-
|
|
2838
|
-
frame.setAttribute("busy", "");
|
|
2995
|
+
formSubmissionStarted({ formElement }) {
|
|
2996
|
+
markAsBusy(formElement, this.findFrameElement(formElement));
|
|
2839
2997
|
}
|
|
2840
2998
|
formSubmissionSucceededWithResponse(formSubmission, response) {
|
|
2841
2999
|
const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
|
|
3000
|
+
this.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
|
|
2842
3001
|
frame.delegate.loadResponse(response);
|
|
2843
3002
|
}
|
|
2844
3003
|
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
|
@@ -2847,9 +3006,8 @@ class FrameController {
|
|
|
2847
3006
|
formSubmissionErrored(formSubmission, error) {
|
|
2848
3007
|
console.error(error);
|
|
2849
3008
|
}
|
|
2850
|
-
formSubmissionFinished(
|
|
2851
|
-
|
|
2852
|
-
frame.removeAttribute("busy");
|
|
3009
|
+
formSubmissionFinished({ formElement }) {
|
|
3010
|
+
clearBusyState(formElement, this.findFrameElement(formElement));
|
|
2853
3011
|
}
|
|
2854
3012
|
allowsImmediateRender(snapshot, resume) {
|
|
2855
3013
|
return true;
|
|
@@ -2859,10 +3017,14 @@ class FrameController {
|
|
|
2859
3017
|
viewInvalidated() {
|
|
2860
3018
|
}
|
|
2861
3019
|
async visit(url) {
|
|
2862
|
-
|
|
3020
|
+
var _a;
|
|
3021
|
+
const request = new FetchRequest(this, FetchMethod.get, expandURL(url), new URLSearchParams, this.element);
|
|
3022
|
+
(_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
|
|
3023
|
+
this.currentFetchRequest = request;
|
|
2863
3024
|
return new Promise(resolve => {
|
|
2864
3025
|
this.resolveVisitPromise = () => {
|
|
2865
3026
|
this.resolveVisitPromise = () => { };
|
|
3027
|
+
this.currentFetchRequest = null;
|
|
2866
3028
|
resolve();
|
|
2867
3029
|
};
|
|
2868
3030
|
request.perform();
|
|
@@ -2870,12 +3032,27 @@ class FrameController {
|
|
|
2870
3032
|
}
|
|
2871
3033
|
navigateFrame(element, url, submitter) {
|
|
2872
3034
|
const frame = this.findFrameElement(element, submitter);
|
|
3035
|
+
this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
|
|
2873
3036
|
frame.setAttribute("reloadable", "");
|
|
2874
3037
|
frame.src = url;
|
|
2875
3038
|
}
|
|
3039
|
+
proposeVisitIfNavigatedWithAction(frame, element, submitter) {
|
|
3040
|
+
const action = getAttribute("data-turbo-action", submitter, element, frame);
|
|
3041
|
+
if (isAction(action)) {
|
|
3042
|
+
const { visitCachedSnapshot } = new SnapshotSubstitution(frame);
|
|
3043
|
+
frame.delegate.fetchResponseLoaded = (fetchResponse) => {
|
|
3044
|
+
if (frame.src) {
|
|
3045
|
+
const { statusCode, redirected } = fetchResponse;
|
|
3046
|
+
const responseHTML = frame.ownerDocument.documentElement.outerHTML;
|
|
3047
|
+
const response = { statusCode, redirected, responseHTML };
|
|
3048
|
+
session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false });
|
|
3049
|
+
}
|
|
3050
|
+
};
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
2876
3053
|
findFrameElement(element, submitter) {
|
|
2877
3054
|
var _a;
|
|
2878
|
-
const id =
|
|
3055
|
+
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
|
2879
3056
|
return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
|
|
2880
3057
|
}
|
|
2881
3058
|
async extractForeignFrameElement(container) {
|
|
@@ -2896,8 +3073,15 @@ class FrameController {
|
|
|
2896
3073
|
}
|
|
2897
3074
|
return new FrameElement();
|
|
2898
3075
|
}
|
|
3076
|
+
formActionIsVisitable(form, submitter) {
|
|
3077
|
+
const action = getAction(form, submitter);
|
|
3078
|
+
return locationIsVisitable(expandURL(action), this.rootLocation);
|
|
3079
|
+
}
|
|
2899
3080
|
shouldInterceptNavigation(element, submitter) {
|
|
2900
|
-
const id =
|
|
3081
|
+
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
|
3082
|
+
if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {
|
|
3083
|
+
return false;
|
|
3084
|
+
}
|
|
2901
3085
|
if (!this.enabled || id == "_top") {
|
|
2902
3086
|
return false;
|
|
2903
3087
|
}
|
|
@@ -2954,6 +3138,23 @@ class FrameController {
|
|
|
2954
3138
|
get isActive() {
|
|
2955
3139
|
return this.element.isActive && this.connected;
|
|
2956
3140
|
}
|
|
3141
|
+
get rootLocation() {
|
|
3142
|
+
var _a;
|
|
3143
|
+
const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
|
|
3144
|
+
const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
|
|
3145
|
+
return expandURL(root);
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
class SnapshotSubstitution {
|
|
3149
|
+
constructor(element) {
|
|
3150
|
+
this.visitCachedSnapshot = ({ element }) => {
|
|
3151
|
+
var _a;
|
|
3152
|
+
const { id, clone } = this;
|
|
3153
|
+
(_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
|
|
3154
|
+
};
|
|
3155
|
+
this.clone = element.cloneNode(true);
|
|
3156
|
+
this.id = element.id;
|
|
3157
|
+
}
|
|
2957
3158
|
}
|
|
2958
3159
|
function getFrameElementById(id) {
|
|
2959
3160
|
if (id != null) {
|
|
@@ -2974,6 +3175,7 @@ function activateElement(element, currentURL) {
|
|
|
2974
3175
|
}
|
|
2975
3176
|
if (element instanceof FrameElement) {
|
|
2976
3177
|
element.connectedCallback();
|
|
3178
|
+
element.disconnectedCallback();
|
|
2977
3179
|
return element;
|
|
2978
3180
|
}
|
|
2979
3181
|
}
|
|
@@ -3144,4 +3346,4 @@ customElements.define("turbo-stream", StreamElement);
|
|
|
3144
3346
|
window.Turbo = Turbo;
|
|
3145
3347
|
start();
|
|
3146
3348
|
|
|
3147
|
-
export { PageRenderer, PageSnapshot, clearCache, connectStreamSource, disconnectStreamSource, navigator, registerAdapter, renderStreamMessage, session, setProgressBarDelay, start, visit };
|
|
3349
|
+
export { PageRenderer, PageSnapshot, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setProgressBarDelay, start, visit };
|