@hotwired/turbo 7.2.0-beta.2 → 7.2.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.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") {
@@ -451,6 +454,9 @@ Copyright © 2022 Basecamp, LLC
451
454
  const action = getAttribute("data-turbo-action", ...elements);
452
455
  return isAction(action) ? action : null;
453
456
  }
457
+ function getBodyElementId() {
458
+ return getMetaContent("turbo-body");
459
+ }
454
460
  function getMetaElement(name) {
455
461
  return document.querySelector(`meta[name="${name}"]`);
456
462
  }
@@ -685,7 +691,7 @@ Copyright © 2022 Basecamp, LLC
685
691
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
686
692
  this.mustRedirect = mustRedirect;
687
693
  }
688
- static confirmMethod(message, _element) {
694
+ static confirmMethod(message, _element, _submitter) {
689
695
  return Promise.resolve(confirm(message));
690
696
  }
691
697
  get method() {
@@ -723,17 +729,11 @@ Copyright © 2022 Basecamp, LLC
723
729
  return entries.concat(typeof value == "string" ? [[name, value]] : []);
724
730
  }, []);
725
731
  }
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
732
  async start() {
734
733
  const { initialized, requesting } = FormSubmissionState;
735
- if (this.needsConfirmation) {
736
- const answer = await FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
734
+ const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
735
+ if (typeof confirmationMessage === "string") {
736
+ const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);
737
737
  if (!answer) {
738
738
  return;
739
739
  }
@@ -815,7 +815,7 @@ Copyright © 2022 Basecamp, LLC
815
815
  return !request.isIdempotent && this.mustRedirect;
816
816
  }
817
817
  requestAcceptsTurboStreamResponse(request) {
818
- return !request.isIdempotent || this.formElement.hasAttribute("data-turbo-stream");
818
+ return !request.isIdempotent || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
819
819
  }
820
820
  }
821
821
  function buildFormData(formElement, submitter) {
@@ -881,10 +881,10 @@ Copyright © 2022 Basecamp, LLC
881
881
  return null;
882
882
  }
883
883
  get permanentElements() {
884
- return [...this.element.querySelectorAll("[id][data-turbo-permanent]")];
884
+ return queryPermanentElementsAll(this.element);
885
885
  }
886
886
  getPermanentElementById(id) {
887
- return this.element.querySelector(`#${id}[data-turbo-permanent]`);
887
+ return getPermanentElementById(this.element, id);
888
888
  }
889
889
  getPermanentElementMapForSnapshot(snapshot) {
890
890
  const permanentElementMap = {};
@@ -898,6 +898,12 @@ Copyright © 2022 Basecamp, LLC
898
898
  return permanentElementMap;
899
899
  }
900
900
  }
901
+ function getPermanentElementById(node, id) {
902
+ return node.querySelector(`#${id}[data-turbo-permanent]`);
903
+ }
904
+ function queryPermanentElementsAll(node) {
905
+ return node.querySelectorAll("[id][data-turbo-permanent]");
906
+ }
901
907
 
902
908
  class FormSubmitObserver {
903
909
  constructor(delegate, eventTarget) {
@@ -1139,6 +1145,9 @@ Copyright © 2022 Basecamp, LLC
1139
1145
  const turboFrame = link.getAttribute("data-turbo-frame");
1140
1146
  if (turboFrame)
1141
1147
  form.setAttribute("data-turbo-frame", turboFrame);
1148
+ const turboAction = link.getAttribute("data-turbo-action");
1149
+ if (turboAction)
1150
+ form.setAttribute("data-turbo-action", turboAction);
1142
1151
  const turboConfirm = link.getAttribute("data-turbo-confirm");
1143
1152
  if (turboConfirm)
1144
1153
  form.setAttribute("data-turbo-confirm", turboConfirm);
@@ -1147,8 +1156,8 @@ Copyright © 2022 Basecamp, LLC
1147
1156
  form.setAttribute("data-turbo-stream", "");
1148
1157
  this.delegate.submittedFormLinkToLocation(link, location, form);
1149
1158
  document.body.appendChild(form);
1150
- form.requestSubmit();
1151
- form.remove();
1159
+ form.addEventListener("turbo:submit-end", () => form.remove(), { once: true });
1160
+ requestAnimationFrame(() => form.requestSubmit());
1152
1161
  }
1153
1162
  }
