@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/index.js CHANGED
@@ -943,12 +943,15 @@ var init_transition_controller = __esm({
943
943
  animation.pause();
944
944
  animation.currentTime = 0;
945
945
  }
946
+ const animationDurations = animations.map((animation) => this.getAnimationDuration(animation, duration));
946
947
  this.currentAnimations = animations;
947
948
  this.interactiveBackTransition = {
948
949
  enteringState,
949
950
  leavingState,
950
951
  animations,
951
- duration
952
+ animationDurations,
953
+ duration,
954
+ progress: 0
952
955
  };
953
956
  return true;
954
957
  }
@@ -961,9 +964,12 @@ var init_transition_controller = __esm({
961
964
  return;
962
965
  }
963
966
  const progress = Math.max(0, Math.min(step, 0.9999));
964
- for (const animation of transition.animations) {
965
- const duration = this.getAnimationDuration(animation, transition.duration);
966
- animation.pause();
967
+ if (Math.abs(progress - transition.progress) < 5e-4) {
968
+ return;
969
+ }
970
+ transition.progress = progress;
971
+ for (const [index, animation] of transition.animations.entries()) {
972
+ const duration = transition.animationDurations[index] ?? transition.duration;
967
973
  animation.currentTime = duration * progress;
968
974
  }
969
975
  }
@@ -1142,15 +1148,16 @@ var init_transition_controller = __esm({
1142
1148
  return;
1143
1149
  }
1144
1150
  if (releaseDuration <= 0) {
1145
- for (const animation of transition.animations) {
1146
- const duration = this.getAnimationDuration(animation, transition.duration);
1151
+ transition.progress = targetProgress;
1152
+ for (const [index, animation] of transition.animations.entries()) {
1153
+ const duration = transition.animationDurations[index] ?? transition.duration;
1147
1154
  animation.pause();
1148
1155
  animation.currentTime = duration * targetProgress;
1149
1156
  }
1150
1157
  return;
1151
1158
  }
1152
- const finished = transition.animations.map((animation) => {
1153
- const duration = this.getAnimationDuration(animation, transition.duration);
1159
+ const finished = transition.animations.map((animation, index) => {
1160
+ const duration = transition.animationDurations[index] ?? transition.duration;
1154
1161
  const currentTime = typeof animation.currentTime === "number" ? animation.currentTime : 0;
1155
1162
  const targetTime = duration * targetProgress;
1156
1163
  const distance = Math.abs(targetTime - currentTime);
@@ -1163,6 +1170,7 @@ var init_transition_controller = __esm({
1163
1170
  return animation.finished.catch(() => void 0);
1164
1171
  });
1165
1172
  await Promise.all(finished);
1173
+ transition.progress = targetProgress;
1166
1174
  }
1167
1175
  /**
1168
1176
  * Resolve configured easing presets after platform/direction are known.
@@ -1334,13 +1342,32 @@ var init_cap_router_outlet = __esm({
1334
1342
  pendingPage = null;
1335
1343
  ignoredNodes = /* @__PURE__ */ new WeakSet();
1336
1344
  swipeGesturePointer = null;
1345
+ swipeGestureFrame = 0;
1346
+ swipeGesturePendingStep = null;
1337
1347
  swipeGestureListenersActive = false;
1338
1348
  skipNextHistoryBackTransition = false;
1339
1349
  swipeBackDepth = 0;
1340
1350
  lastNavigationHref = null;
1351
+ lastNavigationPosition = null;
1352
+ pendingHistoryDirection = null;
1353
+ navigationHrefs = [];
1341
1354
  swipeGestureEdgeWidth = 50;
1342
1355
  swipeGestureThreshold = 10;
1343
1356
  swipeGestureMinimumVelocity = 0.2;
1357
+ handleHistoryPopState = () => {
1358
+ const currentPosition = this.getCurrentNavigationPosition();
1359
+ if (currentPosition !== null && this.lastNavigationPosition !== null) {
1360
+ if (currentPosition < this.lastNavigationPosition) {
1361
+ this.pendingHistoryDirection = "back";
1362
+ return;
1363
+ }
1364
+ if (currentPosition > this.lastNavigationPosition) {
1365
+ this.pendingHistoryDirection = "forward";
1366
+ return;
1367
+ }
1368
+ }
1369
+ this.pendingHistoryDirection = "back";
1370
+ };
1344
1371
  static get observedAttributes() {
1345
1372
  return ["platform", "duration", "keep-in-dom", "max-cached", "swipe-gesture"];
1346
1373
  }
@@ -1359,7 +1386,10 @@ var init_cap_router_outlet = __esm({
1359
1386
  this.style.width = "100%";
1360
1387
  this.style.height = "100%";
1361
1388
  this.style.overflow = "hidden";
1389
+ this.style.overscrollBehaviorX = "contain";
1362
1390
  this.lastNavigationHref = this.getCurrentNavigationHref();
1391
+ this.lastNavigationPosition = this.getCurrentNavigationPosition();
1392
+ this.ownerDocument.defaultView?.addEventListener("popstate", this.handleHistoryPopState);
1363
1393
  this.observer = new MutationObserver((mutations) => {
1364
1394
  this.handleMutations(mutations);
1365
1395
  });
@@ -1375,10 +1405,14 @@ var init_cap_router_outlet = __esm({
1375
1405
  }
1376
1406
  disconnectedCallback() {
1377
1407
  this.observer?.disconnect();
1408
+ this.ownerDocument.defaultView?.removeEventListener("popstate", this.handleHistoryPopState);
1378
1409
  this.removeSwipeGestureListeners();
1379
1410
  this.controller.clear();
1380
1411
  this.swipeBackDepth = 0;
1381
1412
  this.lastNavigationHref = null;
1413
+ this.lastNavigationPosition = null;
1414
+ this.pendingHistoryDirection = null;
1415
+ this.navigationHrefs = [];
1382
1416
  }
1383
1417
  attributeChangedCallback(name, _oldValue, newValue) {
1384
1418
  switch (name) {
@@ -1459,13 +1493,16 @@ var init_cap_router_outlet = __esm({
1459
1493
  this.controller.pageStack.push(state);
1460
1494
  this.swipeBackDepth = 0;
1461
1495
  this.lastNavigationHref = this.getCurrentNavigationHref();
1496
+ this.lastNavigationPosition = this.getCurrentNavigationPosition();
1497
+ this.navigationHrefs = [this.lastNavigationHref];
1462
1498
  }
1463
1499
  /**
1464
1500
  * Handle a new page being added
1465
1501
  */
1466
1502
  async handleNewPage(page) {
1467
1503
  const outletDirection = this.dataset.direction;
1468
- const direction = page.dataset.direction || outletDirection || "forward";
1504
+ const explicitDirection = page.dataset.direction || outletDirection;
1505
+ const direction = this.resolveNavigationDirection(explicitDirection);
1469
1506
  if (outletDirection) {
1470
1507
  delete this.dataset.direction;
1471
1508
  }
@@ -1580,7 +1617,7 @@ var init_cap_router_outlet = __esm({
1580
1617
  * Check if we can go back
1581
1618
  */
1582
1619
  get canGoBack() {
1583
- return this.controller.stack.length > 1 && this.swipeBackDepth > 0;
1620
+ return this.getSwipeBackDestination() !== null;
1584
1621
  }
1585
1622
  /**
1586
1623
  * Get whether edge swipe-back gesture is enabled.
@@ -1657,6 +1694,10 @@ var init_cap_router_outlet = __esm({
1657
1694
  this.removeEventListener("pointerup", this.handleSwipeGesturePointerEnd);
1658
1695
  this.removeEventListener("pointercancel", this.handleSwipeGesturePointerCancel);
1659
1696
  this.swipeGestureListenersActive = false;
1697
+ this.clearQueuedSwipeGestureStep();
1698
+ if (this.swipeGesturePointer?.transitionStarted) {
1699
+ this.controller.cancelInteractiveBack();
1700
+ }
1660
1701
  this.swipeGesturePointer = null;
1661
1702
  }
1662
1703
  isSwipeGestureEnabled() {
@@ -1672,30 +1713,132 @@ var init_cap_router_outlet = __esm({
1672
1713
  getCurrentNavigationHref() {
1673
1714
  return this.ownerDocument.defaultView?.location.href ?? null;
1674
1715
  }
1716
+ getCurrentNavigationPosition() {
1717
+ const win = this.ownerDocument.defaultView;
1718
+ if (!win) {
1719
+ return null;
1720
+ }
1721
+ const navigationIndex = win.navigation?.currentEntry?.index;
1722
+ if (typeof navigationIndex === "number" && Number.isFinite(navigationIndex)) {
1723
+ return navigationIndex;
1724
+ }
1725
+ const state = win.history.state;
1726
+ for (const key of ["idx", "position", "index"]) {
1727
+ const value = state?.[key];
1728
+ if (typeof value === "number" && Number.isFinite(value)) {
1729
+ return value;
1730
+ }
1731
+ }
1732
+ return null;
1733
+ }
1734
+ resolveNavigationDirection(explicitDirection) {
1735
+ if (explicitDirection) {
1736
+ this.pendingHistoryDirection = null;
1737
+ return explicitDirection;
1738
+ }
1739
+ const currentHref = this.getCurrentNavigationHref();
1740
+ const existingHrefIndex = this.findNavigationHrefIndex(currentHref, this.navigationHrefs.length - 2);
1741
+ if (existingHrefIndex !== -1) {
1742
+ this.pendingHistoryDirection = null;
1743
+ return "back";
1744
+ }
1745
+ if (currentHref !== null && currentHref === this.lastNavigationHref) {
1746
+ this.pendingHistoryDirection = null;
1747
+ return "none";
1748
+ }
1749
+ if (this.pendingHistoryDirection) {
1750
+ const direction = this.pendingHistoryDirection;
1751
+ this.pendingHistoryDirection = null;
1752
+ return direction;
1753
+ }
1754
+ const currentPosition = this.getCurrentNavigationPosition();
1755
+ if (currentPosition !== null && this.lastNavigationPosition !== null) {
1756
+ if (currentPosition < this.lastNavigationPosition) {
1757
+ return "back";
1758
+ }
1759
+ if (currentPosition === this.lastNavigationPosition) {
1760
+ return "none";
1761
+ }
1762
+ }
1763
+ return "forward";
1764
+ }
1675
1765
  recordCompletedNavigation(direction, options) {
1676
1766
  const currentHref = this.getCurrentNavigationHref();
1767
+ const currentPosition = this.getCurrentNavigationPosition();
1677
1768
  if (!options.hadPageBefore || direction === "root") {
1678
- this.swipeBackDepth = 0;
1679
- this.lastNavigationHref = currentHref;
1769
+ this.resetNavigationDepth(currentHref, currentPosition);
1680
1770
  return;
1681
1771
  }
1682
1772
  if (direction === "back") {
1683
- this.swipeBackDepth = Math.max(0, this.swipeBackDepth - 1);
1773
+ this.recordBackNavigation(currentHref);
1774
+ this.lastNavigationPosition = currentPosition;
1775
+ return;
1776
+ }
1777
+ if (direction === "none") {
1778
+ if (this.navigationHrefs.length === 0) {
1779
+ this.navigationHrefs = [currentHref];
1780
+ } else {
1781
+ this.navigationHrefs[this.navigationHrefs.length - 1] = currentHref;
1782
+ }
1783
+ this.syncSwipeBackDepth();
1684
1784
  this.lastNavigationHref = currentHref;
1785
+ this.lastNavigationPosition = currentPosition;
1685
1786
  return;
1686
1787
  }
1687
- if (direction === "forward" || direction === "none") {
1788
+ if (direction === "forward") {
1688
1789
  const hrefChanged = currentHref === null || this.lastNavigationHref === null || currentHref !== this.lastNavigationHref;
1689
1790
  if (options.forceForward || hrefChanged) {
1690
- this.swipeBackDepth += 1;
1791
+ this.navigationHrefs.push(currentHref);
1792
+ } else if (this.navigationHrefs.length === 0) {
1793
+ this.navigationHrefs = [currentHref];
1691
1794
  }
1795
+ this.syncSwipeBackDepth();
1692
1796
  this.lastNavigationHref = currentHref;
1797
+ this.lastNavigationPosition = currentPosition;
1693
1798
  return;
1694
1799
  }
1695
1800
  this.lastNavigationHref = currentHref;
1801
+ this.lastNavigationPosition = currentPosition;
1802
+ }
1803
+ resetNavigationDepth(currentHref, currentPosition) {
1804
+ this.navigationHrefs = [currentHref];
1805
+ this.swipeBackDepth = 0;
1806
+ this.lastNavigationHref = currentHref;
1807
+ this.lastNavigationPosition = currentPosition;
1808
+ }
1809
+ recordBackNavigation(currentHref) {
1810
+ const existingHrefIndex = this.findNavigationHrefIndex(currentHref, this.navigationHrefs.length - 2);
1811
+ if (existingHrefIndex !== -1) {
1812
+ this.navigationHrefs = this.navigationHrefs.slice(0, existingHrefIndex + 1);
1813
+ } else if (this.navigationHrefs.length > 1) {
1814
+ this.navigationHrefs.pop();
1815
+ this.navigationHrefs[this.navigationHrefs.length - 1] = currentHref;
1816
+ } else {
1817
+ this.navigationHrefs = [currentHref];
1818
+ }
1819
+ this.syncSwipeBackDepth();
1820
+ this.lastNavigationHref = currentHref;
1821
+ }
1822
+ findNavigationHrefIndex(href, fromIndex) {
1823
+ for (let index = Math.min(fromIndex, this.navigationHrefs.length - 1); index >= 0; index -= 1) {
1824
+ if (this.navigationHrefs[index] === href) {
1825
+ return index;
1826
+ }
1827
+ }
1828
+ return -1;
1829
+ }
1830
+ syncSwipeBackDepth() {
1831
+ this.swipeBackDepth = Math.max(0, this.navigationHrefs.length - 1);
1832
+ }
1833
+ getSwipeBackDestination() {
1834
+ const stack = this.controller.stack;
1835
+ if (this.swipeBackDepth <= 0 || stack.length <= 1) {
1836
+ return null;
1837
+ }
1838
+ return stack[stack.length - 2] ?? null;
1696
1839
  }
1697
1840
  canStartSwipeGesture(event) {
1698
- if (!this.isSwipeGestureEnabled() || this.controller.animating || this.pendingPage || !this.canGoBack) {
1841
+ if (!this.isSwipeGestureEnabled() || this.controller.animating || this.pendingPage || !this.getSwipeBackDestination()) {
1699
1842
  return false;
1700
1843
  }
1701
1844
  if (!event.isPrimary || event.pointerType === "mouse" && event.button !== 0) {
@@ -1747,12 +1890,14 @@ var init_cap_router_outlet = __esm({
1747
1890
  if (!this.canStartSwipeGesture(event)) {
1748
1891
  return;
1749
1892
  }
1893
+ const width = Math.max(this.getBoundingClientRect().width, 1);
1750
1894
  this.swipeGesturePointer = {
1751
1895
  pointerId: event.pointerId,
1752
1896
  startX: event.clientX,
1753
1897
  startY: event.clientY,
1754
1898
  currentX: event.clientX,
1755
1899
  currentY: event.clientY,
1900
+ width,
1756
1901
  startTime: performance.now(),
1757
1902
  dragging: false,
1758
1903
  transitionStarted: false
@@ -1782,6 +1927,10 @@ var init_cap_router_outlet = __esm({
1782
1927
  return;
1783
1928
  }
1784
1929
  if (!pointer.dragging && deltaX > this.swipeGestureThreshold && absX > absY) {
1930
+ if (!this.getSwipeBackDestination()) {
1931
+ this.cancelSwipeGesturePointer(event.pointerId);
1932
+ return;
1933
+ }
1785
1934
  pointer.dragging = true;
1786
1935
  pointer.transitionStarted = this.controller.beginInteractiveBack({ direction: "back" });
1787
1936
  if (!pointer.transitionStarted) {
@@ -1790,9 +1939,12 @@ var init_cap_router_outlet = __esm({
1790
1939
  }
1791
1940
  }
1792
1941
  if (pointer.dragging && pointer.transitionStarted) {
1942
+ if (!this.getSwipeBackDestination()) {
1943
+ this.cancelSwipeGesture(event.pointerId);
1944
+ return;
1945
+ }
1793
1946
  if (event.cancelable) event.preventDefault();
1794
- const width = Math.max(this.getBoundingClientRect().width, 1);
1795
- this.controller.stepInteractiveBack(deltaX / width);
1947
+ this.queueSwipeGestureStep(deltaX / pointer.width);
1796
1948
  }
1797
1949
  };
1798
1950
  handleSwipeGesturePointerEnd = (event) => {
@@ -1805,12 +1957,17 @@ var init_cap_router_outlet = __esm({
1805
1957
  const deltaX = this.getSwipeGestureDeltaX(pointer);
1806
1958
  const elapsed = Math.max(performance.now() - pointer.startTime, 1);
1807
1959
  const velocityX = deltaX / elapsed;
1808
- const width = Math.max(this.getBoundingClientRect().width, 1);
1960
+ const width = pointer.width;
1809
1961
  const step = deltaX / width;
1810
1962
  const shouldCommit = pointer.dragging && pointer.transitionStarted && velocityX >= 0 && (velocityX > this.swipeGestureMinimumVelocity || deltaX > width / 2);
1811
1963
  const missing = shouldCommit ? 1 - step : step;
1812
1964
  const missingDistance = Math.max(missing, 0) * width;
1813
1965
  const releaseDuration = missingDistance > 5 && Math.abs(velocityX) > 0 ? Math.min(missingDistance / Math.abs(velocityX), 540) : 0;
1966
+ if (pointer.transitionStarted) {
1967
+ this.flushQueuedSwipeGestureStep();
1968
+ } else {
1969
+ this.clearQueuedSwipeGestureStep();
1970
+ }
1814
1971
  this.releaseSwipeGesturePointer(event.pointerId);
1815
1972
  void this.finishSwipeGestureBack(shouldCommit, releaseDuration);
1816
1973
  };
@@ -1818,6 +1975,7 @@ var init_cap_router_outlet = __esm({
1818
1975
  this.cancelSwipeGesture(event.pointerId);
1819
1976
  };
1820
1977
  cancelSwipeGesturePointer(pointerId) {
1978
+ this.clearQueuedSwipeGestureStep();
1821
1979
  this.releaseSwipeGesturePointer(pointerId);
1822
1980
  }
1823
1981
  releaseSwipeGesturePointer(pointerId) {
@@ -1836,14 +1994,49 @@ var init_cap_router_outlet = __esm({
1836
1994
  return;
1837
1995
  }
1838
1996
  this.releaseSwipeGesturePointer(pointerId);
1997
+ this.clearQueuedSwipeGestureStep();
1839
1998
  if (pointer.transitionStarted) {
1840
1999
  void this.finishSwipeGestureBack(false, 0);
1841
2000
  }
1842
2001
  }
2002
+ queueSwipeGestureStep(step) {
2003
+ this.swipeGesturePendingStep = step;
2004
+ if (this.swipeGestureFrame !== 0) {
2005
+ return;
2006
+ }
2007
+ const win = this.ownerDocument.defaultView;
2008
+ if (!win) {
2009
+ this.flushQueuedSwipeGestureStep();
2010
+ return;
2011
+ }
2012
+ this.swipeGestureFrame = win.requestAnimationFrame(() => {
2013
+ this.swipeGestureFrame = 0;
2014
+ this.flushQueuedSwipeGestureStep();
2015
+ });
2016
+ }
2017
+ flushQueuedSwipeGestureStep() {
2018
+ const step = this.swipeGesturePendingStep;
2019
+ this.swipeGesturePendingStep = null;
2020
+ if (this.swipeGestureFrame !== 0) {
2021
+ this.ownerDocument.defaultView?.cancelAnimationFrame(this.swipeGestureFrame);
2022
+ this.swipeGestureFrame = 0;
2023
+ }
2024
+ if (step !== null) {
2025
+ this.controller.stepInteractiveBack(step);
2026
+ }
2027
+ }
2028
+ clearQueuedSwipeGestureStep() {
2029
+ if (this.swipeGestureFrame !== 0) {
2030
+ this.ownerDocument.defaultView?.cancelAnimationFrame(this.swipeGestureFrame);
2031
+ this.swipeGestureFrame = 0;
2032
+ }
2033
+ this.swipeGesturePendingStep = null;
2034
+ }
1843
2035
  async finishSwipeGestureBack(shouldComplete, releaseDuration) {
1844
- const shouldUseHistory = shouldComplete && typeof window !== "undefined" && window.history.length > 1;
1845
- await this.controller.endInteractiveBack(shouldComplete, releaseDuration, !shouldUseHistory);
1846
- if (!shouldComplete) {
2036
+ const canComplete = shouldComplete && this.getSwipeBackDestination() !== null;
2037
+ const shouldUseHistory = canComplete && typeof window !== "undefined" && window.history.length > 1;
2038
+ await this.controller.endInteractiveBack(canComplete, canComplete ? releaseDuration : 0, !shouldUseHistory);
2039
+ if (!canComplete) {
1847
2040
  return;
1848
2041
  }
1849
2042
  if (shouldUseHistory) {