@hotwired/turbo 7.0.0-rc.4 → 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/README.md +0 -2
- package/dist/turbo.es2017-esm.js +270 -60
- package/dist/turbo.es2017-umd.js +271 -60
- 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_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 +14 -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.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;
|
|
@@ -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
|
|
@@ -241,7 +308,12 @@ class FetchResponse {
|
|
|
241
308
|
|
|
242
309
|
function dispatch(eventName, { target, cancelable, detail } = {}) {
|
|
243
310
|
const event = new CustomEvent(eventName, { cancelable, bubbles: true, detail });
|
|
244
|
-
|
|
311
|
+
if (target && target.isConnected) {
|
|
312
|
+
target.dispatchEvent(event);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
document.documentElement.dispatchEvent(event);
|
|
316
|
+
}
|
|
245
317
|
return event;
|
|
246
318
|
}
|
|
247
319
|
function nextAnimationFrame() {
|
|
@@ -284,6 +356,29 @@ function uuid() {
|
|
|
284
356
|
}
|
|
285
357
|
}).join("");
|
|
286
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
|
+
}
|
|
287
382
|
|
|
288
383
|
var FetchMethod;
|
|
289
384
|
(function (FetchMethod) {
|
|
@@ -513,6 +608,9 @@ class FormSubmission {
|
|
|
513
608
|
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
|
|
514
609
|
this.mustRedirect = mustRedirect;
|
|
515
610
|
}
|
|
611
|
+
static confirmMethod(message, element) {
|
|
612
|
+
return confirm(message);
|
|
613
|
+
}
|
|
516
614
|
get method() {
|
|
517
615
|
var _a;
|
|
518
616
|
const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
|
|
@@ -546,8 +644,20 @@ class FormSubmission {
|
|
|
546
644
|
return entries.concat(typeof value == "string" ? [[name, value]] : []);
|
|
547
645
|
}, []);
|
|
548
646
|
}
|
|
647
|
+
get confirmationMessage() {
|
|
648
|
+
return this.formElement.getAttribute("data-turbo-confirm");
|
|
649
|
+
}
|
|
650
|
+
get needsConfirmation() {
|
|
651
|
+
return this.confirmationMessage !== null;
|
|
652
|
+
}
|
|
549
653
|
async start() {
|
|
550
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
|
+
}
|
|
551
661
|
if (this.state == initialized) {
|
|
552
662
|
this.state = requesting;
|
|
553
663
|
return this.fetchRequest.perform();
|
|
@@ -571,7 +681,9 @@ class FormSubmission {
|
|
|
571
681
|
}
|
|
572
682
|
}
|
|
573
683
|
requestStarted(request) {
|
|
684
|
+
var _a;
|
|
574
685
|
this.state = FormSubmissionState.waiting;
|
|
686
|
+
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
|
|
575
687
|
dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } });
|
|
576
688
|
this.delegate.formSubmissionStarted(this);
|
|
577
689
|
}
|
|
@@ -601,7 +713,9 @@ class FormSubmission {
|
|
|
601
713
|
this.delegate.formSubmissionErrored(this, error);
|
|
602
714
|
}
|
|
603
715
|
requestFinished(request) {
|
|
716
|
+
var _a;
|
|
604
717
|
this.state = FormSubmissionState.stopped;
|
|
718
|
+
(_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
|
|
605
719
|
dispatch("turbo:submit-end", { target: this.formElement, detail: Object.assign({ formSubmission: this }, this.result) });
|
|
606
720
|
this.delegate.formSubmissionFinished(this);
|
|
607
721
|
}
|
|
@@ -677,10 +791,11 @@ class Snapshot {
|
|
|
677
791
|
class FormInterceptor {
|
|
678
792
|
constructor(delegate, element) {
|
|
679
793
|
this.submitBubbled = ((event) => {
|
|
680
|
-
|
|
681
|
-
|
|
794
|
+
const form = event.target;
|
|
795
|
+
if (form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
|
|
682
796
|
const submitter = event.submitter || undefined;
|
|
683
|
-
|
|
797
|
+
const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
|
|
798
|
+
if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
|
|
684
799
|
event.preventDefault();
|
|
685
800
|
event.stopImmediatePropagation();
|
|
686
801
|
this.delegate.formSubmissionIntercepted(form, submitter);
|
|
@@ -1274,7 +1389,8 @@ var VisitState;
|
|
|
1274
1389
|
})(VisitState || (VisitState = {}));
|
|
1275
1390
|
const defaultOptions = {
|
|
1276
1391
|
action: "advance",
|
|
1277
|
-
|
|
1392
|
+
delegate: {},
|
|
1393
|
+
historyChanged: false,
|
|
1278
1394
|
};
|
|
1279
1395
|
var SystemStatusCode;
|
|
1280
1396
|
(function (SystemStatusCode) {
|
|
@@ -1294,13 +1410,14 @@ class Visit {
|
|
|
1294
1410
|
this.delegate = delegate;
|
|
1295
1411
|
this.location = location;
|
|
1296
1412
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
|
1297
|
-
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);
|
|
1298
1414
|
this.action = action;
|
|
1299
1415
|
this.historyChanged = historyChanged;
|
|
1300
1416
|
this.referrer = referrer;
|
|
1301
1417
|
this.snapshotHTML = snapshotHTML;
|
|
1302
1418
|
this.response = response;
|
|
1303
1419
|
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
|
1420
|
+
this.optionalDelegate = optionalDelegate;
|
|
1304
1421
|
}
|
|
1305
1422
|
get adapter() {
|
|
1306
1423
|
return this.delegate.adapter;
|
|
@@ -1323,6 +1440,8 @@ class Visit {
|
|
|
1323
1440
|
this.state = VisitState.started;
|
|
1324
1441
|
this.adapter.visitStarted(this);
|
|
1325
1442
|
this.delegate.visitStarted(this);
|
|
1443
|
+
if (this.optionalDelegate.visitStarted)
|
|
1444
|
+
this.optionalDelegate.visitStarted(this);
|
|
1326
1445
|
}
|
|
1327
1446
|
}
|
|
1328
1447
|
cancel() {
|
|
@@ -1452,7 +1571,8 @@ class Visit {
|
|
|
1452
1571
|
}
|
|
1453
1572
|
}
|
|
1454
1573
|
followRedirect() {
|
|
1455
|
-
|
|
1574
|
+
var _a;
|
|
1575
|
+
if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
|
|
1456
1576
|
this.adapter.visitProposedToLocation(this.redirectedToLocation, {
|
|
1457
1577
|
action: 'replace',
|
|
1458
1578
|
response: this.response
|
|
@@ -1475,25 +1595,27 @@ class Visit {
|
|
|
1475
1595
|
}
|
|
1476
1596
|
async requestSucceededWithResponse(request, response) {
|
|
1477
1597
|
const responseHTML = await response.responseHTML;
|
|
1598
|
+
const { redirected, statusCode } = response;
|
|
1478
1599
|
if (responseHTML == undefined) {
|
|
1479
|
-
this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch });
|
|
1600
|
+
this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
|
|
1480
1601
|
}
|
|
1481
1602
|
else {
|
|
1482
1603
|
this.redirectedToLocation = response.redirected ? response.location : undefined;
|
|
1483
|
-
this.recordResponse({ statusCode:
|
|
1604
|
+
this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
|
|
1484
1605
|
}
|
|
1485
1606
|
}
|
|
1486
1607
|
async requestFailedWithResponse(request, response) {
|
|
1487
1608
|
const responseHTML = await response.responseHTML;
|
|
1609
|
+
const { redirected, statusCode } = response;
|
|
1488
1610
|
if (responseHTML == undefined) {
|
|
1489
|
-
this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch });
|
|
1611
|
+
this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
|
|
1490
1612
|
}
|
|
1491
1613
|
else {
|
|
1492
|
-
this.recordResponse({ statusCode:
|
|
1614
|
+
this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
|
|
1493
1615
|
}
|
|
1494
1616
|
}
|
|
1495
1617
|
requestErrored(request, error) {
|
|
1496
|
-
this.recordResponse({ statusCode: SystemStatusCode.networkFailure });
|
|
1618
|
+
this.recordResponse({ statusCode: SystemStatusCode.networkFailure, redirected: false });
|
|
1497
1619
|
}
|
|
1498
1620
|
requestFinished() {
|
|
1499
1621
|
this.finishRequest();
|
|
@@ -1557,6 +1679,8 @@ class Visit {
|
|
|
1557
1679
|
if (!this.snapshotCached) {
|
|
1558
1680
|
this.view.cacheSnapshot();
|
|
1559
1681
|
this.snapshotCached = true;
|
|
1682
|
+
if (this.optionalDelegate.visitCachedSnapshot)
|
|
1683
|
+
this.optionalDelegate.visitCachedSnapshot(this);
|
|
1560
1684
|
}
|
|
1561
1685
|
}
|
|
1562
1686
|
async render(callback) {
|
|
@@ -1705,7 +1829,7 @@ class FormSubmitObserver {
|
|
|
1705
1829
|
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
|
1706
1830
|
const submitter = event.submitter || undefined;
|
|
1707
1831
|
if (form) {
|
|
1708
|
-
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");
|
|
1709
1833
|
if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
|
|
1710
1834
|
event.preventDefault();
|
|
1711
1835
|
this.delegate.formSubmitted(form, submitter);
|
|
@@ -1749,12 +1873,11 @@ class FrameRedirector {
|
|
|
1749
1873
|
linkClickIntercepted(element, url) {
|
|
1750
1874
|
const frame = this.findFrameElement(element);
|
|
1751
1875
|
if (frame) {
|
|
1752
|
-
frame.
|
|
1753
|
-
frame.src = url;
|
|
1876
|
+
frame.delegate.linkClickIntercepted(element, url);
|
|
1754
1877
|
}
|
|
1755
1878
|
}
|
|
1756
1879
|
shouldInterceptFormSubmission(element, submitter) {
|
|
1757
|
-
return this.
|
|
1880
|
+
return this.shouldSubmit(element, submitter);
|
|
1758
1881
|
}
|
|
1759
1882
|
formSubmissionIntercepted(element, submitter) {
|
|
1760
1883
|
const frame = this.findFrameElement(element, submitter);
|
|
@@ -1763,6 +1886,13 @@ class FrameRedirector {
|
|
|
1763
1886
|
frame.delegate.formSubmissionIntercepted(element, submitter);
|
|
1764
1887
|
}
|
|
1765
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
|
+
}
|
|
1766
1896
|
shouldRedirect(element, submitter) {
|
|
1767
1897
|
const frame = this.findFrameElement(element, submitter);
|
|
1768
1898
|
return frame ? frame != element.closest("turbo-frame") : false;
|
|
@@ -1920,7 +2050,12 @@ class Navigator {
|
|
|
1920
2050
|
}
|
|
1921
2051
|
proposeVisit(location, options = {}) {
|
|
1922
2052
|
if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
|
|
1923
|
-
|
|
2053
|
+
if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
|
|
2054
|
+
this.delegate.visitProposedToLocation(location, options);
|
|
2055
|
+
}
|
|
2056
|
+
else {
|
|
2057
|
+
window.location.href = location.toString();
|
|
2058
|
+
}
|
|
1924
2059
|
}
|
|
1925
2060
|
}
|
|
1926
2061
|
startVisit(locatable, restorationIdentifier, options = {}) {
|
|
@@ -1931,12 +2066,7 @@ class Navigator {
|
|
|
1931
2066
|
submitForm(form, submitter) {
|
|
1932
2067
|
this.stop();
|
|
1933
2068
|
this.formSubmission = new FormSubmission(this, form, submitter, true);
|
|
1934
|
-
|
|
1935
|
-
this.proposeVisit(this.formSubmission.fetchRequest.url, { action: this.getActionForFormSubmission(this.formSubmission) });
|
|
1936
|
-
}
|
|
1937
|
-
else {
|
|
1938
|
-
this.formSubmission.start();
|
|
1939
|
-
}
|
|
2069
|
+
this.formSubmission.start();
|
|
1940
2070
|
}
|
|
1941
2071
|
stop() {
|
|
1942
2072
|
if (this.formSubmission) {
|
|
@@ -1969,8 +2099,9 @@ class Navigator {
|
|
|
1969
2099
|
if (formSubmission.method != FetchMethod.get) {
|
|
1970
2100
|
this.view.clearSnapshotCache();
|
|
1971
2101
|
}
|
|
1972
|
-
const { statusCode } = fetchResponse;
|
|
1973
|
-
const
|
|
2102
|
+
const { statusCode, redirected } = fetchResponse;
|
|
2103
|
+
const action = this.getActionForFormSubmission(formSubmission);
|
|
2104
|
+
const visitOptions = { action, response: { statusCode, responseHTML, redirected } };
|
|
1974
2105
|
this.proposeVisit(fetchResponse.location, visitOptions);
|
|
1975
2106
|
}
|
|
1976
2107
|
}
|
|
@@ -2003,6 +2134,9 @@ class Navigator {
|
|
|
2003
2134
|
visitCompleted(visit) {
|
|
2004
2135
|
this.delegate.visitCompleted(visit);
|
|
2005
2136
|
}
|
|
2137
|
+
visitCachedSnapshot(visit) {
|
|
2138
|
+
this.delegate.visitCachedSnapshot(visit);
|
|
2139
|
+
}
|
|
2006
2140
|
locationWithActionIsSamePage(location, action) {
|
|
2007
2141
|
const anchor = getAnchor(location);
|
|
2008
2142
|
const currentAnchor = getAnchor(this.view.lastRenderedLocation);
|
|
@@ -2022,7 +2156,7 @@ class Navigator {
|
|
|
2022
2156
|
}
|
|
2023
2157
|
getActionForFormSubmission(formSubmission) {
|
|
2024
2158
|
const { formElement, submitter } = formSubmission;
|
|
2025
|
-
const action =
|
|
2159
|
+
const action = getAttribute("data-turbo-action", submitter, formElement);
|
|
2026
2160
|
return isAction(action) ? action : "advance";
|
|
2027
2161
|
}
|
|
2028
2162
|
}
|
|
@@ -2472,7 +2606,7 @@ class Session {
|
|
|
2472
2606
|
}
|
|
2473
2607
|
willFollowLinkToLocation(link, location) {
|
|
2474
2608
|
return this.elementDriveEnabled(link)
|
|
2475
|
-
&&
|
|
2609
|
+
&& locationIsVisitable(location, this.snapshot.rootLocation)
|
|
2476
2610
|
&& this.applicationAllowsFollowingLinkToLocation(link, location);
|
|
2477
2611
|
}
|
|
2478
2612
|
followedLinkToLocation(link, location) {
|
|
@@ -2480,13 +2614,24 @@ class Session {
|
|
|
2480
2614
|
this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, { action });
|
|
2481
2615
|
}
|
|
2482
2616
|
convertLinkWithMethodClickToFormSubmission(link) {
|
|
2483
|
-
var _a;
|
|
2484
2617
|
const linkMethod = link.getAttribute("data-turbo-method");
|
|
2485
2618
|
if (linkMethod) {
|
|
2486
2619
|
const form = document.createElement("form");
|
|
2487
2620
|
form.method = linkMethod;
|
|
2488
2621
|
form.action = link.getAttribute("href") || "undefined";
|
|
2489
|
-
|
|
2622
|
+
form.hidden = true;
|
|
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);
|
|
2490
2635
|
return dispatch("submit", { cancelable: true, target: form });
|
|
2491
2636
|
}
|
|
2492
2637
|
else {
|
|
@@ -2509,6 +2654,8 @@ class Session {
|
|
|
2509
2654
|
visitCompleted(visit) {
|
|
2510
2655
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
|
2511
2656
|
}
|
|
2657
|
+
visitCachedSnapshot(visit) {
|
|
2658
|
+
}
|
|
2512
2659
|
locationWithActionIsSamePage(location, action) {
|
|
2513
2660
|
return this.navigator.locationWithActionIsSamePage(location, action);
|
|
2514
2661
|
}
|
|
@@ -2516,7 +2663,10 @@ class Session {
|
|
|
2516
2663
|
this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
|
|
2517
2664
|
}
|
|
2518
2665
|
willSubmitForm(form, submitter) {
|
|
2519
|
-
|
|
2666
|
+
const action = getAction(form, submitter);
|
|
2667
|
+
return this.elementDriveEnabled(form)
|
|
2668
|
+
&& (!submitter || this.elementDriveEnabled(submitter))
|
|
2669
|
+
&& locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
|
|
2520
2670
|
}
|
|
2521
2671
|
formSubmitted(form, submitter) {
|
|
2522
2672
|
this.navigator.submitForm(form, submitter);
|
|
@@ -2572,6 +2722,7 @@ class Session {
|
|
|
2572
2722
|
return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
|
|
2573
2723
|
}
|
|
2574
2724
|
notifyApplicationAfterVisitingLocation(location, action) {
|
|
2725
|
+
markAsBusy(document.documentElement);
|
|
2575
2726
|
return dispatch("turbo:visit", { detail: { url: location.href, action } });
|
|
2576
2727
|
}
|
|
2577
2728
|
notifyApplicationBeforeCachingSnapshot() {
|
|
@@ -2584,6 +2735,7 @@ class Session {
|
|
|
2584
2735
|
return dispatch("turbo:render");
|
|
2585
2736
|
}
|
|
2586
2737
|
notifyApplicationAfterPageLoad(timing = {}) {
|
|
2738
|
+
clearBusyState(document.documentElement);
|
|
2587
2739
|
return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
|
|
2588
2740
|
}
|
|
2589
2741
|
notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
|
|
@@ -2618,8 +2770,17 @@ class Session {
|
|
|
2618
2770
|
const action = link.getAttribute("data-turbo-action");
|
|
2619
2771
|
return isAction(action) ? action : "advance";
|
|
2620
2772
|
}
|
|
2621
|
-
|
|
2622
|
-
|
|
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
|
+
}
|
|
2623
2784
|
}
|
|
2624
2785
|
get snapshot() {
|
|
2625
2786
|
return this.view.snapshot;
|
|
@@ -2637,7 +2798,7 @@ const deprecatedLocationPropertyDescriptors = {
|
|
|
2637
2798
|
};
|
|
2638
2799
|
|
|
2639
2800
|
const session = new Session;
|
|
2640
|
-
const { navigator } = session;
|
|
2801
|
+
const { navigator: navigator$1 } = session;
|
|
2641
2802
|
function start() {
|
|
2642
2803
|
session.start();
|
|
2643
2804
|
}
|
|
@@ -2662,10 +2823,13 @@ function clearCache() {
|
|
|
2662
2823
|
function setProgressBarDelay(delay) {
|
|
2663
2824
|
session.setProgressBarDelay(delay);
|
|
2664
2825
|
}
|
|
2826
|
+
function setConfirmMethod(confirmMethod) {
|
|
2827
|
+
FormSubmission.confirmMethod = confirmMethod;
|
|
2828
|
+
}
|
|
2665
2829
|
|
|
2666
2830
|
var Turbo = /*#__PURE__*/Object.freeze({
|
|
2667
2831
|
__proto__: null,
|
|
2668
|
-
navigator: navigator,
|
|
2832
|
+
navigator: navigator$1,
|
|
2669
2833
|
session: session,
|
|
2670
2834
|
PageRenderer: PageRenderer,
|
|
2671
2835
|
PageSnapshot: PageSnapshot,
|
|
@@ -2676,11 +2840,13 @@ var Turbo = /*#__PURE__*/Object.freeze({
|
|
|
2676
2840
|
disconnectStreamSource: disconnectStreamSource,
|
|
2677
2841
|
renderStreamMessage: renderStreamMessage,
|
|
2678
2842
|
clearCache: clearCache,
|
|
2679
|
-
setProgressBarDelay: setProgressBarDelay
|
|
2843
|
+
setProgressBarDelay: setProgressBarDelay,
|
|
2844
|
+
setConfirmMethod: setConfirmMethod
|
|
2680
2845
|
});
|
|
2681
2846
|
|
|
2682
2847
|
class FrameController {
|
|
2683
2848
|
constructor(element) {
|
|
2849
|
+
this.currentFetchRequest = null;
|
|
2684
2850
|
this.resolveVisitPromise = () => { };
|
|
2685
2851
|
this.connected = false;
|
|
2686
2852
|
this.hasBeenLoaded = false;
|
|
@@ -2740,7 +2906,6 @@ class FrameController {
|
|
|
2740
2906
|
this.appearanceObserver.stop();
|
|
2741
2907
|
await this.element.loaded;
|
|
2742
2908
|
this.hasBeenLoaded = true;
|
|
2743
|
-
session.frameLoaded(this.element);
|
|
2744
2909
|
}
|
|
2745
2910
|
catch (error) {
|
|
2746
2911
|
this.currentURL = previousURL;
|
|
@@ -2763,6 +2928,7 @@ class FrameController {
|
|
|
2763
2928
|
await this.view.renderPromise;
|
|
2764
2929
|
await this.view.render(renderer);
|
|
2765
2930
|
session.frameRendered(fetchResponse, this.element);
|
|
2931
|
+
session.frameLoaded(this.element);
|
|
2766
2932
|
}
|
|
2767
2933
|
}
|
|
2768
2934
|
catch (error) {
|
|
@@ -2794,20 +2960,15 @@ class FrameController {
|
|
|
2794
2960
|
}
|
|
2795
2961
|
this.reloadable = false;
|
|
2796
2962
|
this.formSubmission = new FormSubmission(this, element, submitter);
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
else {
|
|
2801
|
-
const { fetchRequest } = this.formSubmission;
|
|
2802
|
-
this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
|
|
2803
|
-
this.formSubmission.start();
|
|
2804
|
-
}
|
|
2963
|
+
const { fetchRequest } = this.formSubmission;
|
|
2964
|
+
this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
|
|
2965
|
+
this.formSubmission.start();
|
|
2805
2966
|
}
|
|
2806
2967
|
prepareHeadersForRequest(headers, request) {
|
|
2807
2968
|
headers["Turbo-Frame"] = this.id;
|
|
2808
2969
|
}
|
|
2809
2970
|
requestStarted(request) {
|
|
2810
|
-
this.element
|
|
2971
|
+
markAsBusy(this.element);
|
|
2811
2972
|
}
|
|
2812
2973
|
requestPreventedHandlingResponse(request, response) {
|
|
2813
2974
|
this.resolveVisitPromise();
|
|
@@ -2825,14 +2986,14 @@ class FrameController {
|
|
|
2825
2986
|
this.resolveVisitPromise();
|
|
2826
2987
|
}
|
|
2827
2988
|
requestFinished(request) {
|
|
2828
|
-
this.element
|
|
2989
|
+
clearBusyState(this.element);
|
|
2829
2990
|
}
|
|
2830
|
-
formSubmissionStarted(
|
|
2831
|
-
|
|
2832
|
-
frame.setAttribute("busy", "");
|
|
2991
|
+
formSubmissionStarted({ formElement }) {
|
|
2992
|
+
markAsBusy(formElement, this.findFrameElement(formElement));
|
|
2833
2993
|
}
|
|
2834
2994
|
formSubmissionSucceededWithResponse(formSubmission, response) {
|
|
2835
2995
|
const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
|
|
2996
|
+
this.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
|
|
2836
2997
|
frame.delegate.loadResponse(response);
|
|
2837
2998
|
}
|
|
2838
2999
|
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
|
@@ -2841,9 +3002,8 @@ class FrameController {
|
|
|
2841
3002
|
formSubmissionErrored(formSubmission, error) {
|
|
2842
3003
|
console.error(error);
|
|
2843
3004
|
}
|
|
2844
|
-
formSubmissionFinished(
|
|
2845
|
-
|
|
2846
|
-
frame.removeAttribute("busy");
|
|
3005
|
+
formSubmissionFinished({ formElement }) {
|
|
3006
|
+
clearBusyState(formElement, this.findFrameElement(formElement));
|
|
2847
3007
|
}
|
|
2848
3008
|
allowsImmediateRender(snapshot, resume) {
|
|
2849
3009
|
return true;
|
|
@@ -2853,10 +3013,14 @@ class FrameController {
|
|
|
2853
3013
|
viewInvalidated() {
|
|
2854
3014
|
}
|
|
2855
3015
|
async visit(url) {
|
|
2856
|
-
|
|
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;
|
|
2857
3020
|
return new Promise(resolve => {
|
|
2858
3021
|
this.resolveVisitPromise = () => {
|
|
2859
3022
|
this.resolveVisitPromise = () => { };
|
|
3023
|
+
this.currentFetchRequest = null;
|
|
2860
3024
|
resolve();
|
|
2861
3025
|
};
|
|
2862
3026
|
request.perform();
|
|
@@ -2864,12 +3028,29 @@ class FrameController {
|
|
|
2864
3028
|
}
|
|
2865
3029
|
navigateFrame(element, url, submitter) {
|
|
2866
3030
|
const frame = this.findFrameElement(element, submitter);
|
|
3031
|
+
this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
|
|
2867
3032
|
frame.setAttribute("reloadable", "");
|
|
2868
3033
|
frame.src = url;
|
|
2869
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
|
+
}
|
|
2870
3051
|
findFrameElement(element, submitter) {
|
|
2871
3052
|
var _a;
|
|
2872
|
-
const id =
|
|
3053
|
+
const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
|
|
2873
3054
|
return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
|
|
2874
3055
|
}
|
|
2875
3056
|
async extractForeignFrameElement(container) {
|
|
@@ -2890,8 +3071,15 @@ class FrameController {
|
|
|
2890
3071
|
}
|
|
2891
3072
|
return new FrameElement();
|
|
2892
3073
|
}
|
|
3074
|
+
formActionIsVisitable(form, submitter) {
|
|
3075
|
+
const action = getAction(form, submitter);
|
|
3076
|
+
return locationIsVisitable(expandURL(action), this.rootLocation);
|
|
3077
|
+
}
|
|
2893
3078
|
shouldInterceptNavigation(element, submitter) {
|
|
2894
|
-
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
|
+
}
|
|
2895
3083
|
if (!this.enabled || id == "_top") {
|
|
2896
3084
|
return false;
|
|
2897
3085
|
}
|
|
@@ -2948,6 +3136,28 @@ class FrameController {
|
|
|
2948
3136
|
get isActive() {
|
|
2949
3137
|
return this.element.isActive && this.connected;
|
|
2950
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
|
+
}
|
|
2951
3161
|
}
|
|
2952
3162
|
function getFrameElementById(id) {
|
|
2953
3163
|
if (id != null) {
|
|
@@ -3138,4 +3348,4 @@ customElements.define("turbo-stream", StreamElement);
|
|
|
3138
3348
|
window.Turbo = Turbo;
|
|
3139
3349
|
start();
|
|
3140
3350
|
|
|
3141
|
-
export { PageRenderer, PageSnapshot, clearCache, connectStreamSource, disconnectStreamSource, 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 };
|