@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.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Turbo 7.0.0-rc.3
2
+ Turbo 7.0.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
@@ -247,7 +314,12 @@ Copyright © 2021 Basecamp, LLC
247
314
 
248
315
  function dispatch(eventName, { target, cancelable, detail } = {}) {
249
316
  const event = new CustomEvent(eventName, { cancelable, bubbles: true, detail });
250
- void (target || document.documentElement).dispatchEvent(event);
317
+ if (target && target.isConnected) {
318
+ target.dispatchEvent(event);
319
+ }
320
+ else {
321
+ document.documentElement.dispatchEvent(event);
322
+ }
251
323
  return event;
252
324
  }
253
325
  function nextAnimationFrame() {
@@ -290,6 +362,29 @@ Copyright © 2021 Basecamp, LLC
290
362
  }
291
363
  }).join("");
292
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
+ }
293
388
 
294
389
  var FetchMethod;
295
390
  (function (FetchMethod) {
@@ -519,6 +614,9 @@ Copyright © 2021 Basecamp, LLC
519
614
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
520
615
  this.mustRedirect = mustRedirect;
521
616
  }
617
+ static confirmMethod(message, element) {
618
+ return confirm(message);
619
+ }
522
620
  get method() {
523
621
  var _a;
524
622
  const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
@@ -552,8 +650,20 @@ Copyright © 2021 Basecamp, LLC
552
650
  return entries.concat(typeof value == "string" ? [[name, value]] : []);
553
651
  }, []);
554
652
  }
653
+ get confirmationMessage() {
654
+ return this.formElement.getAttribute("data-turbo-confirm");
655
+ }
656
+ get needsConfirmation() {
657
+ return this.confirmationMessage !== null;
658
+ }
555
659
  async start() {
556
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
+ }
557
667
  if (this.state == initialized) {
558
668
  this.state = requesting;
559
669
  return this.fetchRequest.perform();
@@ -577,7 +687,9 @@ Copyright © 2021 Basecamp, LLC
577
687
  }
578
688
  }
579
689
  requestStarted(request) {
690
+ var _a;
580
691
  this.state = FormSubmissionState.waiting;
692
+ (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
581
693
  dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } });
582
694
  this.delegate.formSubmissionStarted(this);
583
695
  }
@@ -607,7 +719,9 @@ Copyright © 2021 Basecamp, LLC
607
719
  this.delegate.formSubmissionErrored(this, error);
608
720
  }
