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