@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.
@@ -796,12 +796,15 @@ var TransitionController = class {
796
796
  animation.pause();
797
797
  animation.currentTime = 0;
798
798
  }
799
+ const animationDurations = animations.map((animation) => this.getAnimationDuration(animation, duration));
799
800
  this.currentAnimations = animations;
800
801
  this.interactiveBackTransition = {
801
802
  enteringState,
802
803
  leavingState,
803
804
  animations,
804
- duration
805
+ animationDurations,
806
+ duration,
807
+ progress: 0
805
808
  };
806
809
  return true;
807
810
  }
@@ -814,9 +817,12 @@ var TransitionController = class {
814
817
  return;
815
818
  }
816
819
  const progress = Math.max(0, Math.min(step, 0.9999));
817
- for (const animation of transition.animations) {
818
- const duration = this.getAnimationDuration(animation, transition.duration);
819
- animation.pause();
820
+ if (Math.abs(progress - transition.progress) < 5e-4) {
821
+ return;
822
+ }
823
+ transition.progress = progress;
824
+ for (const [index, animation] of transition.animations.entries()) {
825
+ const duration = transition.animationDurations[index] ?? transition.duration;
820
826
  animation.currentTime = duration * progress;
821
827
  }
822
828
  }
@@ -995,15 +1001,16 @@ var TransitionController = class {
995
1001
  return;
996
1002
  }
