@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.
@@ -830,12 +830,15 @@ var TransitionController = class {
830
830
  animation.pause();
831
831
  animation.currentTime = 0;
832
832
  }
833
+ const animationDurations = animations.map((animation) => this.getAnimationDuration(animation, duration));
833
834
  this.currentAnimations = animations;
834
835
  this.interactiveBackTransition = {
835
836
  enteringState,
836
837
  leavingState,
837
838
  animations,
838
- duration
839
+ animationDurations,
840
+ duration,
841
+ progress: 0
839
842
  };
840
843
  return true;
841
844
  }
@@ -848,9 +851,12 @@ var TransitionController = class {
848
851
  return;
849
852
  }
850
853
  const progress = Math.max(0, Math.min(step, 0.9999));
851
- for (const animation of transition.animations) {
852
- const duration = this.getAnimationDuration(animation, transition.duration);
853
- animation.pause();
854
+ if (Math.abs(progress - transition.progress) < 5e-4) {
855
+ return;
856
+ }
857
+ transition.progress = progress;
858
+ for (const [index, animation] of transition.animations.entries()) {
859
+ const duration = transition.animationDurations[index] ?? transition.duration;
854
860
  animation.currentTime = duration * progress;
855
861
  }
856
862
  }
@@ -1029,15 +1035,16 @@ var TransitionController = class {
1029
1035
  return;
1030
1036
  }
