diff --git a/core/src/components/modal/gestures/sheet.ts b/core/src/components/modal/gestures/sheet.ts
index cacf61c5b43..a4c155a731e 100644
--- a/core/src/components/modal/gestures/sheet.ts
+++ b/core/src/components/modal/gestures/sheet.ts
@@ -7,7 +7,7 @@ import type { Animation, ModalDragEventDetail } from '../../../interface';
import type { GestureDetail } from '../../../utils/gesture';
import { getBackdropValueForSheet } from '../utils';
-import { calculateSpringStep, handleCanDismiss } from './utils';
+import { calculateSpringStep, canSwipeOnContent, handleCanDismiss } from './utils';
export interface MoveSheetToBreakpointOptions {
/**
@@ -261,9 +261,6 @@ export const createSheetGesture = (
const canStart = (detail: GestureDetail) => {
/**
- * If we are swiping on the content, swiping should only be possible if the content
- * is scrolled all the way to the top so that we do not interfere with scrolling.
- *
* We cannot assume that the `ion-content` target will remain consistent between swipes.
* For example, when using ion-nav within a modal it is possible to swipe, push a view,
* and then swipe again. The target content will not be the same between swipes.
@@ -272,27 +269,11 @@ export const createSheetGesture = (
currentBreakpoint = getCurrentBreakpoint();
/**
- * If `expandToScroll` is disabled, we should not allow the swipe gesture
- * to start if the content is not scrolled to the top.
+ * Upwards swipes on the content cannot move the sheet anyway, so this only
+ * blocks swiping the sheet down from the content.
*/
- if (!expandToScroll && contentEl) {
- const scrollEl = isIonContent(contentEl) ? getElementRoot(contentEl).querySelector('.inner-scroll') : contentEl;
- return scrollEl!.scrollTop === 0;
- }
-
- if (currentBreakpoint === 1 && contentEl) {
- /**
- * The modal should never swipe to close on the content with a refresher.
- * Note 1: We cannot solve this by making this gesture have a higher priority than
- * the refresher gesture as the iOS native refresh gesture uses a scroll listener in
- * addition to a gesture.
- *
- * Note 2: Do not use getScrollElement here because we need this to be a synchronous
- * operation, and getScrollElement is asynchronous.
- */
- const scrollEl = isIonContent(contentEl) ? getElementRoot(contentEl).querySelector('.inner-scroll') : contentEl;
- const hasRefresherInContent = !!contentEl.querySelector('ion-refresher');
- return !hasRefresherInContent && scrollEl!.scrollTop === 0;
+ if (contentEl && (!expandToScroll || currentBreakpoint === 1)) {
+ return canSwipeOnContent(contentEl);
}
return true;
diff --git a/core/src/components/modal/gestures/swipe-to-close.ts b/core/src/components/modal/gestures/swipe-to-close.ts
index d04220c88e7..f5b753d8b01 100644
--- a/core/src/components/modal/gestures/swipe-to-close.ts
+++ b/core/src/components/modal/gestures/swipe-to-close.ts
@@ -1,7 +1,7 @@
import { getTimeGivenProgression } from '@utils/animation/cubic-bezier';
import { isIonContent, findClosestIonContent, disableContentScrollY, resetContentScrollY } from '@utils/content';
import { createGesture } from '@utils/gesture';
-import { clamp, getElementRoot } from '@utils/helpers';
+import { clamp } from '@utils/helpers';
import { OVERLAY_GESTURE_PRIORITY } from '@utils/overlays';
import type { Animation, ModalDragEventDetail } from '../../../interface';
@@ -9,7 +9,7 @@ import type { GestureDetail } from '../../../utils/gesture';
import type { Style as StatusBarStyle } from '../../../utils/native/status-bar';
import { setCardStatusBarDark, setCardStatusBarDefault } from '../utils';
-import { calculateSpringStep, handleCanDismiss } from './utils';
+import { calculateSpringStep, canSwipeOnContent, handleCanDismiss } from './utils';
// Defaults for the card swipe animation
export const SwipeToCloseDefaults = {
@@ -35,7 +35,6 @@ export const createSwipeToCloseGesture = (
let isOpen = false;
let canDismissBlocksGesture = false;
let contentEl: HTMLElement | null = null;
- let scrollEl: HTMLElement | null = null;
const canDismissMaxStep = 0.2;
let initialScrollY = true;
let lastStep = 0;
@@ -60,12 +59,6 @@ export const createSwipeToCloseGesture = (
}
/**
- * If we are swiping on the content,
- * swiping should only be possible if
- * the content is scrolled all the way
- * to the top so that we do not interfere
- * with scrolling.
- *
* We cannot assume that the `ion-content`
* target will remain consistent between
* swipes. For example, when using
@@ -76,29 +69,7 @@ export const createSwipeToCloseGesture = (
*/
contentEl = findClosestIonContent(target);
if (contentEl) {
- /**
- * The card should never swipe to close
- * on the content with a refresher.
- * Note: We cannot solve this by making the
- * swipeToClose gesture have a higher priority
- * than the refresher gesture as the iOS native
- * refresh gesture uses a scroll listener in
- * addition to a gesture.
- *
- * Note: Do not use getScrollElement here
- * because we need this to be a synchronous
- * operation, and getScrollElement is
- * asynchronous.
- */
- if (isIonContent(contentEl)) {
- const root = getElementRoot(contentEl);
- scrollEl = root.querySelector('.inner-scroll');
- } else {
- scrollEl = contentEl;
- }
-
- const hasRefresherInContent = !!contentEl.querySelector('ion-refresher');
- return !hasRefresherInContent && scrollEl!.scrollTop === 0;
+ return canSwipeOnContent(contentEl);
}
/**
diff --git a/core/src/components/modal/gestures/utils.ts b/core/src/components/modal/gestures/utils.ts
index 694227d630d..270341719a8 100644
--- a/core/src/components/modal/gestures/utils.ts
+++ b/core/src/components/modal/gestures/utils.ts
@@ -1,7 +1,26 @@
+import { findRefresherInContent, isIonContent } from '@utils/content';
+import { getElementRoot } from '@utils/helpers';
import { GESTURE } from '@utils/overlays';
import type { Animation } from '../../../interface';
+/**
+ * Swiping is only possible when the content is scrolled to the top, so that we
+ * do not interfere with scrolling, and never on content with a refresher.
+ *
+ * Note: We cannot solve the refresher case with gesture priority as the iOS
+ * native refresh gesture uses a scroll listener in addition to a gesture.
+ *
+ * Note: Do not use `getScrollElement` here because we need this to be a
+ * synchronous operation, and `getScrollElement` is asynchronous.
+ */
+export const canSwipeOnContent = (contentEl: HTMLElement) => {
+ const scrollEl = isIonContent(contentEl) ? getElementRoot(contentEl).querySelector('.inner-scroll') : contentEl;
+ const hasRefresherInContent = !!findRefresherInContent(contentEl);
+
+ return !hasRefresherInContent && scrollEl!.scrollTop === 0;
+};
+
export const handleCanDismiss = async (el: HTMLIonModalElement, animation: Animation) => {
/**
* If canDismiss is not a function
diff --git a/core/src/components/modal/test/refresher-scroll-target/index.html b/core/src/components/modal/test/refresher-scroll-target/index.html
new file mode 100644
index 00000000000..12206de68df
--- /dev/null
+++ b/core/src/components/modal/test/refresher-scroll-target/index.html
@@ -0,0 +1,136 @@
+
+
+