1154
1163
 
@@ -1519,19 +1528,19 @@ Copyright © 2022 Basecamp, LLC
1519
1528
  return element.getAttribute("data-turbo-track") == "reload";
1520
1529
  }
1521
1530
  function elementIsScript(element) {
1522
- const tagName = element.tagName.toLowerCase();
1531
+ const tagName = element.localName;
1523
1532
  return tagName == "script";
1524
1533
  }
1525
1534
  function elementIsNoscript(element) {
1526
- const tagName = element.tagName.toLowerCase();
1535
+ const tagName = element.localName;
1527
1536
  return tagName == "noscript";
1528
1537
  }
1529
1538
  function elementIsStylesheet(element) {
1530
- const tagName = element.tagName.toLowerCase();
1539
+ const tagName = element.localName;
1531
1540
  return tagName == "style" || (tagName == "link" && element.getAttribute("rel") == "stylesheet");
1532
1541
  }
1533
1542
  function elementIsMetaElementWithName(element, name) {
1534
- const tagName = element.tagName.toLowerCase();
1543
+ const tagName = element.localName;
1535
1544
  return tagName == "meta" && element.getAttribute("name") == name;
1536
1545
  }
1537
1546
  function elementWithoutNonce(element) {
@@ -1556,7 +1565,20 @@ Copyright © 2022 Basecamp, LLC
1556
1565
  return new this(body, new HeadSnapshot(head));
1557
1566
  }
1558
1567
  clone() {
1559
- return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
1568
+ const clonedElement = this.element.cloneNode(true);
1569
+ const selectElements = this.element.querySelectorAll("select");
1570
+ const clonedSelectElements = clonedElement.querySelectorAll("select");
1571
+ for (const [index, source] of selectElements.entries()) {
1572
+ const clone = clonedSelectElements[index];
1573
+ for (const option of clone.selectedOptions)
1574
+ option.selected = false;
1575
+ for (const option of source.selectedOptions)
1576
+ clone.options[option.index].selected = true;
1577
+ }
1578
+ for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
1579
+ clonedPasswordInput.value = "";
1580
+ }
1581
+ return new PageSnapshot(clonedElement, this.headSnapshot);
1560
1582
  }
1561
1583
  get headElement() {
1562
1584
  return this.headSnapshot.element;
@@ -1606,6 +1628,7 @@ Copyright © 2022 Basecamp, LLC
1606
1628
  updateHistory: true,
1607
1629
  shouldCacheSnapshot: true,
1608
1630
  acceptsStreamResponse: false,
1631
+ initiator: document.documentElement,
1609
1632
  };
1610
1633
  var SystemStatusCode;
1611
1634
  (function (SystemStatusCode) {
@@ -1615,7 +1638,6 @@ Copyright © 2022 Basecamp, LLC
1615
1638
  })(SystemStatusCode || (SystemStatusCode = {}));
1616
1639
  class Visit {
1617
1640
  constructor(delegate, location, restorationIdentifier, options = {}) {
1618
- this.identifier = uuid();
1619
1641
  this.timingMetrics = {};
1620
1642
  this.followedRedirect = false;
1621
1643
  this.historyChanged = false;
@@ -1628,7 +1650,7 @@ Copyright © 2022 Basecamp, LLC
1628
1650
  this.location = location;
1629
1651
  this.restorationIdentifier = restorationIdentifier || uuid();
1630
1652
  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);
1653
+ const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, initiator, } = Object.assign(Object.assign({}, defaultOptions), options);
1632
1654
  this.action = action;