1031
1037
  if (releaseDuration <= 0) {
1032
- for (const animation of transition.animations) {
1033
- const duration = this.getAnimationDuration(animation, transition.duration);
1038
+ transition.progress = targetProgress;
1039
+ for (const [index, animation] of transition.animations.entries()) {
1040
+ const duration = transition.animationDurations[index] ?? transition.duration;
1034
1041
  animation.pause();
1035
1042
  animation.currentTime = duration * targetProgress;
1036
1043
  }
1037
1044
  return;
1038
1045
  }
1039
- const finished = transition.animations.map((animation) => {
1040
- const duration = this.getAnimationDuration(animation, transition.duration);
1046
+ const finished = transition.animations.map((animation, index) => {
1047
+ const duration = transition.animationDurations[index] ?? transition.duration;
1041
1048
  const currentTime = typeof animation.currentTime === "number" ? animation.currentTime : 0;
1042
1049
  const targetTime = duration * targetProgress;
1043
1050
  const distance = Math.abs(targetTime - currentTime);
@@ -1050,6 +1057,7 @@ var TransitionController = class {
1050
1057
  return animation.finished.catch(() => void 0);
1051
1058
  });
1052
1059
  await Promise.all(finished);
1060
+ transition.progress = targetProgress;
1053
1061
  }
1054
1062
  /**
1055
1063
  * Resolve configured easing presets after platform/direction are known.
@@ -1239,13 +1247,32 @@ var CapRouterOutlet = class extends HTMLElement {
1239
1247
  pendingPage = null;
1240
1248
  ignoredNodes = /* @__PURE__ */ new WeakSet();
1241
1249
  swipeGesturePointer = null;
1250
+ swipeGestureFrame = 0;
1251
+ swipeGesturePendingStep = null;
1242
1252
  swipeGestureListenersActive = false;
1243
1253
  skipNextHistoryBackTransition = false;
1244
1254
  swipeBackDepth = 0;
1245
1255
  lastNavigationHref = null;
1256
+ lastNavigationPosition = null;
1257
+ pendingHistoryDirection = null;
1258
+ navigationHrefs = [];
1246
1259
  swipeGestureEdgeWidth = 50;
1247
1260
  swipeGestureThreshold = 10;
1248
1261
  swipeGestureMinimumVelocity = 0.2;
1262
+ handleHistoryPopState = () => {
1263
+ const currentPosition = this.getCurrentNavigationPosition();
1264
+ if (currentPosition !== null && this.lastNavigationPosition !== null) {
1265
+ if (currentPosition < this.lastNavigationPosition) {
1266
+ this.pendingHistoryDirection = "back";
1267
+ return;
1268
+ }
1269
+ if (currentPosition > this.lastNavigationPosition) {
1270
+ this.pendingHistoryDirection = "forward";
1271
+ return;
1272
+ }
1273
+ }
1274
+ this.pendingHistoryDirection = "back";
1275
+ };
1249
1276
  static get observedAttributes() {
1250
1277
  return ["platform", "duration", "keep-in-dom", "max-cached", "swipe-gesture"];
1251
1278
  }
@@ -1264,7 +1291,10 @@ var CapRouterOutlet = class extends HTMLElement {
1264
1291
  this.style.width = "100%";
1265
1292
  this.style.height = "100%";
1266
1293
  this.style.overflow = "hidden";
1294
+ this.style.overscrollBehaviorX = "contain";
1267
1295
  this.lastNavigationHref = this.getCurrentNavigationHref();
1296
+ this.lastNavigationPosition = this.getCurrentNavigationPosition();
1297
+ this.ownerDocument.defaultView?.addEventListener("popstate", this.handleHistoryPopState);
1268
1298
  this.observer = new MutationObserver((mutations) => {
1269
1299
  this.handleMutations(mutations);
1270
1300
  });
@@ -1280,10 +1310,14 @@ var CapRouterOutlet = class extends HTMLElement {
1280
1310
  }
1281
1311
  disconnectedCallback() {
1282
1312
  this.observer?.disconnect();
1313
+ this.ownerDocument.defaultView?.removeEventListener("popstate", this.handleHistoryPopState);
1283
1314
  this.removeSwipeGestureListeners();
1284
1315
  this.controller.clear();
1285
1316
  this.swipeBackDepth = 0;
1286
1317
  this.lastNavigationHref = null;
1318
+ this.lastNavigationPosition = null;
1319
+ this.pendingHistoryDirection = null;
1320
+ this.navigationHrefs = [];
1287
1321
  }
1288
1322
  attributeChangedCallback(name, _oldValue, newValue) {
1289
1323
  switch (name) {
@@ -1364,13 +1398,16 @@ var CapRouterOutlet = class extends HTMLElement {
1364
1398
  this.controller.pageStack.push(state);
1365
1399
  this.swipeBackDepth = 0;
1366
1400
  this.lastNavigationHref = this.getCurrentNavigationHref();
1401
+ this.lastNavigationPosition = this.getCurrentNavigationPosition();
1402
+ this.navigationHrefs = [this.lastNavigationHref];
1367
1403
  }
1368
1404
  /**
1369
1405
  * Handle a new page being added
1370
1406
  */
1371
1407
  async handleNewPage(page2) {
1372
1408
  const outletDirection = this.dataset.direction;
1373
- const direction = page2.dataset.direction || outletDirection || "forward";
1409
+ const explicitDirection = page2.dataset.direction || outletDirection;
1410
+ const direction = this.resolveNavigationDirection(explicitDirection);
1374
1411
  if (outletDirection) {
1375
1412
  delete this.dataset.direction;
1376
1413
  }
@@ -1485,7 +1522,7 @@ var CapRouterOutlet = class extends HTMLElement {
1485
1522
  * Check if we can go back
1486
1523
  */
1487
1524
  get canGoBack() {
1488
- return this.controller.stack.length > 1 && this.swipeBackDepth > 0;
1525
+ return this.getSwipeBackDestination() !== null;
1489
1526
  }
1490
1527
  /**
1491
1528
  * Get whether edge swipe-back gesture is enabled.
@@ -1562,6 +1599,10 @@ var CapRouterOutlet = class extends HTMLElement {
1562
1599
  this.removeEventListener("pointerup", this.handleSwipeGesturePointerEnd);
1563
1600
  this.removeEventListener("pointercancel", this.handleSwipeGesturePointerCancel);
1564
1601
  this.swipeGestureListenersActive = false;
1602
+ this.clearQueuedSwipeGestureStep();
1603
+ if (this.swipeGesturePointer?.transitionStarted) {
1604
+ this.controller.cancelInteractiveBack();
1605
+ }
1565
1606
  this.swipeGesturePointer = null;
1566
1607
  }
1567
1608
  isSwipeGestureEnabled() {
@@ -1577,30 +1618,132 @@ var CapRouterOutlet = class extends HTMLElement {
1577
1618
  getCurrentNavigationHref() {
1578
1619
  return this.ownerDocument.defaultView?.location.href ?? null;
1579
1620
  }
1621
+ getCurrentNavigationPosition() {
1622
+ const win = this.ownerDocument.defaultView;
1623
+ if (!win) {
1624
+ return null;
1625
+ }
1626
+ const navigationIndex = win.navigation?.currentEntry?.index;
1627
+ if (typeof navigationIndex === "number" && Number.isFinite(navigationIndex)) {
1628
+ return navigationIndex;
1629
+ }
1630
+ const state = win.history.state;
1631
+ for (const key of ["idx", "position", "index"]) {
1632
+ const value = state?.[key];
1633
+ if (typeof value === "number" && Number.isFinite(value)) {
1634
+ return value;
1635
+ }
1636
+ }
1637
+ return null;
1638
+ }
1639
+ resolveNavigationDirection(explicitDirection) {
1640
+ if (explicitDirection) {
1641
+ this.pendingHistoryDirection = null;
1642
+ return explicitDirection;
1643
+ }
1644
+ const currentHref = this.getCurrentNavigationHref();
1645
+ const existingHrefIndex = this.findNavigationHrefIndex(currentHref, this.navigationHrefs.length - 2);
1646
+ if (existingHrefIndex !== -1) {
1647
+ this.pendingHistoryDirection = null;
1648
+ return "back";
1649
+ }
1650
+ if (currentHref !== null && currentHref === this.lastNavigationHref) {
1651
+ this.pendingHistoryDirection = null;
1652
+ return "none";
1653
+ }
1654
+ if (this.pendingHistoryDirection) {
1655
+ const direction = this.pendingHistoryDirection;
1656
+ this.pendingHistoryDirection = null;
1657
+ return direction;
1658
+ }
1659
+ const currentPosition = this.getCurrentNavigationPosition();
1660
+ if (currentPosition !== null && this.lastNavigationPosition !== null) {
1661
+ if (currentPosition < this.lastNavigationPosition) {
1662
+ return "back";
1663
+ }
1664
+ if (currentPosition === this.lastNavigationPosition) {
1665
+ return "none";
1666
+ }
1667
+ }
1668
+ return "forward";
1669
+ }
1580
1670
  recordCompletedNavigation(direction, options) {
1581
1671
  const currentHref = this.getCurrentNavigationHref();
1672
+ const currentPosition = this.getCurrentNavigationPosition();
1582
1673
  if (!options.hadPageBefore || direction === "root") {
1583
- this.swipeBackDepth = 0;
1584
- this.lastNavigationHref = currentHref;
1674
+ this.resetNavigationDepth(currentHref, currentPosition);
1585
1675
  return;
1586
1676
  }
1587
1677
  if (direction === "back") {
1588
- this.swipeBackDepth = Math.max(0, this.swipeBackDepth - 1);
1678
+ this.recordBackNavigation(currentHref);
1679
+ this.lastNavigationPosition = currentPosition;
1680
+ return;
1681
+ }
1682
+ if (direction === "none") {
1683
+ if (this.navigationHrefs.length === 0) {
1684
+ this.navigationHrefs = [currentHref];
1685
+ } else {
1686
+ this.navigationHrefs[this.navigationHrefs.length - 1] = currentHref;
1687
+ }
1688
+ this.syncSwipeBackDepth();
1589
1689
  this.lastNavigationHref = currentHref;
1690
+ this.lastNavigationPosition = currentPosition;
1590
1691
  return;
1591
1692
  }
1592
- if (direction === "forward" || direction === "none") {
1693
+ if (direction === "forward") {
1593
1694
  const hrefChanged = currentHref === null || this.lastNavigationHref === null || currentHref !== this.lastNavigationHref;
1594
1695
  if (options.forceForward || hrefChanged) {
1595
- this.swipeBackDepth += 1;
1696
+ this.navigationHrefs.push(currentHref);
1697
+ } else if (this.navigationHrefs.length === 0) {
1698
+ this.navigationHrefs = [currentHref];
1596
1699
  }
1700
+ this.syncSwipeBackDepth();
1597
1701
  this.lastNavigationHref = currentHref;
1702
+ this.lastNavigationPosition = currentPosition;
1598
1703
  return;
1599
1704
  }
1600
1705
  this.lastNavigationHref = currentHref;
1706
+ this.lastNavigationPosition = currentPosition;
1707
+ }
1708
+ resetNavigationDepth(currentHref, currentPosition) {
1709
+ this.navigationHrefs = [currentHref];
1710
+ this.swipeBackDepth = 0;
1711
+ this.lastNavigationHref = currentHref;
1712
+ this.lastNavigationPosition = currentPosition;
1713
+ }
1714
+ recordBackNavigation(currentHref) {
1715
+ const existingHrefIndex = this.findNavigationHrefIndex(currentHref, this.navigationHrefs.length - 2);
1716
+ if (existingHrefIndex !== -1) {
1717
+ this.navigationHrefs = this.navigationHrefs.slice(0, existingHrefIndex + 1);
1718
+ } else if (this.navigationHrefs.length > 1) {
1719
+ this.navigationHrefs.pop();
1720
+ this.navigationHrefs[this.navigationHrefs.length - 1] = currentHref;
1721
+ } else {
1722
+ this.navigationHrefs = [currentHref];
1723
+ }
1724
+ this.syncSwipeBackDepth();
1725
+ this.lastNavigationHref = currentHref;
1726
+ }
1727
+ findNavigationHrefIndex(href, fromIndex) {
1728
+ for (let index = Math.min(fromIndex, this.navigationHrefs.length - 1); index >= 0; index -= 1) {
1729
+ if (this.navigationHrefs[index] === href) {
1730
+ return index;
1731
+ }
1732
+ }
1733
+ return -1;
1734
+ }
1735
+ syncSwipeBackDepth() {
1736
+ this.swipeBackDepth = Math.max(0, this.navigationHrefs.length - 1);
1737
+ }
1738
+ getSwipeBackDestination() {
1739
+ const stack = this.controller.stack;
1740
+ if (this.swipeBackDepth <= 0 || stack.length <= 1) {
1741
+ return null;
1742
+ }
1743
+ return stack[stack.length - 2] ?? null;
1601
1744
  }
1602
1745
  canStartSwipeGesture(event) {
1603
- if (!this.isSwipeGestureEnabled() || this.controller.animating || this.pendingPage || !this.canGoBack) {
1746
+ if (!this.isSwipeGestureEnabled() || this.controller.animating || this.pendingPage || !this.getSwipeBackDestination()) {
1604
1747
  return false;
1605
1748
  }
1606
1749
  if (!event.isPrimary || event.pointerType === "mouse" && event.button !== 0) {
@@ -1652,12 +1795,14 @@ var CapRouterOutlet = class extends HTMLElement {
1652
1795
  if (!this.canStartSwipeGesture(event)) {
1653
1796
  return;
1654
1797
  }
1798
+ const width = Math.max(this.getBoundingClientRect().width, 1);
1655
1799
  this.swipeGesturePointer = {
1656
1800
  pointerId: event.pointerId,
1657
1801
  startX: event.clientX,
1658
1802
  startY: event.clientY,
1659
1803
  currentX: event.clientX,
1660
1804
  currentY: event.clientY,
1805
+ width,
1661
1806
  startTime: performance.now(),
1662
1807
  dragging: false,
1663
1808
  transitionStarted: false
@@ -1687,6 +1832,10 @@ var CapRouterOutlet = class extends HTMLElement {
1687
1832
  return;
1688
1833
  }
1689
1834
  if (!pointer.dragging && deltaX > this.swipeGestureThreshold && absX > absY) {
1835
+ if (!this.getSwipeBackDestination()) {
1836
+ this.cancelSwipeGesturePointer(event.pointerId);
1837
+ return;
1838
+ }
1690
1839
  pointer.dragging = true;
1691
1840
  pointer.transitionStarted = this.controller.beginInteractiveBack({ direction: "back" });
1692
1841
  if (!pointer.transitionStarted) {
@@ -1695,9 +1844,12 @@ var CapRouterOutlet = class extends HTMLElement {
1695
1844
  }
1696
1845
  }
1697
1846
  if (pointer.dragging && pointer.transitionStarted) {
1847
+ if (!this.getSwipeBackDestination()) {
1848
+ this.cancelSwipeGesture(event.pointerId);
1849
+ return;
1850
+ }
1698
1851
  if (event.cancelable) event.preventDefault();
1699
- const width = Math.max(this.getBoundingClientRect().width, 1);
1700
- this.controller.stepInteractiveBack(deltaX / width);
1852
+ this.queueSwipeGestureStep(deltaX / pointer.width);
1701
1853
  }
1702
1854
  };
1703
1855
  handleSwipeGesturePointerEnd = (event) => {
@@ -1710,12 +1862,17 @@ var CapRouterOutlet = class extends HTMLElement {
1710
1862
  const deltaX = this.getSwipeGestureDeltaX(pointer);
1711
1863
  const elapsed = Math.max(performance.now() - pointer.startTime, 1);
1712
1864
  const velocityX = deltaX / elapsed;
1713
- const width = Math.max(this.getBoundingClientRect().width, 1);
1865
+ const width = pointer.width;
1714
1866
  const step = deltaX / width;
1715
1867
  const shouldCommit = pointer.dragging && pointer.transitionStarted && velocityX >= 0 && (velocityX > this.swipeGestureMinimumVelocity || deltaX > width / 2);
1716
1868
  const missing = shouldCommit ? 1 - step : step;
1717
1869
  const missingDistance = Math.max(missing, 0) * width;
1718
1870
  const releaseDuration = missingDistance > 5 && Math.abs(velocityX) > 0 ? Math.min(missingDistance / Math.abs(velocityX), 540) : 0;
1871
+ if (pointer.transitionStarted) {
1872
+ this.flushQueuedSwipeGestureStep();
1873
+ } else {
1874
+ this.clearQueuedSwipeGestureStep();
1875
+ }
1719
1876
  this.releaseSwipeGesturePointer(event.pointerId);
1720
1877
  void this.finishSwipeGestureBack(shouldCommit, releaseDuration);
1721
1878
  };
@@ -1723,6 +1880,7 @@ var CapRouterOutlet = class extends HTMLElement {
1723
1880
  this.cancelSwipeGesture(event.pointerId);
1724
1881
  };
1725
1882
  cancelSwipeGesturePointer(pointerId) {
1883
+ this.clearQueuedSwipeGestureStep();
1726
1884
  this.releaseSwipeGesturePointer(pointerId);
1727
1885
  }
1728
1886
  releaseSwipeGesturePointer(pointerId) {
@@ -1741,14 +1899,49 @@ var CapRouterOutlet = class extends HTMLElement {
1741
1899
  return;
1742
1900
  }
1743
1901
  this.releaseSwipeGesturePointer(pointerId);
1902
+ this.clearQueuedSwipeGestureStep();
1744
1903
  if (pointer.transitionStarted) {
1745
1904
  void this.finishSwipeGestureBack(false, 0);
1746
1905
  }
1747
1906
  }
1907
+ queueSwipeGestureStep(step) {
1908
+ this.swipeGesturePendingStep = step;
1909
+ if (this.swipeGestureFrame !== 0) {
1910
+ return;
1911
+ }
1912
+ const win = this.ownerDocument.defaultView;
1913
+ if (!win) {
1914
+ this.flushQueuedSwipeGestureStep();
1915
+ return;
1916
+ }
1917
+ this.swipeGestureFrame = win.requestAnimationFrame(() => {
1918
+ this.swipeGestureFrame = 0;
1919
+ this.flushQueuedSwipeGestureStep();
1920
+ });
1921
+ }
1922
+ flushQueuedSwipeGestureStep() {
1923
+ const step = this.swipeGesturePendingStep;
1924
+ this.swipeGesturePendingStep = null;
1925
+ if (this.swipeGestureFrame !== 0) {
1926
+ this.ownerDocument.defaultView?.cancelAnimationFrame(this.swipeGestureFrame);
1927
+ this.swipeGestureFrame = 0;
1928
+ }
1929
+ if (step !== null) {
1930
+ this.controller.stepInteractiveBack(step);
1931
+ }
1932
+ }
1933
+ clearQueuedSwipeGestureStep() {
1934
+ if (this.swipeGestureFrame !== 0) {
1935
+ this.ownerDocument.defaultView?.cancelAnimationFrame(this.swipeGestureFrame);
1936
+ this.swipeGestureFrame = 0;
1937
+ }
1938
+ this.swipeGesturePendingStep = null;
1939
+ }
1748
1940
  async finishSwipeGestureBack(shouldComplete, releaseDuration) {
1749
- const shouldUseHistory = shouldComplete && typeof window !== "undefined" && window.history.length > 1;
1750
- await this.controller.endInteractiveBack(shouldComplete, releaseDuration, !shouldUseHistory);
1751
- if (!shouldComplete) {
1941
+ const canComplete = shouldComplete && this.getSwipeBackDestination() !== null;
1942
+ const shouldUseHistory = canComplete && typeof window !== "undefined" && window.history.length > 1;
1943
+ await this.controller.endInteractiveBack(canComplete, canComplete ? releaseDuration : 0, !shouldUseHistory);
1944
+ if (!canComplete) {
1752
1945
  return;
1753
1946
  }
1754
1947
  if (shouldUseHistory) {