@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.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Turbo 7.0.0-rc.5
2
+ Turbo 7.1.0-rc.1
3
3
  Copyright © 2021 Basecamp, LLC
4
4
  */
5
5
  (function (global, factory) {
@@ -26,6 +26,58 @@ Copyright © 2021 Basecamp, LLC
26
26
  Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);
27
27
  })();
28
28
 
29
+ /**
30
+ * The MIT License (MIT)
31
+ *
32
+ * Copyright (c) 2019 Javan Makhmali
33
+ *
34
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
35
+ * of this software and associated documentation files (the "Software"), to deal
36
+ * in the Software without restriction, including without limitation the rights
37
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
38
+ * copies of the Software, and to permit persons to whom the Software is
39
+ * furnished to do so, subject to the following conditions:
40
+ *
41
+ * The above copyright notice and this permission notice shall be included in
42
+ * all copies or substantial portions of the Software.
43
+ *
44
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
45
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
46
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
47
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
48
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
49
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
50
+ * THE SOFTWARE.
51
+ */
52
+
53
+ (function(prototype) {
54
+ if (typeof prototype.requestSubmit == "function") return
55
+
56
+ prototype.requestSubmit = function(submitter) {
57
+ if (submitter) {
58
+ validateSubmitter(submitter, this);
59
+ submitter.click();
60
+ } else {
61
+ submitter = document.createElement("input");
62
+ submitter.type = "submit";
63
+ submitter.hidden = true;
64
+ this.appendChild(submitter);
65
+ submitter.click();
66
+ this.removeChild(submitter);
67
+ }
68
+ };
69
+
70
+ function validateSubmitter(submitter, form) {
71
+ submitter instanceof HTMLElement || raise(TypeError, "parameter 1 is not of type 'HTMLElement'");
72
+ submitter.type == "submit" || raise(TypeError, "The specified element is not a submit button");
73
+ submitter.form == form || raise(DOMException, "The specified element is not owned by this form element", "NotFoundError");
74
+ }
75
+
76
+ function raise(errorConstructor, message, name) {
77
+ throw new errorConstructor("Failed to execute 'requestSubmit' on 'HTMLFormElement': " + message + ".", name)
78
+ }
79
+ })(HTMLFormElement.prototype);
80
+
29
81
  const submittersByForm = new WeakMap;
30
82
  function findSubmitterFromClickTarget(target) {
31
83
  const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
@@ -39,12 +91,20 @@ Copyright © 2021 Basecamp, LLC
39
91
  }
40
92
  }
