@capgo/capacitor-transitions 8.0.1 → 8.0.3

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
@@ -1,5 +1,12 @@
1
1
  # @capgo/capacitor-transitions
2
2
 
3
+ <a href="https://capgo.app/">
4
+ <img
5
+ src="https://capgo.app/readme-banner.svg?repo=Cap-go/capacitor-transitions"
6
+ alt="Capgo - Instant updates for Capacitor"
7
+ />
8
+ </a>
9
+
3
10
  Framework-agnostic page transitions for Capacitor apps. iOS-style navigation without opinions.
4
11
 
5
12
  ## Features
@@ -887,12 +887,15 @@ var TransitionController = class {
887
887
  animation.pause();
888
888
  animation.currentTime = 0;
889
889
  }
890
+ const animationDurations = animations.map((animation) => this.getAnimationDuration(animation, duration));
890
891
  this.currentAnimations = animations;
891
892
  this.interactiveBackTransition = {
892
893
  enteringState,
893
894
  leavingState,
894
895
  animations,
895
- duration
896
+ animationDurations,
897
+ duration,
898
+ progress: 0
896
899
  };
897
900
  return true;
898
901
  }
@@ -905,9 +908,12 @@ var TransitionController = class {
905
908
  return;
906
909
  }
907
910
  const progress = Math.max(0, Math.min(step, 0.9999));
908
- for (const animation of transition.animations) {
909
- const duration = this.getAnimationDuration(animation, transition.duration);
910
- animation.pause();
911
+ if (Math.abs(progress - transition.progress) < 5e-4) {
912
+ return;
913
+ }
914
+ transition.progress = progress;
915
+ for (const [index, animation] of transition.animations.entries()) {
916
+ const duration = transition.animationDurations[index] ?? transition.duration;
911
917
  animation.currentTime = duration * progress;
912
918
  }
913
919
  }
@@ -1086,15 +1092,16 @@ var TransitionController = class {
1086
1092
  return;
1087
1093
  }
