@hotwired/turbo-rails 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.
@@ -385,6 +385,10 @@ function getAttribute(attributeName, ...elements) {
385
385
  return null;
386
386
  }
387
387
 
388
+ function hasAttribute(attributeName, ...elements) {
389
+ return elements.some((element => element && element.hasAttribute(attributeName)));
390
+ }
391
+
388
392
  function markAsBusy(...elements) {
389
393
  for (const element of elements) {
390
394
  if (element.localName == "turbo-frame") {
@@ -519,7 +523,9 @@ class FetchRequest {
519
523
  return await this.receive(response);
520
524
  } catch (error) {
521
525
  if (error.name !== "AbortError") {
522
- this.delegate.requestErrored(this, error);
526
+ if (this.willDelegateErrorHandling(error)) {
527
+ this.delegate.requestErrored(this, error);
528
+ }
523
529
  throw error;
524
530
  }
525
531
  } finally {
@@ -583,6 +589,17 @@ class FetchRequest {
583
589
  });
584
590
  if (event.defaultPrevented) await requestInterception;
585
591
  }
592
+ willDelegateErrorHandling(error) {
593
+ const event = dispatch("turbo:fetch-request-error", {
594
+ target: this.target,
595
+ cancelable: true,
596
+ detail: {
597
+ request: this,
598
+ error: error
599
+ }
600
+ });
601
+ return !event.defaultPrevented;
602
+ }
586
603
  }
587
604
 
588
605
  class AppearanceObserver {
@@ -684,7 +701,7 @@ class FormSubmission {
684
701
  this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
685
702
  this.mustRedirect = mustRedirect;
686
703
  }
687
- static confirmMethod(message, _element) {
704
+ static confirmMethod(message, _element, _submitter) {
688
705
  return Promise.resolve(confirm(message));
689
706
  }
690
707
  get method() {
@@ -718,17 +735,11 @@ class FormSubmission {
718
735
  get stringFormData() {
719
736
  return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
720
737
  }
721
- get confirmationMessage() {
722
- var _a;
723
- return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("data-turbo-confirm")) || this.formElement.getAttribute("data-turbo-confirm");
724
- }
725
- get needsConfirmation() {
726
- return this.confirmationMessage !== null;
727
- }
728
738
  async start() {
729
739
  const {initialized: initialized, requesting: requesting} = FormSubmissionState;
730
- if (this.needsConfirmation) {
731
- const answer = await FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
740
+ const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
741
+ if (typeof confirmationMessage === "string") {
742
+ const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);
732
743
  if (!answer) {
733
744
  return;
734
745
  }
@@ -802,13 +813,6 @@ class FormSubmission {
802
813
  success: false,
803
814
  error: error
804
815
  };
805
- dispatch("turbo:fetch-request-error", {
806
- target: this.formElement,
807
- detail: {
808
- request: request,
809
- error: error
810
- }
811
- });
812
816
  this.delegate.formSubmissionErrored(this, error);
813
817
  }
814
818
  requestFinished(_request) {
@@ -827,7 +831,7 @@ class FormSubmission {
827
831
  return !request.isIdempotent && this.mustRedirect;
828
832
  }
829
833
  requestAcceptsTurboStreamResponse(request) {
830
- return !request.isIdempotent || this.formElement.hasAttribute("data-turbo-stream");
834
+ return !request.isIdempotent || hasAttribute("data-turbo-stream", this.submitter, this.formElement);
831
835
  }
832
836
  }
833
837
 
@@ -893,10 +897,10 @@ class Snapshot {
893
897
  return null;
894
898
  }
895
899
  get permanentElements() {
896
- return [ ...this.element.querySelectorAll("[id][data-turbo-permanent]") ];
900
+ return queryPermanentElementsAll(this.element);
897
901
  }
898
902
  getPermanentElementById(id) {
899
- return this.element.querySelector(`#${id}[data-turbo-permanent]`);
903
+ return getPermanentElementById(this.element, id);
900
904
  }
901
905
  getPermanentElementMapForSnapshot(snapshot) {
902
906
  const permanentElementMap = {};
@@ -911,6 +915,14 @@ class Snapshot {
911
915
  }
912
916
  }
913
917
 
918
+ function getPermanentElementById(node, id) {
919
+ return node.querySelector(`#${id}[data-turbo-permanent]`);
920
+ }
921
+
922
+ function queryPermanentElementsAll(node) {
923
+ return node.querySelectorAll("[id][data-turbo-permanent]");
924
+ }
925
+
914
926
  class FormSubmitObserver {
915
927
  constructor(delegate, eventTarget) {
916
928
  this.started = false;
@@ -1143,14 +1155,18 @@ class FormLinkClickObserver {
1143
1155
  if (method) form.setAttribute("method", method);
1144
1156
  const turboFrame = link.getAttribute("data-turbo-frame");
1145
1157
  if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame);
1158
+ const turboAction = link.getAttribute("data-turbo-action");
1159
+ if (turboAction) form.setAttribute("data-turbo-action", turboAction);
1146
1160
  const turboConfirm = link.getAttribute("data-turbo-confirm");
1147
1161
  if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm);
1148
1162
  const turboStream = link.hasAttribute("data-turbo-stream");
1149
1163
  if (turboStream) form.setAttribute("data-turbo-stream", "");
1150
1164
  this.delegate.submittedFormLinkToLocation(link, location, form);
1151
1165
  document.body.appendChild(form);
1152
- form.requestSubmit();
1153
- form.remove();
1166
+ form.addEventListener("turbo:submit-end", (() => form.remove()), {
1167
+ once: true
1168
+ });
1169
+ requestAnimationFrame((() => form.requestSubmit()));
1154
1170
  }
1155
1171
  }
1156
1172
 
@@ -1522,22 +1538,22 @@ function elementIsTracked(element) {
1522
1538
  }
1523
1539
 
1524
1540
  function elementIsScript(element) {
1525
- const tagName = element.tagName.toLowerCase();
1541
+ const tagName = element.localName;
1526
1542
  return tagName == "script";
1527
1543
  }
1528
1544
 
1529
1545
  function elementIsNoscript(element) {
1530
- const tagName = element.tagName.toLowerCase();
1546
+ const tagName = element.localName;
1531
1547
  return tagName == "noscript";
1532
1548
  }
1533
1549
 
1534
1550
  function elementIsStylesheet(element) {
1535
- const tagName = element.tagName.toLowerCase();
1551
+ const tagName = element.localName;
1536
1552
  return tagName == "style" || tagName == "link" && element.getAttribute("rel") == "stylesheet";
1537
1553
  }
1538
1554
 
1539
1555
  function elementIsMetaElementWithName(element, name) {
1540
- const tagName = element.tagName.toLowerCase();
1556
+ const tagName = element.localName;
1541
1557
  return tagName == "meta" && element.getAttribute("name") == name;
1542
1558
  }
1543
1559
 
@@ -1563,7 +1579,18 @@ class PageSnapshot extends Snapshot {
1563
1579
  return new this(body, new HeadSnapshot(head));
1564
1580
  }
1565
1581
  clone() {
1566
- return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
1582
+ const clonedElement = this.element.cloneNode(true);
1583
+ const selectElements = this.element.querySelectorAll("select");
1584
+ const clonedSelectElements = clonedElement.querySelectorAll("select");
1585
+ for (const [index, source] of selectElements.entries()) {
1586
+ const clone = clonedSelectElements[index];
1587
+ for (const option of clone.selectedOptions) option.selected = false;
1588
+ for (const option of source.selectedOptions) clone.options[option.index].selected = true;
1589
+ }
1590
+ for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
1591
+ clonedPasswordInput.value = "";
1592
+ }
1593
+ return new PageSnapshot(clonedElement, this.headSnapshot);
1567
1594
  }
1568
1595
  get headElement() {
1569
1596
  return this.headSnapshot.element;
@@ -1616,7 +1643,8 @@ const defaultOptions = {
1616
1643
  willRender: true,
1617
1644
  updateHistory: true,
1618
1645
  shouldCacheSnapshot: true,
1619
- acceptsStreamResponse: false
1646
+ acceptsStreamResponse: false,
1647
+ initiator: document.documentElement
1620
1648
  };
1621
1649
 
1622
1650
  var SystemStatusCode;
@@ -1629,7 +1657,6 @@ var SystemStatusCode;
1629
1657
 
1630
1658
  class Visit {
1631
1659
  constructor(delegate, location, restorationIdentifier, options = {}) {
1632
- this.identifier = uuid();
1633
1660
  this.timingMetrics = {};
1634
1661
  this.followedRedirect = false;
1635
1662
  this.historyChanged = false;
@@ -1645,7 +1672,7 @@ class Visit {
1645
1672
  resolve: resolve,
1646
1673
  reject: reject
1647
1674
  }));
1648
- const {action: action, historyChanged: historyChanged, referrer: referrer, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse} = Object.assign(Object.assign({}, defaultOptions), options);
1675
+ const {action: action, historyChanged: historyChanged, referrer: referrer, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse, initiator: initiator} = Object.assign(Object.assign({}, defaultOptions), options);
1649
1676
  this.action = action;
1650
1677
  this.historyChanged = historyChanged;
1651
1678
  this.referrer = referrer;
@@ -1658,6 +1685,7 @@ class Visit {
1658
1685
  this.scrolled = !willRender;
1659
1686
  this.shouldCacheSnapshot = shouldCacheSnapshot;
1660
1687
  this.acceptsStreamResponse = acceptsStreamResponse;
1688
+ this.initiator = initiator;
1661
1689
  }
1662
1690
  get adapter() {
1663
1691
  return this.delegate.adapter;
@@ -1724,7 +1752,7 @@ class Visit {
1724
1752
  if (this.hasPreloadedResponse()) {
1725
1753
  this.simulateRequest();
1726
1754
  } else if (this.shouldIssueRequest() && !this.request) {
1727
- this.request = new FetchRequest(this, FetchMethod.get, this.location);
1755
+ this.request = new FetchRequest(this, FetchMethod.get, this.location, undefined, this.initiator);
1728
1756
  this.request.perform();
1729
1757
  }
1730
1758
  }
@@ -1814,7 +1842,6 @@ class Visit {
1814
1842
  if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
1815
1843
  this.adapter.visitProposedToLocation(this.redirectedToLocation, {
1816
1844
  action: "replace",
1817
- willRender: false,
1818
1845
  response: this.response
1819
1846
  });
1820
1847
  this.followedRedirect = true;
@@ -2229,7 +2256,7 @@ class Navigator {
2229
2256
  this.delegate = delegate;
2230
2257
  }
2231
2258
  proposeVisit(location, options = {}) {
2232
- if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
2259
+ if (this.delegate.allowsVisitingLocation(location, options)) {
2233
2260
  if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
2234
2261
  return this.delegate.visitProposedToLocation(location, options);
2235
2262
  } else {
@@ -2442,6 +2469,31 @@ class ScrollObserver {
2442
2469
  }
2443
2470
  }
2444
2471
 
2472
+ class StreamMessageRenderer {
2473
+ render({fragment: fragment}) {
2474
+ Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => document.documentElement.appendChild(fragment)));
2475
+ }
2476
+ enteringBardo(currentPermanentElement, newPermanentElement) {
2477
+ newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
2478
+ }
2479
+ leavingBardo() {}
2480
+ }
2481
+
2482
+ function getPermanentElementMapForFragment(fragment) {
2483
+ const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
2484
+ const permanentElementMap = {};
2485
+ for (const permanentElementInDocument of permanentElementsInDocument) {
2486
+ const {id: id} = permanentElementInDocument;
2487
+ for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
2488
+ const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
2489
+ if (elementInStream) {
2490
+ permanentElementMap[id] = [ permanentElementInDocument, elementInStream ];
2491
+ }
2492
+ }
2493
+ }
2494
+ return permanentElementMap;
2495
+ }
2496
+
2445
2497
  class StreamObserver {
2446
2498
  constructor(delegate) {
2447
2499
  this.sources = new Set;
@@ -2803,6 +2855,7 @@ class Session {
2803
2855
  this.streamObserver = new StreamObserver(this);
2804
2856
  this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);
2805
2857
  this.frameRedirector = new FrameRedirector(this, document.documentElement);
2858
+ this.streamMessageRenderer = new StreamMessageRenderer;
2806
2859
  this.drive = true;
2807
2860
  this.enabled = true;
2808
2861
  this.progressBarDelay = 500;
@@ -2846,7 +2899,7 @@ class Session {
2846
2899
  this.adapter = adapter;
2847
2900
  }
2848
2901
  visit(location, options = {}) {
2849
- const frameElement = document.getElementById(options.frame || "");
2902
+ const frameElement = options.frame ? document.getElementById(options.frame) : null;
2850
2903
  if (frameElement instanceof FrameElement) {
2851
2904
  frameElement.src = location.toString();
2852
2905
  return frameElement.loaded;
@@ -2861,7 +2914,7 @@ class Session {
2861
2914
  this.streamObserver.disconnectStreamSource(source);
2862
2915
  }
2863
2916
  renderStreamMessage(message) {
2864
- document.documentElement.appendChild(StreamMessage.wrap(message).fragment);
2917
+ this.streamMessageRenderer.render(StreamMessage.wrap(message));
2865
2918
  }
2866
2919
  clearCache() {
2867
2920
  this.view.clearSnapshotCache();
@@ -2907,23 +2960,28 @@ class Session {
2907
2960
  const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
2908
2961
  this.visit(location.href, {
2909
2962
  action: action,
2910
- acceptsStreamResponse: acceptsStreamResponse
2963
+ acceptsStreamResponse: acceptsStreamResponse,
2964
+ initiator: link
2911
2965
  });
2912
2966
  }
2913
- allowsVisitingLocationWithAction(location, action) {
2914
- return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
2967
+ allowsVisitingLocation(location, options = {}) {
2968
+ return this.locationWithActionIsSamePage(location, options.action) || this.applicationAllowsVisitingLocation(location, options);
2915
2969
  }
2916
2970
  visitProposedToLocation(location, options) {
2917
2971
  extendURLWithDeprecatedProperties(location);
2918
2972
  return this.adapter.visitProposedToLocation(location, options);
2919
2973
  }
2920
2974
  visitStarted(visit) {
2975
+ if (!visit.acceptsStreamResponse) {
2976
+ markAsBusy(document.documentElement);
2977
+ }
2921
2978
  extendURLWithDeprecatedProperties(visit.location);
2922
2979
  if (!visit.silent) {
2923
- this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
2980
+ this.notifyApplicationAfterVisitingLocation(visit.location, visit.action, visit.initiator);
2924
2981
  }
2925
2982
  }
2926
2983
  visitCompleted(visit) {
2984
+ clearBusyState(document.documentElement);
2927
2985
  this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
2928
2986
  }
2929
2987
  locationWithActionIsSamePage(location, action) {
@@ -2982,16 +3040,12 @@ class Session {
2982
3040
  frameRendered(fetchResponse, frame) {
2983
3041
  this.notifyApplicationAfterFrameRender(fetchResponse, frame);
2984
3042
  }
2985
- frameMissing(frame, fetchResponse) {
2986
- console.warn(`Completing full-page visit as matching frame for #${frame.id} was missing from the response`);
2987
- return this.visit(fetchResponse.location);
2988
- }
2989
3043
  applicationAllowsFollowingLinkToLocation(link, location, ev) {
2990
3044
  const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
2991
3045
  return !event.defaultPrevented;
2992
3046
  }
2993
- applicationAllowsVisitingLocation(location) {
2994
- const event = this.notifyApplicationBeforeVisitingLocation(location);
3047
+ applicationAllowsVisitingLocation(location, options = {}) {
3048
+ const event = this.notifyApplicationBeforeVisitingLocation(location, options.initiator);
2995
3049
  return !event.defaultPrevented;
2996
3050
  }
2997
3051
  notifyApplicationAfterClickingLinkToLocation(link, location, event) {
@@ -3004,17 +3058,18 @@ class Session {
3004
3058
  cancelable: true
3005
3059
  });
3006
3060
  }
3007
- notifyApplicationBeforeVisitingLocation(location) {
3061
+ notifyApplicationBeforeVisitingLocation(location, element) {
3008
3062
  return dispatch("turbo:before-visit", {
3063
+ target: element,
3009
3064
  detail: {
3010
3065
  url: location.href
3011
3066
  },
3012
3067
  cancelable: true
3013
3068
  });
3014
3069
  }
3015
- notifyApplicationAfterVisitingLocation(location, action) {
3016
- markAsBusy(document.documentElement);
3070
+ notifyApplicationAfterVisitingLocation(location, action, element) {
3017
3071
  return dispatch("turbo:visit", {
3072
+ target: element,
3018
3073
  detail: {
3019
3074
  url: location.href,
3020
3075
  action: action
@@ -3036,7 +3091,6 @@ class Session {
3036
3091
  return dispatch("turbo:render");
3037
3092
  }
3038
3093
  notifyApplicationAfterPageLoad(timing = {}) {
3039
- clearBusyState(document.documentElement);
3040
3094
  return dispatch("turbo:load", {
3041
3095
  detail: {
3042
3096
  url: this.location.href,
@@ -3334,8 +3388,9 @@ class FrameController {
3334
3388
  session.frameRendered(fetchResponse, this.element);
3335
3389
  session.frameLoaded(this.element);
3336
3390
  this.fetchResponseLoaded(fetchResponse);
3337
- } else if (this.sessionWillHandleMissingFrame(fetchResponse)) {
3338
- await session.frameMissing(this.element, fetchResponse);
3391
+ } else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {
3392
+ console.warn(`A matching frame for #${this.element.id} was missing from the response, transforming into full-page Visit.`);
3393
+ this.visitResponse(fetchResponse.response);
3339
3394
  }
3340
3395
  }
3341
3396
  } catch (error) {
@@ -3390,19 +3445,13 @@ class FrameController {
3390
3445
  await this.loadResponse(response);
3391
3446
  this.resolveVisitPromise();
3392
3447
  }
3393
- requestFailedWithResponse(request, response) {
3448
+ async requestFailedWithResponse(request, response) {
3394
3449
  console.error(response);
3450
+ await this.loadResponse(response);
3395
3451
  this.resolveVisitPromise();
3396
3452
  }
3397
3453
  requestErrored(request, error) {
3398
3454
  console.error(error);
3399
- dispatch("turbo:fetch-request-error", {
3400
- target: this.element,
3401
- detail: {
3402
- request: request,
3403
- error: error
3404
- }
3405
- });
3406
3455
  this.resolveVisitPromise();
3407
3456
  }
3408
3457
  requestFinished(_request) {
@@ -3501,17 +3550,38 @@ class FrameController {
3501
3550
  session.history.update(method, expandURL(this.frame.src || ""), this.restorationIdentifier);
3502
3551
  }
3503
3552
  }
3504
- sessionWillHandleMissingFrame(fetchResponse) {
3553
+ willHandleFrameMissingFromResponse(fetchResponse) {
3505
3554
  this.element.setAttribute("complete", "");
3555
+ const response = fetchResponse.response;
3556
+ const visit = async (url, options = {}) => {
3557
+ if (url instanceof Response) {
3558
+ this.visitResponse(url);
3559
+ } else {
3560
+ session.visit(url, options);
3561
+ }
3562
+ };
3506
3563
  const event = dispatch("turbo:frame-missing", {
3507
3564
  target: this.element,
3508
3565
  detail: {
3509
- fetchResponse: fetchResponse
3566
+ response: response,
3567
+ visit: visit
3510
3568
  },
3511
3569
  cancelable: true
3512
3570
  });
3513
3571
  return !event.defaultPrevented;
3514
3572
  }
3573
+ async visitResponse(response) {
3574
+ const wrapped = new FetchResponse(response);
3575
+ const responseHTML = await wrapped.responseHTML;
3576
+ const {location: location, redirected: redirected, statusCode: statusCode} = wrapped;
3577
+ return session.visit(location, {
3578
+ response: {
3579
+ redirected: redirected,
3580
+ statusCode: statusCode,
3581
+ responseHTML: responseHTML
3582
+ }
3583
+ });
3584
+ }
3515
3585
  findFrameElement(element, submitter) {
3516
3586
  var _a;
3517
3587
  const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
@@ -3647,6 +3717,9 @@ function activateElement(element, currentURL) {
3647
3717
  }
3648
3718
 
3649
3719
  class StreamElement extends HTMLElement {
3720
+ static async renderElement(newElement) {
3721
+ await newElement.performAction();
3722
+ }
3650
3723
  async connectedCallback() {
3651
3724
  try {
3652
3725
  await this.render();
@@ -3659,9 +3732,10 @@ class StreamElement extends HTMLElement {
3659
3732
  async render() {
3660
3733
  var _a;
3661
3734
  return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
3662
- if (this.dispatchEvent(this.beforeRenderEvent)) {
3735
+ const event = this.beforeRenderEvent;
3736
+ if (this.dispatchEvent(event)) {
3663
3737
  await nextAnimationFrame();
3664
- this.performAction();
3738
+ await event.detail.render(this);
3665
3739
  }
3666
3740
  })();
3667
3741
  }
@@ -3702,7 +3776,11 @@ class StreamElement extends HTMLElement {
3702
3776
  return this.templateElement.content.cloneNode(true);
3703
3777
  }
3704
3778
  get templateElement() {
3705
- if (this.firstElementChild instanceof HTMLTemplateElement) {
3779
+ if (this.firstElementChild === null) {
3780
+ const template = this.ownerDocument.createElement("template");
3781
+ this.appendChild(template);
3782
+ return template;
3783
+ } else if (this.firstElementChild instanceof HTMLTemplateElement) {
3706
3784
  return this.firstElementChild;
3707
3785
  }
3708
3786
  this.raise("first child element must be a <template> element");
@@ -3728,7 +3806,8 @@ class StreamElement extends HTMLElement {
3728
3806
  bubbles: true,
3729
3807
  cancelable: true,
3730
3808
  detail: {
3731
- newStream: this
3809
+ newStream: this,
3810
+ render: StreamElement.renderElement
3732
3811
  }
3733
3812
  });
3734
3813
  }
@@ -3813,10 +3892,16 @@ start();
3813
3892
 
3814
3893
  var turbo_es2017Esm = Object.freeze({
3815
3894
  __proto__: null,
3895
+ FrameElement: FrameElement,
3896
+ get FrameLoadingStyle() {
3897
+ return FrameLoadingStyle;
3898
+ },
3816
3899
  FrameRenderer: FrameRenderer,
3817
3900
  PageRenderer: PageRenderer,
3818
3901
  PageSnapshot: PageSnapshot,
3819
3902
  StreamActions: StreamActions,
3903
+ StreamElement: StreamElement,
3904
+ StreamSourceElement: StreamSourceElement,
3820
3905
  cache: cache,
3821
3906
  clearCache: clearCache,
3822
3907
  connectStreamSource: connectStreamSource,
@@ -3907,13 +3992,26 @@ class TurboCableStreamSourceElement extends HTMLElement {
3907
3992
 
3908
3993
  customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement);
3909
3994
 
3910
- function overrideMethodWithFormmethod({detail: {formSubmission: {fetchRequest: fetchRequest, submitter: submitter}}}) {
3911
- if (submitter && submitter.formMethod && fetchRequest.body.has("_method")) {
3912
- fetchRequest.body.set("_method", submitter.formMethod);
3995
+ function encodeMethodIntoRequestBody(event) {
3996
+ if (event.target instanceof HTMLFormElement) {
3997
+ const {target: form, detail: {fetchOptions: fetchOptions}} = event;
3998
+ form.addEventListener("turbo:submit-start", (({detail: {formSubmission: {submitter: submitter}}}) => {
3999
+ const method = submitter && submitter.formMethod || fetchOptions.body.get("_method") || form.getAttribute("method");
4000
+ if (!/get/i.test(method)) {
4001
+ if (/post/i.test(method)) {
4002
+ fetchOptions.body.delete("_method");
4003
+ } else {
4004
+ fetchOptions.body.set("_method", method);
4005
+ }
4006
+ fetchOptions.method = "post";
4007
+ }
4008
+ }), {
4009
+ once: true
4010
+ });
3913
4011
  }
3914
4012
  }
3915
4013
 
3916
- addEventListener("turbo:submit-start", overrideMethodWithFormmethod);
4014
+ addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody);
3917
4015
 
3918
4016
  var adapters = {
3919
4017
  logger: self.console,
@@ -0,0 +1,19 @@
1
+ export function encodeMethodIntoRequestBody(event) {
2
+ if (event.target instanceof HTMLFormElement) {
3
+ const { target: form, detail: { fetchOptions } } = event
4
+
5
+ form.addEventListener("turbo:submit-start", ({ detail: { formSubmission: { submitter } } }) => {
6
+ const method = (submitter && submitter.formMethod) || fetchOptions.body.get("_method") || form.getAttribute("method")
7
+
8
+ if (!/get/i.test(method)) {
9
+ if (/post/i.test(method)) {
10
+ fetchOptions.body.delete("_method")
11
+ } else {
12
+ fetchOptions.body.set("_method", method)
13
+ }
14
+
15
+ fetchOptions.method = "post"
16
+ }
17
+ }, { once: true })
18
+ }
19
+ }
@@ -1,5 +1,4 @@
1
1
  import "./cable_stream_source_element"
2
- import { overrideMethodWithFormmethod } from "./form_submissions"
3
2
 
4
3
  import * as Turbo from "@hotwired/turbo"
5
4
  export { Turbo }
@@ -7,4 +6,6 @@ export { Turbo }
7
6
  import * as cable from "./cable"
8
7
  export { cable }
9
8
 
10
- addEventListener("turbo:submit-start", overrideMethodWithFormmethod)
9
+ import { encodeMethodIntoRequestBody } from "./fetch_requests"
10
+
11
+ addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotwired/turbo-rails",
3
- "version": "7.2.0-beta.2",
3
+ "version": "7.2.0-rc.2",
4
4
  "description": "The speed of a single-page web application without having to write any JavaScript",
5
5
  "module": "app/javascript/turbo/index.js",
6
6
  "main": "app/assets/javascripts/turbo.js",
@@ -13,7 +13,7 @@
13
13
  "release": "npm publish && git commit -am \"$npm_package_name v$npm_package_version\" && git push"
14
14
  },
15
15
  "dependencies": {
16
- "@hotwired/turbo": "^7.2.0-beta.2",
16
+ "@hotwired/turbo": "^7.2.0-rc.2",
17
17
  "@rails/actioncable": "^7.0"
18
18
  },
19
19
  "devDependencies": {
@@ -1,5 +0,0 @@
1
- export function overrideMethodWithFormmethod({ detail: { formSubmission: { fetchRequest, submitter } } }) {
2
- if (submitter && submitter.formMethod && fetchRequest.body.has("_method")) {
3
- fetchRequest.body.set("_method", submitter.formMethod)
4
- }
5
- }