997
1003
  if (releaseDuration <= 0) {
998
- for (const animation of transition.animations) {
999
- const duration = this.getAnimationDuration(animation, transition.duration);
1004
+ transition.progress = targetProgress;
1005
+ for (const [index, animation] of transition.animations.entries()) {
1006
+ const duration = transition.animationDurations[index] ?? transition.duration;
1000
1007
  animation.pause();
1001
1008
  animation.currentTime = duration * targetProgress;
1002
1009
  }
1003
1010
  return;
1004
1011
  }
1005
- const finished = transition.animations.map((animation) => {
1006
- const duration = this.getAnimationDuration(animation, transition.duration);
1012
+ const finished = transition.animations.map((animation, index) => {
1013
+ const duration = transition.animationDurations[index] ?? transition.duration;
1007
1014
  const currentTime = typeof animation.currentTime === "number" ? animation.currentTime : 0;
1008
1015
  const targetTime = duration * targetProgress;
1009
1016
  const distance = Math.abs(targetTime - currentTime);
@@ -1016,6 +1023,7 @@ var TransitionController = class {
1016
1023
  return animation.finished.catch(() => void 0);
1017
1024
  });
1018
1025
  await Promise.all(finished);
1026
+ transition.progress = targetProgress;
1019
1027
  }
1020
1028
  /**
1021
1029
  * Resolve configured easing presets after platform/direction are known.
@@ -1205,13 +1213,32 @@ var CapRouterOutlet = class extends HTMLElement {
1205
1213
  pendingPage = null;
1206
1214
  ignoredNodes = /* @__PURE__ */ new WeakSet();
1207
1215
  swipeGesturePointer = null;
1216
+ swipeGestureFrame = 0;
1217
+ swipeGesturePendingStep = null;
1208
1218
  swipeGestureListenersActive = false;
1209
1219
  skipNextHistoryBackTransition = false;
1210
1220
  swipeBackDepth = 0;
1211
1221
  lastNavigationHref = null;
1222
+ lastNavigationPosition = null;
1223
+ pendingHistoryDirection = null;
1224
+ navigationHrefs = [];
1212
1225
  swipeGestureEdgeWidth = 50;
1213
1226
  swipeGestureThreshold = 10;
1214
1227
  swipeGestureMinimumVelocity = 0.2;
1228
+ handleHistoryPopState = () => {
1229
+ const currentPosition = this.getCurrentNavigationPosition();
1230
+ if (currentPosition !== null && this.lastNavigationPosition !== null) {
1231
+ if (currentPosition < this.lastNavigationPosition) {
1232
+ this.pendingHistoryDirection = "back";
1233
+ return;
1234
+ }
1235
+ if (currentPosition > this.lastNavigationPosition) {
1236
+ this.pendingHistoryDirection = "forward";
1237
+ return;
1238
+ }
1239
+ }
1240
+ this.pendingHistoryDirection = "back";
1241
+ };
1215
1242
  static get observedAttributes() {
1216
1243
  return ["platform", "duration", "keep-in-dom", "max-cached", "swipe-gesture"];
1217
1244
  }
@@ -1230,7 +1257,10 @@ var CapRouterOutlet = class extends HTMLElement {
1230
1257
  this.style.width = "100%";
1231
1258
  this.style.height = "100%";
1232
1259
  this.style.overflow = "hidden";
1260
+ this.style.overscrollBehaviorX = "contain";
1233
1261
  this.lastNavigationHref = this.getCurrentNavigationHref();
1262
+ this.lastNavigationPosition = this.getCurrentNavigationPosition();
1263
+ this.ownerDocument.defaultView?.addEventListener("popstate", this.handleHistoryPopState);
1234
1264
  this.observer = new MutationObserver((mutations) => {
1235
1265
  this.handleMutations(mutations);
1236
1266
  });
@@ -1246,10 +1276,14 @@ var CapRouterOutlet = class extends HTMLElement {
1246
1276
  }
1247
1277
  disconnectedCallback() {
1248
1278
  this.observer?.disconnect();
1279
+ this.ownerDocument.defaultView?.removeEventListener("popstate", this.handleHistoryPopState);
1249
1280
  this.removeSwipeGestureListeners();
1250
1281
  this.controller.clear();
1251
1282
  this.swipeBackDepth = 0;
1252
1283
  this.lastNavigationHref = null;
1284
+ this.lastNavigationPosition = null;
1285
+ this.pendingHistoryDirection = null;
1286
+ this.navigationHrefs = [];
1253
1287
  }
1254
1288
  attributeChangedCallback(name, _oldValue, newValue) {
1255
1289
  switch (name) {
@@ -1330,13 +1364,16 @@ var CapRouterOutlet = class extends HTMLElement {
1330
1364
  this.controller.pageStack.push(state);
1331
1365
  this.swipeBackDepth = 0;
1332
1366
  this.lastNavigationHref = this.getCurrentNavigationHref();
1367
+ this.lastNavigationPosition = this.getCurrentNavigationPosition();
1368
+ this.navigationHrefs = [this.lastNavigationHref];
1333
1369
  }
1334
1370
  /**
1335
1371
  * Handle a new page being added
1336
1372
  */
1337
1373
  async handleNewPage(page) {
1338
1374
  const outletDirection = this.dataset.direction;
1339
- const direction = page.dataset.direction || outletDirection || "forward";
1375
+ const explicitDirection = page.dataset.direction || outletDirection;
1376
+ const direction = this.resolveNavigationDirection(explicitDirection);
1340
1377
  if (outletDirection) {
1341
1378
  delete this.dataset.direction;
1342
1379
  }
@@ -1451,7 +1488,7 @@ var CapRouterOutlet = class extends HTMLElement {
1451
1488
  * Check if we can go back
1452
1489
  */
1453
1490
  get canGoBack() {
1454
- return this.controller.stack.length > 1 && this.swipeBackDepth > 0;
1491
+ return this.getSwipeBackDestination() !== null;
1455
1492
  }
1456
1493
  /**
1457
1494
  * Get whether edge swipe-back gesture is enabled.
@@ -1528,6 +1565,10 @@ var CapRouterOutlet = class extends HTMLElement {
1528
1565
  this.removeEventListener("pointerup", this.handleSwipeGesturePointerEnd);
1529
1566
  this.removeEventListener("pointercancel", this.handleSwipeGesturePointerCancel);
1530
1567
  this.swipeGestureListenersActive = false;
1568
+ this.clearQueuedSwipeGestureStep();
1569
+ if (this.swipeGesturePointer?.transitionStarted) {
1570
+ this.controller.cancelInteractiveBack();
1571
+ }
1531
1572
  this.swipeGesturePointer = null;
1532
1573
  }
1533
1574
  isSwipeGestureEnabled() {
@@ -1543,30 +1584,132 @@ var CapRouterOutlet = class extends HTMLElement {
1543
1584
  getCurrentNavigationHref() {
1544
1585
  return this.ownerDocument.defaultView?.location.href ?? null;
1545
1586
  }
1587
+ getCurrentNavigationPosition() {
1588
+ const win = this.ownerDocument.defaultView;
1589
+ if (!win) {
1590
+ return null;
1591
+ }
1592
+ const navigationIndex = win.navigation?.currentEntry?.index;
1593
+ if (typeof navigationIndex === "number" && Number.isFinite(navigationIndex)) {
1594
+ return navigationIndex;
1595
+ }
1596
+ const state = win.history.state;
1597
+ for (const key of ["idx", "position", "index"]) {
1598
+ const value = state?.[key];
1599
+ if (typeof value === "number" && Number.isFinite(value)) {
1600
+ return value;
1601
+ }
1602
+ }
1603
+ return null;
1604
+ }
1605
+ resolveNavigationDirection(explicitDirection) {
1606
+ if (explicitDirection) {
1607
+ this.pendingHistoryDirection = null;
1608
+ return explicitDirection;
1609
+ }
1610
+ const currentHref = this.getCurrentNavigationHref();
1611
+ const existingHrefIndex = this.findNavigationHrefIndex(currentHref, this.navigationHrefs.length - 2);
1612
+ if (existingHrefIndex !== -1) {
1613
+ this.pendingHistoryDirection = null;
1614
+ return "back";
1615
+ }
1616
+ if (currentHref !== null && currentHref === this.lastNavigationHref) {
1617
+ this.pendingHistoryDirection = null;
1618
+ return "none";
1619
+ }
1620
+ if (this.pendingHistoryDirection) {
1621
+ const direction = this.pendingHistoryDirection;
1622
+ this.pendingHistoryDirection = null;
1623
+ return direction;
1624
+ }
1625
+ const currentPosition = this.getCurrentNavigationPosition();
1626
+ if (currentPosition !== null && this.lastNavigationPosition !== null) {
1627
+ if (currentPosition < this.lastNavigationPosition) {
1628
+ return "back";
1629
+ }
1630
+ if (currentPosition === this.lastNavigationPosition) {
1631
+ return "none";
1632
+ }
1633
+ }
1634
+ return "forward";
1635
+ }
1546
1636
  recordCompletedNavigation(direction, options) {
1547
1637
  const currentHref = this.getCurrentNavigationHref();
1638
+ const currentPosition = this.getCurrentNavigationPosition();
1548
1639
  if (!options.hadPageBefore || direction === "root") {
1549
- this.swipeBackDepth = 0;
1550
- this.lastNavigationHref = currentHref;
1640
+ this.resetNavigationDepth(currentHref, currentPosition);
1551
1641
  return;
1552
1642
  }
1553
1643
  if (direction === "back") {
1554
- this.swipeBackDepth = Math.max(0, this.swipeBackDepth - 1);
1644
+ this.recordBackNavigation(currentHref);
1645
+ this.lastNavigationPosition = currentPosition;
1646
+ return;
1647
+ }
1648
+ if (direction === "none") {
1649
+ if (this.navigationHrefs.length === 0) {
1650
+ this.navigationHrefs = [currentHref];
1651
+ } else {
1652
+ this.navigationHrefs[this.navigationHrefs.length - 1] = currentHref;
1653
+ }
1654
+ this.syncSwipeBackDepth();
1555
1655
  this.lastNavigationHref = currentHref;
1656
+ this.lastNavigationPosition = currentPosition;
1556
1657
  return;
1557
1658
  }
1558
- if (direction === "forward" || direction === "none") {
1659
+ if (direction === "forward") {
1559
1660
  const hrefChanged = currentHref === null || this.lastNavigationHref === null || currentHref !== this.lastNavigationHref;
1560
1661
  if (options.forceForward || hrefChanged) {
1561
- this.swipeBackDepth += 1;
1662
+ this.navigationHrefs.push(currentHref);
1663
+ } else if (this.navigationHrefs.length === 0) {
1664
+ this.navigationHrefs = [currentHref];
1562
1665
  }
1666
+ this.syncSwipeBackDepth();
1563
1667
  this.lastNavigationHref = currentHref;
1668
+ this.lastNavigationPosition = currentPosition;
1564
1669
  return;
1565
1670
  }
1566
1671
  this.lastNavigationHref = currentHref;
1672
+ this.lastNavigationPosition = currentPosition;
1673
+ }
1674
+ resetNavigationDepth(currentHref, currentPosition) {
1675
+ this.navigationHrefs = [currentHref];
1676
+ this.swipeBackDepth = 0;
1677
+ this.lastNavigationHref = currentHref;
1678
+ this.lastNavigationPosition = currentPosition;
1679
+ }
1680
+ recordBackNavigation(currentHref) {
1681
+ const existingHrefIndex = this.findNavigationHrefIndex(currentHref, this.navigationHrefs.length - 2);
1682
+ if (existingHrefIndex !== -1) {
1683
+ this.navigationHrefs = this.navigationHrefs.slice(0, existingHrefIndex + 1);
1684
+ } else if (this.navigationHrefs.length > 1) {
1685
+ this.navigationHrefs.pop();
1686
+ this.navigationHrefs[this.navigationHrefs.length - 1] = currentHref;
1687
+ } else {
1688
+ this.navigationHrefs = [currentHref];
1689
+ }
1690
+ this.syncSwipeBackDepth();
1691
+ this.lastNavigationHref = currentHref;
1692
+ }
1693
+ findNavigationHrefIndex(href, fromIndex) {
1694
+ for (let index = Math.min(fromIndex, this.navigationHrefs.length - 1); index >= 0; index -= 1) {
1695
+ if (this.navigationHrefs[index] === href) {
1696
+ return index;
1697
+ }
1698
+ }
1699
+ return -1;
1700
+ }
1701
+ syncSwipeBackDepth() {
1702
+ this.swipeBackDepth = Math.max(0, this.navigationHrefs.length - 1);
1703
+ }
1704
+ getSwipeBackDestination() {
1705
+ const stack = this.controller.stack;
1706
+ if (this.swipeBackDepth <= 0 || stack.length <= 1) {
1707
+ return null;
1708
+ }
1709
+ return stack[stack.length - 2] ?? null;
1567
1710
  }
1568
1711
  canStartSwipeGesture(event) {
1569
- if (!this.isSwipeGestureEnabled() || this.controller.animating || this.pendingPage || !this.canGoBack) {
1712
+ if (!this.isSwipeGestureEnabled() || this.controller.animating || this.pendingPage || !this.getSwipeBackDestination()) {
1570
1713
  return false;
1571
1714
  }
1572
1715
  if (!event.isPrimary || event.pointerType === "mouse" && event.button !== 0) {
@@ -1618,12 +1761,14 @@ var CapRouterOutlet = class extends HTMLElement {
1618
1761
  if (!this.canStartSwipeGesture(event)) {
1619
1762
  return;
1620
1763
  }
1764
+ const width = Math.max(this.getBoundingClientRect().width, 1);
1621
1765
  this.swipeGesturePointer = {
1622
1766
  pointerId: event.pointerId,
1623
1767
  startX: event.clientX,
1624
1768
  startY: event.clientY,
1625
1769
  currentX: event.clientX,
1626
1770
  currentY: event.clientY,
1771
+ width,
1627
1772
  startTime: performance.now(),
1628
1773
  dragging: false,
1629
1774
  transitionStarted: false
@@ -1653,6 +1798,10 @@ var CapRouterOutlet = class extends HTMLElement {
1653
1798
  return;
1654
1799
  }
1655
1800
  if (!pointer.dragging && deltaX > this.swipeGestureThreshold && absX > absY) {
1801
+ if (!this.getSwipeBackDestination()) {
1802
+ this.cancelSwipeGesturePointer(event.pointerId);
1803
+ return;
1804
+ }
1656
1805
  pointer.dragging = true;
1657
1806
  pointer.transitionStarted = this.controller.beginInteractiveBack({ direction: "back" });
1658
1807
  if (!pointer.transitionStarted) {
@@ -1661,9 +1810,12 @@ var CapRouterOutlet = class extends HTMLElement {
1661
1810
  }
1662
1811
  }
1663
1812
  if (pointer.dragging && pointer.transitionStarted) {
1813
+ if (!this.getSwipeBackDestination()) {
1814
+ this.cancelSwipeGesture(event.pointerId);
1815
+ return;
1816
+ }
1664
1817
  if (event.cancelable) event.preventDefault();
1665
- const width = Math.max(this.getBoundingClientRect().width, 1);
1666
- this.controller.stepInteractiveBack(deltaX / width);
1818
+ this.queueSwipeGestureStep(deltaX / pointer.width);
1667
1819
  }
1668
1820
  };
1669
1821
  handleSwipeGesturePointerEnd = (event) => {
@@ -1676,12 +1828,17 @@ var CapRouterOutlet = class extends HTMLElement {
1676
1828
  const deltaX = this.getSwipeGestureDeltaX(pointer);
1677
1829
  const elapsed = Math.max(performance.now() - pointer.startTime, 1);
1678
1830
  const velocityX = deltaX / elapsed;
1679
- const width = Math.max(this.getBoundingClientRect().width, 1);
1831
+ const width = pointer.width;
1680
1832
  const step = deltaX / width;
1681
1833
  const shouldCommit = pointer.dragging && pointer.transitionStarted && velocityX >= 0 && (velocityX > this.swipeGestureMinimumVelocity || deltaX > width / 2);
1682
1834
  const missing = shouldCommit ? 1 - step : step;
1683
1835
  const missingDistance = Math.max(missing, 0) * width;
1684
1836
  const releaseDuration = missingDistance > 5 && Math.abs(velocityX) > 0 ? Math.min(missingDistance / Math.abs(velocityX), 540) : 0;
1837
+ if (pointer.transitionStarted) {
1838
+ this.flushQueuedSwipeGestureStep();
1839
+ } else {
1840
+ this.clearQueuedSwipeGestureStep();
1841
+ }
1685
1842
  this.releaseSwipeGesturePointer(event.pointerId);
1686
1843
  void this.finishSwipeGestureBack(shouldCommit, releaseDuration);
1687
1844
  };
@@ -1689,6 +1846,7 @@ var CapRouterOutlet = class extends HTMLElement {
1689
1846
  this.cancelSwipeGesture(event.pointerId);
1690
1847
  };
1691
1848
  cancelSwipeGesturePointer(pointerId) {
1849
+ this.clearQueuedSwipeGestureStep();
1692
1850
  this.releaseSwipeGesturePointer(pointerId);
1693
1851
  }
1694
1852
  releaseSwipeGesturePointer(pointerId) {
@@ -1707,14 +1865,49 @@ var CapRouterOutlet = class extends HTMLElement {
1707
1865
  return;
1708
1866
  }
1709
1867
  this.releaseSwipeGesturePointer(pointerId);
1868
+ this.clearQueuedSwipeGestureStep();
1710
1869
  if (pointer.transitionStarted) {
1711
1870
  void this.finishSwipeGestureBack(false, 0);
1712
1871
  }
1713
1872
  }
1873
+ queueSwipeGestureStep(step) {
1874
+ this.swipeGesturePendingStep = step;
1875
+ if (this.swipeGestureFrame !== 0) {
1876
+ return;
1877
+ }
1878
+ const win = this.ownerDocument.defaultView;
1879
+ if (!win) {
1880
+ this.flushQueuedSwipeGestureStep();
1881
+ return;
1882
+ }
1883
+ this.swipeGestureFrame = win.requestAnimationFrame(() => {
1884
+ this.swipeGestureFrame = 0;
1885
+ this.flushQueuedSwipeGestureStep();
1886
+ });
1887
+ }
1888
+ flushQueuedSwipeGestureStep() {
1889
+ const step = this.swipeGesturePendingStep;
1890
+ this.swipeGesturePendingStep = null;
1891
+ if (this.swipeGestureFrame !== 0) {
1892
+ this.ownerDocument.defaultView?.cancelAnimationFrame(this.swipeGestureFrame);
1893
+ this.swipeGestureFrame = 0;
1894
+ }
1895
+ if (step !== null) {
1896
+ this.controller.stepInteractiveBack(step);
1897
+ }
1898
+ }
1899
+ clearQueuedSwipeGestureStep() {
1900
+ if (this.swipeGestureFrame !== 0) {
1901
+ this.ownerDocument.defaultView?.cancelAnimationFrame(this.swipeGestureFrame);
1902
+ this.swipeGestureFrame = 0;
1903
+ }
1904
+ this.swipeGesturePendingStep = null;
1905
+ }
1714
1906
  async finishSwipeGestureBack(shouldComplete, releaseDuration) {
1715
- const shouldUseHistory = shouldComplete && typeof window !== "undefined" && window.history.length > 1;
1716
- await this.controller.endInteractiveBack(shouldComplete, releaseDuration, !shouldUseHistory);
1717
- if (!shouldComplete) {
1907
+ const canComplete = shouldComplete && this.getSwipeBackDestination() !== null;
1908
+ const shouldUseHistory = canComplete && typeof window !== "undefined" && window.history.length > 1;
1909
+ await this.controller.endInteractiveBack(canComplete, canComplete ? releaseDuration : 0, !shouldUseHistory);
1910
+ if (!canComplete) {
1718
1911
  return;
1719
1912
  }
1720
1913
  if (shouldUseHistory) {