1088
1094
  if (releaseDuration <= 0) {
1089
- for (const animation of transition.animations) {
1090
- const duration = this.getAnimationDuration(animation, transition.duration);
1095
+ transition.progress = targetProgress;
1096
+ for (const [index, animation] of transition.animations.entries()) {
1097
+ const duration = transition.animationDurations[index] ?? transition.duration;
1091
1098
  animation.pause();
1092
1099
  animation.currentTime = duration * targetProgress;
1093
1100
  }
1094
1101
  return;
1095
1102
  }
1096
- const finished = transition.animations.map((animation) => {
1097
- const duration = this.getAnimationDuration(animation, transition.duration);
1103
+ const finished = transition.animations.map((animation, index) => {
1104
+ const duration = transition.animationDurations[index] ?? transition.duration;
1098
1105
  const currentTime = typeof animation.currentTime === "number" ? animation.currentTime : 0;
1099
1106
  const targetTime = duration * targetProgress;
1100
1107
  const distance = Math.abs(targetTime - currentTime);
@@ -1107,6 +1114,7 @@ var TransitionController = class {
1107
1114
  return animation.finished.catch(() => void 0);
1108
1115
  });
1109
1116
  await Promise.all(finished);
1117
+ transition.progress = targetProgress;
1110
1118
  }
1111
1119
  /**
1112
1120
  * Resolve configured easing presets after platform/direction are known.
@@ -1281,13 +1289,32 @@ var CapRouterOutlet = class extends HTMLElement {
1281
1289
  pendingPage = null;
1282
1290
  ignoredNodes = /* @__PURE__ */ new WeakSet();
1283
1291
  swipeGesturePointer = null;
1292
+ swipeGestureFrame = 0;
1293
+ swipeGesturePendingStep = null;
1284
1294
  swipeGestureListenersActive = false;
1285
1295
  skipNextHistoryBackTransition = false;
1286
1296
  swipeBackDepth = 0;
1287
1297
  lastNavigationHref = null;
1298
+ lastNavigationPosition = null;
1299
+ pendingHistoryDirection = null;
1300
+ navigationHrefs = [];
1288
1301
  swipeGestureEdgeWidth = 50;
1289
1302
  swipeGestureThreshold = 10;
1290
1303
  swipeGestureMinimumVelocity = 0.2;
1304
+ handleHistoryPopState = () => {
1305
+ const currentPosition = this.getCurrentNavigationPosition();
1306
+ if (currentPosition !== null && this.lastNavigationPosition !== null) {
1307
+ if (currentPosition < this.lastNavigationPosition) {
1308
+ this.pendingHistoryDirection = "back";
1309
+ return;
1310
+ }
1311
+ if (currentPosition > this.lastNavigationPosition) {
1312
+ this.pendingHistoryDirection = "forward";
1313
+ return;
1314
+ }
1315
+ }
1316
+ this.pendingHistoryDirection = "back";
1317
+ };
1291
1318
  static get observedAttributes() {
1292
1319
  return ["platform", "duration", "keep-in-dom", "max-cached", "swipe-gesture"];
1293
1320
  }
@@ -1306,7 +1333,10 @@ var CapRouterOutlet = class extends HTMLElement {
1306
1333
  this.style.width = "100%";
1307
1334
  this.style.height = "100%";
1308
1335
  this.style.overflow = "hidden";
1336
+ this.style.overscrollBehaviorX = "contain";
1309
1337
  this.lastNavigationHref = this.getCurrentNavigationHref();
1338
+ this.lastNavigationPosition = this.getCurrentNavigationPosition();
1339
+ this.ownerDocument.defaultView?.addEventListener("popstate", this.handleHistoryPopState);
1310
1340
  this.observer = new MutationObserver((mutations) => {
1311
1341
  this.handleMutations(mutations);
1312
1342
  });
@@ -1322,10 +1352,14 @@ var CapRouterOutlet = class extends HTMLElement {
1322
1352
  }
1323
1353
  disconnectedCallback() {
1324
1354
  this.observer?.disconnect();
1355
+ this.ownerDocument.defaultView?.removeEventListener("popstate", this.handleHistoryPopState);
1325
1356
  this.removeSwipeGestureListeners();
1326
1357
  this.controller.clear();
1327
1358
  this.swipeBackDepth = 0;
1328
1359
  this.lastNavigationHref = null;
1360
+ this.lastNavigationPosition = null;
1361
+ this.pendingHistoryDirection = null;
1362
+ this.navigationHrefs = [];
1329
1363
  }
1330
1364
  attributeChangedCallback(name, _oldValue, newValue) {
1331
1365
  switch (name) {
@@ -1406,13 +1440,16 @@ var CapRouterOutlet = class extends HTMLElement {
1406
1440
  this.controller.pageStack.push(state);
1407
1441
  this.swipeBackDepth = 0;
1408
1442
  this.lastNavigationHref = this.getCurrentNavigationHref();
1443
+ this.lastNavigationPosition = this.getCurrentNavigationPosition();
1444
+ this.navigationHrefs = [this.lastNavigationHref];
1409
1445
  }
1410
1446
  /**
1411
1447
  * Handle a new page being added
1412
1448
  */
1413
1449
  async handleNewPage(page) {
1414
1450
  const outletDirection = this.dataset.direction;
1415
- const direction = page.dataset.direction || outletDirection || "forward";
1451
+ const explicitDirection = page.dataset.direction || outletDirection;
1452
+ const direction = this.resolveNavigationDirection(explicitDirection);
1416
1453
  if (outletDirection) {
1417
1454
  delete this.dataset.direction;
1418
1455
  }
@@ -1527,7 +1564,7 @@ var CapRouterOutlet = class extends HTMLElement {
1527
1564
  * Check if we can go back
1528
1565
  */
1529
1566
  get canGoBack() {
1530
- return this.controller.stack.length > 1 && this.swipeBackDepth > 0;
1567
+ return this.getSwipeBackDestination() !== null;
1531
1568
  }
1532
1569
  /**
1533
1570
  * Get whether edge swipe-back gesture is enabled.
@@ -1604,6 +1641,10 @@ var CapRouterOutlet = class extends HTMLElement {
1604
1641
  this.removeEventListener("pointerup", this.handleSwipeGesturePointerEnd);
1605
1642
  this.removeEventListener("pointercancel", this.handleSwipeGesturePointerCancel);
1606
1643
  this.swipeGestureListenersActive = false;
1644
+ this.clearQueuedSwipeGestureStep();
1645
+ if (this.swipeGesturePointer?.transitionStarted) {
1646
+ this.controller.cancelInteractiveBack();
1647
+ }
1607
1648
  this.swipeGesturePointer = null;
1608
1649
  }
1609
1650
  isSwipeGestureEnabled() {
@@ -1619,30 +1660,132 @@ var CapRouterOutlet = class extends HTMLElement {
1619
1660
  getCurrentNavigationHref() {
1620
1661
  return this.ownerDocument.defaultView?.location.href ?? null;
1621
1662
  }
1663
+ getCurrentNavigationPosition() {
1664
+ const win = this.ownerDocument.defaultView;
1665
+ if (!win) {
1666
+ return null;
1667
+ }
1668
+ const navigationIndex = win.navigation?.currentEntry?.index;
1669
+ if (typeof navigationIndex === "number" && Number.isFinite(navigationIndex)) {
1670
+ return navigationIndex;
1671
+ }
1672
+ const state = win.history.state;
1673
+ for (const key of ["idx", "position", "index"]) {
1674
+ const value = state?.[key];
1675
+ if (typeof value === "number" && Number.isFinite(value)) {
1676
+ return value;
1677
+ }
1678
+ }
1679
+ return null;
1680
+ }
1681
+ resolveNavigationDirection(explicitDirection) {
1682
+ if (explicitDirection) {
1683
+ this.pendingHistoryDirection = null;
1684
+ return explicitDirection;
1685
+ }
1686
+ const currentHref = this.getCurrentNavigationHref();
1687
+ const existingHrefIndex = this.findNavigationHrefIndex(currentHref, this.navigationHrefs.length - 2);
1688
+ if (existingHrefIndex !== -1) {
1689
+ this.pendingHistoryDirection = null;
1690
+ return "back";
1691
+ }
1692
+ if (currentHref !== null && currentHref === this.lastNavigationHref) {
1693
+ this.pendingHistoryDirection = null;
1694
+ return "none";
1695
+ }
1696
+ if (this.pendingHistoryDirection) {
1697
+ const direction = this.pendingHistoryDirection;
1698
+ this.pendingHistoryDirection = null;
1699
+ return direction;
1700
+ }
1701
+ const currentPosition = this.getCurrentNavigationPosition();
1702
+ if (currentPosition !== null && this.lastNavigationPosition !== null) {
1703
+ if (currentPosition < this.lastNavigationPosition) {
1704
+ return "back";
1705
+ }
1706
+ if (currentPosition === this.lastNavigationPosition) {
1707
+ return "none";
1708
+ }
1709
+ }
1710
+ return "forward";
1711
+ }
1622
1712
  recordCompletedNavigation(direction, options) {
1623
1713
  const currentHref = this.getCurrentNavigationHref();
1714
+ const currentPosition = this.getCurrentNavigationPosition();
1624
1715
  if (!options.hadPageBefore || direction === "root") {
1625
- this.swipeBackDepth = 0;
1626
- this.lastNavigationHref = currentHref;
1716
+ this.resetNavigationDepth(currentHref, currentPosition);
1627
1717
  return;
1628
1718
  }
1629
1719
  if (direction === "back") {
1630
- this.swipeBackDepth = Math.max(0, this.swipeBackDepth - 1);
1720
+ this.recordBackNavigation(currentHref);
1721
+ this.lastNavigationPosition = currentPosition;
1722
+ return;
1723
+ }
1724
+ if (direction === "none") {
1725
+ if (this.navigationHrefs.length === 0) {
1726
+ this.navigationHrefs = [currentHref];
1727
+ } else {
1728
+ this.navigationHrefs[this.navigationHrefs.length - 1] = currentHref;
1729
+ }
1730
+ this.syncSwipeBackDepth();
1631
1731
  this.lastNavigationHref = currentHref;
1732
+ this.lastNavigationPosition = currentPosition;
1632
1733
  return;
1633
1734
  }
1634
- if (direction === "forward" || direction === "none") {
1735
+ if (direction === "forward") {
1635
1736
  const hrefChanged = currentHref === null || this.lastNavigationHref === null || currentHref !== this.lastNavigationHref;
1636
1737
  if (options.forceForward || hrefChanged) {
1637
- this.swipeBackDepth += 1;
1738
+ this.navigationHrefs.push(currentHref);
1739
+ } else if (this.navigationHrefs.length === 0) {
1740
+ this.navigationHrefs = [currentHref];
1638
1741
  }
1742
+ this.syncSwipeBackDepth();
1639
1743
  this.lastNavigationHref = currentHref;
1744
+ this.lastNavigationPosition = currentPosition;
1640
1745
  return;
1641
1746
  }
1642
1747
  this.lastNavigationHref = currentHref;
1748
+ this.lastNavigationPosition = currentPosition;
1749
+ }
1750
+ resetNavigationDepth(currentHref, currentPosition) {
1751
+ this.navigationHrefs = [currentHref];
1752
+ this.swipeBackDepth = 0;
1753
+ this.lastNavigationHref = currentHref;
1754
+ this.lastNavigationPosition = currentPosition;
1755
+ }
1756
+ recordBackNavigation(currentHref) {
1757
+ const existingHrefIndex = this.findNavigationHrefIndex(currentHref, this.navigationHrefs.length - 2);
1758
+ if (existingHrefIndex !== -1) {
1759
+ this.navigationHrefs = this.navigationHrefs.slice(0, existingHrefIndex + 1);
1760
+ } else if (this.navigationHrefs.length > 1) {
1761
+ this.navigationHrefs.pop();
1762
+ this.navigationHrefs[this.navigationHrefs.length - 1] = currentHref;
1763
+ } else {
1764
+ this.navigationHrefs = [currentHref];
1765
+ }
1766
+ this.syncSwipeBackDepth();
1767
+ this.lastNavigationHref = currentHref;
1768
+ }
1769
+ findNavigationHrefIndex(href, fromIndex) {
1770
+ for (let index = Math.min(fromIndex, this.navigationHrefs.length - 1); index >= 0; index -= 1) {
1771
+ if (this.navigationHrefs[index] === href) {
1772
+ return index;
1773
+ }
1774
+ }
1775
+ return -1;
1776
+ }
1777
+ syncSwipeBackDepth() {
1778
+ this.swipeBackDepth = Math.max(0, this.navigationHrefs.length - 1);
1779
+ }
1780
+ getSwipeBackDestination() {
1781
+ const stack = this.controller.stack;
1782
+ if (this.swipeBackDepth <= 0 || stack.length <= 1) {
1783
+ return null;
1784
+ }
1785
+ return stack[stack.length - 2] ?? null;
1643
1786
  }
1644
1787
  canStartSwipeGesture(event) {
1645
- if (!this.isSwipeGestureEnabled() || this.controller.animating || this.pendingPage || !this.canGoBack) {
1788
+ if (!this.isSwipeGestureEnabled() || this.controller.animating || this.pendingPage || !this.getSwipeBackDestination()) {
1646
1789
  return false;
1647
1790
  }
1648
1791
  if (!event.isPrimary || event.pointerType === "mouse" && event.button !== 0) {
@@ -1694,12 +1837,14 @@ var CapRouterOutlet = class extends HTMLElement {
1694
1837
  if (!this.canStartSwipeGesture(event)) {
1695
1838
  return;
1696
1839
  }
1840
+ const width = Math.max(this.getBoundingClientRect().width, 1);
1697
1841
  this.swipeGesturePointer = {
1698
1842
  pointerId: event.pointerId,
1699
1843
  startX: event.clientX,
1700
1844
  startY: event.clientY,
1701
1845
  currentX: event.clientX,
1702
1846
  currentY: event.clientY,
1847
+ width,
1703
1848
  startTime: performance.now(),
1704
1849
  dragging: false,
1705
1850
  transitionStarted: false
@@ -1729,6 +1874,10 @@ var CapRouterOutlet = class extends HTMLElement {
1729
1874
  return;
1730
1875
  }
1731
1876
  if (!pointer.dragging && deltaX > this.swipeGestureThreshold && absX > absY) {
1877
+ if (!this.getSwipeBackDestination()) {
1878
+ this.cancelSwipeGesturePointer(event.pointerId);
1879
+ return;
1880
+ }
1732
1881
  pointer.dragging = true;
1733
1882
  pointer.transitionStarted = this.controller.beginInteractiveBack({ direction: "back" });
1734
1883
  if (!pointer.transitionStarted) {
@@ -1737,9 +1886,12 @@ var CapRouterOutlet = class extends HTMLElement {
1737
1886
  }
1738
1887
  }
1739
1888
  if (pointer.dragging && pointer.transitionStarted) {
1889
+ if (!this.getSwipeBackDestination()) {
1890
+ this.cancelSwipeGesture(event.pointerId);
1891
+ return;
1892
+ }
1740
1893
  if (event.cancelable) event.preventDefault();
1741
- const width = Math.max(this.getBoundingClientRect().width, 1);
1742
- this.controller.stepInteractiveBack(deltaX / width);
1894
+ this.queueSwipeGestureStep(deltaX / pointer.width);
1743
1895
  }
1744
1896
  };
1745
1897
  handleSwipeGesturePointerEnd = (event) => {
@@ -1752,12 +1904,17 @@ var CapRouterOutlet = class extends HTMLElement {
1752
1904
  const deltaX = this.getSwipeGestureDeltaX(pointer);
1753
1905
  const elapsed = Math.max(performance.now() - pointer.startTime, 1);
1754
1906
  const velocityX = deltaX / elapsed;
1755
- const width = Math.max(this.getBoundingClientRect().width, 1);
1907
+ const width = pointer.width;
1756
1908
  const step = deltaX / width;
1757
1909
  const shouldCommit = pointer.dragging && pointer.transitionStarted && velocityX >= 0 && (velocityX > this.swipeGestureMinimumVelocity || deltaX > width / 2);
1758
1910
  const missing = shouldCommit ? 1 - step : step;
1759
1911
  const missingDistance = Math.max(missing, 0) * width;
1760
1912
  const releaseDuration = missingDistance > 5 && Math.abs(velocityX) > 0 ? Math.min(missingDistance / Math.abs(velocityX), 540) : 0;
1913
+ if (pointer.transitionStarted) {
1914
+ this.flushQueuedSwipeGestureStep();
1915
+ } else {
1916
+ this.clearQueuedSwipeGestureStep();
1917
+ }
1761
1918
  this.releaseSwipeGesturePointer(event.pointerId);
1762
1919
  void this.finishSwipeGestureBack(shouldCommit, releaseDuration);
1763
1920
  };
@@ -1765,6 +1922,7 @@ var CapRouterOutlet = class extends HTMLElement {
1765
1922
  this.cancelSwipeGesture(event.pointerId);
1766
1923
  };
1767
1924
  cancelSwipeGesturePointer(pointerId) {
1925
+ this.clearQueuedSwipeGestureStep();
1768
1926
  this.releaseSwipeGesturePointer(pointerId);
1769
1927
  }
1770
1928
  releaseSwipeGesturePointer(pointerId) {
@@ -1783,14 +1941,49 @@ var CapRouterOutlet = class extends HTMLElement {
1783
1941
  return;
1784
1942
  }
1785
1943
  this.releaseSwipeGesturePointer(pointerId);
1944
+ this.clearQueuedSwipeGestureStep();
1786
1945
  if (pointer.transitionStarted) {
1787
1946
  void this.finishSwipeGestureBack(false, 0);
1788
1947
  }
1789
1948
  }
1949
+ queueSwipeGestureStep(step) {
1950
+ this.swipeGesturePendingStep = step;
1951
+ if (this.swipeGestureFrame !== 0) {
1952
+ return;
1953
+ }
1954
+ const win = this.ownerDocument.defaultView;
1955
+ if (!win) {
1956
+ this.flushQueuedSwipeGestureStep();
1957
+ return;
1958
+ }
1959
+ this.swipeGestureFrame = win.requestAnimationFrame(() => {
1960
+ this.swipeGestureFrame = 0;
1961
+ this.flushQueuedSwipeGestureStep();
1962
+ });
1963
+ }
1964
+ flushQueuedSwipeGestureStep() {
1965
+ const step = this.swipeGesturePendingStep;
1966
+ this.swipeGesturePendingStep = null;
1967
+ if (this.swipeGestureFrame !== 0) {
1968
+ this.ownerDocument.defaultView?.cancelAnimationFrame(this.swipeGestureFrame);
1969
+ this.swipeGestureFrame = 0;
1970
+ }
1971
+ if (step !== null) {
1972
+ this.controller.stepInteractiveBack(step);
1973
+ }
1974
+ }
1975
+ clearQueuedSwipeGestureStep() {
1976
+ if (this.swipeGestureFrame !== 0) {
1977
+ this.ownerDocument.defaultView?.cancelAnimationFrame(this.swipeGestureFrame);
1978
+ this.swipeGestureFrame = 0;
1979
+ }
1980
+ this.swipeGesturePendingStep = null;
1981
+ }
1790
1982
  async finishSwipeGestureBack(shouldComplete, releaseDuration) {
1791
- const shouldUseHistory = shouldComplete && typeof window !== "undefined" && window.history.length > 1;
1792
- await this.controller.endInteractiveBack(shouldComplete, releaseDuration, !shouldUseHistory);
1793
- if (!shouldComplete) {
1983
+ const canComplete = shouldComplete && this.getSwipeBackDestination() !== null;
1984
+ const shouldUseHistory = canComplete && typeof window !== "undefined" && window.history.length > 1;
1985
+ await this.controller.endInteractiveBack(canComplete, canComplete ? releaseDuration : 0, !shouldUseHistory);
1986
+ if (!canComplete) {
1794
1987
  return;
1795
1988
  }
1796
1989
  if (shouldUseHistory) {
@@ -2245,4 +2438,4 @@ export {
2245
2438
  CapContent,
2246
2439
  CapFooter
2247
2440
  };
2248
- //# sourceMappingURL=chunk-RLOQDT3I.mjs.map
2441
+ //# sourceMappingURL=chunk-LABKGL2K.mjs.map