@hotwired/turbo 7.2.0-beta.2 → 7.2.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.2.0-beta.2
2
+ Turbo 7.2.0-rc.1
3
3
  Copyright © 2022 Basecamp, LLC
4
4
  */
5
5
  (function (global, factory) {
@@ -113,11 +113,11 @@ Copyright © 2022 Basecamp, LLC
113
113
  });
114
114
  })();
115
115
 
116
- var FrameLoadingStyle;
116
+ exports.FrameLoadingStyle = void 0;
117
117
  (function (FrameLoadingStyle) {
118
118
  FrameLoadingStyle["eager"] = "eager";
119
119
  FrameLoadingStyle["lazy"] = "lazy";
120
- })(FrameLoadingStyle || (FrameLoadingStyle = {}));
120
+ })(exports.FrameLoadingStyle || (exports.FrameLoadingStyle = {}));
121
121
  class FrameElement extends HTMLElement {
122
122
  constructor() {
123
123
  super();
@@ -212,9 +212,9 @@ Copyright © 2022 Basecamp, LLC
212
212
  function frameLoadingStyleFromString(style) {
213
213
  switch (style.toLowerCase()) {
214
214
  case "lazy":
215
- return FrameLoadingStyle.lazy;
215
+ return exports.FrameLoadingStyle.lazy;
216
216
  default:
217
- return FrameLoadingStyle.eager;
217
+ return exports.FrameLoadingStyle.eager;
218
218
  }
219
219
  }
220
220
 
@@ -410,6 +410,9 @@ Copyright © 2022 Basecamp, LLC
410
410
  }
411
411
  return null;
412
412
  }
413
+ function hasAttribute(attributeName, ...elements) {
414
+ return elements.some((element) => element && element.hasAttribute(attributeName));
415
+ }
413
416
  function markAsBusy(...elements) {
414
417
  for (const element of elements) {
415
418
  if (element.localName == "turbo-frame") {
@@ -526,7 +529,9 @@ Copyright © 2022 Basecamp, LLC
526
529
  }
527
530
  catch (error) {
528
531
  if (error.name !== "AbortError") {
529
- this.delegate.requestErrored(this, error);
532
+ if (this.willDelegateErrorHandling(error)) {
533
+ this.delegate.requestErrored(this, error);
534
+ }
530
535
  throw error;
531
536
  }
532
537
  }
@@ -592,6 +597,14 @@ Copyright © 2022 Basecamp, LLC
592
597
  if (event.defaultPrevented)
593
598
  await requestInterception;
594
599
  }
600
+ willDelegateErrorHandling(error) {
601
+ const event = dispatch("turbo:fetch-request-error", {
602
+ target: this.target,
603
+ cancelable: true,
604
+ detail: { request: this, error: error },
605
+ });
606
+ return !event.defaultPrevented;
607
+ }
595
608
  }
596
609
 
597
610
  class AppearanceObserver {
@@ -685,7 +698,7 @@ Copyright © 2022 Basecamp, LLC
685
698
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
686
699
  this.mustRedirect = mustRedirect;
687
700
  }
688
- static confirmMethod(message, _element) {
701
+ static confirmMethod(message, _element, _submitter) {
689
702
  return Promise.resolve(confirm(message));
690
703
  }
691
704
  get method() {
@@ -723,17 +736,11 @@ Copyright © 2022 Basecamp, LLC
723
736
  return entries.concat(typeof value == "string" ? [[name, value]] : []);
724
737
  }, []);
725
738
  }