609
721
  requestFinished(request) {
722
+ var _a;
610
723
  this.state = FormSubmissionState.stopped;
724
+ (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
611
725
  dispatch("turbo:submit-end", { target: this.formElement, detail: Object.assign({ formSubmission: this }, this.result) });
612
726
  this.delegate.formSubmissionFinished(this);
613
727
  }
@@ -683,10 +797,11 @@ Copyright © 2021 Basecamp, LLC
683
797
  class FormInterceptor {
684
798
  constructor(delegate, element) {
685
799
  this.submitBubbled = ((event) => {
686
- if (event.target instanceof HTMLFormElement) {
687
- const form = event.target;
800
+ const form = event.target;
801
+ if (form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
688
802
  const submitter = event.submitter || undefined;
689
- 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)) {
690
805
  event.preventDefault();
691
806
  event.stopImmediatePropagation();
692
807
  this.delegate.formSubmissionIntercepted(form, submitter);
@@ -1280,7 +1395,8 @@ Copyright © 2021 Basecamp, LLC
1280
1395
  })(VisitState || (VisitState = {}));
1281
1396
  const defaultOptions = {
1282
1397
  action: "advance",
1283
- historyChanged: false
1398
+ delegate: {},
1399
+ historyChanged: false,
1284
1400
  };
1285
1401
  var SystemStatusCode;
1286
1402
  (function (SystemStatusCode) {
@@ -1300,13 +1416,14 @@ Copyright © 2021 Basecamp, LLC
1300
1416
  this.delegate = delegate;
1301
1417
  this.location = location;
1302
1418
  this.restorationIdentifier = restorationIdentifier || uuid();
1303
- const { action, historyChanged, referrer, snapshotHTML, response } = Object.assign(Object.assign({}, defaultOptions), options);
1419
+ const { action, historyChanged, referrer, snapshotHTML, response, delegate: optionalDelegate } = Object.assign(Object.assign({}, defaultOptions), options);
1304
1420
  this.action = action;
1305
1421
  this.historyChanged = historyChanged;
1306
1422
  this.referrer = referrer;
1307
1423
  this.snapshotHTML = snapshotHTML;
1308
1424
  this.response = response;
1309
1425
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
1426
+ this.optionalDelegate = optionalDelegate;
1310
1427
  }
1311
1428
  get adapter() {
1312
1429
  return this.delegate.adapter;
@@ -1329,6 +1446,8 @@ Copyright © 2021 Basecamp, LLC
1329
1446
  this.state = VisitState.started;
1330
1447
  this.adapter.visitStarted(this);
1331
1448
  this.delegate.visitStarted(this);
1449
+ if (this.optionalDelegate.visitStarted)
1450
+ this.optionalDelegate.visitStarted(this);
1332
1451
  }
1333
1452
  }
1334
1453
  cancel() {
@@ -1458,7 +1577,8 @@ Copyright © 2021 Basecamp, LLC
1458
1577
  }
1459
1578
  }
1460
1579
  followRedirect() {
1461
- if (this.redirectedToLocation && !this.followedRedirect) {
1580
+ var _a;
1581
+ if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1462
1582
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1463
1583
  action: 'replace',
1464
1584
  response: this.response
@@ -1481,25 +1601,27 @@ Copyright © 2021 Basecamp, LLC
1481
1601
  }
1482
1602
  async requestSucceededWithResponse(request, response) {
1483
1603
  const responseHTML = await response.responseHTML;
1604
+ const { redirected, statusCode } = response;
1484
1605
  if (responseHTML == undefined) {
1485
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch });
1606
+ this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1486
1607
  }
1487
1608
  else {
1488
1609
  this.redirectedToLocation = response.redirected ? response.location : undefined;
1489
- this.recordResponse({ statusCode: response.statusCode, responseHTML });
1610
+ this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
1490
1611
  }
1491
1612
  }
1492
1613
  async requestFailedWithResponse(request, response) {
1493
1614
  const responseHTML = await response.responseHTML;
1615
+ const { redirected, statusCode } = response;
1494
1616
  if (responseHTML == undefined) {
1495
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch });
1617
+ this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1496
1618
  }
1497
1619
  else {
1498
- this.recordResponse({ statusCode: response.statusCode, responseHTML });
1620
+ this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
1499
1621
  }
1500
1622
  }
1501
1623
  requestErrored(request, error) {
1502
- this.recordResponse({ statusCode: SystemStatusCode.networkFailure });
1624
+ this.recordResponse({ statusCode: SystemStatusCode.networkFailure, redirected: false });
1503
1625
  }
1504
1626
  requestFinished() {
1505
1627
  this.finishRequest();
@@ -1563,6 +1685,8 @@ Copyright © 2021 Basecamp, LLC
1563
1685
  if (!this.snapshotCached) {
1564
1686
  this.view.cacheSnapshot();
1565
1687
  this.snapshotCached = true;
1688
+ if (this.optionalDelegate.visitCachedSnapshot)
1689
+ this.optionalDelegate.visitCachedSnapshot(this);
1566
1690
  }
1567
1691
  }
1568
1692
  async render(callback) {
@@ -1711,7 +1835,7 @@ Copyright © 2021 Basecamp, LLC
1711
1835
  const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1712
1836
  const submitter = event.submitter || undefined;
1713
1837
  if (form) {
1714
- 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");
1715
1839
  if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
1716
1840
  event.preventDefault();
1717
1841
  this.delegate.formSubmitted(form, submitter);
@@ -1755,12 +1879,11 @@ Copyright © 2021 Basecamp, LLC
1755
1879
  linkClickIntercepted(element, url) {
1756
1880
  const frame = this.findFrameElement(element);
1757
1881
  if (frame) {
1758
- frame.setAttribute("reloadable", "");
1759
- frame.src = url;
1882
+ frame.delegate.linkClickIntercepted(element, url);
1760
1883
  }
1761
1884
  }
1762
1885
  shouldInterceptFormSubmission(element, submitter) {
1763
- return this.shouldRedirect(element, submitter);
1886
+ return this.shouldSubmit(element, submitter);
1764
1887
  }
1765
1888
  formSubmissionIntercepted(element, submitter) {
1766
1889
  const frame = this.findFrameElement(element, submitter);
@@ -1769,6 +1892,13 @@ Copyright © 2021 Basecamp, LLC
1769
1892
  frame.delegate.formSubmissionIntercepted(element, submitter);
1770
1893
  }
1771
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
+ }
1772
1902
  shouldRedirect(element, submitter) {
1773
1903
  const frame = this.findFrameElement(element, submitter);
1774
1904
  return frame ? frame != element.closest("turbo-frame") : false;
@@ -1926,7 +2056,12 @@ Copyright © 2021 Basecamp, LLC
1926
2056
  }
1927
2057
  proposeVisit(location, options = {}) {
1928
2058
  if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
1929
- 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
+ }
1930
2065
  }
