@hotwired/turbo 7.0.1 → 7.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -0
- package/dist/turbo.es2017-esm.js +248 -52
- package/dist/turbo.es2017-umd.js +248 -51
- package/dist/types/core/drive/form_submission.d.ts +3 -0
- package/dist/types/core/drive/navigator.d.ts +1 -0
- package/dist/types/core/drive/visit.d.ts +4 -0
- package/dist/types/core/frames/frame_controller.d.ts +7 -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/session.d.ts +2 -1
- package/dist/types/core/url.d.ts +2 -0
- package/dist/types/elements/frame_element.d.ts +1 -0
- package/dist/types/polyfills/index.d.ts +1 -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 +12 -1
- package/dist/types/tests/functional/navigation_tests.d.ts +4 -2
- package/dist/types/util.d.ts +3 -0
- package/package.json +1 -1
- 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/CHANGELOG.md
ADDED
package/dist/turbo.es2017-esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Turbo 7.0.
|
|
2
|
+
Turbo 7.0.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;
|
|
@@ -165,6 +217,10 @@ function getAnchor(url) {
|
|
|
165
217
|
return anchorMatch[1];
|
|
166
218
|
}
|
|
167
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
|
+
}
|
|
168
224
|
function getExtension(url) {
|
|
169
225
|
return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
|
|
170
226
|
}
|
|
@@ -175,6 +231,9 @@ function isPrefixedBy(baseURL, url) {
|
|
|
175
231
|
const prefix = getPrefix(url);
|
|
176
232
|
return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
|
|
177
233
|
}
|
|
234
|
+
function locationIsVisitable(location, rootLocation) {
|
|
235
|
+
return isPrefixedBy(location, rootLocation) && isHTML(location);
|
|
236
|
+
}
|
|
178
237
|
function getRequestURL(url) {
|
|
179
238
|
const anchor = getAnchor(url);
|
|
180
239
|
return anchor != null
|
|
@@ -297,6 +356,29 @@ function uuid() {
|
|
|
297
356
|
}
|
|
298
357
|
}).join("");
|
|
299
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
|
+
}
|
|
300
382
|
|
|
301
383
|
var FetchMethod;
|
|
302
384
|
(function (FetchMethod) {
|
|
@@ -526,6 +608,9 @@ class FormSubmission {
|
|
|
526
608
|
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
|
|
527
609
|
this.mustRedirect = mustRedirect;
|
|
528
610
|
}
|
|
611
|
+
static confirmMethod(message, element) {
|
|
612
|
+
return confirm(message);
|
|
613
|
+
}
|
|
529
614
|
get method() {
|
|
530
615
|
var _a;
|
|
531
616
|
const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
|
|
@@ -559,8 +644,20 @@ class FormSubmission {
|
|
|
559
644
|
return entries.concat(typeof value == "string" ? [[name, value]] : []);
|
|
560
645
|
}, []);
|
|
561
646
|
}
|
|
647
|
+
get confirmationMessage() {
|
|
648
|
+
return this.formElement.getAttribute("data-turbo-confirm");
|
|
649
|
+
}
|
|
650
|
+
get needsConfirmation() {
|
|
651
|
+
return this.confirmationMessage !== null;
|
|
652
|
+
}
|
|
562
653
|
async start() {
|
|
563
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
|
+
}
|
|
564
661
|
if (this.state == initialized) {
|
|
565
662
|
this.state = requesting;
|
|
566
663
|
return this.fetchRequest.perform();
|
|
@@ -584,7 +681,9 @@ class FormSubmission {
|
|
|
584
681
|
}
|
|
585
682
|
}
|
|
586
683
|
requestStarted(request) {
|
|
684
|
+
var _a;
|
|
587
685
|
this.state = FormSubmissionState.waiting;
|
|
686
|
+
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
|
|
588
687
|
dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } });
|
|
589
688
|
this.delegate.formSubmissionStarted(this);
|
|
590
689
|
}
|
|
@@ -614,7 +713,9 @@ class FormSubmission {
|
|
|
614
713
|
this.delegate.formSubmissionErrored(this, error);
|
|
615
714
|
}
|
|
616
715
|
requestFinished(request) {
|
|
716
|
+
var _a;
|
|
617
717
|
this.state = FormSubmissionState.stopped;
|
|
718
|
+
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
|
|
618
719
|
dispatch("turbo:submit-end", { target: this.formElement, detail: Object.assign({ formSubmission: this }, this.result) });
|
|
619
720
|
this.delegate.formSubmissionFinished(this);
|
|
620
721
|
}
|
|
@@ -693,7 +794,8 @@ class FormInterceptor {
|
|
|
693
794
|
const form = event.target;
|
|
694
795
|
if (form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
|
|
695
796
|
const submitter = event.submitter || undefined;
|
|
696
|
-
|
|
797
|
+
const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
|
|
798
|
+
if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
|
|
697
799
|
event.preventDefault();
|
|
698
800
|
event.stopImmediatePropagation();
|
|
699
801
|
this.delegate.formSubmissionIntercepted(form, submitter);
|
|
@@ -1287,7 +1389,8 @@ var VisitState;
|
|
|
1287
1389
|
})(VisitState || (VisitState = {}));
|
|
1288
1390
|
const defaultOptions = {
|
|
1289
1391
|
action: "advance",
|
|
1290
|
-
|
|
1392
|
+
delegate: {},
|
|
1393
|
+
historyChanged: false,
|
|
1291
1394
|
};
|
|
1292
1395
|
var SystemStatusCode;
|
|
1293
1396
|
(function (SystemStatusCode) {
|
|
@@ -1307,13 +1410,14 @@ class Visit {
|
|
|
1307
1410
|
this.delegate = delegate;
|
|
1308
1411
|
this.location = location;
|
|
1309
1412
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
|
1310
|
-
const { action, historyChanged, referrer, snapshotHTML, response } = Object.assign(Object.assign({}, defaultOptions), options);
|
|
1413
|
+
const { action, historyChanged, referrer, snapshotHTML, response, delegate: optionalDelegate } = Object.assign(Object.assign({}, defaultOptions), options);
|
|
1311
1414
|
this.action = action;
|
|
1312
1415
|
this.historyChanged = historyChanged;
|
|
1313
1416
|
this.referrer = referrer;
|
|
1314
1417
|
this.snapshotHTML = snapshotHTML;
|
|
1315
1418
|
this.response = response;
|
|
1316
1419
|
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
|
1420
|
+
this.optionalDelegate = optionalDelegate;
|
|
1317
1421
|
}
|
|
1318
1422
|
get adapter() {
|
|
1319
1423
|
return this.delegate.adapter;
|
|
@@ -1336,6 +1440,8 @@ class Visit {
|
|
|
1336
1440
|
this.state = VisitState.started;
|
|
1337
1441
|
this.adapter.visitStarted(this);
|
|
1338
1442
|
this.delegate.visitStarted(this);
|
|
1443
|
+
if (this.optionalDelegate.visitStarted)
|
|
1444
|
+
this.optionalDelegate.visitStarted(this);
|
|
1339
1445
|
}
|
|
1340
1446
|
}
|
|
1341
1447
|
cancel() {
|
|
@@ -1465,7 +1571,8 @@ class Visit {
|
|
|
1465
1571
|
}
|
|
1466
1572
|
}
|
|
1467
1573
|
followRedirect() {
|
|
1468
|
-
|
|
1574
|
+
var _a;
|
|
1575
|
+
if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
|
|
1469
1576
|
this.adapter.visitProposedToLocation(this.redirectedToLocation, {
|
|
1470
1577
|
action: 'replace',
|
|
1471
1578
|
response: this.response
|
|
@@ -1488,25 +1595,27 @@ class Visit {
|
|
|
1488
1595
|
}
|
|
1489
1596
|
async requestSucceededWithResponse(request, response) {
|
|
1490
1597
|
const responseHTML = await response.responseHTML;
|
|
1598
|
+
const { redirected, statusCode } = response;
|
|
1491
1599
|
if (responseHTML == undefined) {
|
|
1492
|
-
this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch });
|
|
1600
|
+
this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
|
|
1493
1601
|
}
|
|
1494
1602
|
else {
|
|
1495
1603
|
this.redirectedToLocation = response.redirected ? response.location : undefined;
|
|
1496
|
-
this.recordResponse({ statusCode:
|
|
1604
|
+
this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
|
|
1497
1605
|
}
|
|
1498
1606
|
}
|
|
1499
1607
|
async requestFailedWithResponse(request, response) {
|
|
1500
1608
|
const responseHTML = await response.responseHTML;
|
|
1609
|
+
const { redirected, statusCode } = response;
|
|
1501
1610
|
if (responseHTML == undefined) {
|
|
1502
|
-
this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch });
|
|
1611
|
+
this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
|
|
1503
1612
|
}
|
|
1504
1613
|
else {
|
|
1505
|
-
this.recordResponse({ statusCode:
|
|
1614
|
+
this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
|
|
1506
1615
|
}
|
|
1507
1616
|
}
|
|
1508
1617
|
requestErrored(request, error) {
|
|
1509
|
-
this.recordResponse({ statusCode: SystemStatusCode.networkFailure });
|
|
1618
|
+
this.recordResponse({ statusCode: SystemStatusCode.networkFailure, redirected: false });
|
|
1510
1619
|
}
|
|
1511
1620
|
requestFinished() {
|
|
1512
1621
|
this.finishRequest();
|
|
@@ -1570,6 +1679,8 @@ class Visit {
|
|
|
1570
1679
|
if (!this.snapshotCached) {
|
|
1571
1680
|
this.view.cacheSnapshot();
|
|
1572
1681
|
this.snapshotCached = true;
|
|
1682
|
+
if (this.optionalDelegate.visitCachedSnapshot)
|
|
1683
|
+
this.optionalDelegate.visitCachedSnapshot(this);
|
|
1573
1684
|
}
|
|
1574
1685
|
}
|
|
1575
1686
|
async render(callback) {
|
|
@@ -1718,7 +1829,7 @@ class FormSubmitObserver {
|
|
|
1718
1829
|
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
|
1719
1830
|
const submitter = event.submitter || undefined;
|
|
1720
1831
|
if (form) {
|
|
1721
|
-
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");
|
|
1722
1833
|
if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
|
|
1723
1834
|
event.preventDefault();
|
|
1724
1835
|
this.delegate.formSubmitted(form, submitter);
|
|
@@ -1762,12 +1873,11 @@ class FrameRedirector {
|
|
|
1762
1873
|
linkClickIntercepted(element, url) {
|
|
1763
1874
|
const frame = this.findFrameElement(element);
|
|
1764
1875
|
if (frame) {
|
|
1765
|
-
frame.
|
|
1766
|
-
frame.src = url;
|
|
1876
|
+
frame.delegate.linkClickIntercepted(element, url);
|
|
1767
1877
|
}
|
|
1768
1878
|
}
|
|
1769
1879
|
shouldInterceptFormSubmission(element, submitter) {
|
|
1770
|
-
return this.
|
|
1880
|
+
return this.shouldSubmit(element, submitter);
|
|
1771
1881
|
}
|
|
1772
1882
|
formSubmissionIntercepted(element, submitter) {
|
|
1773
1883
|
const frame = this.findFrameElement(element, submitter);
|
|
@@ -1776,6 +1886,13 @@ class FrameRedirector {
|
|
|
1776
1886
|
frame.delegate.formSubmissionIntercepted(element, submitter);
|
|
1777
1887
|
}
|
|
1778
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
|
+
}
|
|
1779
1896
|
shouldRedirect(element, submitter) {
|
|
1780
1897
|
const frame = this.findFrameElement(element, submitter);
|
|
1781
1898
|
return frame ? frame != element.closest("turbo-frame") : false;
|
|
@@ -1933,7 +2050,12 @@ class Navigator {
|
|
|
1933
2050
|
}
|
|
1934
2051
|
proposeVisit(location, options = {}) {
|
|
1935
2052
|
if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
|
|
1936
|
-
|
|
2053
|
+
if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
|
|
2054
|
+
this.delegate.visitProposedToLocation(location, options);
|
|
2055
|
+
}
|
|
2056
|
+
else {
|
|
2057
|
+
window.location.href = location.toString();
|
|
2058
|
+
}
|
|
1937
2059
|
}
|
|
1938
2060
|
}
|
|
1939
2061
|
startVisit(locatable, restorationIdentifier, options = {}) {
|
|
@@ -1944,12 +2066,7 @@ class Navigator {
|
|
|
1944
2066
|
submitForm(form, submitter) {
|
|
1945
2067
|
this.stop();
|
|
1946
2068
|
this.formSubmission = new FormSubmission(this, form, submitter, true);
|
|
1947
|
-
|
|
1948
|
-
this.proposeVisit(this.formSubmission.fetchRequest.url, { action: this.getActionForFormSubmission(this.formSubmission) });
|
|
1949
|
-
}
|
|
1950
|
-
else {
|
|
1951
|
-
this.formSubmission.start();
|
|
1952
|
-
}
|
|
2069
|
+
this.formSubmission.start();
|
|
1953
2070
|
}
|
|
1954
2071
|
stop() {
|
|
1955
2072
|
if (this.formSubmission) {
|
|
@@ -1982,8 +2099,9 @@ class Navigator {
|
|
|
1982
2099
|
if (formSubmission.method != FetchMethod.get) {
|
|
1983
2100
|
this.view.clearSnapshotCache();
|
|
1984
2101
|
}
|
|
1985
|
-
const { statusCode } = fetchResponse;
|
|
1986
|
-
const
|
|
2102
|
+
const { statusCode, redirected } = fetchResponse;
|
|
2103
|
+
const action = this.getActionForFormSubmission(formSubmission);
|
|
2104
|
+
const visitOptions = { action, response: { statusCode, responseHTML, redirected } };
|
|
1987
2105
|
this.proposeVisit(fetchResponse.location, visitOptions);
|
|
1988
2106
|
}
|
|
1989
2107
|
}
|
|
@@ -2016,6 +2134,9 @@ class Navigator {
|
|
|
2016
2134
|
visitCompleted(visit) {
|
|
2017
2135
|
this.delegate.visitCompleted(visit);
|
|
2018
2136
|
}
|
|
2137
|
+
visitCachedSnapshot(visit) {
|
|
2138
|
+
this.delegate.visitCachedSnapshot(visit);
|
|
2139
|
+
}
|
|
2019
2140
|
locationWithActionIsSamePage(location, action) {
|
|
2020
2141
|
const anchor = getAnchor(location);
|
|
2021
2142
|
const currentAnchor = getAnchor(this.view.lastRenderedLocation);
|
|
@@ -2035,7 +2156,7 @@ class Navigator {
|
|
|
2035
2156
|
}
|
|
2036
2157
|
getActionForFormSubmission(formSubmission) {
|
|
2037
2158
|
const { formElement, submitter } = formSubmission;
|
|
2038
|
-
const action =
|
|
2159
|
+
const action = getAttribute("data-turbo-action", submitter, formElement);
|
|
2039
2160
|
return isAction(action) ? action : "advance";
|
|
2040
2161
|
}
|
|
2041
2162
|
}
|
|
@@ -2485,7 +2606,7 @@ class Session {
|
|
|
2485
2606
|
}
|
|
2486
2607
|
willFollowLinkToLocation(link, location) {
|
|
2487
2608
|
return this.elementDriveEnabled(link)
|
|
2488
|
-
&&
|
|
2609
|
+
&& locationIsVisitable(location, this.snapshot.rootLocation)
|
|
2489
2610
|
&& this.applicationAllowsFollowingLinkToLocation(link, location);
|
|
2490
2611
|
}
|
|
2491
2612
|
followedLinkToLocation(link, location) {
|
|
@@ -2493,14 +2614,24 @@ class Session {
|
|
|
2493
2614
|
this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, { action });
|
|
2494
2615
|
}
|
|
2495
2616
|
convertLinkWithMethodClickToFormSubmission(link) {
|
|
2496
|
-
var _a;
|
|
2497
2617
|
const linkMethod = link.getAttribute("data-turbo-method");
|
|
2498
2618
|
if (linkMethod) {
|
|
2499
2619
|
const form = document.createElement("form");
|
|
2500
2620
|
form.method = linkMethod;
|
|
2501
2621
|
form.action = link.getAttribute("href") || "undefined";
|
|
2502
2622
|
form.hidden = true;
|
|
2503
|
-
(
|
|
2623
|
+
if (link.hasAttribute("data-turbo-confirm")) {
|
|
2624
|
+
form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
|
|
2625
|
+
}
|
|
2626
|
+
const frame = this.getTargetFrameForLink(link);
|
|
2627
|
+
if (frame) {
|
|
2628
|
+
form.setAttribute("data-turbo-frame", frame);
|
|
2629
|
+
form.addEventListener("turbo:submit-start", () => form.remove());
|
|
2630
|
+
}
|
|
2631
|
+
else {
|
|
2632
|
+
form.addEventListener("submit", () => form.remove());
|
|
2633
|
+
}
|
|
2634
|
+
document.body.appendChild(form);
|
|
2504
2635
|
return dispatch("submit", { cancelable: true, target: form });
|
|
2505
2636
|
}
|
|
2506
2637
|
else {
|
|
@@ -2523,6 +2654,8 @@ class Session {
|
|
|
2523
2654
|
visitCompleted(visit) {
|
|
2524
2655
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
|
2525
2656
|
}
|
|
2657
|
+
visitCachedSnapshot(visit) {
|
|
2658
|
+
}
|
|
2526
2659
|
locationWithActionIsSamePage(location, action) {
|
|
2527
2660
|
return this.navigator.locationWithActionIsSamePage(location, action);
|
|
2528
2661
|
}
|
|
@@ -2530,7 +2663,10 @@ class Session {
|
|
|
2530
2663
|
this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
|
|
2531
2664
|
}
|
|
2532
2665
|
willSubmitForm(form, submitter) {
|
|
2533
|
-
|
|
2666
|
+
const action = getAction(form, submitter);
|
|
2667
|
+
return this.elementDriveEnabled(form)
|
|
2668
|
+
&& (!submitter || this.elementDriveEnabled(submitter))
|
|
2669
|
+
&& locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
|
|
2534
2670
|
}
|
|
2535
2671
|
formSubmitted(form, submitter) {
|
|
2536
2672
|
this.navigator.submitForm(form, submitter);
|
|
@@ -2586,6 +2722,7 @@ class Session {
|
|
|
2586
2722
|
return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
|
|
2587
2723
|
}
|
|
2588
2724
|
notifyApplicationAfterVisitingLocation(location, action) {
|
|
2725
|
+
markAsBusy(document.documentElement);
|
|
2589
2726
|
return dispatch("turbo:visit", { detail: { url: location.href, action } });
|
|
2590
2727
|
}
|
|
2591
2728
|
notifyApplicationBeforeCachingSnapshot() {
|
|
@@ -2598,6 +2735,7 @@ class Session {
|
|
|
2598
2735
|
return dispatch("turbo:render");
|
|
2599
2736
|
}
|
|
2600
2737
|
notifyApplicationAfterPageLoad(timing = {}) {
|
|
2738
|
+
clearBusyState(document.documentElement);
|
|
2601
2739
|
return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
|
|
2602
2740
|
}
|
|
2603
2741
|
notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
|
|
@@ -2632,8 +2770,17 @@ class Session {
|
|
|
2632
2770
|
const action = link.getAttribute("data-turbo-action");
|
|
2633
2771
|
return isAction(action) ? action : "advance";
|
|
2634
2772
|
}
|
|
2635
|
-
|
|
2636
|
-
|
|
2773
|
+
getTargetFrameForLink(link) {
|
|
2774
|
+
const frame = link.getAttribute("data-turbo-frame");
|
|
2775
|
+
if (frame) {
|
|
2776
|
+
return frame;
|
|
2777
|
+
}
|
|
2778
|
+
else {
|
|
2779
|
+
const container = link.closest("turbo-frame");
|
|
2780
|
+
if (container) {
|
|
2781
|
+
return container.id;
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2637
2784
|
}
|
|
2638
2785
|
get snapshot() {
|
|
2639
2786
|
return this.view.snapshot;
|
|
@@ -2676,6 +2823,9 @@ function clearCache() {
|
|
|
2676
2823
|
function setProgressBarDelay(delay) {
|
|
2677
2824
|
session.setProgressBarDelay(delay);
|
|
2678
2825
|
}
|
|
2826
|
+
function setConfirmMethod(confirmMethod) {
|
|
2827
|
+
FormSubmission.confirmMethod = confirmMethod;
|
|
2828
|
+
}
|
|
2679
2829
|
|
|
2680
2830
|
var Turbo = /*#__PURE__*/Object.freeze({
|
|
2681
2831
|
__proto__: null,
|
|
@@ -2690,11 +2840,13 @@ var Turbo = /*#__PURE__*/Object.freeze({
|
|
|
2690
2840
|
disconnectStreamSource: disconnectStreamSource,
|
|
2691
2841
|
renderStreamMessage: renderStreamMessage,
|
|
2692
2842
|
clearCache: clearCache,
|
|
2693
|
-
setProgressBarDelay: setProgressBarDelay
|
|
2843
|
+
setProgressBarDelay: setProgressBarDelay,
|
|
2844
|
+
setConfirmMethod: setConfirmMethod
|
|
2694
2845
|
});
|
|
2695
2846
|
|
|
2696
2847
|
class FrameController {
|
|
2697
2848
|
constructor(element) {
|
|
2849
|
+
this.currentFetchRequest = null;
|
|
2698
2850
|
this.resolveVisitPromise = () => { };
|
|
2699
2851
|
this.connected = false;
|
|
2700
2852
|
this.hasBeenLoaded = false;
|
|
@@ -2754,7 +2906,6 @@ class FrameController {
|
|
|
2754
2906
|
this.appearanceObserver.stop();
|
|
2755
2907
|
await this.element.loaded;
|
|
2756
2908
|
this.hasBeenLoaded = true;
|
|
2757
|
-
session.frameLoaded(this.element);
|
|
2758
2909
|
}
|
|
2759
2910
|
catch (error) {
|
|
2760
2911
|
this.currentURL = previousURL;
|
|
@@ -2777,6 +2928,7 @@ class FrameController {
|
|
|
2777
2928
|
await this.view.renderPromise;
|
|
2778
2929
|
await this.view.render(renderer);
|
|
2779
2930
|
session.frameRendered(fetchResponse, this.element);
|
|
2931
|
+
session.frameLoaded(this.element);
|
|
2780
2932
|
}
|
|
2781
2933
|
}
|
|
2782
2934
|
catch (error) {
|
|
@@ -2808,20 +2960,15 @@ class FrameController {
|
|
|
2808
2960
|
}
|
|
2809
2961
|
this.reloadable = false;
|
|
2810
2962
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
else {
|
|
2815
|
-
const { fetchRequest } = this.formSubmission;
|
|
2816
|
-
this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
|
|
2817
|
-
this.formSubmission.start();
|
|
2818
|
-
}
|
|
2963
|
+
const { fetchRequest } = this.formSubmission;
|
|
2964
|
+
this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
|
|
2965
|
+
this.formSubmission.start();
|
|
2819
2966
|
}
|
|
2820
2967
|
prepareHeadersForRequest(headers, request) {
|
|
2821
2968
|
headers["Turbo-Frame"] = this.id;
|
|
2822
2969
|
}
|
|
2823
2970
|
requestStarted(request) {
|
|
2824
|
-
this.element
|
|
2971
|
+
markAsBusy(this.element);
|
|
2825
2972
|
}
|
|
2826
2973
|
requestPreventedHandlingResponse(request, response) {
|
|
2827
2974
|
this.resolveVisitPromise();
|
|
@@ -2839,14 +2986,14 @@ class FrameController {
|
|
|
2839
2986
|
this.resolveVisitPromise();
|
|
2840
2987
|
}
|
|
2841
2988
|
requestFinished(request) {
|
|
2842
|
-
this.element
|
|
2989
|
+
clearBusyState(this.element);
|
|
2843
2990
|
}
|
|
2844
|
-
formSubmissionStarted(
|
|
2845
|
-
|
|
2846
|
-
frame.setAttribute("busy", "");
|
|
2991
|
+
formSubmissionStarted({ formElement }) {
|
|
2992
|
+
markAsBusy(formElement, this.findFrameElement(formElement));
|
|
2847
2993
|
}
|
|
2848
2994
|
formSubmissionSucceededWithResponse(formSubmission, response) {
|
|
2849
2995
|
const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
|
|
2996
|
+
this.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
|
|
2850
2997
|
frame.delegate.loadResponse(response);
|
|
2851
2998
|
}
|
|
2852
2999
|
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
|
@@ -2855,9 +3002,8 @@ class FrameController {
|
|
|
2855
3002
|
formSubmissionErrored(formSubmission, error) {
|
|
2856
3003
|
console.error(error);
|
|
2857
3004
|
}
|
|
2858
|
-
formSubmissionFinished(
|
|
2859
|
-
|
|
2860
|
-
frame.removeAttribute("busy");
|
|
3005
|
+
formSubmissionFinished({ formElement }) {
|
|
3006
|
+
clearBusyState(formElement, this.findFrameElement(formElement));
|
|
2861
3007
|
}
|
|
2862
3008
|
allowsImmediateRender(snapshot, resume) {
|
|
2863
3009
|
return true;
|
|
@@ -2867,10 +3013,14 @@ class FrameController {
|
|
|
2867
3013
|
viewInvalidated() {
|
|
2868
3014
|
}
|
|
2869
3015
|
async visit(url) {
|
|
2870
|
-
|
|
3016
|
+
var _a;
|
|
3017
|
+
const request = new FetchRequest(this, FetchMethod.get, expandURL(url), new URLSearchParams, this.element);
|
|
3018
|
+
(_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
|
|
3019
|
+
this.currentFetchRequest = request;
|
|
2871
3020
|
return new Promise(resolve => {
|
|
2872
3021
|
this.resolveVisitPromise = () => {
|
|
2873
3022
|
this.resolveVisitPromise = () => { };
|
|
3023
|
+
this.currentFetchRequest = null;
|
|
2874
3024
|
resolve();
|
|
2875
3025
|
};
|
|
2876
3026
|
request.perform();
|
|
@@ -2878,12 +3028,29 @@ class FrameController {
|
|
|
2878
3028
|
}
|
|
2879
3029
|
navigateFrame(element, url, submitter) {
|
|
2880
3030
|
const frame = this.findFrameElement(element, submitter);
|
|
3031
|
+
this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
|
|
2881
3032
|
frame.setAttribute("reloadable", "");
|
|
2882
3033
|
frame.src = url;
|
|
2883
3034
|
}
|
|
3035
|
+
proposeVisitIfNavigatedWithAction(frame, element, submitter) {
|
|
3036
|
+
const action = getAttribute("data-turbo-action", submitter, element, frame);
|
|
3037
|
+
if (isAction(action)) {
|
|
3038
|
+
const delegate = new SnapshotSubstitution(frame);
|
|
3039
|
+
const proposeVisit = (event) => {
|
|
3040
|
+
const { target, detail: { fetchResponse } } = event;
|
|
3041
|
+
if (target instanceof FrameElement && target.src) {
|
|
3042
|
+
const { statusCode, redirected } = fetchResponse;
|
|
3043
|
+
const responseHTML = target.ownerDocument.documentElement.outerHTML;
|
|
3044
|
+
const response = { statusCode, redirected, responseHTML };
|
|
3045
|
+
session.visit(target.src, { action, response, delegate });
|
|
3046
|
+
}
|
|
3047
|
+
};
|
|
3048
|
+
frame.addEventListener("turbo:frame-render", proposeVisit, { once: true });
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
2884
3051
|
findFrameElement(element, submitter) {
|
|
2885
3052
|
var _a;
|
|
2886
|
-
const id =
|
|
3053
|
+
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
|
2887
3054
|
return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
|
|
2888
3055
|
}
|
|
2889
3056
|
async extractForeignFrameElement(container) {
|
|
@@ -2904,8 +3071,15 @@ class FrameController {
|
|
|
2904
3071
|
}
|
|
2905
3072
|
return new FrameElement();
|
|
2906
3073
|
}
|
|
3074
|
+
formActionIsVisitable(form, submitter) {
|
|
3075
|
+
const action = getAction(form, submitter);
|
|
3076
|
+
return locationIsVisitable(expandURL(action), this.rootLocation);
|
|
3077
|
+
}
|
|
2907
3078
|
shouldInterceptNavigation(element, submitter) {
|
|
2908
|
-
const id =
|
|
3079
|
+
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
|
3080
|
+
if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {
|
|
3081
|
+
return false;
|
|
3082
|
+
}
|
|
2909
3083
|
if (!this.enabled || id == "_top") {
|
|
2910
3084
|
return false;
|
|
2911
3085
|
}
|
|
@@ -2962,6 +3136,28 @@ class FrameController {
|
|
|
2962
3136
|
get isActive() {
|
|
2963
3137
|
return this.element.isActive && this.connected;
|
|
2964
3138
|
}
|
|
3139
|
+
get rootLocation() {
|
|
3140
|
+
var _a;
|
|
3141
|
+
const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
|
|
3142
|
+
const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
|
|
3143
|
+
return expandURL(root);
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
class SnapshotSubstitution {
|
|
3147
|
+
constructor(element) {
|
|
3148
|
+
this.clone = element.cloneNode(true);
|
|
3149
|
+
this.id = element.id;
|
|
3150
|
+
}
|
|
3151
|
+
visitStarted(visit) {
|
|
3152
|
+
this.snapshot = visit.view.snapshot;
|
|
3153
|
+
}
|
|
3154
|
+
visitCachedSnapshot() {
|
|
3155
|
+
var _a;
|
|
3156
|
+
const { snapshot, id, clone } = this;
|
|
3157
|
+
if (snapshot) {
|
|
3158
|
+
(_a = snapshot.element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
2965
3161
|
}
|
|
2966
3162
|
function getFrameElementById(id) {
|
|
2967
3163
|
if (id != null) {
|
|
@@ -3152,4 +3348,4 @@ customElements.define("turbo-stream", StreamElement);
|
|
|
3152
3348
|
window.Turbo = Turbo;
|
|
3153
3349
|
start();
|
|
3154
3350
|
|
|
3155
|
-
export { PageRenderer, PageSnapshot, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setProgressBarDelay, start, visit };
|
|
3351
|
+
export { PageRenderer, PageSnapshot, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setProgressBarDelay, start, visit };
|