726
- get confirmationMessage() {
727
- var _a;
728
- return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-confirm")) || this.formElement.getAttribute("data-turbo-confirm");
729
- }
730
- get needsConfirmation() {
731
- return this.confirmationMessage !== null;
732
- }
733
739
  async start() {
734
740
  const { initialized, requesting } = FormSubmissionState;
735
- if (this.needsConfirmation) {
736
- const answer = await FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
741
+ const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
742
+ if (typeof confirmationMessage === "string") {
743
+ const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);
737
744
  if (!answer) {
738
745
  return;
739
746
  }
@@ -795,10 +802,6 @@ Copyright © 2022 Basecamp, LLC
795
802
  }
796
803
  requestErrored(request, error) {
797
804
  this.result = { success: false, error };
798
- dispatch("turbo:fetch-request-error", {
799
- target: this.formElement,
800
- detail: { request, error },
801
- });
802
805
  this.delegate.formSubmissionErrored(this, error);
803
806
  }
804
807
  requestFinished(_request) {
@@ -815,7 +818,7 @@ Copyright © 2022 Basecamp, LLC
815
818
  return !request.isIdempotent && this.mustRedirect;
816
819
  }
817
820
  requestAcceptsTurboStreamResponse(request) {
818
- return !request.isIdempotent || this.formElement.hasAttribute("data-turbo-stream");
821
+ return !request.isIdempotent || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
819
822
  }
820
823
  }
821
824
  function buildFormData(formElement, submitter) {
@@ -881,10 +884,10 @@ Copyright © 2022 Basecamp, LLC
881
884
  return null;
882
885
  }
883
886
  get permanentElements() {
884
- return [...this.element.querySelectorAll("[id][data-turbo-permanent]")];
887
+ return queryPermanentElementsAll(this.element);
885
888
  }
886
889
  getPermanentElementById(id) {
887
- return this.element.querySelector(`#${id}[data-turbo-permanent]`);
890
+ return getPermanentElementById(this.element, id);
888
891
  }
889
892
  getPermanentElementMapForSnapshot(snapshot) {
890
893
  const permanentElementMap = {};
@@ -898,6 +901,12 @@ Copyright © 2022 Basecamp, LLC
898
901
  return permanentElementMap;
899
902
  }
900
903
  }
904
+ function getPermanentElementById(node, id) {
905
+ return node.querySelector(`#${id}[data-turbo-permanent]`);
906
+ }
907
+ function queryPermanentElementsAll(node) {
908
+ return node.querySelectorAll("[id][data-turbo-permanent]");
909
+ }
901
910
 
902
911
  class FormSubmitObserver {
903
912
  constructor(delegate, eventTarget) {
@@ -1139,6 +1148,9 @@ Copyright © 2022 Basecamp, LLC
1139
1148
  const turboFrame = link.getAttribute("data-turbo-frame");
1140
1149
  if (turboFrame)
1141
1150
  form.setAttribute("data-turbo-frame", turboFrame);
1151
+ const turboAction = link.getAttribute("data-turbo-action");
1152
+ if (turboAction)
1153
+ form.setAttribute("data-turbo-action", turboAction);
1142
1154
  const turboConfirm = link.getAttribute("data-turbo-confirm");
1143
1155
  if (turboConfirm)
1144
1156
  form.setAttribute("data-turbo-confirm", turboConfirm);
@@ -1147,8 +1159,8 @@ Copyright © 2022 Basecamp, LLC
1147
1159
  form.setAttribute("data-turbo-stream", "");
1148
1160
  this.delegate.submittedFormLinkToLocation(link, location, form);
1149
1161
  document.body.appendChild(form);
1150
- form.requestSubmit();
1151
- form.remove();
1162
+ form.addEventListener("turbo:submit-end", () => form.remove(), { once: true });
1163
+ requestAnimationFrame(() => form.requestSubmit());
1152
1164
  }
1153
1165
  }
1154
1166
 
@@ -1519,19 +1531,19 @@ Copyright © 2022 Basecamp, LLC
1519
1531
  return element.getAttribute("data-turbo-track") == "reload";
1520
1532
  }
1521
1533
  function elementIsScript(element) {
1522
- const tagName = element.tagName.toLowerCase();
1534
+ const tagName = element.localName;
1523
1535
  return tagName == "script";
1524
1536
  }
1525
1537
  function elementIsNoscript(element) {
1526
- const tagName = element.tagName.toLowerCase();
1538
+ const tagName = element.localName;
1527
1539
  return tagName == "noscript";
1528
1540
  }
1529
1541
  function elementIsStylesheet(element) {
1530
- const tagName = element.tagName.toLowerCase();
1542
+ const tagName = element.localName;
1531
1543
  return tagName == "style" || (tagName == "link" && element.getAttribute("rel") == "stylesheet");
1532
1544
  }
