diff --git a/core/src/components/refresher-content/refresher-content.tsx b/core/src/components/refresher-content/refresher-content.tsx index 35b6554a77..df94479c9d 100644 --- a/core/src/components/refresher-content/refresher-content.tsx +++ b/core/src/components/refresher-content/refresher-content.tsx @@ -1,13 +1,13 @@ import type { ComponentInterface } from '@stencil/core'; import { Component, Element, Host, Prop, h } from '@stencil/core'; import { ENABLE_HTML_CONTENT_DEFAULT } from '@utils/config'; -import { isPlatform } from '@utils/platform'; import { sanitizeDOMString } from '@utils/sanitization'; import { arrowDown, caretBackSharp } from 'ionicons/icons'; import { config } from '../../global/config'; import { getIonMode } from '../../global/ionic-global'; import type { IonicSafeString } from '../../utils/sanitization'; +import { supportsRubberBandScrolling } from '../refresher/refresher.utils'; import type { SpinnerTypes } from '../spinner/spinner-configs'; import { SPINNERS } from '../spinner/spinner-configs'; @@ -63,11 +63,17 @@ export class RefresherContent implements ComponentInterface { componentWillLoad() { if (this.pullingIcon === undefined) { + /** + * The native iOS refresher uses a spinner instead of + * an icon, so we need to see if this device supports + * the native iOS refresher. + */ + const hasRubberBandScrolling = supportsRubberBandScrolling(); const mode = getIonMode(this); - const overflowRefresher = (this.el.style as any).webkitOverflowScrolling !== undefined ? 'lines' : arrowDown; + const overflowRefresher = hasRubberBandScrolling ? 'lines' : arrowDown; this.pullingIcon = config.get( 'refreshingIcon', - mode === 'ios' && isPlatform('mobile') ? config.get('spinner', overflowRefresher) : 'circular' + mode === 'ios' && hasRubberBandScrolling ? config.get('spinner', overflowRefresher) : 'circular' ); } if (this.refreshingSpinner === undefined) { diff --git a/core/src/components/refresher/refresher.utils.ts b/core/src/components/refresher/refresher.utils.ts index 8ae86de258..4becab356d 100644 --- a/core/src/components/refresher/refresher.utils.ts +++ b/core/src/components/refresher/refresher.utils.ts @@ -1,7 +1,6 @@ import { writeTask } from '@stencil/core'; import { createAnimation } from '@utils/animation/animation'; import { clamp, componentOnReady, transitionEndAsync } from '@utils/helpers'; -import { isPlatform } from '@utils/platform'; // MD Native Refresher // ----------------------------- @@ -195,6 +194,25 @@ export const translateElement = (el?: HTMLElement, value?: string, duration = 20 // Utils // ----------------------------- +/** + * In order to use the native iOS refresher the device must support rubber band scrolling. + * As part of this, we need to exclude Desktop Safari because it has a slightly different rubber band effect that is not compatible with the native refresher in Ionic. + * + * We also need to be careful not to include devices that spoof their user agent. + * For example, when using iOS emulation in Chrome the user agent will be spoofed such that + * navigator.maxTouchPointer > 0. To work around this, + * we check to see if the apple-pay-logo is supported as a named image which is only + * true on Apple devices. + * + * We previously checked referencEl.style.webkitOverflowScrolling to explicitly check + * for rubber band support. However, this property was removed on iPadOS and it's possible + * that this will be removed on iOS in the future too. + * + */ +export const supportsRubberBandScrolling = () => { + return navigator.maxTouchPoints > 0 && CSS.supports('background: -webkit-named-image(apple-pay-logo-black)'); +}; + export const shouldUseNativeRefresher = async (referenceEl: HTMLIonRefresherElement, mode: string) => { const refresherContent = referenceEl.querySelector('ion-refresher-content'); if (!refresherContent) { @@ -209,15 +227,6 @@ export const shouldUseNativeRefresher = async (referenceEl: HTMLIonRefresherElem return ( pullingSpinner !== null && refreshingSpinner !== null && - /** - * We use webkitOverflowScrolling for feature detection with rubber band scrolling - * on iOS. When doing referenceEl.style, webkitOverflowScrolling is undefined on non-iOS platforms. - * However, it will be the empty string on iOS. - * Note that we do not use getPropertyValue (and thus need to cast as any) because calling - * getPropertyValue('-webkit-overflow-scrolling') will return the empty string if it is not - * set on the element, even if the platform does not support that. - */ - ((mode === 'ios' && isPlatform('mobile') && (referenceEl.style as any).webkitOverflowScrolling !== undefined) || - mode === 'md') + ((mode === 'ios' && supportsRubberBandScrolling()) || mode === 'md') ); };