41
93
  (function () {
42
- if ("SubmitEvent" in window)
43
- return;
44
94
  if ("submitter" in Event.prototype)
45
95
  return;
96
+ let prototype;
97
+ if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
98
+ prototype = window.SubmitEvent.prototype;
99
+ }
100
+ else if ("SubmitEvent" in window) {
101
+ return;
102
+ }
103
+ else {
104
+ prototype = window.Event.prototype;
105
+ }
46
106
  addEventListener("click", clickCaptured, true);
47
- Object.defineProperty(Event.prototype, "submitter", {
107
+ Object.defineProperty(prototype, "submitter", {
48
108
  get() {
49
109
  if (this.type == "submit" && this.target instanceof HTMLFormElement) {
50
110
  return submittersByForm.get(this.target);
@@ -163,6 +223,10 @@ Copyright © 2021 Basecamp, LLC
163
223
  return anchorMatch[1];
164
224
  }
165
225
  }
226
+ function getAction(form, submitter) {
227
+ const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formaction")) || form.getAttribute("action") || form.action;
228
+ return expandURL(action);
229
+ }
166
230
  function getExtension(url) {
167
231
  return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
168
232
  }
@@ -173,6 +237,9 @@ Copyright © 2021 Basecamp, LLC
173
237
  const prefix = getPrefix(url);
174
238
  return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
175
239
  }
240
+ function locationIsVisitable(location, rootLocation) {
241
+ return isPrefixedBy(location, rootLocation) && isHTML(location);
242
+ }
176
243
  function getRequestURL(url) {
177
244
  const anchor = getAnchor(url);
178
245
  return anchor != null
@@ -295,6 +362,29 @@ Copyright © 2021 Basecamp, LLC
295
362
  }
296
363
  }).join("");
297
364
  }
365
+ function getAttribute(attributeName, ...elements) {
366
+ for (const value of elements.map(element => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {
367
+ if (typeof value == "string")
368
+ return value;
369
+ }
370
+ return null;
371
+ }
372
+ function markAsBusy(...elements) {
373
+ for (const element of elements) {
374
+ if (element.localName == "turbo-frame") {
375
+ element.setAttribute("busy", "");
376
+ }
377
+ element.setAttribute("aria-busy", "true");
378
+ }
379
+ }
380
+ function clearBusyState(...elements) {
381
+ for (const element of elements) {
382
+ if (element.localName == "turbo-frame") {
383
+ element.removeAttribute("busy");
384
+ }
385
+ element.removeAttribute("aria-busy");
386
+ }
387
+ }
298
388
 
299
389
  var FetchMethod;
300
390
  (function (FetchMethod) {
@@ -524,6 +614,9 @@ Copyright © 2021 Basecamp, LLC
524
614
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
525
615
  this.mustRedirect = mustRedirect;
526
616
  }
617
+ static confirmMethod(message, element) {
618
+ return confirm(message);
619
+ }
527
620
  get method() {
528
621
  var _a;
529
622
  const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
@@ -557,8 +650,20 @@ Copyright © 2021 Basecamp, LLC
557
650
  return entries.concat(typeof value == "string" ? [[name, value]] : []);
558
651
  }, []);
559
652
  }
653
+ get confirmationMessage() {
654
+ return this.formElement.getAttribute("data-turbo-confirm");
655
+ }
656
+ get needsConfirmation() {
657
+ return this.confirmationMessage !== null;
658
+ }
560
659
  async start() {
561
660
  const { initialized, requesting } = FormSubmissionState;
661
+ if (this.needsConfirmation) {
662
+ const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
663
+ if (!answer) {
664
+ return;
665
+ }
666
+ }
562
667
  if (this.state == initialized) {
563
668
  this.state = requesting;
564
669
  return this.fetchRequest.perform();
@@ -582,7 +687,9 @@ Copyright © 2021 Basecamp, LLC
582
687
  }
583
688
  }
584
689
  requestStarted(request) {
690
+ var _a;
585
691
  this.state = FormSubmissionState.waiting;
692
+ (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
586
693
  dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } });
587
694
  this.delegate.formSubmissionStarted(this);
588
695
  }
@@ -612,7 +719,9 @@ Copyright © 2021 Basecamp, LLC
612
719
  this.delegate.formSubmissionErrored(this, error);
613
720
  }
