@hotwired/turbo 7.0.1 → 7.1.0

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
2
+ Turbo 7.1.0
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;
@@ -171,6 +223,10 @@ Copyright © 2021 Basecamp, LLC
171
223
  return anchorMatch[1];
172
224
  }
173
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
+ }
174
230
  function getExtension(url) {
175
231
  return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
176
232
  }
@@ -181,6 +237,9 @@ Copyright © 2021 Basecamp, LLC
181
237
  const prefix = getPrefix(url);
182
238
  return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
183
239
  }
240
+ function locationIsVisitable(location, rootLocation) {
241
+ return isPrefixedBy(location, rootLocation) && isHTML(location);
242
+ }
184
243
  function getRequestURL(url) {
185
244
  const anchor = getAnchor(url);
186
245
  return anchor != null
@@ -303,6 +362,29 @@ Copyright © 2021 Basecamp, LLC
303
362
  }
304
363
  }).join("");
305
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
+ }
306
388
 
307
389
  var FetchMethod;
308
390
  (function (FetchMethod) {
@@ -328,13 +410,8 @@ Copyright © 2021 Basecamp, LLC
328
410
  this.delegate = delegate;
329
411
  this.method = method;
330
412
  this.headers = this.defaultHeaders;
331
- if (this.isIdempotent) {
332
- this.url = mergeFormDataEntries(location, [...body.entries()]);
333
- }
334
- else {
335
- this.body = body;
336
- this.url = location;
337
- }
413
+ this.body = body;
414
+ this.url = location;
338
415
  this.target = target;
339
416
  }
340
417
  get location() {
@@ -390,7 +467,7 @@ Copyright © 2021 Basecamp, LLC
390
467
  credentials: "same-origin",
391
468
  headers: this.headers,
392
469
  redirect: "follow",
393
- body: this.body,
470
+ body: this.isIdempotent ? null : this.body,
394
471
  signal: this.abortSignal,
395
472
  referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
396
473
  };
@@ -412,7 +489,7 @@ Copyright © 2021 Basecamp, LLC
412
489
  cancelable: true,
413
490
  detail: {
414
491
  fetchOptions,
415
- url: this.url.href,
492
+ url: this.url,
416
493
  resume: this.resolveRequestPromise
417
494
  },
418
495
  target: this.target
@@ -421,21 +498,6 @@ Copyright © 2021 Basecamp, LLC
421
498
  await requestInterception;
422
499
  }
423
500
  }
424
- function mergeFormDataEntries(url, entries) {
425
- const currentSearchParams = new URLSearchParams(url.search);
426
- for (const [name, value] of entries) {
427
- if (value instanceof File)
428
- continue;
429
- if (currentSearchParams.has(name)) {
430
- currentSearchParams.delete(name);
431
- url.searchParams.set(name, value);
432
- }
433
- else {
434
- url.searchParams.append(name, value);
435
- }
436
- }
437
- return url;
438
- }
439
501
 
440
502
  class AppearanceObserver {
441
503
  constructor(delegate, element) {
@@ -529,9 +591,16 @@ Copyright © 2021 Basecamp, LLC
529
591
  this.formElement = formElement;
530
592
  this.submitter = submitter;
531
593
  this.formData = buildFormData(formElement, submitter);
594
+ this.location = expandURL(this.action);
595
+ if (this.method == FetchMethod.get) {
596
+ mergeFormDataEntries(this.location, [...this.body.entries()]);
597
+ }
532
598
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
533
599
  this.mustRedirect = mustRedirect;
534
600
  }
601
+ static confirmMethod(message, element) {
602
+ return confirm(message);
603
+ }
535
604
  get method() {
536
605
  var _a;
537
606
  const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
@@ -542,9 +611,6 @@ Copyright © 2021 Basecamp, LLC
542
611
  const formElementAction = typeof this.formElement.action === 'string' ? this.formElement.action : null;
543
612
  return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.getAttribute("action") || formElementAction || "";
544
613
  }
545
- get location() {
546
- return expandURL(this.action);
547
- }
548
614
  get body() {
549
615
  if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
550
616
  return new URLSearchParams(this.stringFormData);
@@ -565,8 +631,20 @@ Copyright © 2021 Basecamp, LLC
565
631
  return entries.concat(typeof value == "string" ? [[name, value]] : []);
566
632
  }, []);
