@hotwired/turbo 7.2.0-rc.2 → 7.2.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.
package/README.md CHANGED
@@ -15,4 +15,4 @@ Read more on [turbo.hotwired.dev](https://turbo.hotwired.dev).
15
15
 
16
16
  Please read [CONTRIBUTING.md](./CONTRIBUTING.md).
17
17
 
18
- © 2021 Basecamp, LLC.
18
+ © 2021 37signals LLC.
@@ -1,6 +1,6 @@
1
1
  /*
2
- Turbo 7.2.0-rc.1
3
- Copyright © 2022 Basecamp, LLC
2
+ Turbo 7.2.0
3
+ Copyright © 2022 37signals LLC
4
4
  */
5
5
  (function () {
6
6
  if (window.Reflect === undefined ||
@@ -1625,7 +1625,6 @@ const defaultOptions = {
1625
1625
  updateHistory: true,
1626
1626
  shouldCacheSnapshot: true,
1627
1627
  acceptsStreamResponse: false,
1628
- initiator: document.documentElement,
1629
1628
  };
1630
1629
  var SystemStatusCode;
1631
1630
  (function (SystemStatusCode) {
@@ -1635,6 +1634,7 @@ var SystemStatusCode;
1635
1634
  })(SystemStatusCode || (SystemStatusCode = {}));
1636
1635
  class Visit {
1637
1636
  constructor(delegate, location, restorationIdentifier, options = {}) {
1637
+ this.identifier = uuid();
1638
1638
  this.timingMetrics = {};
1639
1639
  this.followedRedirect = false;
1640
1640
  this.historyChanged = false;
@@ -1646,8 +1646,7 @@ class Visit {
1646
1646
  this.delegate = delegate;
1647
1647
  this.location = location;
1648
1648
  this.restorationIdentifier = restorationIdentifier || uuid();
1649
- this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));
1650
- const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, initiator, } = Object.assign(Object.assign({}, defaultOptions), options);
1649
+ const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, } = Object.assign(Object.assign({}, defaultOptions), options);
1651
1650
  this.action = action;
1652
1651
  this.historyChanged = historyChanged;
1653
1652
  this.referrer = referrer;
@@ -1660,7 +1659,6 @@ class Visit {
1660
1659
  this.scrolled = !willRender;
1661
1660
  this.shouldCacheSnapshot = shouldCacheSnapshot;
1662
1661
  this.acceptsStreamResponse = acceptsStreamResponse;
1663
- this.initiator = initiator;
1664
1662
  }
1665
1663
  get adapter() {
1666
1664
  return this.delegate.adapter;
@@ -1692,7 +1690,6 @@ class Visit {
1692
1690
  }
1693
1691
  this.cancelRender();
1694
1692
  this.state = VisitState.canceled;
1695
- this.resolvingFunctions.reject();
1696
1693
  }
1697
1694
  }
1698
1695
  complete() {
@@ -1704,14 +1701,12 @@ class Visit {
1704
1701
  this.adapter.visitCompleted(this);
1705
1702
  this.delegate.visitCompleted(this);
1706
1703
  }
1707
- this.resolvingFunctions.resolve();
1708
1704
  }
1709
1705
  }
1710
1706
  fail() {
1711
1707
  if (this.state == VisitState.started) {
1712
1708
  this.state = VisitState.failed;
1713
1709
  this.adapter.visitFailed(this);
1714
- this.resolvingFunctions.reject();
1715
1710
  }
1716
1711
  }
1717
1712
  changeHistory() {
@@ -1728,7 +1723,7 @@ class Visit {
1728
1723
  this.simulateRequest();
1729
1724
  }
1730
1725
  else if (this.shouldIssueRequest() && !this.request) {
1731
- this.request = new FetchRequest(this, FetchMethod.get, this.location, undefined, this.initiator);
1726
+ this.request = new FetchRequest(this, FetchMethod.get, this.location);
1732
1727
  this.request.perform();
1733
1728
  }
1734
1729
  }
@@ -1974,7 +1969,7 @@ class BrowserAdapter {
1974
1969
  this.session = session;
1975
1970
  }
1976
1971
  visitProposedToLocation(location, options) {
1977
- return this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
1972
+ this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
1978
1973
  }
1979
1974
  visitStarted(visit) {
1980
1975
  this.location = visit.location;
@@ -2099,8 +2094,8 @@ class FrameRedirector {
2099
2094
  this.linkClickObserver.stop();
2100
2095
  this.formSubmitObserver.stop();
2101
2096
  }
2102
- willFollowLinkToLocation(element) {
2103
- return this.shouldRedirect(element);
2097
+ willFollowLinkToLocation(element, location, event) {
2098
+ return this.shouldRedirect(element) && this.frameAllowsVisitingLocation(element, location, event);
2104
2099
  }
2105
2100
  followedLinkToLocation(element, url) {
2106
2101
  const frame = this.findFrameElement(element);
@@ -2119,6 +2114,14 @@ class FrameRedirector {
2119
2114
  frame.delegate.formSubmitted(element, submitter);
2120
2115
  }
2121
2116
  }
2117
+ frameAllowsVisitingLocation(target, { href: url }, originalEvent) {
2118
+ const event = dispatch("turbo:click", {
2119
+ target,
2120
+ detail: { url, originalEvent },
2121
+ cancelable: true,
2122
+ });
2123
+ return !event.defaultPrevented;
2124
+ }
2122
2125
  shouldSubmit(form, submitter) {
2123
2126
  var _a;
2124
2127
  const action = getAction(form, submitter);
@@ -2233,25 +2236,20 @@ class Navigator {
2233
2236
  this.delegate = delegate;
2234
2237
  }
2235
2238
  proposeVisit(location, options = {}) {
2236
- if (this.delegate.allowsVisitingLocation(location, options)) {
2239
+ if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
2237
2240
  if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
2238
- return this.delegate.visitProposedToLocation(location, options);
2241
+ this.delegate.visitProposedToLocation(location, options);
2239
2242
  }
2240
2243
  else {
2241
2244
  window.location.href = location.toString();
2242
- return Promise.resolve();
2243
2245
  }
2244
2246
  }
2245
- else {
2246
- return Promise.reject();
2247
- }
2248
2247
  }
2249
2248
  startVisit(locatable, restorationIdentifier, options = {}) {
2250
2249
  this.lastVisit = this.currentVisit;
2251
2250
  this.stop();
2252
2251
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));
2253
2252
  this.currentVisit.start();
2254
- return this.currentVisit.promise;
2255
2253
  }
2256
2254
  submitForm(form, submitter) {
2257
2255
  this.stop();
@@ -2872,10 +2870,10 @@ class Session {
2872
2870
  const frameElement = options.frame ? document.getElementById(options.frame) : null;
2873
2871
  if (frameElement instanceof FrameElement) {
2874
2872
  frameElement.src = location.toString();
2875
- return frameElement.loaded;
2873
+ frameElement.loaded;
2876
2874
  }
2877
2875
  else {
2878
- return this.navigator.proposeVisit(expandURL(location), options);
2876
+ this.navigator.proposeVisit(expandURL(location), options);
2879
2877
  }
2880
2878
  }
2881
2879
  connectStreamSource(source) {
@@ -2930,15 +2928,14 @@ class Session {
2930
2928
  followedLinkToLocation(link, location) {
2931
2929
  const action = this.getActionForLink(link);
2932
2930
  const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
2933
- this.visit(location.href, { action, acceptsStreamResponse, initiator: link });
2931
+ this.visit(location.href, { action, acceptsStreamResponse });
2934
2932
  }
2935
- allowsVisitingLocation(location, options = {}) {
2936
- return (this.locationWithActionIsSamePage(location, options.action) ||
2937
- this.applicationAllowsVisitingLocation(location, options));
2933
+ allowsVisitingLocationWithAction(location, action) {
2934
+ return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
2938
2935
  }
2939
2936
  visitProposedToLocation(location, options) {
2940
2937
  extendURLWithDeprecatedProperties(location);
2941
- return this.adapter.visitProposedToLocation(location, options);
2938
+ this.adapter.visitProposedToLocation(location, options);
2942
2939
  }
2943
2940
  visitStarted(visit) {
2944
2941
  if (!visit.acceptsStreamResponse) {
@@ -2946,7 +2943,7 @@ class Session {
2946
2943
  }
2947
2944
  extendURLWithDeprecatedProperties(visit.location);
2948
2945
  if (!visit.silent) {
2949
- this.notifyApplicationAfterVisitingLocation(visit.location, visit.action, visit.initiator);
2946
+ this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
2950
2947
  }
2951
2948
  }
2952
2949
  visitCompleted(visit) {
@@ -3014,8 +3011,8 @@ class Session {
3014
3011
  const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
3015
3012
  return !event.defaultPrevented;
3016
3013
  }
3017
- applicationAllowsVisitingLocation(location, options = {}) {
3018
- const event = this.notifyApplicationBeforeVisitingLocation(location, options.initiator);
3014
+ applicationAllowsVisitingLocation(location) {
3015
+ const event = this.notifyApplicationBeforeVisitingLocation(location);
3019
3016
  return !event.defaultPrevented;
3020
3017
  }
3021
3018
  notifyApplicationAfterClickingLinkToLocation(link, location, event) {
@@ -3025,18 +3022,14 @@ class Session {
3025
3022
  cancelable: true,
3026
3023
  });
3027
3024
  }
3028
- notifyApplicationBeforeVisitingLocation(location, element) {
3025
+ notifyApplicationBeforeVisitingLocation(location) {
3029
3026
  return dispatch("turbo:before-visit", {
3030
- target: element,
3031
3027
  detail: { url: location.href },
3032
3028
  cancelable: true,
3033
3029
  });
3034
3030
  }
3035
- notifyApplicationAfterVisitingLocation(location, action, element) {
3036
- return dispatch("turbo:visit", {
3037
- target: element,
3038
- detail: { url: location.href, action },
3039
- });
3031
+ notifyApplicationAfterVisitingLocation(location, action) {
3032
+ return dispatch("turbo:visit", { detail: { url: location.href, action } });
3040
3033
  }
3041
3034
  notifyApplicationBeforeCachingSnapshot() {
3042
3035
  return dispatch("turbo:before-cache");
@@ -3181,7 +3174,7 @@ function registerAdapter(adapter) {
3181
3174
  session.registerAdapter(adapter);
3182
3175
  }
3183
3176
  function visit(location, options) {
3184
- return session.visit(location, options);
3177
+ session.visit(location, options);
3185
3178
  }
3186
3179
  function connectStreamSource(source) {
3187
3180
  session.connectStreamSource(source);
@@ -3357,8 +3350,8 @@ class FrameController {
3357
3350
  if (frame)
3358
3351
  form.setAttribute("data-turbo-frame", frame.id);
3359
3352
  }
3360
- willFollowLinkToLocation(element) {
3361
- return this.shouldInterceptNavigation(element);
3353
+ willFollowLinkToLocation(element, location, event) {
3354
+ return this.shouldInterceptNavigation(element) && this.frameAllowsVisitingLocation(element, location, event);
3362
3355
  }
3363
3356
  followedLinkToLocation(element, location) {
3364
3357
  this.navigateFrame(element, location.href);
@@ -3611,6 +3604,14 @@ class FrameController {
3611
3604
  const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3612
3605
  return expandURL(root);
3613
3606
  }
3607
+ frameAllowsVisitingLocation(target, { href: url }, originalEvent) {
3608
+ const event = dispatch("turbo:click", {
3609
+ target,
3610
+ detail: { url, originalEvent },
3611
+ cancelable: true,
3612
+ });
3613
+ return !event.defaultPrevented;
3614
+ }
3614
3615
  isIgnoringChangesTo(attributeName) {
3615
3616
  return this.ignoredAttributes.has(attributeName);
3616
3617
  }
@@ -1,6 +1,6 @@
1
1
  /*
2
- Turbo 7.2.0-rc.1
3
- Copyright © 2022 Basecamp, LLC
2
+ Turbo 7.2.0
3
+ Copyright © 2022 37signals LLC
4
4
  */
5
5
  (function (global, factory) {
6
6
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
@@ -1631,7 +1631,6 @@ Copyright © 2022 Basecamp, LLC
1631
1631
  updateHistory: true,
1632
1632
  shouldCacheSnapshot: true,
1633
1633
  acceptsStreamResponse: false,
1634
- initiator: document.documentElement,
1635
1634
  };
1636
1635
  var SystemStatusCode;
1637
1636
  (function (SystemStatusCode) {
@@ -1641,6 +1640,7 @@ Copyright © 2022 Basecamp, LLC
1641
1640
  })(SystemStatusCode || (SystemStatusCode = {}));
1642
1641
  class Visit {
1643
1642
  constructor(delegate, location, restorationIdentifier, options = {}) {
1643
+ this.identifier = uuid();
1644
1644
  this.timingMetrics = {};
1645
1645
  this.followedRedirect = false;
1646
1646
  this.historyChanged = false;
@@ -1652,8 +1652,7 @@ Copyright © 2022 Basecamp, LLC
1652
1652
  this.delegate = delegate;
1653
1653
  this.location = location;
1654
1654
  this.restorationIdentifier = restorationIdentifier || uuid();
1655
- this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));
1656
- const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, initiator, } = Object.assign(Object.assign({}, defaultOptions), options);
1655
+ const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, } = Object.assign(Object.assign({}, defaultOptions), options);
1657
1656
  this.action = action;
1658
1657
  this.historyChanged = historyChanged;
1659
1658
  this.referrer = referrer;
@@ -1666,7 +1665,6 @@ Copyright © 2022 Basecamp, LLC
1666
1665
  this.scrolled = !willRender;
1667
1666
  this.shouldCacheSnapshot = shouldCacheSnapshot;
1668
1667
  this.acceptsStreamResponse = acceptsStreamResponse;
1669
- this.initiator = initiator;
1670
1668
  }
1671
1669
  get adapter() {
1672
1670
  return this.delegate.adapter;
@@ -1698,7 +1696,6 @@ Copyright © 2022 Basecamp, LLC
1698
1696
  }
1699
1697
  this.cancelRender();
1700
1698
  this.state = VisitState.canceled;
1701
- this.resolvingFunctions.reject();
1702
1699
  }
1703
1700
  }
1704
1701
  complete() {
@@ -1710,14 +1707,12 @@ Copyright © 2022 Basecamp, LLC
1710
1707
  this.adapter.visitCompleted(this);
1711
1708
  this.delegate.visitCompleted(this);
1712
1709
  }
1713
- this.resolvingFunctions.resolve();
1714
1710
  }
1715
1711
  }
1716
1712
  fail() {
1717
1713
  if (this.state == VisitState.started) {
1718
1714
  this.state = VisitState.failed;
1719
1715
  this.adapter.visitFailed(this);
1720
- this.resolvingFunctions.reject();
1721
1716
  }
1722
1717
  }
1723
1718
  changeHistory() {
@@ -1734,7 +1729,7 @@ Copyright © 2022 Basecamp, LLC
1734
1729
  this.simulateRequest();
1735
1730
  }
1736
1731
  else if (this.shouldIssueRequest() && !this.request) {
1737
- this.request = new FetchRequest(this, FetchMethod.get, this.location, undefined, this.initiator);
1732
+ this.request = new FetchRequest(this, FetchMethod.get, this.location);
1738
1733
  this.request.perform();
1739
1734
  }
1740
1735
  }
@@ -1980,7 +1975,7 @@ Copyright © 2022 Basecamp, LLC
1980
1975
  this.session = session;
1981
1976
  }
1982
1977
  visitProposedToLocation(location, options) {
1983
- return this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
1978
+ this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);
1984
1979
  }
1985
1980
  visitStarted(visit) {
1986
1981
  this.location = visit.location;
@@ -2105,8 +2100,8 @@ Copyright © 2022 Basecamp, LLC
2105
2100
  this.linkClickObserver.stop();
2106
2101
  this.formSubmitObserver.stop();
2107
2102
  }
2108
- willFollowLinkToLocation(element) {
2109
- return this.shouldRedirect(element);
2103
+ willFollowLinkToLocation(element, location, event) {
2104
+ return this.shouldRedirect(element) && this.frameAllowsVisitingLocation(element, location, event);
2110
2105
  }
2111
2106
  followedLinkToLocation(element, url) {
2112
2107
  const frame = this.findFrameElement(element);
@@ -2125,6 +2120,14 @@ Copyright © 2022 Basecamp, LLC
2125
2120
  frame.delegate.formSubmitted(element, submitter);
2126
2121
  }
2127
2122
  }
2123
+ frameAllowsVisitingLocation(target, { href: url }, originalEvent) {
2124
+ const event = dispatch("turbo:click", {
2125
+ target,
2126
+ detail: { url, originalEvent },
2127
+ cancelable: true,
2128
+ });
2129
+ return !event.defaultPrevented;
2130
+ }
2128
2131
  shouldSubmit(form, submitter) {
2129
2132
  var _a;
2130
2133
  const action = getAction(form, submitter);
@@ -2239,25 +2242,20 @@ Copyright © 2022 Basecamp, LLC
2239
2242
  this.delegate = delegate;
2240
2243
  }
2241
2244
  proposeVisit(location, options = {}) {
2242
- if (this.delegate.allowsVisitingLocation(location, options)) {
2245
+ if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
2243
2246
  if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
2244
- return this.delegate.visitProposedToLocation(location, options);
2247
+ this.delegate.visitProposedToLocation(location, options);
2245
2248
  }
2246
2249
  else {
2247
2250
  window.location.href = location.toString();
2248
- return Promise.resolve();
2249
2251
  }
2250
2252
  }
2251
- else {
2252
- return Promise.reject();
2253
- }
2254
2253
  }
2255
2254
  startVisit(locatable, restorationIdentifier, options = {}) {
2256
2255
  this.lastVisit = this.currentVisit;
2257
2256
  this.stop();
2258
2257
  this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));
2259
2258
  this.currentVisit.start();
2260
- return this.currentVisit.promise;
2261
2259
  }
2262
2260
  submitForm(form, submitter) {
2263
2261
  this.stop();
@@ -2878,10 +2876,10 @@ Copyright © 2022 Basecamp, LLC
2878
2876
  const frameElement = options.frame ? document.getElementById(options.frame) : null;
2879
2877
  if (frameElement instanceof FrameElement) {
2880
2878
  frameElement.src = location.toString();
2881
- return frameElement.loaded;
2879
+ frameElement.loaded;
2882
2880
  }
2883
2881
  else {
2884
- return this.navigator.proposeVisit(expandURL(location), options);
2882
+ this.navigator.proposeVisit(expandURL(location), options);
2885
2883
  }
2886
2884
  }
2887
2885
  connectStreamSource(source) {
@@ -2936,15 +2934,14 @@ Copyright © 2022 Basecamp, LLC
2936
2934
  followedLinkToLocation(link, location) {
2937
2935
  const action = this.getActionForLink(link);
2938
2936
  const acceptsStreamResponse = link.hasAttribute("data-turbo-stream");
2939
- this.visit(location.href, { action, acceptsStreamResponse, initiator: link });
2937
+ this.visit(location.href, { action, acceptsStreamResponse });
2940
2938
  }
2941
- allowsVisitingLocation(location, options = {}) {
2942
- return (this.locationWithActionIsSamePage(location, options.action) ||
2943
- this.applicationAllowsVisitingLocation(location, options));
2939
+ allowsVisitingLocationWithAction(location, action) {
2940
+ return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
2944
2941
  }
2945
2942
  visitProposedToLocation(location, options) {
2946
2943
  extendURLWithDeprecatedProperties(location);
2947
- return this.adapter.visitProposedToLocation(location, options);
2944
+ this.adapter.visitProposedToLocation(location, options);
2948
2945
  }
2949
2946
  visitStarted(visit) {
2950
2947
  if (!visit.acceptsStreamResponse) {
@@ -2952,7 +2949,7 @@ Copyright © 2022 Basecamp, LLC
2952
2949
  }
2953
2950
  extendURLWithDeprecatedProperties(visit.location);
2954
2951
  if (!visit.silent) {
2955
- this.notifyApplicationAfterVisitingLocation(visit.location, visit.action, visit.initiator);
2952
+ this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
2956
2953
  }
2957
2954
  }
2958
2955
  visitCompleted(visit) {
@@ -3020,8 +3017,8 @@ Copyright © 2022 Basecamp, LLC
3020
3017
  const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);
3021
3018
  return !event.defaultPrevented;
3022
3019
  }
3023
- applicationAllowsVisitingLocation(location, options = {}) {
3024
- const event = this.notifyApplicationBeforeVisitingLocation(location, options.initiator);
3020
+ applicationAllowsVisitingLocation(location) {
3021
+ const event = this.notifyApplicationBeforeVisitingLocation(location);
3025
3022
  return !event.defaultPrevented;
3026
3023
  }
3027
3024
  notifyApplicationAfterClickingLinkToLocation(link, location, event) {
@@ -3031,18 +3028,14 @@ Copyright © 2022 Basecamp, LLC
3031
3028
  cancelable: true,
3032
3029
  });
3033
3030
  }
3034
- notifyApplicationBeforeVisitingLocation(location, element) {
3031
+ notifyApplicationBeforeVisitingLocation(location) {
3035
3032
  return dispatch("turbo:before-visit", {
3036
- target: element,
3037
3033
  detail: { url: location.href },
3038
3034
  cancelable: true,
3039
3035
  });
3040
3036
  }
3041
- notifyApplicationAfterVisitingLocation(location, action, element) {
3042
- return dispatch("turbo:visit", {
3043
- target: element,
3044
- detail: { url: location.href, action },
3045
- });
3037
+ notifyApplicationAfterVisitingLocation(location, action) {
3038
+ return dispatch("turbo:visit", { detail: { url: location.href, action } });
3046
3039
  }
3047
3040
  notifyApplicationBeforeCachingSnapshot() {
3048
3041
  return dispatch("turbo:before-cache");
@@ -3187,7 +3180,7 @@ Copyright © 2022 Basecamp, LLC
3187
3180
  session.registerAdapter(adapter);
3188
3181
  }
3189
3182
  function visit(location, options) {
3190
- return session.visit(location, options);
3183
+ session.visit(location, options);
3191
3184
  }
3192
3185
  function connectStreamSource(source) {
3193
3186
  session.connectStreamSource(source);
@@ -3363,8 +3356,8 @@ Copyright © 2022 Basecamp, LLC
3363
3356
  if (frame)
3364
3357
  form.setAttribute("data-turbo-frame", frame.id);
3365
3358
  }
3366
- willFollowLinkToLocation(element) {
3367
- return this.shouldInterceptNavigation(element);
3359
+ willFollowLinkToLocation(element, location, event) {
3360
+ return this.shouldInterceptNavigation(element) && this.frameAllowsVisitingLocation(element, location, event);
3368
3361
  }
3369
3362
  followedLinkToLocation(element, location) {
3370
3363
  this.navigateFrame(element, location.href);
@@ -3617,6 +3610,14 @@ Copyright © 2022 Basecamp, LLC
3617
3610
  const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
3618
3611
  return expandURL(root);
3619
3612
  }
3613
+ frameAllowsVisitingLocation(target, { href: url }, originalEvent) {
3614
+ const event = dispatch("turbo:click", {
3615
+ target,
3616
+ detail: { url, originalEvent },
3617
+ cancelable: true,
3618
+ });
3619
+ return !event.defaultPrevented;
3620
+ }
3620
3621
  isIgnoringChangesTo(attributeName) {
3621
3622
  return this.ignoredAttributes.has(attributeName);
3622
3623
  }
@@ -4,8 +4,8 @@ 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
- allowsVisitingLocation(location: URL, options: Partial<VisitOptions>): boolean;
8
- visitProposedToLocation(location: URL, options: Partial<VisitOptions>): Promise<void>;
7
+ allowsVisitingLocationWithAction(location: URL, action?: Action): boolean;
8
+ visitProposedToLocation(location: URL, options: Partial<VisitOptions>): void;
9
9
  notifyApplicationAfterVisitingSamePageLocation(oldURL: URL, newURL: URL): void;
10
10
  };
11
11
  export declare class Navigator {
@@ -14,8 +14,8 @@ export declare class Navigator {
14
14
  currentVisit?: Visit;
15
15
  lastVisit?: Visit;
16
16
  constructor(delegate: NavigatorDelegate);
17
- proposeVisit(location: URL, options?: Partial<VisitOptions>): Promise<void>;
18
- startVisit(locatable: Locatable, restorationIdentifier: string, options?: Partial<VisitOptions>): Promise<void>;
17
+ proposeVisit(location: URL, options?: Partial<VisitOptions>): void;
18
+ startVisit(locatable: Locatable, restorationIdentifier: string, options?: Partial<VisitOptions>): void;
19
19
  submitForm(form: HTMLFormElement, submitter?: HTMLElement): void;
20
20
  stop(): void;
21
21
  get adapter(): import("../native/adapter").Adapter;
@@ -44,7 +44,6 @@ export declare type VisitOptions = {
44
44
  shouldCacheSnapshot: boolean;
45
45
  frame?: string;
46
46
  acceptsStreamResponse: boolean;
47
- initiator: Element;
48
47
  };
49
48
  export declare type VisitResponse = {
50
49
  statusCode: number;
@@ -58,6 +57,7 @@ export declare enum SystemStatusCode {
58
57
  }
59
58
  export declare class Visit implements FetchRequestDelegate {
60
59
  readonly delegate: VisitDelegate;
60
+ readonly identifier: string;
61
61
  readonly restorationIdentifier: string;
62
62
  readonly action: Action;
63
63
  readonly referrer?: URL;
@@ -65,9 +65,6 @@ export declare class Visit implements FetchRequestDelegate {
65
65
  readonly visitCachedSnapshot: (snapshot: Snapshot) => void;
66
66
  readonly willRender: boolean;
67
67
  readonly updateHistory: boolean;
68
- readonly promise: Promise<void>;
69
- readonly initiator: Element;
70
- private resolvingFunctions;
71
68
  followedRedirect: boolean;
72
69
  frame?: number;
73
70
  historyChanged: boolean;
@@ -47,7 +47,7 @@ export declare class FrameController implements AppearanceObserverDelegate, Fetc
47
47
  elementAppearedInViewport(_element: Element): void;
48
48
  willSubmitFormLinkToLocation(link: Element): boolean;
49
49
  submittedFormLinkToLocation(link: Element, _location: URL, form: HTMLFormElement): void;
50
- willFollowLinkToLocation(element: Element): boolean;
50
+ willFollowLinkToLocation(element: Element, location: URL, event: MouseEvent): boolean;
51
51
  followedLinkToLocation(element: Element, location: URL): void;
52
52
  willSubmitForm(element: HTMLFormElement, submitter?: HTMLElement): boolean;
53
53
  formSubmitted(element: HTMLFormElement, submitter?: HTMLElement): void;
@@ -89,6 +89,7 @@ export declare class FrameController implements AppearanceObserverDelegate, Fetc
89
89
  set complete(value: boolean);
90
90
  get isActive(): boolean;
91
91
  get rootLocation(): URL;
92
+ private frameAllowsVisitingLocation;
92
93
  private isIgnoringChangesTo;
93
94
  private ignoringChangesToAttribute;
94
95
  private withCurrentNavigationElement;
@@ -9,10 +9,11 @@ export declare class FrameRedirector implements LinkClickObserverDelegate, FormS
9
9
  constructor(session: Session, element: Element);
10
10
  start(): void;
11
11
  stop(): void;
12
- willFollowLinkToLocation(element: Element): boolean;
12
+ willFollowLinkToLocation(element: Element, location: URL, event: MouseEvent): boolean;
13
13
  followedLinkToLocation(element: Element, url: URL): void;
14
14
  willSubmitForm(element: HTMLFormElement, submitter?: HTMLElement): boolean;
15
15
  formSubmitted(element: HTMLFormElement, submitter?: HTMLElement): void;
16
+ private frameAllowsVisitingLocation;
16
17
  private shouldSubmit;
17
18
  private shouldRedirect;
18
19
  private findFrameElement;
@@ -12,13 +12,13 @@ declare const session: Session;
12
12
  declare const cache: Cache;
13
13
  declare const navigator: import("./drive/navigator").Navigator;
14
14
  export { navigator, session, cache, PageRenderer, PageSnapshot, FrameRenderer };
15
- export { TurboBeforeCacheEvent, TurboBeforeRenderEvent, TurboBeforeVisitEvent, TurboClickEvent, TurboFrameLoadEvent, TurboFrameRenderEvent, TurboLoadEvent, TurboRenderEvent, TurboVisitEvent, } from "./session";
15
+ export { TurboBeforeCacheEvent, TurboBeforeRenderEvent, TurboBeforeVisitEvent, TurboClickEvent, TurboBeforeFrameRenderEvent, TurboFrameLoadEvent, TurboFrameRenderEvent, TurboLoadEvent, TurboRenderEvent, TurboVisitEvent, } from "./session";
16
16
  export { TurboSubmitStartEvent, TurboSubmitEndEvent } from "./drive/form_submission";
17
17
  export { TurboFrameMissingEvent } from "./frames/frame_controller";
18
18
  export { StreamActions, TurboStreamAction, TurboStreamActions } from "./streams/stream_actions";
19
19
  export declare function start(): void;
20
20
  export declare function registerAdapter(adapter: Adapter): void;
21
- export declare function visit(location: Locatable, options?: Partial<VisitOptions>): Promise<void>;
21
+ export declare function visit(location: Locatable, options?: Partial<VisitOptions>): void;
22
22
  export declare function connectStreamSource(source: StreamSource): void;
23
23
  export declare function disconnectStreamSource(source: StreamSource): void;
24
24
  export declare function renderStreamMessage(message: StreamMessage | string): void;
@@ -2,7 +2,7 @@ import { Visit, VisitOptions } from "../drive/visit";
2
2
  import { FormSubmission } from "../drive/form_submission";
3
3
  import { ReloadReason } from "./browser_adapter";
4
4
  export interface Adapter {
5
- visitProposedToLocation(location: URL, options?: Partial<VisitOptions>): Promise<void>;
5
+ visitProposedToLocation(location: URL, options?: Partial<VisitOptions>): void;
6
6
  visitStarted(visit: Visit): void;
7
7
  visitCompleted(visit: Visit): void;
8
8
  visitFailed(visit: Visit): void;
@@ -17,7 +17,7 @@ export declare class BrowserAdapter implements Adapter {
17
17
  formProgressBarTimeout?: number;
18
18
  location?: URL;
19
19
  constructor(session: Session);
20
- visitProposedToLocation(location: URL, options?: Partial<VisitOptions>): Promise<void>;
20
+ visitProposedToLocation(location: URL, options?: Partial<VisitOptions>): void;
21
21
  visitStarted(visit: Visit): void;
22
22
  visitRequestStarted(visit: Visit): void;
23
23
  visitRequestCompleted(visit: Visit): void;
@@ -74,7 +74,7 @@ export declare class Session implements FormSubmitObserverDelegate, HistoryDeleg
74
74
  disable(): void;
75
75
  stop(): void;
76
76
  registerAdapter(adapter: Adapter): void;
77
- visit(location: Locatable, options?: Partial<VisitOptions>): Promise<void>;
77
+ visit(location: Locatable, options?: Partial<VisitOptions>): void;
78
78
  connectStreamSource(source: StreamSource): void;
79
79
  disconnectStreamSource(source: StreamSource): void;
80
80
  renderStreamMessage(message: StreamMessage | string): void;
@@ -89,8 +89,8 @@ export declare class Session implements FormSubmitObserverDelegate, HistoryDeleg
89
89
  submittedFormLinkToLocation(): void;
90
90
  willFollowLinkToLocation(link: Element, location: URL, event: MouseEvent): boolean;
91
91
  followedLinkToLocation(link: Element, location: URL): void;
92
- allowsVisitingLocation(location: URL, options?: Partial<VisitOptions>): boolean;
93
- visitProposedToLocation(location: URL, options: Partial<VisitOptions>): Promise<void>;
92
+ allowsVisitingLocationWithAction(location: URL, action?: Action): boolean;
93
+ visitProposedToLocation(location: URL, options: Partial<VisitOptions>): void;
94
94
  visitStarted(visit: Visit): void;
95
95
  visitCompleted(visit: Visit): void;
96
96
  locationWithActionIsSamePage(location: URL, action?: Action): boolean;
@@ -109,15 +109,15 @@ export declare class Session implements FormSubmitObserverDelegate, HistoryDeleg
109
109
  frameLoaded(frame: FrameElement): void;
110
110
  frameRendered(fetchResponse: FetchResponse, frame: FrameElement): void;
111
111
  applicationAllowsFollowingLinkToLocation(link: Element, location: URL, ev: MouseEvent): boolean;
112
- applicationAllowsVisitingLocation(location: URL, options?: Partial<VisitOptions>): boolean;
112
+ applicationAllowsVisitingLocation(location: URL): boolean;
113
113
  notifyApplicationAfterClickingLinkToLocation(link: Element, location: URL, event: MouseEvent): CustomEvent<{
114
114
  url: string;
115
115
  originalEvent: MouseEvent;
116
116
  }>;
117
- notifyApplicationBeforeVisitingLocation(location: URL, element?: Element): CustomEvent<{
117
+ notifyApplicationBeforeVisitingLocation(location: URL): CustomEvent<{
118
118
  url: string;
119
119
  }>;
120
- notifyApplicationAfterVisitingLocation(location: URL, action: Action, element?: Element): CustomEvent<{
120
+ notifyApplicationAfterVisitingLocation(location: URL, action: Action): CustomEvent<{
121
121
  url: string;
122
122
  action: Action;
123
123
  }>;
@@ -8,7 +8,3 @@ export declare type StreamSource = {
8
8
  addEventListener(type: "message", listener: (event: MessageEvent) => void, options?: boolean | AddEventListenerOptions): void;
9
9
  removeEventListener(type: "message", listener: (event: MessageEvent) => void, options?: boolean | EventListenerOptions): void;
10
10
  };
11
- export declare type ResolvingFunctions<T = unknown> = {
12
- resolve(value: T | PromiseLike<T>): void;
13
- reject(reason?: any): void;
14
- };
@@ -1,4 +1,5 @@
1
1
  import { FetchResponse } from "./fetch_response";
2
+ import { FrameElement } from "../elements/frame_element";
2
3
  export declare type TurboBeforeFetchRequestEvent = CustomEvent<{
3
4
  fetchOptions: RequestInit;
4
5
  url: URL;
@@ -44,10 +45,10 @@ export declare class FetchRequest {
44
45
  readonly headers: FetchRequestHeaders;
45
46
  readonly url: URL;
46
47
  readonly body?: FetchRequestBody;
47
- readonly target?: Element | null;
48
+ readonly target?: FrameElement | HTMLFormElement | null;
48
49
  readonly abortController: AbortController;
49
50
  private resolveRequestPromise;
50
- constructor(delegate: FetchRequestDelegate, method: FetchMethod, location: URL, body?: FetchRequestBody, target?: Element | null);
51
+ constructor(delegate: FetchRequestDelegate, method: FetchMethod, location: URL, body?: FetchRequestBody, target?: FrameElement | HTMLFormElement | null);
51
52
  get location(): URL;
52
53
  get params(): URLSearchParams;
53
54
  get entries(): [string, FormDataEntryValue][];
@@ -7,6 +7,8 @@ declare type MutationAttributeName = string;
7
7
  declare type MutationAttributeValue = string | null;
8
8
  declare type MutationLog = [MutationAttributeName, Target, MutationAttributeValue];
9
9
  export declare function attributeForSelector(page: Page, selector: string, attributeName: string): Promise<string | null>;
10
+ declare type CancellableEvent = "turbo:click" | "turbo:before-visit";
11
+ export declare function cancelNextEvent(page: Page, eventName: CancellableEvent): Promise<void>;
10
12
  export declare function clickWithoutScrolling(page: Page, selector: string, options?: {}): Promise<false | void>;
11
13
  export declare function clearLocalStorage(page: Page): Promise<void>;
12
14
  export declare function disposeAll(...handles: JSHandle[]): Promise<void[]>;
@@ -9,7 +9,7 @@ export declare class DeprecatedAdapterSupportTest extends DOMTestCase implements
9
9
  teardown(): Promise<void>;
10
10
  "test visit proposal location includes deprecated absoluteURL property"(): Promise<void>;
11
11
  "test visit start location includes deprecated absoluteURL property"(): Promise<void>;
12
- visitProposedToLocation(location: URL, _options?: Partial<VisitOptions>): Promise<void>;
12
+ visitProposedToLocation(location: URL, _options?: Partial<VisitOptions>): void;
13
13
  visitStarted(visit: Visit): void;
14
14
  visitCompleted(_visit: Visit): void;
15
15
  visitFailed(_visit: Visit): void;
@@ -0,0 +1,5 @@
1
+ import { DOMTestCase } from "../helpers/dom_test_case";
2
+ export { PageRenderer, PageSnapshot, FrameRenderer, FrameElement, StreamActions, StreamElement, StreamSourceElement, TurboBeforeCacheEvent, TurboBeforeFetchRequestEvent, TurboBeforeFetchResponseEvent, TurboBeforeFrameRenderEvent, TurboBeforeRenderEvent, TurboBeforeStreamRenderEvent, TurboBeforeVisitEvent, TurboClickEvent, TurboFetchRequestErrorEvent, TurboFrameLoadEvent, TurboFrameMissingEvent, TurboFrameRenderEvent, TurboLoadEvent, TurboRenderEvent, TurboStreamAction, TurboStreamActions, TurboSubmitEndEvent, TurboSubmitStartEvent, TurboVisitEvent, } from "../../index";
3
+ export declare class ExportTests extends DOMTestCase {
4
+ "test Turbo interface"(): Promise<void>;
5
+ }
@@ -1,2 +1,3 @@
1
+ export * from "./export_tests";
1
2
  export * from "./deprecated_adapter_support_test";
2
3
  export * from "./stream_element_tests";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotwired/turbo",
3
- "version": "7.2.0-rc.2",
3
+ "version": "7.2.0",
4
4
  "description": "The speed of a single-page web application without having to write any JavaScript",
5
5
  "module": "dist/turbo.es2017-esm.js",
6
6
  "main": "dist/turbo.es2017-umd.js",
@@ -20,7 +20,7 @@
20
20
  "browser",
21
21
  "pushstate"
22
22
  ],
23
- "author": "Basecamp, LLC",
23
+ "author": "37signals LLC",
24
24
  "contributors": [
25
25
  "Jeffrey Hardy <jeff@basecamp.com>",
26
26
  "Javan Makhmali <javan@javan.us>",