1931
2066
  }
1932
2067
  startVisit(locatable, restorationIdentifier, options = {}) {
@@ -1937,12 +2072,7 @@ Copyright © 2021 Basecamp, LLC
1937
2072
  submitForm(form, submitter) {
1938
2073
  this.stop();
1939
2074
  this.formSubmission = new FormSubmission(this, form, submitter, true);
1940
- if (this.formSubmission.isIdempotent) {
1941
- this.proposeVisit(this.formSubmission.fetchRequest.url, { action: this.getActionForFormSubmission(this.formSubmission) });
1942
- }
1943
- else {
1944
- this.formSubmission.start();
1945
- }
2075
+ this.formSubmission.start();
1946
2076
  }
1947
2077
  stop() {
1948
2078
  if (this.formSubmission) {
@@ -1975,8 +2105,9 @@ Copyright © 2021 Basecamp, LLC
1975
2105
  if (formSubmission.method != FetchMethod.get) {
1976
2106
  this.view.clearSnapshotCache();
1977
2107
  }
1978
- const { statusCode } = fetchResponse;
1979
- const visitOptions = { response: { statusCode, responseHTML } };
2108
+ const { statusCode, redirected } = fetchResponse;
2109
+ const action = this.getActionForFormSubmission(formSubmission);
2110
+ const visitOptions = { action, response: { statusCode, responseHTML, redirected } };
1980
2111
  this.proposeVisit(fetchResponse.location, visitOptions);
1981
2112
  }
1982
2113
  }
@@ -2009,6 +2140,9 @@ Copyright © 2021 Basecamp, LLC
2009
2140
  visitCompleted(visit) {
2010
2141
  this.delegate.visitCompleted(visit);
2011
2142
  }
2143
+ visitCachedSnapshot(visit) {
2144
+ this.delegate.visitCachedSnapshot(visit);
2145
+ }
2012
2146
  locationWithActionIsSamePage(location, action) {
2013
2147
  const anchor = getAnchor(location);
2014
2148
  const currentAnchor = getAnchor(this.view.lastRenderedLocation);
@@ -2028,7 +2162,7 @@ Copyright © 2021 Basecamp, LLC
2028
2162
  }
2029
2163
  getActionForFormSubmission(formSubmission) {
2030
2164
  const { formElement, submitter } = formSubmission;
2031
- const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-action")) || formElement.getAttribute("data-turbo-action");
2165
+ const action = getAttribute("data-turbo-action", submitter, formElement);
2032
2166
  return isAction(action) ? action : "advance";
2033
2167
  }
2034
2168
  }
@@ -2478,7 +2612,7 @@ Copyright © 2021 Basecamp, LLC
2478
2612
  }
2479
2613
  willFollowLinkToLocation(link, location) {
2480
2614
  return this.elementDriveEnabled(link)
2481
- && this.locationIsVisitable(location)
2615
+ && locationIsVisitable(location, this.snapshot.rootLocation)
2482
2616
  && this.applicationAllowsFollowingLinkToLocation(link, location);
2483
2617
  }
2484
2618
  followedLinkToLocation(link, location) {
@@ -2486,13 +2620,24 @@ Copyright © 2021 Basecamp, LLC
2486
2620
  this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, { action });
2487
2621
  }