614
721
  requestFinished(request) {
722
+ var _a;
615
723
  this.state = FormSubmissionState.stopped;
724
+ (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
616
725
  dispatch("turbo:submit-end", { target: this.formElement, detail: Object.assign({ formSubmission: this }, this.result) });
617
726
  this.delegate.formSubmissionFinished(this);
618
727
  }
@@ -688,10 +797,11 @@ Copyright © 2021 Basecamp, LLC
688
797
  class FormInterceptor {
689
798
  constructor(delegate, element) {
690
799
  this.submitBubbled = ((event) => {
691
- if (event.target instanceof HTMLFormElement) {
692
- const form = event.target;
800
+ const form = event.target;
801
+ if (form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
693
802
  const submitter = event.submitter || undefined;
694
- if (this.delegate.shouldInterceptFormSubmission(form, submitter)) {
803
+ const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
804
+ if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
695
805
  event.preventDefault();
696
806
  event.stopImmediatePropagation();
697
807
  this.delegate.formSubmissionIntercepted(form, submitter);
@@ -906,10 +1016,11 @@ Copyright © 2021 Basecamp, LLC
906
1016
  }
907
1017
 
908
1018
  class Renderer {
909
- constructor(currentSnapshot, newSnapshot, isPreview) {
1019
+ constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
910
1020
  this.currentSnapshot = currentSnapshot;
911
1021
  this.newSnapshot = newSnapshot;
912
1022
  this.isPreview = isPreview;
1023
+ this.willRender = willRender;
913
1024
  this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
914
1025
  }
915
1026
  get shouldRender() {
@@ -1285,7 +1396,9 @@ Copyright © 2021 Basecamp, LLC
1285
1396
  })(VisitState || (VisitState = {}));
1286
1397
  const defaultOptions = {
1287
1398
  action: "advance",
1288
- historyChanged: false
1399
+ historyChanged: false,
1400
+ visitCachedSnapshot: () => { },
1401
+ willRender: true,
1289
1402
  };
1290
1403
  var SystemStatusCode;
1291
1404
  (function (SystemStatusCode) {
@@ -1305,13 +1418,16 @@ Copyright © 2021 Basecamp, LLC
1305
1418
  this.delegate = delegate;
1306
1419
  this.location = location;
1307
1420
  this.restorationIdentifier = restorationIdentifier || uuid();
1308
- const { action, historyChanged, referrer, snapshotHTML, response } = Object.assign(Object.assign({}, defaultOptions), options);
1421
+ const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender } = Object.assign(Object.assign({}, defaultOptions), options);
1309
1422
  this.action = action;
1310
1423
  this.historyChanged = historyChanged;
1311
1424
  this.referrer = referrer;
1312
1425
  this.snapshotHTML = snapshotHTML;
1313
1426
  this.response = response;
1314
1427
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
1428
+ this.visitCachedSnapshot = visitCachedSnapshot;
1429
+ this.willRender = willRender;
1430
+ this.scrolled = !willRender;
1315
1431
  }
1316
1432
  get adapter() {
1317
1433
  return this.delegate.adapter;
@@ -1413,7 +1529,7 @@ Copyright © 2021 Basecamp, LLC
1413
1529
  if (this.view.renderPromise)
1414
1530
  await this.view.renderPromise;
1415
1531
  if (isSuccessful(statusCode) && responseHTML != null) {
1416
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML));
1532
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
1417
1533
  this.adapter.visitRendered(this);
1418
1534
  this.complete();
1419
1535
  }
@@ -1453,7 +1569,7 @@ Copyright © 2021 Basecamp, LLC
1453
1569
  else {
1454
1570
  if (this.view.renderPromise)
1455
1571
  await this.view.renderPromise;
1456
- await this.view.renderPage(snapshot, isPreview);
1572
+ await this.view.renderPage(snapshot, isPreview, this.willRender);
1457
1573
  this.adapter.visitRendered(this);
1458
1574
  if (!isPreview) {
1459
1575
  this.complete();
@@ -1463,7 +1579,8 @@ Copyright © 2021 Basecamp, LLC
1463
1579
  }
1464
1580
  }
1465
1581
  followRedirect() {
1466
- if (this.redirectedToLocation && !this.followedRedirect) {
1582
+ var _a;
1583
+ if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1467
1584
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1468
1585
  action: 'replace',
1469
1586
  response: this.response
@@ -1486,25 +1603,27 @@ Copyright © 2021 Basecamp, LLC
1486
1603
  }
1487
1604
  async requestSucceededWithResponse(request, response) {
1488
1605
  const responseHTML = await response.responseHTML;
1606
+ const { redirected, statusCode } = response;
1489
1607
  if (responseHTML == undefined) {
1490
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch });
1608
+ this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1491
1609
  }
1492
1610
  else {
1493
1611
  this.redirectedToLocation = response.redirected ? response.location : undefined;
1494
- this.recordResponse({ statusCode: response.statusCode, responseHTML });
1612
+ this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
1495
1613
  }
1496
1614
  }
1497
1615
  async requestFailedWithResponse(request, response) {
1498
1616
  const responseHTML = await response.responseHTML;
1617
+ const { redirected, statusCode } = response;
1499
1618
  if (responseHTML == undefined) {
1500
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch });
1619
+ this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1501
1620
  }
1502
1621
  else {
1503
- this.recordResponse({ statusCode: response.statusCode, responseHTML });
1622
+ this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
1504
1623
  }
1505
1624
  }
1506
1625
  requestErrored(request, error) {
1507
- this.recordResponse({ statusCode: SystemStatusCode.networkFailure });
1626
+ this.recordResponse({ statusCode: SystemStatusCode.networkFailure, redirected: false });
1508
1627
  }
1509
1628
  requestFinished() {
1510
1629
  this.finishRequest();
@@ -1561,12 +1680,12 @@ Copyright © 2021 Basecamp, LLC
1561
1680
  return !this.hasCachedSnapshot();
1562
1681
  }
1563
1682
  else {
1564
- return true;
1683
+ return this.willRender;
1565
1684
  }
1566
1685
  }
1567
1686
  cacheSnapshot() {
1568
1687
  if (!this.snapshotCached) {
1569
- this.view.cacheSnapshot();
1688
+ this.view.cacheSnapshot().then(snapshot => snapshot && this.visitCachedSnapshot(snapshot));
1570
1689
  this.snapshotCached = true;
1571
1690
  }
1572
1691
  }
@@ -1716,7 +1835,7 @@ Copyright © 2021 Basecamp, LLC
1716
1835
  const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1717
1836
  const submitter = event.submitter || undefined;
1718
1837
  if (form) {
1719
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
1838
+ const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
1720
1839
  if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
1721
1840
  event.preventDefault();
1722
1841
  this.delegate.formSubmitted(form, submitter);
@@ -1760,12 +1879,11 @@ Copyright © 2021 Basecamp, LLC
1760
1879
  linkClickIntercepted(element, url) {
1761
1880
  const frame = this.findFrameElement(element);
1762
1881
  if (frame) {
1763
- frame.setAttribute("reloadable", "");
1764
- frame.src = url;
1882
+ frame.delegate.linkClickIntercepted(element, url);
1765
1883
  }
1766
1884
  }
1767
1885
  shouldInterceptFormSubmission(element, submitter) {
1768
- return this.shouldRedirect(element, submitter);
1886
+ return this.shouldSubmit(element, submitter);
1769
1887
  }
1770
1888
  formSubmissionIntercepted(element, submitter) {
1771
1889
  const frame = this.findFrameElement(element, submitter);
@@ -1774,6 +1892,13 @@ Copyright © 2021 Basecamp, LLC
1774
1892
  frame.delegate.formSubmissionIntercepted(element, submitter);
1775
1893
  }
1776
1894
  }
1895
+ shouldSubmit(form, submitter) {
1896
+ var _a;
1897
+ const action = getAction(form, submitter);
1898
+ const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
1899
+ const rootLocation = expandURL((_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/");
1900
+ return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
1901
+ }
1777
1902
  shouldRedirect(element, submitter) {
1778
1903
  const frame = this.findFrameElement(element, submitter);
1779
1904
  return frame ? frame != element.closest("turbo-frame") : false;
@@ -1931,7 +2056,12 @@ Copyright © 2021 Basecamp, LLC
1931
2056
  }
1932
2057
  proposeVisit(location, options = {}) {
1933
2058
  if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
1934
- this.delegate.visitProposedToLocation(location, options);
2059
+ if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
2060
+ this.delegate.visitProposedToLocation(location, options);
2061
+ }
2062
+ else {
2063
+ window.location.href = location.toString();
2064
+ }
1935
2065
  }
1936
2066
  }
1937
2067
  startVisit(locatable, restorationIdentifier, options = {}) {
@@ -1942,12 +2072,7 @@ Copyright © 2021 Basecamp, LLC
1942
2072
  submitForm(form, submitter) {
1943
2073
  this.stop();
1944
2074
  this.formSubmission = new FormSubmission(this, form, submitter, true);
1945
- if (this.formSubmission.isIdempotent) {
1946
- this.proposeVisit(this.formSubmission.fetchRequest.url, { action: this.getActionForFormSubmission(this.formSubmission) });
1947
- }
1948
- else {
1949
- this.formSubmission.start();
1950
- }
2075
+ this.formSubmission.start();
1951
2076
  }
1952
2077
  stop() {
1953
2078
  if (this.formSubmission) {
@@ -1980,8 +2105,9 @@ Copyright © 2021 Basecamp, LLC
1980
2105
  if (formSubmission.method != FetchMethod.get) {
1981
2106
  this.view.clearSnapshotCache();
1982
2107
  }
1983
- const { statusCode } = fetchResponse;
1984
- const visitOptions = { response: { statusCode, responseHTML } };
2108
+ const { statusCode, redirected } = fetchResponse;
2109
+ const action = this.getActionForFormSubmission(formSubmission);
2110
+ const visitOptions = { action, response: { statusCode, responseHTML, redirected } };
1985
2111
  this.proposeVisit(fetchResponse.location, visitOptions);
1986
2112
  }
1987
2113
  }
@@ -2033,7 +2159,7 @@ Copyright © 2021 Basecamp, LLC
2033
2159
  }
2034
2160
  getActionForFormSubmission(formSubmission) {
2035
2161
  const { formElement, submitter } = formSubmission;
2036
- const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-action")) || formElement.getAttribute("data-turbo-action");
2162
+ const action = getAttribute("data-turbo-action", submitter, formElement);
2037
2163
  return isAction(action) ? action : "advance";
2038
2164
  }
2039
2165
  }
@@ -2227,7 +2353,9 @@ Copyright © 2021 Basecamp, LLC
2227
2353
  this.mergeHead();
2228
2354
  }