1533
1545
  function elementIsMetaElementWithName(element, name) {
1534
- const tagName = element.tagName.toLowerCase();
1546
+ const tagName = element.localName;
1535
1547
  return tagName == "meta" && element.getAttribute("name") == name;
1536
1548
  }
1537
1549
  function elementWithoutNonce(element) {
@@ -1556,7 +1568,20 @@ Copyright © 2022 Basecamp, LLC
1556
1568
  return new this(body, new HeadSnapshot(head));
1557
1569
  }
1558
1570
  clone() {
1559
- return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
1571
+ const clonedElement = this.element.cloneNode(true);
1572
+ const selectElements = this.element.querySelectorAll("select");
1573
+ const clonedSelectElements = clonedElement.querySelectorAll("select");
1574
+ for (const [index, source] of selectElements.entries()) {
1575
+ const clone = clonedSelectElements[index];
1576
+ for (const option of clone.selectedOptions)
1577
+ option.selected = false;
1578
+ for (const option of source.selectedOptions)
1579
+ clone.options[option.index].selected = true;
1580
+ }
1581
+ for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
1582
+ clonedPasswordInput.value = "";
1583
+ }
1584
+ return new PageSnapshot(clonedElement, this.headSnapshot);
1560
1585
  }
1561
1586
  get headElement() {
1562
1587
  return this.headSnapshot.element;
@@ -1606,6 +1631,7 @@ Copyright © 2022 Basecamp, LLC
1606
1631
  updateHistory: true,
1607
1632
  shouldCacheSnapshot: true,
1608
1633
  acceptsStreamResponse: false,
1634
+ initiator: document.documentElement,
1609
1635
  };
1610
1636
  var SystemStatusCode;
1611
1637
  (function (SystemStatusCode) {
@@ -1615,7 +1641,6 @@ Copyright © 2022 Basecamp, LLC
1615
1641
  })(SystemStatusCode || (SystemStatusCode = {}));
1616
1642
  class Visit {
1617
1643
  constructor(delegate, location, restorationIdentifier, options = {}) {
1618
- this.identifier = uuid();
1619
1644
  this.timingMetrics = {};
1620
1645
  this.followedRedirect = false;
1621
1646
  this.historyChanged = false;
@@ -1628,7 +1653,7 @@ Copyright © 2022 Basecamp, LLC
1628
1653
  this.location = location;
1629
1654
  this.restorationIdentifier = restorationIdentifier || uuid();
1630
1655
  this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));
1631
- const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, } = Object.assign(Object.assign({}, defaultOptions), options);
1656
+ const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, initiator, } = Object.assign(Object.assign({}, defaultOptions), options);
1632
1657
  this.action = action;
1633
1658
  this.historyChanged = historyChanged;
1634
1659
  this.referrer = referrer;
@@ -1641,6 +1666,7 @@ Copyright © 2022 Basecamp, LLC
1641
1666
  this.scrolled = !willRender;
1642
1667
  this.shouldCacheSnapshot = shouldCacheSnapshot;
1643
1668
  this.acceptsStreamResponse = acceptsStreamResponse;
1669
+ this.initiator = initiator;
1644
1670
  }
1645
1671
  get adapter() {
1646
1672
  return this.delegate.adapter;
@@ -1708,7 +1734,7 @@ Copyright © 2022 Basecamp, LLC
1708
1734
  this.simulateRequest();
1709
1735
  }
1710
1736
  else if (this.shouldIssueRequest() && !this.request) {
1711
- this.request = new FetchRequest(this, FetchMethod.get, this.location);
1737
+ this.request = new FetchRequest(this, FetchMethod.get, this.location, undefined, this.initiator);
1712
1738
  this.request.perform();
1713
1739
  }
1714
1740
  }
@@ -1804,7 +1830,6 @@ Copyright © 2022 Basecamp, LLC
1804
1830
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1805
1831
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1806
1832
  action: "replace",
1807
- willRender: false,
1808
1833
  response: this.response,
1809
1834
  });
1810
1835
  this.followedRedirect = true;
@@ -2214,7 +2239,7 @@ Copyright © 2022 Basecamp, LLC
2214
2239
  this.delegate = delegate;
2215
2240
  }