2488
2622
  convertLinkWithMethodClickToFormSubmission(link) {
2489
- var _a;
2490
2623
  const linkMethod = link.getAttribute("data-turbo-method");
2491
2624
  if (linkMethod) {
2492
2625
  const form = document.createElement("form");
2493
2626
  form.method = linkMethod;
2494
2627
  form.action = link.getAttribute("href") || "undefined";
2495
- (_a = link.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(form, link);
2628
+ form.hidden = true;
2629
+ if (link.hasAttribute("data-turbo-confirm")) {
2630
+ form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
2631
+ }
2632
+ const frame = this.getTargetFrameForLink(link);
2633
+ if (frame) {
2634
+ form.setAttribute("data-turbo-frame", frame);
2635
+ form.addEventListener("turbo:submit-start", () => form.remove());
2636
+ }
2637
+ else {
2638
+ form.addEventListener("submit", () => form.remove());
2639
+ }
2640
+ document.body.appendChild(form);
2496
2641
  return dispatch("submit", { cancelable: true, target: form });
2497
2642
  }
2498
2643
  else {
@@ -2515,6 +2660,8 @@ Copyright © 2021 Basecamp, LLC
2515
2660
  visitCompleted(visit) {
2516
2661
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2517
2662
  }
2663
+ visitCachedSnapshot(visit) {
2664
+ }
2518
2665
  locationWithActionIsSamePage(location, action) {
2519
2666
  return this.navigator.locationWithActionIsSamePage(location, action);
2520
2667
  }
@@ -2522,7 +2669,10 @@ Copyright © 2021 Basecamp, LLC
2522
2669
  this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
2523
2670
  }
2524
2671
  willSubmitForm(form, submitter) {
2525
- return this.elementDriveEnabled(form) && this.elementDriveEnabled(submitter);
2672
+ const action = getAction(form, submitter);
2673
+ return this.elementDriveEnabled(form)
2674
+ && (!submitter || this.elementDriveEnabled(submitter))
2675
+ && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
2526
2676
  }
2527
2677
  formSubmitted(form, submitter) {
2528
2678
  this.navigator.submitForm(form, submitter);
@@ -2578,6 +2728,7 @@ Copyright © 2021 Basecamp, LLC
2578
2728
  return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
2579
2729
  }
2580
2730
  notifyApplicationAfterVisitingLocation(location, action) {
2731
+ markAsBusy(document.documentElement);
2581
2732
  return dispatch("turbo:visit", { detail: { url: location.href, action } });
2582
2733
  }
2583
2734
  notifyApplicationBeforeCachingSnapshot() {
@@ -2590,6 +2741,7 @@ Copyright © 2021 Basecamp, LLC
2590
2741
  return dispatch("turbo:render");
2591
2742
  }
2592
2743
  notifyApplicationAfterPageLoad(timing = {}) {
2744
+ clearBusyState(document.documentElement);
2593
2745
  return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
2594
2746
  }
2595
2747
  notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
@@ -2624,8 +2776,17 @@ Copyright © 2021 Basecamp, LLC
2624
2776
  const action = link.getAttribute("data-turbo-action");
2625
2777
  return isAction(action) ? action : "advance";
2626
2778
  }
2627
- locationIsVisitable(location) {
2628
- return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
2779
+ getTargetFrameForLink(link) {
2780
+ const frame = link.getAttribute("data-turbo-frame");
2781
+ if (frame) {
2782
+ return frame;
2783
+ }
2784
+ else {
2785
+ const container = link.closest("turbo-frame");
2786
+ if (container) {
2787
+ return container.id;
2788
+ }
2789
+ }
2629
2790
  }
2630
2791
  get snapshot() {
2631
2792
  return this.view.snapshot;
@@ -2643,7 +2804,7 @@ Copyright © 2021 Basecamp, LLC
2643
2804
  };
2644
2805
 
2645
2806
  const session = new Session;
2646
- const { navigator } = session;
2807
+ const { navigator: navigator$1 } = session;
2647
2808
  function start() {
2648
2809
  session.start();
2649
2810
  }
@@ -2668,10 +2829,13 @@ Copyright © 2021 Basecamp, LLC
2668
2829
  function setProgressBarDelay(delay) {
2669
2830
  session.setProgressBarDelay(delay);
2670
2831
  }
2832
+ function setConfirmMethod(confirmMethod) {
2833
+ FormSubmission.confirmMethod = confirmMethod;
2834
+ }
2671
2835
 
2672
2836
  var Turbo = /*#__PURE__*/Object.freeze({
2673
2837
  __proto__: null,
2674
- navigator: navigator,
2838
+ navigator: navigator$1,
2675
2839
  session: session,
2676
2840
  PageRenderer: PageRenderer,
2677
2841
  PageSnapshot: PageSnapshot,
@@ -2682,11 +2846,13 @@ Copyright © 2021 Basecamp, LLC
2682
2846
  disconnectStreamSource: disconnectStreamSource,
2683
2847
  renderStreamMessage: renderStreamMessage,
2684
2848
  clearCache: clearCache,
2685
- setProgressBarDelay: setProgressBarDelay
2849
+ setProgressBarDelay: setProgressBarDelay,
2850
+ setConfirmMethod: setConfirmMethod
2686
2851
  });
2687
2852
 
2688
2853
  class FrameController {
2689
2854
  constructor(element) {
2855
+ this.currentFetchRequest = null;
2690
2856
  this.resolveVisitPromise = () => { };
2691
2857
  this.connected = false;
2692
2858
  this.hasBeenLoaded = false;
@@ -2746,7 +2912,6 @@ Copyright © 2021 Basecamp, LLC
2746
2912
  this.appearanceObserver.stop();
2747
2913
  await this.element.loaded;
2748
2914
  this.hasBeenLoaded = true;
2749
- session.frameLoaded(this.element);
2750
2915
  }
2751
2916
  catch (error) {
2752
2917
  this.currentURL = previousURL;
@@ -2769,6 +2934,7 @@ Copyright © 2021 Basecamp, LLC
2769
2934
  await this.view.renderPromise;
2770
2935
  await this.view.render(renderer);
2771
2936
  session.frameRendered(fetchResponse, this.element);
2937
+ session.frameLoaded(this.element);
2772
2938
  }
2773
2939
  }
2774
2940
  catch (error) {
@@ -2800,20 +2966,15 @@ Copyright © 2021 Basecamp, LLC
2800
2966
  }
2801
2967
  this.reloadable = false;
2802
2968
  this.formSubmission = new FormSubmission(this, element, submitter);
2803
- if (this.formSubmission.fetchRequest.isIdempotent) {
2804
- this.navigateFrame(element, this.formSubmission.fetchRequest.url.href, submitter);
2805
- }
2806
- else {
2807
- const { fetchRequest } = this.formSubmission;
2808
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
2809
- this.formSubmission.start();
2810
- }
2969
+ const { fetchRequest } = this.formSubmission;
2970
+ this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
2971
+ this.formSubmission.start();
2811
2972
  }
2812
2973
  prepareHeadersForRequest(headers, request) {
2813
2974
  headers["Turbo-Frame"] = this.id;
2814
2975
  }
2815
2976
  requestStarted(request) {
2816
- this.element.setAttribute("busy", "");
2977
+ markAsBusy(this.element);
2817
2978
  }
2818
2979
  requestPreventedHandlingResponse(request, response) {
2819
2980
  this.resolveVisitPromise();
@@ -2831,14 +2992,14 @@ Copyright © 2021 Basecamp, LLC
2831
2992
  this.resolveVisitPromise();
2832
2993
  }
2833
2994
  requestFinished(request) {
2834
- this.element.removeAttribute("busy");
2995
+ clearBusyState(this.element);
2835
2996
  }
2836
- formSubmissionStarted(formSubmission) {
2837
- const frame = this.findFrameElement(formSubmission.formElement);
2838
- frame.setAttribute("busy", "");
2997
+ formSubmissionStarted({ formElement }) {
2998
+ markAsBusy(formElement, this.findFrameElement(formElement));
2839
2999
  }
2840
3000
  formSubmissionSucceededWithResponse(formSubmission, response) {
2841
3001
  const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
3002
+ this.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
2842
3003
  frame.delegate.loadResponse(response);
2843
3004
  }
2844
3005
  formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
@@ -2847,9 +3008,8 @@ Copyright © 2021 Basecamp, LLC
2847
3008
  formSubmissionErrored(formSubmission, error) {
2848
3009
  console.error(error);
2849
3010
  }
2850
- formSubmissionFinished(formSubmission) {
2851
- const frame = this.findFrameElement(formSubmission.formElement);
2852
- frame.removeAttribute("busy");
3011
+ formSubmissionFinished({ formElement }) {
3012
+ clearBusyState(formElement, this.findFrameElement(formElement));
2853
3013
  }
2854
3014
  allowsImmediateRender(snapshot, resume) {
2855
3015
  return true;
@@ -2859,10 +3019,14 @@ Copyright © 2021 Basecamp, LLC
2859
3019
  viewInvalidated() {
2860
3020
  }
2861
3021
  async visit(url) {
2862
- const request = new FetchRequest(this, FetchMethod.get, expandURL(url), undefined, this.element);
3022
+ var _a;
3023
+ const request = new FetchRequest(this, FetchMethod.get, expandURL(url), new URLSearchParams, this.element);
3024
+ (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
3025
+ this.currentFetchRequest = request;
2863
3026
  return new Promise(resolve => {
2864
3027
  this.resolveVisitPromise = () => {
2865
3028
  this.resolveVisitPromise = () => { };
3029
+ this.currentFetchRequest = null;
2866
3030
  resolve();
2867
3031
  };
2868
3032
  request.perform();
@@ -2870,12 +3034,29 @@ Copyright © 2021 Basecamp, LLC
2870
3034
  }
2871
3035
  navigateFrame(element, url, submitter) {
2872
3036
  const frame = this.findFrameElement(element, submitter);
3037
+ this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
2873
3038
  frame.setAttribute("reloadable", "");
2874
3039
  frame.src = url;
2875
3040
  }
3041
+ proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3042
+ const action = getAttribute("data-turbo-action", submitter, element, frame);
3043
+ if (isAction(action)) {
3044
+ const delegate = new SnapshotSubstitution(frame);
3045
+ const proposeVisit = (event) => {
3046
+ const { target, detail: { fetchResponse } } = event;
3047
+ if (target instanceof FrameElement && target.src) {
3048
+ const { statusCode, redirected } = fetchResponse;
3049
+ const responseHTML = target.ownerDocument.documentElement.outerHTML;
3050
+ const response = { statusCode, redirected, responseHTML };
3051
+ session.visit(target.src, { action, response, delegate });
3052
+ }
3053
+ };
3054
+ frame.addEventListener("turbo:frame-render", proposeVisit, { once: true });
3055
+ }
3056
+ }
2876
3057
  findFrameElement(element, submitter) {
2877
3058
  var _a;
2878
- const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
3059
+ const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
2879
3060
  return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
2880
3061
  }
2881
3062
  async extractForeignFrameElement(container) {
@@ -2896,8 +3077,15 @@ Copyright © 2021 Basecamp, LLC
2896
3077
  }
2897
3078
  return new FrameElement();
2898
3079
  }
3080
+ formActionIsVisitable(form, submitter) {
3081
+ const action = getAction(form, submitter);
3082
+ return locationIsVisitable(expandURL(action), this.rootLocation);
3083
+ }
2899
3084
  shouldInterceptNavigation(element, submitter) {
2900
- const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
3085
+ const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
3086
+ if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {
3087
+ return false;
3088
+ }
2901
3089
  if (!this.enabled || id == "_top") {
2902
3090
  return false;
2903
3091
  }
@@ -2954,6 +3142,28 @@ Copyright © 2021 Basecamp, LLC
2954
3142
  get isActive() {
2955
3143
  return this.element.isActive && this.connected;
2956
3144
  }
3145
+ get rootLocation() {
3146
+ var _a;
3147
+ const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
3148
+ const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3149
+ return expandURL(root);
3150
+ }
3151
+ }
3152
+ class SnapshotSubstitution {
3153
+ constructor(element) {
3154
+ this.clone = element.cloneNode(true);
3155
+ this.id = element.id;
3156
+ }
3157
+ visitStarted(visit) {
3158
+ this.snapshot = visit.view.snapshot;
3159
+ }
3160
+ visitCachedSnapshot() {
3161
+ var _a;
3162
+ const { snapshot, id, clone } = this;
3163
+ if (snapshot) {
3164
+ (_a = snapshot.element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
3165
+ }
3166
+ }
2957
3167
  }
2958
3168
  function getFrameElementById(id) {
2959
3169
  if (id != null) {
@@ -3149,10 +3359,11 @@ Copyright © 2021 Basecamp, LLC
3149
3359
  exports.clearCache = clearCache;
3150
3360
  exports.connectStreamSource = connectStreamSource;
3151
3361
  exports.disconnectStreamSource = disconnectStreamSource;
3152
- exports.navigator = navigator;
3362
+ exports.navigator = navigator$1;
3153
3363
  exports.registerAdapter = registerAdapter;
3154
3364
  exports.renderStreamMessage = renderStreamMessage;
3155
3365
  exports.session = session;
3366
+ exports.setConfirmMethod = setConfirmMethod;
3156
3367
  exports.setProgressBarDelay = setProgressBarDelay;
3157
3368
  exports.start = start;
3158
3369
  exports.visit = visit;