2229
2355
  async render() {
2230
- this.replaceBody();
2356
+ if (this.willRender) {
2357
+ this.replaceBody();
2358
+ }
2231
2359
  }
2232
2360
  finishRendering() {
2233
2361
  super.finishRendering();
@@ -2365,8 +2493,8 @@ Copyright © 2021 Basecamp, LLC
2365
2493
  this.snapshotCache = new SnapshotCache(10);
2366
2494
  this.lastRenderedLocation = new URL(location.href);
2367
2495
  }
2368
- renderPage(snapshot, isPreview = false) {
2369
- const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
2496
+ renderPage(snapshot, isPreview = false, willRender = true) {
2497
+ const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
2370
2498
  return this.render(renderer);
2371
2499
  }
2372
2500
  renderError(snapshot) {
@@ -2381,7 +2509,9 @@ Copyright © 2021 Basecamp, LLC
2381
2509
  this.delegate.viewWillCacheSnapshot();
2382
2510
  const { snapshot, lastRenderedLocation: location } = this;
2383
2511
  await nextEventLoopTick();
2384
- this.snapshotCache.put(location, snapshot.clone());
2512
+ const cachedSnapshot = snapshot.clone();
2513
+ this.snapshotCache.put(location, cachedSnapshot);
2514
+ return cachedSnapshot;
2385
2515
  }
2386
2516
  }