2216
2241
  proposeVisit(location, options = {}) {
2217
- if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
2242
+ if (this.delegate.allowsVisitingLocation(location, options)) {
2218
2243
  if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
2219
2244
  return this.delegate.visitProposedToLocation(location, options);
2220
2245
  }
@@ -2422,6 +2447,30 @@ Copyright © 2022 Basecamp, LLC
2422
2447
  }
2423
2448
  }
2424
2449
 
2450
+ class StreamMessageRenderer {
2451
+ render({ fragment }) {
2452
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => document.documentElement.appendChild(fragment));
2453
+ }
2454
+ enteringBardo(currentPermanentElement, newPermanentElement) {
2455
+ newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
2456
+ }
2457
+ leavingBardo() { }
2458
+ }
2459
+ function getPermanentElementMapForFragment(fragment) {
2460
+ const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
2461
+ const permanentElementMap = {};
2462
+ for (const permanentElementInDocument of permanentElementsInDocument) {
2463
+ const { id } = permanentElementInDocument;
2464
+ for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
2465
+ const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
2466
+ if (elementInStream) {
2467
+ permanentElementMap[id] = [permanentElementInDocument, elementInStream];
2468
+ }
2469
+ }
2470
+ }
2471
+ return permanentElementMap;
2472
+ }
2473
+
2425
2474
  class StreamObserver {
2426
2475
  constructor(delegate) {
2427
2476
  this.sources = new Set();
@@ -2782,6 +2831,7 @@ Copyright © 2022 Basecamp, LLC
2782
2831
  this.streamObserver = new StreamObserver(this);
2783
2832
  this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
2784
2833
  this.frameRedirector = new FrameRedirector(this, document.documentElement);
2834
+ this.streamMessageRenderer = new StreamMessageRenderer();
2785
2835
  this.drive = true;
2786
2836
  this.enabled = true;
2787
2837
  this.progressBarDelay = 500;
@@ -2825,7 +2875,7 @@ Copyright © 2022 Basecamp, LLC
2825
2875
  this.adapter = adapter;
2826
2876
  }
2827
2877
  visit(location, options = {}) {
2828
- const frameElement = document.getElementById(options.frame || "");
2878
+ const frameElement = options.frame ? document.getElementById(options.frame) : null;
2829
2879
  if (frameElement instanceof FrameElement) {
2830
2880
  frameElement.src = location.toString();
2831
2881
  return frameElement.loaded;
@@ -2841,7 +2891,7 @@ Copyright © 2022 Basecamp, LLC
2841
2891
  this.streamObserver.disconnectStreamSource(source);
2842
2892
  }
2843
2893
  renderStreamMessage(message) {
2844
- document.documentElement.appendChild(StreamMessage.wrap(message).fragment);
2894
+ this.streamMessageRenderer.render(StreamMessage.wrap(message));
2845
2895
  }
2846
2896
  clearCache() {
2847
2897
  this.view.clearSnapshotCache();
@@ -2886,22 +2936,27 @@ Copyright © 2022 Basecamp, LLC
2886
2936
  followedLinkToLocation(link, location) {
2887
2937
  const action = this.getActionForLink(link);
2888
2938
  const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
2889
- this.visit(location.href, { action, acceptsStreamResponse });
2939
+ this.visit(location.href, { action, acceptsStreamResponse, initiator: link });
2890
2940
  }
2891
- allowsVisitingLocationWithAction(location, action) {
2892
- return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
2941
+ allowsVisitingLocation(location, options = {}) {
2942
+ return (this.locationWithActionIsSamePage(location, options.action) ||
2943
+ this.applicationAllowsVisitingLocation(location, options));
2893
2944
  }
2894
2945
  visitProposedToLocation(location, options) {
2895
2946
  extendURLWithDeprecatedProperties(location);
2896
2947
  return this.adapter.visitProposedToLocation(location, options);
2897
2948
  }
2898
2949
  visitStarted(visit) {
2950
+ if (!visit.acceptsStreamResponse) {
2951
+ markAsBusy(document.documentElement);
2952
+ }
2899
2953
  extendURLWithDeprecatedProperties(visit.location);
2900
2954
  if (!visit.silent) {
2901
- this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
2955
+ this.notifyApplicationAfterVisitingLocation(visit.location, visit.action, visit.initiator);
2902
2956
  }
2903
2957
  }
2904
2958
  visitCompleted(visit) {
2959
+ clearBusyState(document.documentElement);
2905
2960
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2906
2961
  }
2907
2962
  locationWithActionIsSamePage(location, action) {
@@ -2961,16 +3016,12 @@ Copyright © 2022 Basecamp, LLC
2961
3016
  frameRendered(fetchResponse, frame) {
2962
3017
  this.notifyApplicationAfterFrameRender(fetchResponse, frame);
2963
3018
  }
2964
- frameMissing(frame, fetchResponse) {
2965
- console.warn(`Completing full-page visit as matching frame for #${frame.id} was missing from the response`);
2966
- return this.visit(fetchResponse.location);
2967
- }
2968
3019
  applicationAllowsFollowingLinkToLocation(link, location, ev) {
2969
3020
  const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
2970
3021
  return !event.defaultPrevented;
2971
3022
  }
2972
- applicationAllowsVisitingLocation(location) {
2973
- const event = this.notifyApplicationBeforeVisitingLocation(location);
3023
+ applicationAllowsVisitingLocation(location, options = {}) {
3024
+ const event = this.notifyApplicationBeforeVisitingLocation(location, options.initiator);
2974
3025
  return !event.defaultPrevented;
2975
3026
  }
2976
3027
  notifyApplicationAfterClickingLinkToLocation(link, location, event) {
@@ -2980,15 +3031,18 @@ Copyright © 2022 Basecamp, LLC
2980
3031
  cancelable: true,
2981
3032
  });
2982
3033
  }
2983
- notifyApplicationBeforeVisitingLocation(location) {
3034
+ notifyApplicationBeforeVisitingLocation(location, element) {
2984
3035
  return dispatch("turbo:before-visit", {
3036
+ target: element,
2985
3037
  detail: { url: location.href },
2986
3038
  cancelable: true,
2987
3039
  });
2988
3040
  }
2989
- notifyApplicationAfterVisitingLocation(location, action) {
2990
- markAsBusy(document.documentElement);
2991
- return dispatch("turbo:visit", { detail: { url: location.href, action } });
3041
+ notifyApplicationAfterVisitingLocation(location, action, element) {
3042
+ return dispatch("turbo:visit", {
3043
+ target: element,
3044
+ detail: { url: location.href, action },
3045
+ });
2992
3046
  }
2993
3047
  notifyApplicationBeforeCachingSnapshot() {
2994
3048
  return dispatch("turbo:before-cache");
@@ -3003,7 +3057,6 @@ Copyright © 2022 Basecamp, LLC
3003
3057
  return dispatch("turbo:render");
3004
3058
  }
3005
3059
  notifyApplicationAfterPageLoad(timing = {}) {
3006
- clearBusyState(document.documentElement);
3007
3060
  return dispatch("turbo:load", {
3008
3061
  detail: { url: this.location.href, timing },
3009
3062
  });
@@ -3207,7 +3260,7 @@ Copyright © 2022 Basecamp, LLC
3207
3260
  connect() {
3208
3261
  if (!this.connected) {
3209
3262
  this.connected = true;
3210
- if (this.loadingStyle == FrameLoadingStyle.lazy) {
3263
+ if (this.loadingStyle == exports.FrameLoadingStyle.lazy) {
3211
3264
  this.appearanceObserver.start();
3212
3265
  }
3213
3266
  else {
@@ -3228,7 +3281,7 @@ Copyright © 2022 Basecamp, LLC
3228
3281
  }
3229
3282
  }
3230
3283
  disabledChanged() {
3231
- if (this.loadingStyle == FrameLoadingStyle.eager) {
3284
+ if (this.loadingStyle == exports.FrameLoadingStyle.eager) {
3232
3285
  this.loadSourceURL();
3233
3286
  }
3234
3287
  }
@@ -3238,7 +3291,7 @@ Copyright © 2022 Basecamp, LLC
3238
3291
  if (this.element.isConnected) {
3239
3292
  this.complete = false;
3240
3293
  }
3241
- if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
3294
+ if (this.loadingStyle == exports.FrameLoadingStyle.eager || this.hasBeenLoaded) {
3242
3295
  this.loadSourceURL();
3243
3296
  }
3244
3297
  }
@@ -3248,7 +3301,7 @@ Copyright © 2022 Basecamp, LLC
3248
3301
  this.loadSourceURL();
3249
3302
  }
3250
3303
  loadingStyleChanged() {
3251
- if (this.loadingStyle == FrameLoadingStyle.lazy) {
3304
+ if (this.loadingStyle == exports.FrameLoadingStyle.lazy) {
3252
3305
  this.appearanceObserver.start();
3253
3306
  }
3254
3307
  else {
@@ -3285,8 +3338,9 @@ Copyright © 2022 Basecamp, LLC
3285
3338
  session.frameLoaded(this.element);
3286
3339
  this.fetchResponseLoaded(fetchResponse);
3287
3340
  }
3288
- else if (this.sessionWillHandleMissingFrame(fetchResponse)) {
3289
- await session.frameMissing(this.element, fetchResponse);
3341
+ else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3342
+ console.warn(`A matching frame for #${this.element.id} was missing from the response, transforming into full-page Visit.`);
3343
+ this.visitResponse(fetchResponse.response);
3290
3344
  }
3291
3345
  }
3292
3346
  }
@@ -3344,16 +3398,13 @@ Copyright © 2022 Basecamp, LLC
3344
3398
  await this.loadResponse(response);
3345
3399
  this.resolveVisitPromise();
3346
3400
  }
3347
- requestFailedWithResponse(request, response) {
3401
+ async requestFailedWithResponse(request, response) {
3348
3402
  console.error(response);
3403
+ await this.loadResponse(response);
3349
3404
  this.resolveVisitPromise();
3350
3405
  }
3351
3406
  requestErrored(request, error) {
3352
3407
  console.error(error);
3353
- dispatch("turbo:fetch-request-error", {
3354
- target: this.element,
3355
- detail: { request, error },
3356
- });
3357
3408
  this.resolveVisitPromise();
3358
3409
  }
3359
3410
  requestFinished(_request) {
@@ -3447,15 +3498,30 @@ Copyright © 2022 Basecamp, LLC
3447
3498
  session.history.update(method, expandURL(this.frame.src || ""), this.restorationIdentifier);
3448
3499
  }
3449
3500
  }
3450
- sessionWillHandleMissingFrame(fetchResponse) {
3501
+ willHandleFrameMissingFromResponse(fetchResponse) {
3451
3502
  this.element.setAttribute("complete", "");
3503
+ const response = fetchResponse.response;
3504
+ const visit = async (url, options = {}) => {
3505
+ if (url instanceof Response) {
3506
+ this.visitResponse(url);
3507
+ }
3508
+ else {
3509
+ session.visit(url, options);
3510
+ }
3511
+ };
3452
3512
  const event = dispatch("turbo:frame-missing", {
3453
3513
  target: this.element,
3454
- detail: { fetchResponse },
3514
+ detail: { response, visit },
3455
3515
  cancelable: true,
3456
3516
  });
3457
3517
  return !event.defaultPrevented;
3458
3518
  }
3519
+ async visitResponse(response) {
3520
+ const wrapped = new FetchResponse(response);
3521
+ const responseHTML = await wrapped.responseHTML;
3522
+ const { location, redirected, statusCode } = wrapped;
3523
+ return session.visit(location, { response: { redirected, statusCode, responseHTML } });
3524
+ }
3459
3525
  findFrameElement(element, submitter) {
3460
3526
  var _a;
3461
3527
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
@@ -3591,6 +3657,9 @@ Copyright © 2022 Basecamp, LLC
3591
3657
  }
3592
3658
 
3593
3659
  class StreamElement extends HTMLElement {
3660
+ static async renderElement(newElement) {
3661
+ await newElement.performAction();
3662
+ }
3594
3663
  async connectedCallback() {
3595
3664
  try {
3596
3665
  await this.render();
@@ -3605,9 +3674,10 @@ Copyright © 2022 Basecamp, LLC
3605
3674
  async render() {
3606
3675
  var _a;
3607
3676
  return ((_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
3608
- if (this.dispatchEvent(this.beforeRenderEvent)) {
3677
+ const event = this.beforeRenderEvent;
3678
+ if (this.dispatchEvent(event)) {
3609
3679
  await nextAnimationFrame();
3610
- this.performAction();
3680
+ await event.detail.render(this);
3611
3681
  }
3612
3682
  })()));
3613
3683
  }
@@ -3651,7 +3721,12 @@ Copyright © 2022 Basecamp, LLC
3651
3721
  return this.templateElement.content.cloneNode(true);
3652
3722
  }
3653
3723
  get templateElement() {
3654
- if (this.firstElementChild instanceof HTMLTemplateElement) {
3724
+ if (this.firstElementChild === null) {
3725
+ const template = this.ownerDocument.createElement("template");
3726
+ this.appendChild(template);
3727
+ return template;
3728
+ }
3729
+ else if (this.firstElementChild instanceof HTMLTemplateElement) {
3655
3730
  return this.firstElementChild;
3656
3731
  }
3657
3732
  this.raise("first child element must be a <template> element");
@@ -3676,7 +3751,7 @@ Copyright © 2022 Basecamp, LLC
3676
3751
  return new CustomEvent("turbo:before-stream-render", {
3677
3752
  bubbles: true,
3678
3753
  cancelable: true,
3679
- detail: { newStream: this },
3754
+ detail: { newStream: this, render: StreamElement.renderElement },
3680
3755
  });
3681
3756
  }
3682
3757
  get targetElementsById() {
@@ -3758,10 +3833,13 @@ Copyright © 2022 Basecamp, LLC
3758
3833
  window.Turbo = Turbo;
3759
3834
  start();
3760
3835
 
3836
+ exports.FrameElement = FrameElement;
3761
3837
  exports.FrameRenderer = FrameRenderer;
3762
3838
  exports.PageRenderer = PageRenderer;
3763
3839
  exports.PageSnapshot = PageSnapshot;
3764
3840
  exports.StreamActions = StreamActions;
3841
+ exports.StreamElement = StreamElement;
3842
+ exports.StreamSourceElement = StreamSourceElement;
3765
3843
  exports.cache = cache;
3766
3844
  exports.clearCache = clearCache;
3767
3845
  exports.connectStreamSource = connectStreamSource;
@@ -45,7 +45,7 @@ export declare class FormSubmission {
45
45
  readonly mustRedirect: boolean;
46
46
  state: FormSubmissionState;
47
47
  result?: FormSubmissionResult;
48
- static confirmMethod(message: string, _element: HTMLFormElement): Promise<boolean>;
48
+ static confirmMethod(message: string, _element: HTMLFormElement, _submitter: HTMLElement | undefined): Promise<boolean>;
49
49
  constructor(delegate: FormSubmissionDelegate, formElement: HTMLFormElement, submitter?: HTMLElement, mustRedirect?: boolean);
50
50
  get method(): FetchMethod;
51
51
  get action(): string;
@@ -53,8 +53,6 @@ export declare class FormSubmission {
53
53
  get enctype(): FormEnctype;
54
54
  get isIdempotent(): boolean;
55
55
  get stringFormData(): [string, string][];
56
- get confirmationMessage(): string | null;
57
- get needsConfirmation(): boolean;
58
56
  start(): Promise<void | FetchResponse>;
59
57
  stop(): true | undefined;
60
58
  prepareHeadersForRequest(headers: FetchRequestHeaders, request: FetchRequest): void;
@@ -4,7 +4,7 @@ import { FormSubmission } from "./form_submission";
4
4
  import { Locatable } from "../url";
5
5
  import { Visit, VisitDelegate, VisitOptions } from "./visit";
6
6
  export declare type NavigatorDelegate = VisitDelegate & {
7
- allowsVisitingLocationWithAction(location: URL, action?: Action): boolean;
7
+ allowsVisitingLocation(location: URL, options: Partial<VisitOptions>): boolean;
8
8
  visitProposedToLocation(location: URL, options: Partial<VisitOptions>): Promise<void>;
9
9
  notifyApplicationAfterVisitingSamePageLocation(oldURL: URL, newURL: URL): void;
10
10
  };
@@ -44,6 +44,7 @@ export declare type VisitOptions = {
44
44
  shouldCacheSnapshot: boolean;
45
45
  frame?: string;
46
46
  acceptsStreamResponse: boolean;
47
+ initiator: Element;
47
48
  };
48
49
  export declare type VisitResponse = {
49
50
  statusCode: number;
@@ -57,7 +58,6 @@ export declare enum SystemStatusCode {
57
58
  }
58
59
  export declare class Visit implements FetchRequestDelegate {
59
60
  readonly delegate: VisitDelegate;
60
- readonly identifier: string;
61
61
  readonly restorationIdentifier: string;
62
62
  readonly action: Action;
63
63
  readonly referrer?: URL;
@@ -66,6 +66,7 @@ export declare class Visit implements FetchRequestDelegate {
66
66
  readonly willRender: boolean;
67
67
  readonly updateHistory: boolean;
68
68
  readonly promise: Promise<void>;
69
+ readonly initiator: Element;
69
70
  private resolvingFunctions;
70
71
  followedRedirect: boolean;
71
72
  frame?: number;
@@ -5,12 +5,16 @@ import { AppearanceObserver, AppearanceObserverDelegate } from "../../observers/
5
5
  import { FormSubmission, FormSubmissionDelegate } from "../drive/form_submission";
6
6
  import { Snapshot } from "../snapshot";
7
7
  import { ViewDelegate, ViewRenderOptions } from "../view";
8
+ import { Locatable } from "../url";
8
9
  import { FormSubmitObserver, FormSubmitObserverDelegate } from "../../observers/form_submit_observer";
9
10
  import { FrameView } from "./frame_view";
10
11
  import { LinkClickObserver, LinkClickObserverDelegate } from "../../observers/link_click_observer";
11
12
  import { FormLinkClickObserver, FormLinkClickObserverDelegate } from "../../observers/form_link_click_observer";
13
+ import { VisitOptions } from "../drive/visit";
14
+ declare type VisitFallback = (location: Response | Locatable, options: Partial<VisitOptions>) => Promise<void>;
12
15
  export declare type TurboFrameMissingEvent = CustomEvent<{
13
- fetchResponse: FetchResponse;
16
+ response: Response;
17
+ visit: VisitFallback;
14
18
  }>;
15
19
  export declare class FrameController implements AppearanceObserverDelegate, FetchRequestDelegate, FormSubmitObserverDelegate, FormSubmissionDelegate, FrameElementDelegate, FormLinkClickObserverDelegate, LinkClickObserverDelegate, ViewDelegate<FrameElement, Snapshot<FrameElement>> {
16
20
  readonly element: FrameElement;
@@ -51,7 +55,7 @@ export declare class FrameController implements AppearanceObserverDelegate, Fetc
51
55
  requestStarted(_request: FetchRequest): void;
52
56
  requestPreventedHandlingResponse(_request: FetchRequest, _response: FetchResponse): void;
53
57
  requestSucceededWithResponse(request: FetchRequest, response: FetchResponse): Promise<void>;
54
- requestFailedWithResponse(request: FetchRequest, response: FetchResponse): void;
58
+ requestFailedWithResponse(request: FetchRequest, response: FetchResponse): Promise<void>;
55
59
  requestErrored(request: FetchRequest, error: Error): void;
56
60
  requestFinished(_request: FetchRequest): void;
57
61
  formSubmissionStarted({ formElement }: FormSubmission): void;
@@ -69,7 +73,8 @@ export declare class FrameController implements AppearanceObserverDelegate, Fetc
69
73
  private navigateFrame;
70
74
  private proposeVisitIfNavigatedWithAction;
71
75
  changeHistory(): void;
72
- private sessionWillHandleMissingFrame;
76
+ private willHandleFrameMissingFromResponse;
77
+ private visitResponse;
73
78
  private findFrameElement;
74
79
  extractForeignFrameElement(container: ParentNode): Promise<FrameElement | null>;
75
80
  private formActionIsVisitable;
@@ -88,3 +93,4 @@ export declare class FrameController implements AppearanceObserverDelegate, Fetc
88
93
  private ignoringChangesToAttribute;
89
94
  private withCurrentNavigationElement;
90
95
  }
96
+ export {};