1633
1655
  this.historyChanged = historyChanged;
1634
1656
  this.referrer = referrer;
@@ -1641,6 +1663,7 @@ Copyright © 2022 Basecamp, LLC
1641
1663
  this.scrolled = !willRender;
1642
1664
  this.shouldCacheSnapshot = shouldCacheSnapshot;
1643
1665
  this.acceptsStreamResponse = acceptsStreamResponse;
1666
+ this.initiator = initiator;
1644
1667
  }
1645
1668
  get adapter() {
1646
1669
  return this.delegate.adapter;
@@ -1708,7 +1731,7 @@ Copyright © 2022 Basecamp, LLC
1708
1731
  this.simulateRequest();
1709
1732
  }
1710
1733
  else if (this.shouldIssueRequest() && !this.request) {
1711
- this.request = new FetchRequest(this, FetchMethod.get, this.location);
1734
+ this.request = new FetchRequest(this, FetchMethod.get, this.location, undefined, this.initiator);
1712
1735
  this.request.perform();
1713
1736
  }
1714
1737
  }
@@ -1804,7 +1827,6 @@ Copyright © 2022 Basecamp, LLC
1804
1827
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1805
1828
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1806
1829
  action: "replace",
1807
- willRender: false,
1808
1830
  response: this.response,
1809
1831
  });
1810
1832
  this.followedRedirect = true;
@@ -2214,7 +2236,7 @@ Copyright © 2022 Basecamp, LLC
2214
2236
  this.delegate = delegate;
2215
2237
  }
2216
2238
  proposeVisit(location, options = {}) {
2217
- if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
2239
+ if (this.delegate.allowsVisitingLocation(location, options)) {
2218
2240
  if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
2219
2241
  return this.delegate.visitProposedToLocation(location, options);
2220
2242
  }
@@ -2422,6 +2444,30 @@ Copyright © 2022 Basecamp, LLC
2422
2444
  }
2423
2445
  }
2424
2446
 