2387
2517
  getCachedSnapshotForLocation(location) {
@@ -2483,7 +2613,7 @@ Copyright © 2021 Basecamp, LLC
2483
2613
  }
2484
2614
  willFollowLinkToLocation(link, location) {
2485
2615
  return this.elementDriveEnabled(link)
2486
- && this.locationIsVisitable(location)
2616
+ && locationIsVisitable(location, this.snapshot.rootLocation)
2487
2617
  && this.applicationAllowsFollowingLinkToLocation(link, location);
2488
2618
  }
2489
2619
  followedLinkToLocation(link, location) {
@@ -2491,14 +2621,24 @@ Copyright © 2021 Basecamp, LLC
2491
2621
  this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, { action });
2492
2622
  }
2493
2623
  convertLinkWithMethodClickToFormSubmission(link) {
2494
- var _a;
2495
2624
  const linkMethod = link.getAttribute("data-turbo-method");
2496
2625
  if (linkMethod) {
2497
2626
  const form = document.createElement("form");
2498
2627
  form.method = linkMethod;
2499
2628
  form.action = link.getAttribute("href") || "undefined";
2500
2629
  form.hidden = true;
2501
- (_a = link.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(form, link);
2630
+ if (link.hasAttribute("data-turbo-confirm")) {
2631
+ form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
2632
+ }
2633
+ const frame = this.getTargetFrameForLink(link);
2634
+ if (frame) {
2635
+ form.setAttribute("data-turbo-frame", frame);
2636
+ form.addEventListener("turbo:submit-start", () => form.remove());
2637
+ }
2638
+ else {
2639
+ form.addEventListener("submit", () => form.remove());
2640
+ }
2641
+ document.body.appendChild(form);
2502
2642
  return dispatch("submit", { cancelable: true, target: form });
2503
2643
  }
2504
2644
  else {
@@ -2528,7 +2668,10 @@ Copyright © 2021 Basecamp, LLC
2528
2668
  this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
2529
2669
  }
2530
2670
  willSubmitForm(form, submitter) {
2531
- return this.elementDriveEnabled(form) && this.elementDriveEnabled(submitter);
2671
+ const action = getAction(form, submitter);
2672
+ return this.elementDriveEnabled(form)
2673
+ && (!submitter || this.elementDriveEnabled(submitter))
2674
+ && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
2532
2675
  }
2533
2676
  formSubmitted(form, submitter) {
2534
2677
  this.navigator.submitForm(form, submitter);
@@ -2584,6 +2727,7 @@ Copyright © 2021 Basecamp, LLC
2584
2727
  return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
2585
2728
  }
2586
2729
  notifyApplicationAfterVisitingLocation(location, action) {
2730
+ markAsBusy(document.documentElement);
2587
2731
  return dispatch("turbo:visit", { detail: { url: location.href, action } });
2588
2732
  }
2589
2733
  notifyApplicationBeforeCachingSnapshot() {
@@ -2596,6 +2740,7 @@ Copyright © 2021 Basecamp, LLC
2596
2740
  return dispatch("turbo:render");
2597
2741
  }
2598
2742
  notifyApplicationAfterPageLoad(timing = {}) {
2743
+ clearBusyState(document.documentElement);
2599
2744
  return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
2600
2745
  }
2601
2746
  notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
@@ -2630,8 +2775,17 @@ Copyright © 2021 Basecamp, LLC
2630
2775
  const action = link.getAttribute("data-turbo-action");
2631
2776
  return isAction(action) ? action : "advance";
2632
2777
  }
2633
- locationIsVisitable(location) {
2634
- return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
2778
+ getTargetFrameForLink(link) {
2779
+ const frame = link.getAttribute("data-turbo-frame");
2780
+ if (frame) {
2781
+ return frame;
2782
+ }
2783
+ else {
2784
+ const container = link.closest("turbo-frame");
2785
+ if (container) {
2786
+ return container.id;
2787
+ }
2788
+ }
2635
2789
  }
2636
2790
  get snapshot() {
2637
2791
  return this.view.snapshot;
@@ -2649,7 +2803,7 @@ Copyright © 2021 Basecamp, LLC
2649
2803
  };
2650
2804
 
2651
2805
  const session = new Session;
2652
- const { navigator } = session;
2806
+ const { navigator: navigator$1 } = session;
2653
2807
  function start() {
2654
2808
  session.start();
2655
2809
  }
@@ -2674,10 +2828,13 @@ Copyright © 2021 Basecamp, LLC
2674
2828
  function setProgressBarDelay(delay) {
2675
2829
  session.setProgressBarDelay(delay);
2676
2830
  }
2831
+ function setConfirmMethod(confirmMethod) {
2832
+ FormSubmission.confirmMethod = confirmMethod;
2833
+ }
2677
2834
 
2678
2835
  var Turbo = /*#__PURE__*/Object.freeze({
2679
2836
  __proto__: null,
2680
- navigator: navigator,
2837
+ navigator: navigator$1,
2681
2838
  session: session,
2682
2839
  PageRenderer: PageRenderer,
2683
2840
  PageSnapshot: PageSnapshot,
@@ -2688,11 +2845,14 @@ Copyright © 2021 Basecamp, LLC
2688
2845
  disconnectStreamSource: disconnectStreamSource,
2689
2846
  renderStreamMessage: renderStreamMessage,
2690
2847
  clearCache: clearCache,
2691
- setProgressBarDelay: setProgressBarDelay
2848
+ setProgressBarDelay: setProgressBarDelay,
2849
+ setConfirmMethod: setConfirmMethod
2692
2850
  });
2693
2851
 
2694
2852
  class FrameController {
2695
2853
  constructor(element) {
2854
+ this.fetchResponseLoaded = (fetchResponse) => { };
2855
+ this.currentFetchRequest = null;
2696
2856
  this.resolveVisitPromise = () => { };
2697
2857
  this.connected = false;
2698
2858
  this.hasBeenLoaded = false;
@@ -2752,7 +2912,6 @@ Copyright © 2021 Basecamp, LLC
2752
2912
  this.appearanceObserver.stop();
2753
2913
  await this.element.loaded;
2754
2914
  this.hasBeenLoaded = true;
2755
- session.frameLoaded(this.element);
2756
2915
  }
2757
2916
  catch (error) {
2758
2917
  this.currentURL = previousURL;
@@ -2762,7 +2921,7 @@ Copyright © 2021 Basecamp, LLC
2762
2921
  }
2763
2922
  }
2764
2923
  async loadResponse(fetchResponse) {
2765
- if (fetchResponse.redirected) {
2924
+ if (fetchResponse.redirected || (fetchResponse.succeeded && fetchResponse.isHTML)) {
2766
2925
  this.sourceURL = fetchResponse.response.url;
2767
2926
  }
2768
2927
  try {
@@ -2770,17 +2929,22 @@ Copyright © 2021 Basecamp, LLC
2770
2929
  if (html) {
2771
2930
  const { body } = parseHTMLDocument(html);
2772
2931
  const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
2773
- const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
2932
+ const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
2774
2933
  if (this.view.renderPromise)
2775
2934
  await this.view.renderPromise;
2776
2935
  await this.view.render(renderer);
2777
2936
  session.frameRendered(fetchResponse, this.element);
2937
+ session.frameLoaded(this.element);
2938
+ this.fetchResponseLoaded(fetchResponse);
2778
2939
  }
2779
2940
  }
2780
2941
  catch (error) {
2781
2942
  console.error(error);
2782
2943
  this.view.invalidate();
2783
2944
  }
2945
+ finally {
2946
+ this.fetchResponseLoaded = () => { };
2947
+ }
2784
2948
  }
2785
2949
  elementAppearedInViewport(element) {
2786
2950
  this.loadSourceURL();
@@ -2806,20 +2970,15 @@ Copyright © 2021 Basecamp, LLC
2806
2970
  }
2807
2971
  this.reloadable = false;
2808
2972
  this.formSubmission = new FormSubmission(this, element, submitter);
2809
- if (this.formSubmission.fetchRequest.isIdempotent) {
2810
- this.navigateFrame(element, this.formSubmission.fetchRequest.url.href, submitter);
2811
- }
2812
- else {
2813
- const { fetchRequest } = this.formSubmission;
2814
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
2815
- this.formSubmission.start();
2816
- }
2973
+ const { fetchRequest } = this.formSubmission;
2974
+ this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
2975
+ this.formSubmission.start();
2817
2976
  }
2818
2977
  prepareHeadersForRequest(headers, request) {
2819
2978
  headers["Turbo-Frame"] = this.id;
2820
2979
  }
2821
2980
  requestStarted(request) {
2822
- this.element.setAttribute("busy", "");
2981
+ markAsBusy(this.element);
2823
2982
  }
2824
2983
  requestPreventedHandlingResponse(request, response) {
2825
2984
  this.resolveVisitPromise();
@@ -2837,14 +2996,14 @@ Copyright © 2021 Basecamp, LLC
2837
2996
  this.resolveVisitPromise();
2838
2997
  }
2839
2998
  requestFinished(request) {
2840
- this.element.removeAttribute("busy");
2999
+ clearBusyState(this.element);
2841
3000
  }
2842
- formSubmissionStarted(formSubmission) {
2843
- const frame = this.findFrameElement(formSubmission.formElement);
2844
- frame.setAttribute("busy", "");
3001
+ formSubmissionStarted({ formElement }) {
3002
+ markAsBusy(formElement, this.findFrameElement(formElement));
2845
3003
  }
2846
3004
  formSubmissionSucceededWithResponse(formSubmission, response) {
2847
3005
  const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
3006
+ this.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
2848
3007
  frame.delegate.loadResponse(response);
2849
3008
  }
2850
3009
  formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
@@ -2853,9 +3012,8 @@ Copyright © 2021 Basecamp, LLC
2853
3012
  formSubmissionErrored(formSubmission, error) {
2854
3013
  console.error(error);
2855
3014
  }
2856
- formSubmissionFinished(formSubmission) {
2857
- const frame = this.findFrameElement(formSubmission.formElement);
2858
- frame.removeAttribute("busy");
3015
+ formSubmissionFinished({ formElement }) {
3016
+ clearBusyState(formElement, this.findFrameElement(formElement));
2859
3017
  }
2860
3018
  allowsImmediateRender(snapshot, resume) {
2861
3019
  return true;
@@ -2865,10 +3023,14 @@ Copyright © 2021 Basecamp, LLC
2865
3023
  viewInvalidated() {
2866
3024
  }
2867
3025
  async visit(url) {
2868
- const request = new FetchRequest(this, FetchMethod.get, expandURL(url), undefined, this.element);
3026
+ var _a;
3027
+ const request = new FetchRequest(this, FetchMethod.get, expandURL(url), new URLSearchParams, this.element);
3028
+ (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
3029
+ this.currentFetchRequest = request;
2869
3030
  return new Promise(resolve => {
2870
3031
  this.resolveVisitPromise = () => {
2871
3032
  this.resolveVisitPromise = () => { };
3033
+ this.currentFetchRequest = null;
2872
3034
  resolve();
2873
3035
  };
2874
3036
  request.perform();
@@ -2876,12 +3038,27 @@ Copyright © 2021 Basecamp, LLC
2876
3038
  }
2877
3039
  navigateFrame(element, url, submitter) {
2878
3040
  const frame = this.findFrameElement(element, submitter);
3041
+ this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
2879
3042
  frame.setAttribute("reloadable", "");
2880
3043
  frame.src = url;
2881
3044
  }
3045
+ proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3046
+ const action = getAttribute("data-turbo-action", submitter, element, frame);
3047
+ if (isAction(action)) {
3048
+ const { visitCachedSnapshot } = new SnapshotSubstitution(frame);
3049
+ frame.delegate.fetchResponseLoaded = (fetchResponse) => {
3050
+ if (frame.src) {
3051
+ const { statusCode, redirected } = fetchResponse;
3052
+ const responseHTML = frame.ownerDocument.documentElement.outerHTML;
3053
+ const response = { statusCode, redirected, responseHTML };
3054
+ session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false });
3055
+ }
3056
+ };
3057
+ }
3058
+ }
2882
3059
  findFrameElement(element, submitter) {
2883
3060
  var _a;
2884
- const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
3061
+ const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
2885
3062
  return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
2886
3063
  }
2887
3064
  async extractForeignFrameElement(container) {
@@ -2902,8 +3079,15 @@ Copyright © 2021 Basecamp, LLC
2902
3079
  }
2903
3080
  return new FrameElement();
2904
3081
  }
3082
+ formActionIsVisitable(form, submitter) {
3083
+ const action = getAction(form, submitter);
3084
+ return locationIsVisitable(expandURL(action), this.rootLocation);
3085
+ }
2905
3086
  shouldInterceptNavigation(element, submitter) {
2906
- const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
3087
+ const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
3088
+ if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {
3089
+ return false;
3090
+ }
2907
3091
  if (!this.enabled || id == "_top") {
2908
3092
  return false;
2909
3093
  }
@@ -2960,6 +3144,23 @@ Copyright © 2021 Basecamp, LLC
2960
3144
  get isActive() {
2961
3145
  return this.element.isActive && this.connected;
2962
3146
  }
3147
+ get rootLocation() {
3148
+ var _a;
3149
+ const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
3150
+ const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3151
+ return expandURL(root);
3152
+ }
3153
+ }
3154
+ class SnapshotSubstitution {
3155
+ constructor(element) {
3156
+ this.visitCachedSnapshot = ({ element }) => {
3157
+ var _a;
3158
+ const { id, clone } = this;
3159
+ (_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
3160
+ };
3161
+ this.clone = element.cloneNode(true);
3162
+ this.id = element.id;
3163
+ }
2963
3164
  }
2964
3165
  function getFrameElementById(id) {
2965
3166
  if (id != null) {
@@ -2980,6 +3181,7 @@ Copyright © 2021 Basecamp, LLC
2980
3181
  }
2981
3182
  if (element instanceof FrameElement) {
2982
3183
  element.connectedCallback();
3184
+ element.disconnectedCallback();
2983
3185
  return element;
2984
3186
  }
2985
3187
  }
@@ -3155,10 +3357,11 @@ Copyright © 2021 Basecamp, LLC
3155
3357
  exports.clearCache = clearCache;
3156
3358
  exports.connectStreamSource = connectStreamSource;
3157
3359
  exports.disconnectStreamSource = disconnectStreamSource;
3158
- exports.navigator = navigator;
3360
+ exports.navigator = navigator$1;
3159
3361
  exports.registerAdapter = registerAdapter;
3160
3362
  exports.renderStreamMessage = renderStreamMessage;
3161
3363
  exports.session = session;
3364
+ exports.setConfirmMethod = setConfirmMethod;
3162
3365
  exports.setProgressBarDelay = setProgressBarDelay;
3163
3366
  exports.start = start;
3164
3367
  exports.visit = visit;