567
633
  }
634
+ get confirmationMessage() {
635
+ return this.formElement.getAttribute("data-turbo-confirm");
636
+ }
637
+ get needsConfirmation() {
638
+ return this.confirmationMessage !== null;
639
+ }
568
640
  async start() {
569
641
  const { initialized, requesting } = FormSubmissionState;
642
+ if (this.needsConfirmation) {
643
+ const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
644
+ if (!answer) {
645
+ return;
646
+ }
647
+ }
570
648
  if (this.state == initialized) {
571
649
  this.state = requesting;
572
650
  return this.fetchRequest.perform();
@@ -590,7 +668,9 @@ Copyright © 2021 Basecamp, LLC
590
668
  }
591
669
  }
592
670
  requestStarted(request) {
671
+ var _a;
593
672
  this.state = FormSubmissionState.waiting;
673
+ (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
594
674
  dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } });
595
675
  this.delegate.formSubmissionStarted(this);
596
676
  }
@@ -620,7 +700,9 @@ Copyright © 2021 Basecamp, LLC
620
700
  this.delegate.formSubmissionErrored(this, error);
621
701
  }
622
702
  requestFinished(request) {
703
+ var _a;
623
704
  this.state = FormSubmissionState.stopped;
705
+ (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
624
706
  dispatch("turbo:submit-end", { target: this.formElement, detail: Object.assign({ formSubmission: this }, this.result) });
625
707
  this.delegate.formSubmissionFinished(this);
626
708
  }
@@ -654,6 +736,16 @@ Copyright © 2021 Basecamp, LLC
654
736
  function responseSucceededWithoutRedirect(response) {
655
737
  return response.statusCode == 200 && !response.redirected;
656
738
  }
739
+ function mergeFormDataEntries(url, entries) {
740
+ const searchParams = new URLSearchParams;
741
+ for (const [name, value] of entries) {
742
+ if (value instanceof File)
743
+ continue;
744
+ searchParams.append(name, value);
745
+ }
746
+ url.search = searchParams.toString();
747
+ return url;
748
+ }
657
749
 
658
750
  class Snapshot {
659
751
  constructor(element) {
@@ -697,9 +789,10 @@ Copyright © 2021 Basecamp, LLC
697
789
  constructor(delegate, element) {
698
790
  this.submitBubbled = ((event) => {
699
791
  const form = event.target;
700
- if (form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
792
+ if (!event.defaultPrevented && form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
701
793
  const submitter = event.submitter || undefined;
702
- if (this.delegate.shouldInterceptFormSubmission(form, submitter)) {
794
+ const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
795
+ if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
703
796
  event.preventDefault();
704
797
  event.stopImmediatePropagation();
705
798
  this.delegate.formSubmissionIntercepted(form, submitter);
@@ -914,10 +1007,11 @@ Copyright © 2021 Basecamp, LLC
914
1007
  }
915
1008
 
916
1009
  class Renderer {
917
- constructor(currentSnapshot, newSnapshot, isPreview) {
1010
+ constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
918
1011
  this.currentSnapshot = currentSnapshot;
919
1012
  this.newSnapshot = newSnapshot;
920
1013
  this.isPreview = isPreview;
1014
+ this.willRender = willRender;
921
1015
  this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
922
1016
  }
923
1017
  get shouldRender() {
@@ -1293,7 +1387,9 @@ Copyright © 2021 Basecamp, LLC
1293
1387
  })(VisitState || (VisitState = {}));
1294
1388
  const defaultOptions = {
1295
1389
  action: "advance",
1296
- historyChanged: false
1390
+ historyChanged: false,
1391
+ visitCachedSnapshot: () => { },
1392
+ willRender: true,
1297
1393
  };
1298
1394
  var SystemStatusCode;
1299
1395
  (function (SystemStatusCode) {
@@ -1313,13 +1409,16 @@ Copyright © 2021 Basecamp, LLC
1313
1409
  this.delegate = delegate;
1314
1410
  this.location = location;
1315
1411
  this.restorationIdentifier = restorationIdentifier || uuid();
1316
- const { action, historyChanged, referrer, snapshotHTML, response } = Object.assign(Object.assign({}, defaultOptions), options);
1412
+ const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender } = Object.assign(Object.assign({}, defaultOptions), options);
1317
1413
  this.action = action;
1318
1414
  this.historyChanged = historyChanged;
1319
1415
  this.referrer = referrer;
1320
1416
  this.snapshotHTML = snapshotHTML;
1321
1417
  this.response = response;
1322
1418
  this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
1419
+ this.visitCachedSnapshot = visitCachedSnapshot;
1420
+ this.willRender = willRender;
1421
+ this.scrolled = !willRender;
1323
1422
  }
1324
1423
  get adapter() {
1325
1424
  return this.delegate.adapter;
@@ -1421,7 +1520,7 @@ Copyright © 2021 Basecamp, LLC
1421
1520
  if (this.view.renderPromise)
1422
1521
  await this.view.renderPromise;
1423
1522
  if (isSuccessful(statusCode) && responseHTML != null) {
1424
- await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML));
1523
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
1425
1524
  this.adapter.visitRendered(this);
1426
1525
  this.complete();
1427
1526
  }
@@ -1461,7 +1560,7 @@ Copyright © 2021 Basecamp, LLC
1461
1560
  else {
1462
1561
  if (this.view.renderPromise)
1463
1562
  await this.view.renderPromise;
1464
- await this.view.renderPage(snapshot, isPreview);
1563
+ await this.view.renderPage(snapshot, isPreview, this.willRender);
1465
1564
  this.adapter.visitRendered(this);
1466
1565
  if (!isPreview) {
1467
1566
  this.complete();
@@ -1471,7 +1570,8 @@ Copyright © 2021 Basecamp, LLC
1471
1570
  }
1472
1571
  }
1473
1572
  followRedirect() {
1474
- if (this.redirectedToLocation && !this.followedRedirect) {
1573
+ var _a;
1574
+ if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1475
1575
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1476
1576
  action: 'replace',
1477
1577
  response: this.response
@@ -1494,25 +1594,27 @@ Copyright © 2021 Basecamp, LLC
1494
1594
  }
1495
1595
  async requestSucceededWithResponse(request, response) {
1496
1596
  const responseHTML = await response.responseHTML;
1597
+ const { redirected, statusCode } = response;
1497
1598
  if (responseHTML == undefined) {
1498
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch });
1599
+ this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1499
1600
  }
1500
1601
  else {
1501
1602
  this.redirectedToLocation = response.redirected ? response.location : undefined;
1502
- this.recordResponse({ statusCode: response.statusCode, responseHTML });
1603
+ this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
1503
1604
  }
1504
1605
  }
1505
1606
  async requestFailedWithResponse(request, response) {
1506
1607
  const responseHTML = await response.responseHTML;
1608
+ const { redirected, statusCode } = response;
1507
1609
  if (responseHTML == undefined) {
1508
- this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch });
1610
+ this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
1509
1611
  }
1510
1612
  else {
1511
- this.recordResponse({ statusCode: response.statusCode, responseHTML });
1613
+ this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
1512
1614
  }
1513
1615
  }
1514
1616
  requestErrored(request, error) {
1515
- this.recordResponse({ statusCode: SystemStatusCode.networkFailure });
1617
+ this.recordResponse({ statusCode: SystemStatusCode.networkFailure, redirected: false });
1516
1618
  }
1517
1619
  requestFinished() {
1518
1620
  this.finishRequest();
@@ -1569,12 +1671,12 @@ Copyright © 2021 Basecamp, LLC
1569
1671
  return !this.hasCachedSnapshot();
1570
1672
  }
1571
1673
  else {
1572
- return true;
1674
+ return this.willRender;
1573
1675
  }
1574
1676
  }
1575
1677
  cacheSnapshot() {
1576
1678
  if (!this.snapshotCached) {
1577
- this.view.cacheSnapshot();
1679
+ this.view.cacheSnapshot().then(snapshot => snapshot && this.visitCachedSnapshot(snapshot));
1578
1680
  this.snapshotCached = true;
1579
1681
  }
1580
1682
  }
@@ -1610,10 +1712,10 @@ Copyright © 2021 Basecamp, LLC
1610
1712
  this.navigator.startVisit(location, uuid(), options);
1611
1713
  }
1612
1714
  visitStarted(visit) {
1715
+ visit.loadCachedSnapshot();
1613
1716
  visit.issueRequest();
1614
1717
  visit.changeHistory();
1615
1718
  visit.goToSamePageAnchor();
1616
- visit.loadCachedSnapshot();
1617
1719
  }
1618
1720
  visitRequestStarted(visit) {
1619
1721
  this.progressBar.setValue(0);
@@ -1724,7 +1826,7 @@ Copyright © 2021 Basecamp, LLC
1724
1826
  const form = event.target instanceof HTMLFormElement ? event.target : undefined;
1725
1827
  const submitter = event.submitter || undefined;
1726
1828
  if (form) {
1727
- const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
1829
+ const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
1728
1830
  if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
1729
1831
  event.preventDefault();
1730
1832
  this.delegate.formSubmitted(form, submitter);
@@ -1768,12 +1870,11 @@ Copyright © 2021 Basecamp, LLC
1768
1870
  linkClickIntercepted(element, url) {
1769
1871
  const frame = this.findFrameElement(element);
1770
1872
  if (frame) {
1771
- frame.setAttribute("reloadable", "");
1772
- frame.src = url;
1873
+ frame.delegate.linkClickIntercepted(element, url);
1773
1874
  }
1774
1875
  }
1775
1876
  shouldInterceptFormSubmission(element, submitter) {
1776
- return this.shouldRedirect(element, submitter);
1877
+ return this.shouldSubmit(element, submitter);
1777
1878
  }
1778
1879
  formSubmissionIntercepted(element, submitter) {
1779
1880
  const frame = this.findFrameElement(element, submitter);
@@ -1782,6 +1883,13 @@ Copyright © 2021 Basecamp, LLC
1782
1883
  frame.delegate.formSubmissionIntercepted(element, submitter);
1783
1884
  }
1784
1885
  }
1886
+ shouldSubmit(form, submitter) {
1887
+ var _a;
1888
+ const action = getAction(form, submitter);
1889
+ const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
1890
+ const rootLocation = expandURL((_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/");
1891
+ return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
1892
+ }
1785
1893
  shouldRedirect(element, submitter) {
1786
1894
  const frame = this.findFrameElement(element, submitter);
1787
1895
  return frame ? frame != element.closest("turbo-frame") : false;
@@ -1939,7 +2047,12 @@ Copyright © 2021 Basecamp, LLC
1939
2047
  }
1940
2048
  proposeVisit(location, options = {}) {
1941
2049
  if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
1942
- this.delegate.visitProposedToLocation(location, options);
2050
+ if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
2051
+ this.delegate.visitProposedToLocation(location, options);
2052
+ }
2053
+ else {
2054
+ window.location.href = location.toString();
2055
+ }
1943
2056
  }
1944
2057
  }
1945
2058
  startVisit(locatable, restorationIdentifier, options = {}) {
@@ -1950,12 +2063,7 @@ Copyright © 2021 Basecamp, LLC
1950
2063
  submitForm(form, submitter) {
1951
2064
  this.stop();
1952
2065
  this.formSubmission = new FormSubmission(this, form, submitter, true);
1953
- if (this.formSubmission.isIdempotent) {
1954
- this.proposeVisit(this.formSubmission.fetchRequest.url, { action: this.getActionForFormSubmission(this.formSubmission) });
1955
- }
1956
- else {
1957
- this.formSubmission.start();
1958
- }
2066
+ this.formSubmission.start();
1959
2067
  }
1960
2068
  stop() {
1961
2069
  if (this.formSubmission) {
@@ -1988,8 +2096,9 @@ Copyright © 2021 Basecamp, LLC
1988
2096
  if (formSubmission.method != FetchMethod.get) {
1989
2097
  this.view.clearSnapshotCache();
1990
2098
  }
1991
- const { statusCode } = fetchResponse;
1992
- const visitOptions = { response: { statusCode, responseHTML } };
2099
+ const { statusCode, redirected } = fetchResponse;
2100
+ const action = this.getActionForFormSubmission(formSubmission);
2101
+ const visitOptions = { action, response: { statusCode, responseHTML, redirected } };
1993
2102
  this.proposeVisit(fetchResponse.location, visitOptions);
1994
2103
  }
1995
2104
  }
@@ -2041,7 +2150,7 @@ Copyright © 2021 Basecamp, LLC
2041
2150
  }
2042
2151
  getActionForFormSubmission(formSubmission) {
2043
2152
  const { formElement, submitter } = formSubmission;
2044
- const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-action")) || formElement.getAttribute("data-turbo-action");
2153
+ const action = getAttribute("data-turbo-action", submitter, formElement);
2045
2154
  return isAction(action) ? action : "advance";
2046
2155
  }
2047
2156
  }
@@ -2235,7 +2344,9 @@ Copyright © 2021 Basecamp, LLC
2235
2344
  this.mergeHead();
2236
2345
  }
2237
2346
  async render() {
2238
- this.replaceBody();
2347
+ if (this.willRender) {
2348
+ this.replaceBody();
2349
+ }
2239
2350
  }
2240
2351
  finishRendering() {
2241
2352
  super.finishRendering();
@@ -2373,8 +2484,8 @@ Copyright © 2021 Basecamp, LLC
2373
2484
  this.snapshotCache = new SnapshotCache(10);
2374
2485
  this.lastRenderedLocation = new URL(location.href);
2375
2486
  }
2376
- renderPage(snapshot, isPreview = false) {
2377
- const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
2487
+ renderPage(snapshot, isPreview = false, willRender = true) {
2488
+ const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
2378
2489
  return this.render(renderer);
2379
2490
  }
2380
2491
  renderError(snapshot) {
@@ -2389,7 +2500,9 @@ Copyright © 2021 Basecamp, LLC
2389
2500
  this.delegate.viewWillCacheSnapshot();
2390
2501
  const { snapshot, lastRenderedLocation: location } = this;
2391
2502
  await nextEventLoopTick();
2392
- this.snapshotCache.put(location, snapshot.clone());
2503
+ const cachedSnapshot = snapshot.clone();
2504
+ this.snapshotCache.put(location, cachedSnapshot);
2505
+ return cachedSnapshot;
2393
2506
  }
2394
2507
  }
2395
2508
  getCachedSnapshotForLocation(location) {
@@ -2491,7 +2604,7 @@ Copyright © 2021 Basecamp, LLC
2491
2604
  }
2492
2605
  willFollowLinkToLocation(link, location) {
2493
2606
  return this.elementDriveEnabled(link)
2494
- && this.locationIsVisitable(location)
2607
+ && locationIsVisitable(location, this.snapshot.rootLocation)
2495
2608
  && this.applicationAllowsFollowingLinkToLocation(link, location);
2496
2609
  }
2497
2610
  followedLinkToLocation(link, location) {
@@ -2499,14 +2612,24 @@ Copyright © 2021 Basecamp, LLC
2499
2612
  this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, { action });
2500
2613
  }