2447
+ class StreamMessageRenderer {
2448
+ render({ fragment }) {
2449
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => document.documentElement.appendChild(fragment));
2450
+ }
2451
+ enteringBardo(currentPermanentElement, newPermanentElement) {
2452
+ newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
2453
+ }
2454
+ leavingBardo() { }
2455
+ }
2456
+ function getPermanentElementMapForFragment(fragment) {
2457
+ const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
2458
+ const permanentElementMap = {};
2459
+ for (const permanentElementInDocument of permanentElementsInDocument) {
2460
+ const { id } = permanentElementInDocument;
2461
+ for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
2462
+ const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
2463
+ if (elementInStream) {
2464
+ permanentElementMap[id] = [permanentElementInDocument, elementInStream];
2465
+ }
2466
+ }
2467
+ }
2468
+ return permanentElementMap;
2469
+ }
2470
+
2425
2471
  class StreamObserver {
2426
2472
  constructor(delegate) {
2427
2473
  this.sources = new Set();
@@ -2522,16 +2568,19 @@ Copyright © 2022 Basecamp, LLC
2522
2568
  }
2523
2569
 
2524
2570
  class PageRenderer extends Renderer {
2525
- static renderElement(currentElement, newElement) {
2571
+ static async renderElement(currentElement, newElement) {
2572
+ await nextEventLoopTick();
2526
2573
  if (document.body && newElement instanceof HTMLBodyElement) {
2527
- document.body.replaceWith(newElement);
2574
+ const currentBody = PageRenderer.getBodyElement(currentElement);
2575
+ const newBody = PageRenderer.getBodyElement(newElement);
2576
+ currentBody.replaceWith(newBody);
2528
2577
  }
2529
2578
  else {
2530
2579
  document.documentElement.appendChild(newElement);
2531
2580
  }
2532
2581
  }
2533
2582
  get shouldRender() {
2534
- return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
2583
+ return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical && this.bodyElementMatches;
2535
2584
  }
2536
2585
  get reloadReason() {
2537
2586
  if (!this.newSnapshot.isVisitable) {
@@ -2544,6 +2593,11 @@ Copyright © 2022 Basecamp, LLC
2544
2593
  reason: "tracked_element_mismatch",
2545
2594
  };
2546
2595
  }
2596
+ if (!this.bodyElementMatches) {
2597
+ return {
2598
+ reason: "body_element_mismatch",
2599
+ };
2600
+ }
2547
2601
  }
2548
2602
  async prepareToRender() {
2549
2603
  await this.mergeHead();
@@ -2584,6 +2638,16 @@ Copyright © 2022 Basecamp, LLC
2584
2638
  get trackedElementsAreIdentical() {
2585
2639
  return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
2586
2640
  }
2641
+ get bodyElementMatches() {
2642
+ return PageRenderer.getBodyElement(this.newElement) !== null;
2643
+ }
2644
+ static get bodySelector() {
2645
+ const bodyId = getBodyElementId();
2646
+ return bodyId ? `#${bodyId}` : "body";
2647
+ }
2648
+ static getBodyElement(element) {
2649
+ return element.querySelector(this.bodySelector) || element;
2650
+ }
2587
2651
  async copyNewHeadStylesheetElements() {
2588
2652
  const loadingElements = [];
2589
2653
  for (const element of this.newHeadStylesheetElements) {
@@ -2782,6 +2846,7 @@ Copyright © 2022 Basecamp, LLC
2782
2846
  this.streamObserver = new StreamObserver(this);
2783
2847
  this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
2784
2848
  this.frameRedirector = new FrameRedirector(this, document.documentElement);
2849
+ this.streamMessageRenderer = new StreamMessageRenderer();
2785
2850
  this.drive = true;
2786
2851
  this.enabled = true;
2787
2852
  this.progressBarDelay = 500;
@@ -2825,7 +2890,7 @@ Copyright © 2022 Basecamp, LLC
2825
2890
  this.adapter = adapter;
2826
2891
  }
2827
2892
  visit(location, options = {}) {
2828
- const frameElement = document.getElementById(options.frame || "");
2893
+ const frameElement = options.frame ? document.getElementById(options.frame) : null;
2829
2894
  if (frameElement instanceof FrameElement) {
2830
2895
  frameElement.src = location.toString();
2831
2896
  return frameElement.loaded;
@@ -2841,7 +2906,7 @@ Copyright © 2022 Basecamp, LLC
2841
2906
  this.streamObserver.disconnectStreamSource(source);
2842
2907
  }
2843
2908
  renderStreamMessage(message) {
2844
- document.documentElement.appendChild(StreamMessage.wrap(message).fragment);
2909
+ this.streamMessageRenderer.render(StreamMessage.wrap(message));
2845
2910
  }
2846
2911
  clearCache() {
2847
2912
  this.view.clearSnapshotCache();
@@ -2886,22 +2951,27 @@ Copyright © 2022 Basecamp, LLC
2886
2951
  followedLinkToLocation(link, location) {
2887
2952
  const action = this.getActionForLink(link);
2888
2953
  const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
2889
- this.visit(location.href, { action, acceptsStreamResponse });
2954
+ this.visit(location.href, { action, acceptsStreamResponse, initiator: link });
2890
2955
  }
2891
- allowsVisitingLocationWithAction(location, action) {
2892
- return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
2956
+ allowsVisitingLocation(location, options = {}) {
2957
+ return (this.locationWithActionIsSamePage(location, options.action) ||
2958
+ this.applicationAllowsVisitingLocation(location, options));
2893
2959
  }
2894
2960
  visitProposedToLocation(location, options) {
2895
2961
  extendURLWithDeprecatedProperties(location);
2896
2962
  return this.adapter.visitProposedToLocation(location, options);
2897
2963
  }
2898
2964
  visitStarted(visit) {
2965
+ if (!visit.acceptsStreamResponse) {
2966
+ markAsBusy(document.documentElement);
2967
+ }
2899
2968
  extendURLWithDeprecatedProperties(visit.location);
2900
2969
  if (!visit.silent) {
2901
- this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
2970
+ this.notifyApplicationAfterVisitingLocation(visit.location, visit.action, visit.initiator);
2902
2971
  }
2903
2972
  }
2904
2973
  visitCompleted(visit) {
2974
+ clearBusyState(document.documentElement);
2905
2975
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2906
2976
  }
2907
2977
  locationWithActionIsSamePage(location, action) {
@@ -2961,16 +3031,12 @@ Copyright © 2022 Basecamp, LLC
2961
3031
  frameRendered(fetchResponse, frame) {
2962
3032
  this.notifyApplicationAfterFrameRender(fetchResponse, frame);
2963
3033
  }
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
3034
  applicationAllowsFollowingLinkToLocation(link, location, ev) {
2969
3035
  const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
2970
3036
  return !event.defaultPrevented;
2971
3037
  }
2972
- applicationAllowsVisitingLocation(location) {
2973
- const event = this.notifyApplicationBeforeVisitingLocation(location);
3038
+ applicationAllowsVisitingLocation(location, options = {}) {
3039
+ const event = this.notifyApplicationBeforeVisitingLocation(location, options.initiator);
2974
3040
  return !event.defaultPrevented;
2975
3041
  }
2976
3042
  notifyApplicationAfterClickingLinkToLocation(link, location, event) {
@@ -2980,15 +3046,18 @@ Copyright © 2022 Basecamp, LLC
2980
3046
  cancelable: true,
2981
3047
  });
2982
3048
  }
2983
- notifyApplicationBeforeVisitingLocation(location) {
3049
+ notifyApplicationBeforeVisitingLocation(location, element) {
2984
3050
  return dispatch("turbo:before-visit", {
3051
+ target: element,
2985
3052
  detail: { url: location.href },
2986
3053
  cancelable: true,
2987
3054
  });
2988
3055
  }
2989
- notifyApplicationAfterVisitingLocation(location, action) {
2990
- markAsBusy(document.documentElement);
2991
- return dispatch("turbo:visit", { detail: { url: location.href, action } });
3056
+ notifyApplicationAfterVisitingLocation(location, action, element) {
3057
+ return dispatch("turbo:visit", {
3058
+ target: element,
3059
+ detail: { url: location.href, action },
3060
+ });
2992
3061
  }
2993
3062
  notifyApplicationBeforeCachingSnapshot() {
2994
3063
  return dispatch("turbo:before-cache");
@@ -3003,7 +3072,6 @@ Copyright © 2022 Basecamp, LLC
3003
3072
  return dispatch("turbo:render");
3004
3073
  }
3005
3074
  notifyApplicationAfterPageLoad(timing = {}) {
3006
- clearBusyState(document.documentElement);
3007
3075
  return dispatch("turbo:load", {
3008
3076
  detail: { url: this.location.href, timing },
3009
3077
  });
@@ -3207,7 +3275,7 @@ Copyright © 2022 Basecamp, LLC
3207
3275
  connect() {
3208
3276
  if (!this.connected) {
3209
3277
  this.connected = true;
3210
- if (this.loadingStyle == FrameLoadingStyle.lazy) {
3278
+ if (this.loadingStyle == exports.FrameLoadingStyle.lazy) {
3211
3279
  this.appearanceObserver.start();
3212
3280
  }
3213
3281
  else {
@@ -3228,7 +3296,7 @@ Copyright © 2022 Basecamp, LLC
3228
3296
  }
3229
3297
  }
3230
3298
  disabledChanged() {
3231
- if (this.loadingStyle == FrameLoadingStyle.eager) {
3299
+ if (this.loadingStyle == exports.FrameLoadingStyle.eager) {
3232
3300
  this.loadSourceURL();
3233
3301
  }
3234
3302
  }
@@ -3238,7 +3306,7 @@ Copyright © 2022 Basecamp, LLC
3238
3306
  if (this.element.isConnected) {
3239
3307
  this.complete = false;
3240
3308
  }
3241
- if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
3309
+ if (this.loadingStyle == exports.FrameLoadingStyle.eager || this.hasBeenLoaded) {
3242
3310
  this.loadSourceURL();
3243
3311
  }
3244
3312
  }
@@ -3248,7 +3316,7 @@ Copyright © 2022 Basecamp, LLC
3248
3316
  this.loadSourceURL();
3249
3317
  }
3250
3318
  loadingStyleChanged() {
3251
- if (this.loadingStyle == FrameLoadingStyle.lazy) {
3319
+ if (this.loadingStyle == exports.FrameLoadingStyle.lazy) {
3252
3320
  this.appearanceObserver.start();
3253
3321
  }
3254
3322
  else {
@@ -3285,8 +3353,9 @@ Copyright © 2022 Basecamp, LLC
3285
3353
  session.frameLoaded(this.element);
3286
3354
  this.fetchResponseLoaded(fetchResponse);
3287
3355
  }
3288
- else if (this.sessionWillHandleMissingFrame(fetchResponse)) {
3289
- await session.frameMissing(this.element, fetchResponse);
3356
+ else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3357
+ console.warn(`A matching frame for #${this.element.id} was missing from the response, transforming into full-page Visit.`);
3358
+ this.visitResponse(fetchResponse.response);
3290
3359
  }
3291
3360
  }
3292
3361
  }
@@ -3344,8 +3413,9 @@ Copyright © 2022 Basecamp, LLC
3344
3413
  await this.loadResponse(response);
3345
3414
  this.resolveVisitPromise();
3346
3415
  }
3347
- requestFailedWithResponse(request, response) {
3416
+ async requestFailedWithResponse(request, response) {
3348
3417
  console.error(response);
3418
+ await this.loadResponse(response);
3349
3419
  this.resolveVisitPromise();
3350
3420
  }
3351
3421
  requestErrored(request, error) {
@@ -3447,15 +3517,30 @@ Copyright © 2022 Basecamp, LLC
3447
3517
  session.history.update(method, expandURL(this.frame.src || ""), this.restorationIdentifier);
3448
3518
  }
3449
3519
  }
3450
- sessionWillHandleMissingFrame(fetchResponse) {
3520
+ willHandleFrameMissingFromResponse(fetchResponse) {
3451
3521
  this.element.setAttribute("complete", "");
3522
+ const response = fetchResponse.response;
3523
+ const visit = async (url, options = {}) => {
3524
+ if (url instanceof Response) {
3525
+ this.visitResponse(url);
3526
+ }
3527
+ else {
3528
+ session.visit(url, options);
3529
+ }
3530
+ };
3452
3531
  const event = dispatch("turbo:frame-missing", {
3453
3532
  target: this.element,
3454
- detail: { fetchResponse },
3533
+ detail: { response, visit },
3455
3534
  cancelable: true,
3456
3535
  });
3457
3536
  return !event.defaultPrevented;
3458
3537
  }
3538
+ async visitResponse(response) {
3539
+ const wrapped = new FetchResponse(response);
3540
+ const responseHTML = await wrapped.responseHTML;
3541
+ const { location, redirected, statusCode } = wrapped;
3542
+ return session.visit(location, { response: { redirected, statusCode, responseHTML } });
3543
+ }
3459
3544
  findFrameElement(element, submitter) {
3460
3545
  var _a;
3461
3546
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
@@ -3591,6 +3676,9 @@ Copyright © 2022 Basecamp, LLC
3591
3676
  }
3592
3677
 
3593
3678
  class StreamElement extends HTMLElement {
3679
+ static async renderElement(newElement) {
3680
+ await newElement.performAction();
3681
+ }
3594
3682
  async connectedCallback() {
3595
3683
  try {
3596
3684
  await this.render();
@@ -3605,9 +3693,10 @@ Copyright © 2022 Basecamp, LLC
3605
3693
  async render() {
3606
3694
  var _a;
3607
3695
  return ((_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
3608
- if (this.dispatchEvent(this.beforeRenderEvent)) {
3696
+ const event = this.beforeRenderEvent;
3697
+ if (this.dispatchEvent(event)) {
3609
3698
  await nextAnimationFrame();
3610
- this.performAction();
3699
+ await event.detail.render(this);
3611
3700
  }
3612
3701
  })()));
3613
3702
  }
@@ -3651,7 +3740,12 @@ Copyright © 2022 Basecamp, LLC
3651
3740
  return this.templateElement.content.cloneNode(true);
3652
3741
  }
3653
3742
  get templateElement() {
3654
- if (this.firstElementChild instanceof HTMLTemplateElement) {
3743
+ if (this.firstElementChild === null) {
3744
+ const template = this.ownerDocument.createElement("template");
3745
+ this.appendChild(template);
3746
+ return template;
3747
+ }
3748
+ else if (this.firstElementChild instanceof HTMLTemplateElement) {
3655
3749
  return this.firstElementChild;
3656
3750
  }
3657
3751
  this.raise("first child element must be a <template> element");
@@ -3676,7 +3770,7 @@ Copyright © 2022 Basecamp, LLC
3676
3770
  return new CustomEvent("turbo:before-stream-render", {
3677
3771
  bubbles: true,
3678
3772
  cancelable: true,
3679
- detail: { newStream: this },
3773
+ detail: { newStream: this, render: StreamElement.renderElement },
3680
3774
  });
3681
3775
  }
3682
3776
  get targetElementsById() {
@@ -3758,10 +3852,13 @@ Copyright © 2022 Basecamp, LLC
3758
3852
  window.Turbo = Turbo;
3759
3853
  start();
3760
3854
 
3855
+ exports.FrameElement = FrameElement;
3761
3856
  exports.FrameRenderer = FrameRenderer;
3762
3857
  exports.PageRenderer = PageRenderer;
3763
3858
  exports.PageSnapshot = PageSnapshot;
3764
3859
  exports.StreamActions = StreamActions;
3860
+ exports.StreamElement = StreamElement;
3861
+ exports.StreamSourceElement = StreamSourceElement;
3765
3862
  exports.cache = cache;
3766
3863
  exports.clearCache = clearCache;
3767
3864
  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
  };
@@ -2,7 +2,7 @@ import { Renderer } from "../renderer";
2
2
  import { PageSnapshot } from "./page_snapshot";
3
3
  import { ReloadReason } from "../native/browser_adapter";
4
4
  export declare class PageRenderer extends Renderer<HTMLBodyElement, PageSnapshot> {
5
- static renderElement(currentElement: HTMLBodyElement, newElement: HTMLBodyElement): void;
5
+ static renderElement(currentElement: HTMLBodyElement, newElement: HTMLBodyElement): Promise<void>;
6
6
  get shouldRender(): boolean;
7
7
  get reloadReason(): ReloadReason;
8
8
  prepareToRender(): Promise<void>;
@@ -14,6 +14,9 @@ export declare class PageRenderer extends Renderer<HTMLBodyElement, PageSnapshot
14
14
  mergeHead(): Promise<void>;
15
15
  replaceBody(): void;
16
16
  get trackedElementsAreIdentical(): boolean;
17
+ get bodyElementMatches(): boolean;
18
+ static get bodySelector(): string;
19
+ static getBodyElement(element: HTMLElement): HTMLElement;
17
20
  copyNewHeadStylesheetElements(): Promise<void>;
18
21
  copyNewHeadScriptElements(): void;
19
22
  removeCurrentHeadProvisionalElements(): void;
@@ -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;