2501
2614
  convertLinkWithMethodClickToFormSubmission(link) {
2502
- var _a;
2503
2615
  const linkMethod = link.getAttribute("data-turbo-method");
2504
2616
  if (linkMethod) {
2505
2617
  const form = document.createElement("form");
2506
2618
  form.method = linkMethod;
2507
2619
  form.action = link.getAttribute("href") || "undefined";
2508
2620
  form.hidden = true;
2509
- (_a = link.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(form, link);
2621
+ if (link.hasAttribute("data-turbo-confirm")) {
2622
+ form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
2623
+ }
2624
+ const frame = this.getTargetFrameForLink(link);
2625
+ if (frame) {
2626
+ form.setAttribute("data-turbo-frame", frame);
2627
+ form.addEventListener("turbo:submit-start", () => form.remove());
2628
+ }
2629
+ else {
2630
+ form.addEventListener("submit", () => form.remove());
2631
+ }
2632
+ document.body.appendChild(form);
2510
2633
  return dispatch("submit", { cancelable: true, target: form });
2511
2634
  }
2512
2635
  else {
@@ -2536,7 +2659,10 @@ Copyright © 2021 Basecamp, LLC
2536
2659
  this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
2537
2660
  }
2538
2661
  willSubmitForm(form, submitter) {
2539
- return this.elementDriveEnabled(form) && (!submitter || this.elementDriveEnabled(submitter));
2662
+ const action = getAction(form, submitter);
2663
+ return this.elementDriveEnabled(form)
2664
+ && (!submitter || this.elementDriveEnabled(submitter))
2665
+ && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
2540
2666
  }
2541
2667
  formSubmitted(form, submitter) {
2542
2668
  this.navigator.submitForm(form, submitter);
@@ -2592,6 +2718,7 @@ Copyright © 2021 Basecamp, LLC
2592
2718
  return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
2593
2719
  }
2594
2720
  notifyApplicationAfterVisitingLocation(location, action) {
2721
+ markAsBusy(document.documentElement);
2595
2722
  return dispatch("turbo:visit", { detail: { url: location.href, action } });
2596
2723
  }
2597
2724
  notifyApplicationBeforeCachingSnapshot() {
@@ -2604,6 +2731,7 @@ Copyright © 2021 Basecamp, LLC
2604
2731
  return dispatch("turbo:render");
2605
2732
  }
2606
2733
  notifyApplicationAfterPageLoad(timing = {}) {
2734
+ clearBusyState(document.documentElement);
2607
2735
  return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
2608
2736
  }
2609
2737
  notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
@@ -2638,8 +2766,17 @@ Copyright © 2021 Basecamp, LLC
2638
2766
  const action = link.getAttribute("data-turbo-action");
2639
2767
  return isAction(action) ? action : "advance";
2640
2768
  }
2641
- locationIsVisitable(location) {
2642
- return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
2769
+ getTargetFrameForLink(link) {
2770
+ const frame = link.getAttribute("data-turbo-frame");
2771
+ if (frame) {
2772
+ return frame;
2773
+ }
2774
+ else {
2775
+ const container = link.closest("turbo-frame");
2776
+ if (container) {
2777
+ return container.id;
2778
+ }
2779
+ }
2643
2780
  }
2644
2781
  get snapshot() {
2645
2782
  return this.view.snapshot;
@@ -2682,6 +2819,9 @@ Copyright © 2021 Basecamp, LLC
2682
2819
  function setProgressBarDelay(delay) {
2683
2820
  session.setProgressBarDelay(delay);
2684
2821
  }
2822
+ function setConfirmMethod(confirmMethod) {
2823
+ FormSubmission.confirmMethod = confirmMethod;
2824
+ }
2685
2825
 
2686
2826
  var Turbo = /*#__PURE__*/Object.freeze({
2687
2827
  __proto__: null,
@@ -2696,11 +2836,14 @@ Copyright © 2021 Basecamp, LLC
2696
2836
  disconnectStreamSource: disconnectStreamSource,
2697
2837
  renderStreamMessage: renderStreamMessage,
2698
2838
  clearCache: clearCache,
2699
- setProgressBarDelay: setProgressBarDelay
2839
+ setProgressBarDelay: setProgressBarDelay,
2840
+ setConfirmMethod: setConfirmMethod
2700
2841
  });
2701
2842
 
2702
2843
  class FrameController {
2703
2844
  constructor(element) {
2845
+ this.fetchResponseLoaded = (fetchResponse) => { };
2846
+ this.currentFetchRequest = null;
2704
2847
  this.resolveVisitPromise = () => { };
2705
2848
  this.connected = false;
2706
2849
  this.hasBeenLoaded = false;
@@ -2756,11 +2899,10 @@ Copyright © 2021 Basecamp, LLC
2756
2899
  this.currentURL = this.sourceURL;
2757
2900
  if (this.sourceURL) {
2758
2901
  try {
2759
- this.element.loaded = this.visit(this.sourceURL);
2902
+ this.element.loaded = this.visit(expandURL(this.sourceURL));
2760
2903
  this.appearanceObserver.stop();
2761
2904
  await this.element.loaded;
2762
2905
  this.hasBeenLoaded = true;
2763
- session.frameLoaded(this.element);
2764
2906
  }
2765
2907
  catch (error) {
2766
2908
  this.currentURL = previousURL;
@@ -2770,7 +2912,7 @@ Copyright © 2021 Basecamp, LLC
2770
2912
  }
2771
2913
  }
2772
2914
  async loadResponse(fetchResponse) {
2773
- if (fetchResponse.redirected) {
2915
+ if (fetchResponse.redirected || (fetchResponse.succeeded && fetchResponse.isHTML)) {
2774
2916
  this.sourceURL = fetchResponse.response.url;
2775
2917
  }
2776
2918
  try {
@@ -2778,17 +2920,22 @@ Copyright © 2021 Basecamp, LLC
2778
2920
  if (html) {
2779
2921
  const { body } = parseHTMLDocument(html);
2780
2922
  const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
2781
- const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
2923
+ const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
2782
2924
  if (this.view.renderPromise)
2783
2925
  await this.view.renderPromise;
2784
2926
  await this.view.render(renderer);
2785
2927
  session.frameRendered(fetchResponse, this.element);
2928
+ session.frameLoaded(this.element);
2929
+ this.fetchResponseLoaded(fetchResponse);
2786
2930
  }
2787
2931
  }
2788
2932
  catch (error) {
2789
2933
  console.error(error);
2790
2934
  this.view.invalidate();
2791
2935
  }
2936
+ finally {
2937
+ this.fetchResponseLoaded = () => { };
2938
+ }
2792
2939
  }
2793
2940
  elementAppearedInViewport(element) {
2794
2941
  this.loadSourceURL();
@@ -2814,20 +2961,15 @@ Copyright © 2021 Basecamp, LLC
2814
2961
  }
2815
2962
  this.reloadable = false;
2816
2963
  this.formSubmission = new FormSubmission(this, element, submitter);
2817
- if (this.formSubmission.fetchRequest.isIdempotent) {
2818
- this.navigateFrame(element, this.formSubmission.fetchRequest.url.href, submitter);
2819
- }
2820
- else {
2821
- const { fetchRequest } = this.formSubmission;
2822
- this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
2823
- this.formSubmission.start();
2824
- }
2964
+ const { fetchRequest } = this.formSubmission;
2965
+ this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
2966
+ this.formSubmission.start();
2825
2967
  }
2826
2968
  prepareHeadersForRequest(headers, request) {
2827
2969
  headers["Turbo-Frame"] = this.id;
2828
2970
  }
2829
2971
  requestStarted(request) {
2830
- this.element.setAttribute("busy", "");
2972
+ markAsBusy(this.element);
2831
2973
  }
2832
2974
  requestPreventedHandlingResponse(request, response) {
2833
2975
  this.resolveVisitPromise();
@@ -2845,14 +2987,14 @@ Copyright © 2021 Basecamp, LLC
2845
2987
  this.resolveVisitPromise();
2846
2988
  }
2847
2989
  requestFinished(request) {
2848
- this.element.removeAttribute("busy");
2990
+ clearBusyState(this.element);
2849
2991
  }
2850
- formSubmissionStarted(formSubmission) {
2851
- const frame = this.findFrameElement(formSubmission.formElement);
2852
- frame.setAttribute("busy", "");
2992
+ formSubmissionStarted({ formElement }) {
2993
+ markAsBusy(formElement, this.findFrameElement(formElement));
2853
2994
  }
2854
2995
  formSubmissionSucceededWithResponse(formSubmission, response) {
2855
2996
  const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
2997
+ this.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
2856
2998
  frame.delegate.loadResponse(response);
2857
2999
  }
2858
3000
  formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
@@ -2861,9 +3003,8 @@ Copyright © 2021 Basecamp, LLC
2861
3003
  formSubmissionErrored(formSubmission, error) {
2862
3004
  console.error(error);
2863
3005
  }
2864
- formSubmissionFinished(formSubmission) {
2865
- const frame = this.findFrameElement(formSubmission.formElement);
2866
- frame.removeAttribute("busy");
3006
+ formSubmissionFinished({ formElement }) {
3007
+ clearBusyState(formElement, this.findFrameElement(formElement));
2867
3008
  }
2868
3009
  allowsImmediateRender(snapshot, resume) {
2869
3010
  return true;
@@ -2873,10 +3014,14 @@ Copyright © 2021 Basecamp, LLC
2873
3014
  viewInvalidated() {
2874
3015
  }
2875
3016
  async visit(url) {
2876
- const request = new FetchRequest(this, FetchMethod.get, expandURL(url), undefined, this.element);
3017
+ var _a;
3018
+ const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
3019
+ (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
3020
+ this.currentFetchRequest = request;
2877
3021
  return new Promise(resolve => {
2878
3022
  this.resolveVisitPromise = () => {
2879
3023
  this.resolveVisitPromise = () => { };
3024
+ this.currentFetchRequest = null;
2880
3025
  resolve();
2881
3026
  };
2882
3027
  request.perform();
@@ -2884,12 +3029,27 @@ Copyright © 2021 Basecamp, LLC
2884
3029
  }
2885
3030
  navigateFrame(element, url, submitter) {
2886
3031
  const frame = this.findFrameElement(element, submitter);
3032
+ this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
2887
3033
  frame.setAttribute("reloadable", "");
2888
3034
  frame.src = url;
2889
3035
  }
3036
+ proposeVisitIfNavigatedWithAction(frame, element, submitter) {
3037
+ const action = getAttribute("data-turbo-action", submitter, element, frame);
3038
+ if (isAction(action)) {
3039
+ const { visitCachedSnapshot } = new SnapshotSubstitution(frame);
3040
+ frame.delegate.fetchResponseLoaded = (fetchResponse) => {
3041
+ if (frame.src) {
3042
+ const { statusCode, redirected } = fetchResponse;
3043
+ const responseHTML = frame.ownerDocument.documentElement.outerHTML;
3044
+ const response = { statusCode, redirected, responseHTML };
3045
+ session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false });
3046
+ }
3047
+ };
3048
+ }
3049
+ }
2890
3050
  findFrameElement(element, submitter) {
2891
3051
  var _a;
2892
- const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
3052
+ const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
2893
3053
  return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
2894
3054
  }
2895
3055
  async extractForeignFrameElement(container) {
@@ -2910,8 +3070,15 @@ Copyright © 2021 Basecamp, LLC
2910
3070
  }
2911
3071
  return new FrameElement();
2912
3072
  }
3073
+ formActionIsVisitable(form, submitter) {
3074
+ const action = getAction(form, submitter);
3075
+ return locationIsVisitable(expandURL(action), this.rootLocation);
3076
+ }
2913
3077
  shouldInterceptNavigation(element, submitter) {
2914
- const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
3078
+ const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
3079
+ if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {
3080
+ return false;
3081
+ }
2915
3082
  if (!this.enabled || id == "_top") {
2916
3083
  return false;
2917
3084
  }
@@ -2968,6 +3135,23 @@ Copyright © 2021 Basecamp, LLC
2968
3135
  get isActive() {
2969
3136
  return this.element.isActive && this.connected;
2970
3137
  }
3138
+ get rootLocation() {
3139
+ var _a;
3140
+ const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
3141
+ const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3142
+ return expandURL(root);
3143
+ }
3144
+ }
3145
+ class SnapshotSubstitution {
3146
+ constructor(element) {
3147
+ this.visitCachedSnapshot = ({ element }) => {
3148
+ var _a;
3149
+ const { id, clone } = this;
3150
+ (_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
3151
+ };
3152
+ this.clone = element.cloneNode(true);
3153
+ this.id = element.id;
3154
+ }
2971
3155
  }
2972
3156
  function getFrameElementById(id) {
2973
3157
  if (id != null) {
@@ -2988,6 +3172,7 @@ Copyright © 2021 Basecamp, LLC
2988
3172
  }
2989
3173
  if (element instanceof FrameElement) {
2990
3174
  element.connectedCallback();
3175
+ element.disconnectedCallback();
2991
3176
  return element;
2992
3177
  }
2993
3178
  }
@@ -3167,6 +3352,7 @@ Copyright © 2021 Basecamp, LLC
3167
3352
  exports.registerAdapter = registerAdapter;
3168
3353
  exports.renderStreamMessage = renderStreamMessage;
3169
3354
  exports.session = session;
3355
+ exports.setConfirmMethod = setConfirmMethod;
3170
3356
  exports.setProgressBarDelay = setProgressBarDelay;
3171
3357
  exports.start = start;
3172
3358
  exports.visit = visit;