text: import { c as writeTask, r as registerInstance, e as createEvent, f as readTask, h, i as getElement, H as Host } from './index-e806d1f6.js';
  import { i as isPlatform, b as getIonMode, c as config } from './ionic-global-9d5c8ee3.js';
  import { h as clamp, g as getElementRoot, r as raf } from './helpers-90f46169.js';
  import { c as createAnimation } from './animation-54fe0237.js';
  import { g as getTimeGivenProgression } from './cubic-bezier-eea9a7a9.js';
  import { s as sanitizeDOMString } from './index-9e3fe806.js';
  import { d as hapticImpact } from './haptic-27b3f981.js';
  import { S as SPINNERS } from './spinner-configs-cd7845af.js';
  
  const getRefresherAnimationType = (contentEl) => {
    const previousSibling = contentEl.previousElementSibling;
    const hasHeader = previousSibling !== null && previousSibling.tagName === 'ION-HEADER';
    return hasHeader ? 'translate' : 'scale';
  };
  const createPullingAnimation = (type, pullingSpinner) => {
    return type === 'scale' ? createScaleAnimation(pullingSpinner) : createTranslateAnimation(pullingSpinner);
  };
  const createBaseAnimation = (pullingRefresherIcon) => {
    const spinner = pullingRefresherIcon.querySelector('ion-spinner');
    const circle = spinner.shadowRoot.querySelector('circle');
    const spinnerArrowContainer = pullingRefresherIcon.querySelector('.spinner-arrow-container');
    const arrowContainer = pullingRefresherIcon.querySelector('.arrow-container');
    const arrow = (arrowContainer) ? arrowContainer.querySelector('ion-icon') : null;
    const baseAnimation = createAnimation()
      .duration(1000)
      .easing('ease-out');
    const spinnerArrowContainerAnimation = createAnimation()
      .addElement(spinnerArrowContainer)
      .keyframes([
      { offset: 0, opacity: '0.3' },
      { offset: 0.45, opacity: '0.3' },
      { offset: 0.55, opacity: '1' },
      { offset: 1, opacity: '1' }
    ]);
    const circleInnerAnimation = createAnimation()
      .addElement(circle)
      .keyframes([
      { offset: 0, strokeDasharray: '1px, 200px' },
      { offset: 0.20, strokeDasharray: '1px, 200px' },
      { offset: 0.55, strokeDasharray: '100px, 200px' },
      { offset: 1, strokeDasharray: '100px, 200px' }
    ]);
    const circleOuterAnimation = createAnimation()
      .addElement(spinner)
      .keyframes([
      { offset: 0, transform: 'rotate(-90deg)' },
      { offset: 1, transform: 'rotate(210deg)' }
    ]);
    /**
     * Only add arrow animation if present
     * this allows users to customize the spinners
     * without errors being thrown
     */
    if (arrowContainer && arrow) {
      const arrowContainerAnimation = createAnimation()
        .addElement(arrowContainer)
        .keyframes([
        { offset: 0, transform: 'rotate(0deg)' },
        { offset: 0.30, transform: 'rotate(0deg)' },
        { offset: 0.55, transform: 'rotate(280deg)' },
        { offset: 1, transform: 'rotate(400deg)' }
      ]);
      const arrowAnimation = createAnimation()
        .addElement(arrow)
        .keyframes([
        { offset: 0, transform: 'translateX(2px) scale(0)' },
        { offset: 0.30, transform: 'translateX(2px) scale(0)' },
        { offset: 0.55, transform: 'translateX(-1.5px) scale(1)' },
        { offset: 1, transform: 'translateX(-1.5px) scale(1)' }
      ]);
      baseAnimation.addAnimation([arrowContainerAnimation, arrowAnimation]);
    }
    return baseAnimation.addAnimation([spinnerArrowContainerAnimation, circleInnerAnimation, circleOuterAnimation]);
  };
  const createScaleAnimation = (pullingRefresherIcon) => {
    const height = pullingRefresherIcon.clientHeight;
    const spinnerAnimation = createAnimation()
      .addElement(pullingRefresherIcon)
      .keyframes([
      { offset: 0, transform: `scale(0) translateY(-${height + 20}px)` },
      { offset: 1, transform: 'scale(1) translateY(100px)' }
    ]);
    return createBaseAnimation(pullingRefresherIcon).addAnimation([spinnerAnimation]);
  };
  const createTranslateAnimation = (pullingRefresherIcon) => {
    const height = pullingRefresherIcon.clientHeight;
    const spinnerAnimation = createAnimation()
      .addElement(pullingRefresherIcon)
      .keyframes([
      { offset: 0, transform: `translateY(-${height + 20}px)` },
      { offset: 1, transform: 'translateY(100px)' }
    ]);
    return createBaseAnimation(pullingRefresherIcon).addAnimation([spinnerAnimation]);
  };
  const createSnapBackAnimation = (pullingRefresherIcon) => {
    return createAnimation()
      .duration(125)
      .addElement(pullingRefresherIcon)
      .fromTo('transform', 'translateY(var(--ion-pulling-refresher-translate, 100px))', 'translateY(0px)');
  };
  // iOS Native Refresher
  // -----------------------------
  const setSpinnerOpacity = (spinner, opacity) => {
    spinner.style.setProperty('opacity', opacity.toString());
  };
  const handleScrollWhilePulling = (spinner, ticks, opacity, currentTickToShow) => {
    writeTask(() => {
      setSpinnerOpacity(spinner, opacity);
      ticks.forEach((el, i) => el.style.setProperty('opacity', (i <= currentTickToShow) ? '0.99' : '0'));
    });
  };
  const handleScrollWhileRefreshing = (spinner, lastVelocityY) => {
    writeTask(() => {
      // If user pulls down quickly, the spinner should spin faster
      spinner.style.setProperty('--refreshing-rotation-duration', (lastVelocityY >= 1.0) ? '0.5s' : '2s');
      spinner.style.setProperty('opacity', '1');
    });
  };
  const translateElement = (el, value) => {
    if (!el) {
      return Promise.resolve();
    }
    const trans = transitionEndAsync(el, 200);
    writeTask(() => {
      el.style.setProperty('transition', '0.2s all ease-out');
      if (value === undefined) {
        el.style.removeProperty('transform');
      }
      else {
        el.style.setProperty('transform', `translate3d(0px, ${value}, 0px)`);
      }
    });
    return trans;
  };
  // Utils
  // -----------------------------
  const shouldUseNativeRefresher = async (referenceEl, mode) => {
    const refresherContent = referenceEl.querySelector('ion-refresher-content');
    if (!refresherContent) {
      return Promise.resolve(false);
    }
    await refresherContent.componentOnReady();
    const pullingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-pulling ion-spinner');
    const refreshingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-refreshing ion-spinner');
    return (pullingSpinner !== null &&
      refreshingSpinner !== null &&
      ((mode === 'ios' && isPlatform('mobile') && referenceEl.style.webkitOverflowScrolling !== undefined) ||
        mode === 'md'));
  };
  const transitionEndAsync = (el, expectedDuration = 0) => {
    return new Promise(resolve => {
      transitionEnd(el, expectedDuration, resolve);
    });
  };
  const transitionEnd = (el, expectedDuration = 0, callback) => {
    let unRegTrans;
    let animationTimeout;
    const opts = { passive: true };
    const ANIMATION_FALLBACK_TIMEOUT = 500;
    const unregister = () => {
      if (unRegTrans) {
        unRegTrans();
      }
    };
    const onTransitionEnd = (ev) => {
      if (ev === undefined || el === ev.target) {
        unregister();
        callback(ev);
      }
    };
    if (el) {
      el.addEventListener('webkitTransitionEnd', onTransitionEnd, opts);
      el.addEventListener('transitionend', onTransitionEnd, opts);
      animationTimeout = setTimeout(onTransitionEnd, expectedDuration + ANIMATION_FALLBACK_TIMEOUT);
      unRegTrans = () => {
        if (animationTimeout) {
          clearTimeout(animationTimeout);
          animationTimeout = undefined;
        }
        el.removeEventListener('webkitTransitionEnd', onTransitionEnd, opts);
        el.removeEventListener('transitionend', onTransitionEnd, opts);
      };
    }
    return unregister;
  };
  
  const refresherIosCss = "ion-refresher{left:0;top:0;display:none;position:absolute;width:100%;height:60px;pointer-events:none;z-index:-1}[dir=rtl] ion-refresher,:host-context([dir=rtl]) ion-refresher{left:unset;right:unset;right:0}ion-refresher.refresher-active{display:block}ion-refresher-content{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.refresher-pulling,.refresher-refreshing{display:none;width:100%}.refresher-pulling-icon,.refresher-refreshing-icon{-webkit-transform-origin:center;transform-origin:center;-webkit-transition:200ms;transition:200ms;font-size:30px;text-align:center}[dir=rtl] .refresher-pulling-icon,:host-context([dir=rtl]) .refresher-pulling-icon,[dir=rtl] .refresher-refreshing-icon,:host-context([dir=rtl]) .refresher-refreshing-icon{-webkit-transform-origin:calc(100% - center);transform-origin:calc(100% - center)}.refresher-pulling-text,.refresher-refreshing-text{font-size:16px;text-align:center}ion-refresher-content .arrow-container{display:none}.refresher-pulling ion-refresher-content .refresher-pulling{display:block}.refresher-ready ion-refresher-content .refresher-pulling{display:block}.refresher-ready ion-refresher-content .refresher-pulling-icon{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.refresher-refreshing ion-refresher-content .refresher-refreshing{display:block}.refresher-cancelling ion-refresher-content .refresher-pulling{display:block}.refresher-cancelling ion-refresher-content .refresher-pulling-icon{-webkit-transform:scale(0);transform:scale(0)}.refresher-completing ion-refresher-content .refresher-refreshing{display:block}.refresher-completing ion-refresher-content .refresher-refreshing-icon{-webkit-transform:scale(0);transform:scale(0)}.refresher-native .refresher-pulling-text,.refresher-native .refresher-refreshing-text{display:none}.refresher-ios .refresher-pulling-icon,.refresher-ios .refresher-refreshing-icon{color:var(--ion-text-color, #000)}.refresher-ios .refresher-pulling-text,.refresher-ios .refresher-refreshing-text{color:var(--ion-text-color, #000)}.refresher-ios .refresher-refreshing .spinner-lines-ios line,.refresher-ios .refresher-refreshing .spinner-lines-small-ios line,.refresher-ios .refresher-refreshing .spinner-crescent circle{stroke:var(--ion-text-color, #000)}.refresher-ios .refresher-refreshing .spinner-bubbles circle,.refresher-ios .refresher-refreshing .spinner-circles circle,.refresher-ios .refresher-refreshing .spinner-dots circle{fill:var(--ion-text-color, #000)}ion-refresher.refresher-native{display:block;z-index:1}ion-refresher.refresher-native ion-spinner{margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0}@supports ((-webkit-margin-start: 0) or (margin-inline-start: 0)) or (-webkit-margin-start: 0){ion-refresher.refresher-native ion-spinner{margin-left:unset;margin-right:unset;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto}}.refresher-native .refresher-refreshing ion-spinner{--refreshing-rotation-duration:2s;display:none;-webkit-animation:var(--refreshing-rotation-duration) ease-out refresher-rotate forwards;animation:var(--refreshing-rotation-duration) ease-out refresher-rotate forwards}.refresher-native .refresher-refreshing{display:none;-webkit-animation:250ms linear refresher-pop forwards;animation:250ms linear refresher-pop forwards}.refresher-native.refresher-refreshing .refresher-pulling ion-spinner,.refresher-native.refresher-completing .refresher-pulling ion-spinner{display:none}.refresher-native.refresher-refreshing .refresher-refreshing ion-spinner,.refresher-native.refresher-completing .refresher-refreshing ion-spinner{display:block}.refresher-native.refresher-pulling .refresher-pulling ion-spinner{display:block}.refresher-native.refresher-pulling .refresher-refreshing ion-spinner{display:none}@-webkit-keyframes refresher-pop{0%{-webkit-transform:scale(1);transform:scale(1);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}50%{-webkit-transform:scale(1.2);transform:scale(1.2);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}100%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes refresher-pop{0%{-webkit-transform:scale(1);transform:scale(1);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}50%{-webkit-transform:scale(1.2);transform:scale(1.2);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}100%{-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes refresher-rotate{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(180deg);transform:rotate(180deg)}}@keyframes refresher-rotate{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(180deg);transform:rotate(180deg)}}";
  
  const refresherMdCss = "ion-refresher{left:0;top:0;display:none;position:absolute;width:100%;height:60px;pointer-events:none;z-index:-1}[dir=rtl] ion-refresher,:host-context([dir=rtl]) ion-refresher{left:unset;right:unset;right:0}ion-refresher.refresher-active{display:block}ion-refresher-content{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.refresher-pulling,.refresher-refreshing{display:none;width:100%}.refresher-pulling-icon,.refresher-refreshing-icon{-webkit-transform-origin:center;transform-origin:center;-webkit-transition:200ms;transition:200ms;font-size:30px;text-align:center}[dir=rtl] .refresher-pulling-icon,:host-context([dir=rtl]) .refresher-pulling-icon,[dir=rtl] .refresher-refreshing-icon,:host-context([dir=rtl]) .refresher-refreshing-icon{-webkit-transform-origin:calc(100% - center);transform-origin:calc(100% - center)}.refresher-pulling-text,.refresher-refreshing-text{font-size:16px;text-align:center}ion-refresher-content .arrow-container{display:none}.refresher-pulling ion-refresher-content .refresher-pulling{display:block}.refresher-ready ion-refresher-content .refresher-pulling{display:block}.refresher-ready ion-refresher-content .refresher-pulling-icon{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.refresher-refreshing ion-refresher-content .refresher-refreshing{display:block}.refresher-cancelling ion-refresher-content .refresher-pulling{display:block}.refresher-cancelling ion-refresher-content .refresher-pulling-icon{-webkit-transform:scale(0);transform:scale(0)}.refresher-completing ion-refresher-content .refresher-refreshing{display:block}.refresher-completing ion-refresher-content .refresher-refreshing-icon{-webkit-transform:scale(0);transform:scale(0)}.refresher-native .refresher-pulling-text,.refresher-native .refresher-refreshing-text{display:none}.refresher-md .refresher-pulling-icon,.refresher-md .refresher-refreshing-icon{color:var(--ion-text-color, #000)}.refresher-md .refresher-pulling-text,.refresher-md .refresher-refreshing-text{color:var(--ion-text-color, #000)}.refresher-md .refresher-refreshing .spinner-lines-md line,.refresher-md .refresher-refreshing .spinner-lines-small-md line,.refresher-md .refresher-refreshing .spinner-crescent circle{stroke:var(--ion-text-color, #000)}.refresher-md .refresher-refreshing .spinner-bubbles circle,.refresher-md .refresher-refreshing .spinner-circles circle,.refresher-md .refresher-refreshing .spinner-dots circle{fill:var(--ion-text-color, #000)}ion-refresher.refresher-native{display:block;z-index:1}ion-refresher.refresher-native ion-spinner{margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;width:24px;height:24px;color:var(--ion-color-primary, #3880ff)}@supports ((-webkit-margin-start: 0) or (margin-inline-start: 0)) or (-webkit-margin-start: 0){ion-refresher.refresher-native ion-spinner{margin-left:unset;margin-right:unset;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto}}ion-refresher.refresher-native .spinner-arrow-container{display:inherit}ion-refresher.refresher-native .arrow-container{display:block;position:absolute;width:24px;height:24px}ion-refresher.refresher-native .arrow-container ion-icon{margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;left:0;right:0;bottom:-4px;position:absolute;color:var(--ion-color-primary, #3880ff);font-size:12px}@supports ((-webkit-margin-start: 0) or (margin-inline-start: 0)) or (-webkit-margin-start: 0){ion-refresher.refresher-native .arrow-container ion-icon{margin-left:unset;margin-right:unset;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto}}ion-refresher.refresher-native.refresher-pulling ion-refresher-content .refresher-pulling,ion-refresher.refresher-native.refresher-ready ion-refresher-content .refresher-pulling{display:-ms-flexbox;display:flex}ion-refresher.refresher-native.refresher-refreshing ion-refresher-content .refresher-refreshing,ion-refresher.refresher-native.refresher-completing ion-refresher-content .refresher-refreshing,ion-refresher.refresher-native.refresher-cancelling ion-refresher-content .refresher-refreshing{display:-ms-flexbox;display:flex}ion-refresher.refresher-native .refresher-pulling-icon{-webkit-transform:translateY(calc(-100% - 10px));transform:translateY(calc(-100% - 10px))}ion-refresher.refresher-native .refresher-pulling-icon,ion-refresher.refresher-native .refresher-refreshing-icon{margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;border-radius:100%;padding-left:8px;padding-right:8px;padding-top:8px;padding-bottom:8px;display:-ms-flexbox;display:flex;border:1px solid #ececec;background:white;-webkit-box-shadow:0px 1px 6px rgba(0, 0, 0, 0.1);box-shadow:0px 1px 6px rgba(0, 0, 0, 0.1)}@supports ((-webkit-margin-start: 0) or (margin-inline-start: 0)) or (-webkit-margin-start: 0){ion-refresher.refresher-native .refresher-pulling-icon,ion-refresher.refresher-native .refresher-refreshing-icon{margin-left:unset;margin-right:unset;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto}}@supports ((-webkit-margin-start: 0) or (margin-inline-start: 0)) or (-webkit-margin-start: 0){ion-refresher.refresher-native .refresher-pulling-icon,ion-refresher.refresher-native .refresher-refreshing-icon{padding-left:unset;padding-right:unset;-webkit-padding-start:8px;padding-inline-start:8px;-webkit-padding-end:8px;padding-inline-end:8px}}";
  
  const Refresher = class {
    constructor(hostRef) {
      registerInstance(this, hostRef);
      this.ionRefresh = createEvent(this, "ionRefresh", 7);
      this.ionPull = createEvent(this, "ionPull", 7);
      this.ionStart = createEvent(this, "ionStart", 7);
      this.appliedStyles = false;
      this.didStart = false;
      this.progress = 0;
      this.pointerDown = false;
      this.needsCompletion = false;
      this.didRefresh = false;
      this.lastVelocityY = 0;
      this.animations = [];
      this.nativeRefresher = false;
      /**
       * The current state which the refresher is in. The refresher's states include:
       *
       * - `inactive` - The refresher is not being pulled down or refreshing and is currently hidden.
       * - `pulling` - The user is actively pulling down the refresher, but has not reached the point yet that if the user lets go, it'll refresh.
       * - `cancelling` - The user pulled down the refresher and let go, but did not pull down far enough to kick off the `refreshing` state. After letting go, the refresher is in the `cancelling` state while it is closing, and will go back to the `inactive` state once closed.
       * - `ready` - The user has pulled down the refresher far enough that if they let go, it'll begin the `refreshing` state.
       * - `refreshing` - The refresher is actively waiting on the async operation to end. Once the refresh handler calls `complete()` it will begin the `completing` state.
       * - `completing` - The `refreshing` state has finished and the refresher is in the way of closing itself. Once closed, the refresher will go back to the `inactive` state.
       */
      this.state = 1 /* Inactive */;
      /**
       * The minimum distance the user must pull down until the
       * refresher will go into the `refreshing` state.
       * Does not apply when the refresher content uses a spinner,
       * enabling the native refresher.
       */
      this.pullMin = 60;
      /**
       * The maximum distance of the pull until the refresher
       * will automatically go into the `refreshing` state.
       * Defaults to the result of `pullMin + 60`.
       * Does not apply when  the refresher content uses a spinner,
       * enabling the native refresher.
       */
      this.pullMax = this.pullMin + 60;
      /**
       * Time it takes to close the refresher.
       * Does not apply when the refresher content uses a spinner,
       * enabling the native refresher.
       */
      this.closeDuration = '280ms';
      /**
       * Time it takes the refresher to to snap back to the `refreshing` state.
       * Does not apply when the refresher content uses a spinner,
       * enabling the native refresher.
       */
      this.snapbackDuration = '280ms';
      /**
       * How much to multiply the pull speed by. To slow the pull animation down,
       * pass a number less than `1`. To speed up the pull, pass a number greater
       * than `1`. The default value is `1` which is equal to the speed of the cursor.
       * If a negative value is passed in, the factor will be `1` instead.
       *
       * For example: If the value passed is `1.2` and the content is dragged by
       * `10` pixels, instead of `10` pixels the content will be pulled by `12` pixels
       * (an increase of 20 percent). If the value passed is `0.8`, the dragged amount
       * will be `8` pixels, less than the amount the cursor has moved.
       *
       * Does not apply when the refresher content uses a spinner,
       * enabling the native refresher.
       */
      this.pullFactor = 1;
      /**
       * If `true`, the refresher will be hidden.
       */
      this.disabled = false;
    }
    disabledChanged() {
      if (this.gesture) {
        this.gesture.enable(!this.disabled);
      }
    }
    async checkNativeRefresher() {
      const useNativeRefresher = await shouldUseNativeRefresher(this.el, getIonMode(this));
      if (useNativeRefresher && !this.nativeRefresher) {
        const contentEl = this.el.closest('ion-content');
        this.setupNativeRefresher(contentEl);
      }
      else if (!useNativeRefresher) {
        this.destroyNativeRefresher();
      }
    }
    destroyNativeRefresher() {
      if (this.scrollEl && this.scrollListenerCallback) {
        this.scrollEl.removeEventListener('scroll', this.scrollListenerCallback);
        this.scrollListenerCallback = undefined;
      }
      this.nativeRefresher = false;
    }
    async resetNativeRefresher(el, state) {
      this.state = state;
      if (getIonMode(this) === 'ios') {
        await translateElement(el, undefined);
      }
      else {
        await transitionEndAsync(this.el.querySelector('.refresher-refreshing-icon'), 200);
      }
      this.didRefresh = false;
      this.needsCompletion = false;
      this.pointerDown = false;
      this.animations.forEach(ani => ani.destroy());
      this.animations = [];
      this.progress = 0;
      this.state = 1 /* Inactive */;
    }
    async setupiOSNativeRefresher(pullingSpinner, refreshingSpinner) {
      this.elementToTransform = this.scrollEl;
      const ticks = pullingSpinner.shadowRoot.querySelectorAll('svg');
      let MAX_PULL = this.scrollEl.clientHeight * 0.16;
      const NUM_TICKS = ticks.length;
      writeTask(() => ticks.forEach(el => el.style.setProperty('animation', 'none')));
      this.scrollListenerCallback = () => {
        // If pointer is not on screen or refresher is not active, ignore scroll
        if (!this.pointerDown && this.state === 1 /* Inactive */) {
          return;
        }
        readTask(() => {
          // PTR should only be active when overflow scrolling at the top
          const scrollTop = this.scrollEl.scrollTop;
          const refresherHeight = this.el.clientHeight;
          if (scrollTop > 0) {
            /**
             * If refresher is refreshing and user tries to scroll
             * progressively fade refresher out/in
             */
            if (this.state === 8 /* Refreshing */) {
              const ratio = clamp(0, scrollTop / (refresherHeight * 0.5), 1);
              writeTask(() => setSpinnerOpacity(refreshingSpinner, 1 - ratio));
              return;
            }
            writeTask(() => setSpinnerOpacity(pullingSpinner, 0));
            return;
          }
          if (this.pointerDown) {
            if (!this.didStart) {
              this.didStart = true;
              this.ionStart.emit();
            }
            // emit "pulling" on every move
            if (this.pointerDown) {
              this.ionPull.emit();
            }
          }
          // delay showing the next tick marks until user has pulled 30px
          const opacity = clamp(0, Math.abs(scrollTop) / refresherHeight, 0.99);
          const pullAmount = this.progress = clamp(0, (Math.abs(scrollTop) - 30) / MAX_PULL, 1);
          const currentTickToShow = clamp(0, Math.floor(pullAmount * NUM_TICKS), NUM_TICKS - 1);
          const shouldShowRefreshingSpinner = this.state === 8 /* Refreshing */ || currentTickToShow === NUM_TICKS - 1;
          if (shouldShowRefreshingSpinner) {
            if (this.pointerDown) {
              handleScrollWhileRefreshing(refreshingSpinner, this.lastVelocityY);
            }
            if (!this.didRefresh) {
              this.beginRefresh();
              this.didRefresh = true;
              hapticImpact({ style: 'light' });
              /**
               * Translate the content element otherwise when pointer is removed
               * from screen the scroll content will bounce back over the refresher
               */
              if (!this.pointerDown) {
                translateElement(this.elementToTransform, `${refresherHeight}px`);
              }
            }
          }
          else {
            this.state = 2 /* Pulling */;
            handleScrollWhilePulling(pullingSpinner, ticks, opacity, currentTickToShow);
          }
        });
      };
      this.scrollEl.addEventListener('scroll', this.scrollListenerCallback);
      this.gesture = (await import('./index-f49d994d.js')).createGesture({
        el: this.scrollEl,
        gestureName: 'refresher',
        gesturePriority: 31,
        direction: 'y',
        threshold: 5,
        onStart: () => {
          this.pointerDown = true;
          if (!this.didRefresh) {
            translateElement(this.elementToTransform, '0px');
          }
          /**
           * If the content had `display: none` when
           * the refresher was initialized, its clientHeight
           * will be 0. When the gesture starts, the content
           * will be visible, so try to get the correct
           * client height again. This is most common when
           * using the refresher in an ion-menu.
           */
          if (MAX_PULL === 0) {
            MAX_PULL = this.scrollEl.clientHeight * 0.16;
          }
        },
        onMove: ev => {
          this.lastVelocityY = ev.velocityY;
        },
        onEnd: () => {
          this.pointerDown = false;
          this.didStart = false;
          if (this.needsCompletion) {
            this.resetNativeRefresher(this.elementToTransform, 32 /* Completing */);
            this.needsCompletion = false;
          }
          else if (this.didRefresh) {
            readTask(() => translateElement(this.elementToTransform, `${this.el.clientHeight}px`));
          }
        },
      });
      this.disabledChanged();
    }
    async setupMDNativeRefresher(contentEl, pullingSpinner, refreshingSpinner) {
      const circle = getElementRoot(pullingSpinner).querySelector('circle');
      const pullingRefresherIcon = this.el.querySelector('ion-refresher-content .refresher-pulling-icon');
      const refreshingCircle = getElementRoot(refreshingSpinner).querySelector('circle');
      if (circle !== null && refreshingCircle !== null) {
        writeTask(() => {
          circle.style.setProperty('animation', 'none');
          // This lines up the animation on the refreshing spinner with the pulling spinner
          refreshingSpinner.style.setProperty('animation-delay', '-655ms');
          refreshingCircle.style.setProperty('animation-delay', '-655ms');
        });
      }
      this.gesture = (await import('./index-f49d994d.js')).createGesture({
        el: this.scrollEl,
        gestureName: 'refresher',
        gesturePriority: 31,
        direction: 'y',
        threshold: 5,
        canStart: () => this.state !== 8 /* Refreshing */ && this.state !== 32 /* Completing */ && this.scrollEl.scrollTop === 0,
        onStart: (ev) => {
          ev.data = { animation: undefined, didStart: false, cancelled: false };
        },
        onMove: (ev) => {
          if ((ev.velocityY < 0 && this.progress === 0 && !ev.data.didStart) || ev.data.cancelled) {
            ev.data.cancelled = true;
            return;
          }
          if (!ev.data.didStart) {
            ev.data.didStart = true;
            this.state = 2 /* Pulling */;
            writeTask(() => {
              const animationType = getRefresherAnimationType(contentEl);
              const animation = createPullingAnimation(animationType, pullingRefresherIcon);
              ev.data.animation = animation;
              this.scrollEl.style.setProperty('--overflow', 'hidden');
              animation.progressStart(false, 0);
              this.ionStart.emit();
              this.animations.push(animation);
            });
            return;
          }
          // Since we are using an easing curve, slow the gesture tracking down a bit
          this.progress = clamp(0, (ev.deltaY / 180) * 0.5, 1);
          ev.data.animation.progressStep(this.progress);
          this.ionPull.emit();
        },
        onEnd: (ev) => {
          if (!ev.data.didStart) {
            return;
          }
          writeTask(() => this.scrollEl.style.removeProperty('--overflow'));
          if (this.progress <= 0.4) {
            this.gesture.enable(false);
            ev.data.animation
              .progressEnd(0, this.progress, 500)
              .onFinish(() => {
              this.animations.forEach(ani => ani.destroy());
              this.animations = [];
              this.gesture.enable(true);
              this.state = 1 /* Inactive */;
            });
            return;
          }
          const progress = getTimeGivenProgression([0, 0], [0, 0], [1, 1], [1, 1], this.progress)[0];
          const snapBackAnimation = createSnapBackAnimation(pullingRefresherIcon);
          this.animations.push(snapBackAnimation);
          writeTask(async () => {
            pullingRefresherIcon.style.setProperty('--ion-pulling-refresher-translate', `${(progress * 100)}px`);
            ev.data.animation.progressEnd();
            await snapBackAnimation.play();
            this.beginRefresh();
            ev.data.animation.destroy();
          });
        }
      });
      this.disabledChanged();
    }
    async setupNativeRefresher(contentEl) {
      if (this.scrollListenerCallback || !contentEl || this.nativeRefresher || !this.scrollEl) {
        return;
      }
      /**
       * If using non-native refresher before make sure
       * we clean up any old CSS. This can happen when
       * a user manually calls the refresh method in a
       * component create callback before the native
       * refresher is setup.
       */
      this.setCss(0, '', false, '');
      this.nativeRefresher = true;
      const pullingSpinner = this.el.querySelector('ion-refresher-content .refresher-pulling ion-spinner');
      const refreshingSpinner = this.el.querySelector('ion-refresher-content .refresher-refreshing ion-spinner');
      if (getIonMode(this) === 'ios') {
        this.setupiOSNativeRefresher(pullingSpinner, refreshingSpinner);
      }
      else {
        this.setupMDNativeRefresher(contentEl, pullingSpinner, refreshingSpinner);
      }
    }
    componentDidUpdate() {
      this.checkNativeRefresher();
    }
    async connectedCallback() {
      if (this.el.getAttribute('slot') !== 'fixed') {
        console.error('Make sure you use: <ion-refresher slot="fixed">');
        return;
      }
      const contentEl = this.el.closest('ion-content');
      if (!contentEl) {
        console.error('<ion-refresher> must be used inside an <ion-content>');
        return;
      }
      await contentEl.componentOnReady();
      this.scrollEl = await contentEl.getScrollElement();
      this.backgroundContentEl = getElementRoot(contentEl).querySelector('#background-content');
      if (await shouldUseNativeRefresher(this.el, getIonMode(this))) {
        this.setupNativeRefresher(contentEl);
      }
      else {
        this.gesture = (await import('./index-f49d994d.js')).createGesture({
          el: contentEl,
          gestureName: 'refresher',
          gesturePriority: 31,
          direction: 'y',
          threshold: 20,
          passive: false,
          canStart: () => this.canStart(),
          onStart: () => this.onStart(),
          onMove: ev => this.onMove(ev),
          onEnd: () => this.onEnd(),
        });
        this.disabledChanged();
      }
    }
    disconnectedCallback() {
      this.destroyNativeRefresher();
      this.scrollEl = undefined;
      if (this.gesture) {
        this.gesture.destroy();
        this.gesture = undefined;
      }
    }
    /**
     * Call `complete()` when your async operation has completed.
     * For example, the `refreshing` state is while the app is performing
     * an asynchronous operation, such as receiving more data from an
     * AJAX request. Once the data has been received, you then call this
     * method to signify that the refreshing has completed and to close
     * the refresher. This method also changes the refresher's state from
     * `refreshing` to `completing`.
     */
    async complete() {
      if (this.nativeRefresher) {
        this.needsCompletion = true;
        // Do not reset scroll el until user removes pointer from screen
        if (!this.pointerDown) {
          raf(() => raf(() => this.resetNativeRefresher(this.elementToTransform, 32 /* Completing */)));
        }
      }
      else {
        this.close(32 /* Completing */, '120ms');
      }
    }
    /**
     * Changes the refresher's state from `refreshing` to `cancelling`.
     */
    async cancel() {
      if (this.nativeRefresher) {
        // Do not reset scroll el until user removes pointer from screen
        if (!this.pointerDown) {
          raf(() => raf(() => this.resetNativeRefresher(this.elementToTransform, 16 /* Cancelling */)));
        }
      }
      else {
        this.close(16 /* Cancelling */, '');
      }
    }
    /**
     * A number representing how far down the user has pulled.
     * The number `0` represents the user hasn't pulled down at all. The
     * number `1`, and anything greater than `1`, represents that the user
     * has pulled far enough down that when they let go then the refresh will
     * happen. If they let go and the number is less than `1`, then the
     * refresh will not happen, and the content will return to it's original
     * position.
     */
    getProgress() {
      return Promise.resolve(this.progress);
    }
    canStart() {
      if (!this.scrollEl) {
        return false;
      }
      if (this.state !== 1 /* Inactive */) {
        return false;
      }
      // if the scrollTop is greater than zero then it's
      // not possible to pull the content down yet
      if (this.scrollEl.scrollTop > 0) {
        return false;
      }
      return true;
    }
    onStart() {
      this.progress = 0;
      this.state = 1 /* Inactive */;
    }
    onMove(detail) {
      if (!this.scrollEl) {
        return;
      }
      // this method can get called like a bazillion times per second,
      // so it's built to be as efficient as possible, and does its
      // best to do any DOM read/writes only when absolutely necessary
      // if multi-touch then get out immediately
      const ev = detail.event;
      if (ev.touches && ev.touches.length > 1) {
        return;
      }
      // do nothing if it's actively refreshing
      // or it's in the way of closing
      // or this was never a startY
      if ((this.state & 56 /* _BUSY_ */) !== 0) {
        return;
      }
      const pullFactor = (Number.isNaN(this.pullFactor) || this.pullFactor < 0) ? 1 : this.pullFactor;
      const deltaY = detail.deltaY * pullFactor;
      // don't bother if they're scrolling up
      // and have not already started dragging
      if (deltaY <= 0) {
        // the current Y is higher than the starting Y
        // so they scrolled up enough to be ignored
        this.progress = 0;
        this.state = 1 /* Inactive */;
        if (this.appliedStyles) {
          // reset the styles only if they were applied
          this.setCss(0, '', false, '');
          return;
        }
        return;
      }
      if (this.state === 1 /* Inactive */) {
        // this refresh is not already actively pulling down
        // get the content's scrollTop
        const scrollHostScrollTop = this.scrollEl.scrollTop;
        // if the scrollTop is greater than zero then it's
        // not possible to pull the content down yet
        if (scrollHostScrollTop > 0) {
          this.progress = 0;
          return;
        }
        // content scrolled all the way to the top, and dragging down
        this.state = 2 /* Pulling */;
      }
      // prevent native scroll events
      if (ev.cancelable) {
        ev.preventDefault();
      }
      // the refresher is actively pulling at this point
      // move the scroll element within the content element
      this.setCss(deltaY, '0ms', true, '');
      if (deltaY === 0) {
        // don't continue if there's no delta yet
        this.progress = 0;
        return;
      }
      const pullMin = this.pullMin;
      // set pull progress
      this.progress = deltaY / pullMin;
      // emit "start" if it hasn't started yet
      if (!this.didStart) {
        this.didStart = true;
        this.ionStart.emit();
      }
      // emit "pulling" on every move
      this.ionPull.emit();
      // do nothing if the delta is less than the pull threshold
      if (deltaY < pullMin) {
        // ensure it stays in the pulling state, cuz its not ready yet
        this.state = 2 /* Pulling */;
        return;
      }
      if (deltaY > this.pullMax) {
        // they pulled farther than the max, so kick off the refresh
        this.beginRefresh();
        return;
      }
      // pulled farther than the pull min!!
      // it is now in the `ready` state!!
      // if they let go then it'll refresh, kerpow!!
      this.state = 4 /* Ready */;
      return;
    }
    onEnd() {
      // only run in a zone when absolutely necessary
      if (this.state === 4 /* Ready */) {
        // they pulled down far enough, so it's ready to refresh
        this.beginRefresh();
      }
      else if (this.state === 2 /* Pulling */) {
        // they were pulling down, but didn't pull down far enough
        // set the content back to it's original location
        // and close the refresher
        // set that the refresh is actively cancelling
        this.cancel();
      }
    }
    beginRefresh() {
      // assumes we're already back in a zone
      // they pulled down far enough, so it's ready to refresh
      this.state = 8 /* Refreshing */;
      // place the content in a hangout position while it thinks
      this.setCss(this.pullMin, this.snapbackDuration, true, '');
      // emit "refresh" because it was pulled down far enough
      // and they let go to begin refreshing
      this.ionRefresh.emit({
        complete: this.complete.bind(this)
      });
    }
    close(state, delay) {
      // create fallback timer incase something goes wrong with transitionEnd event
      setTimeout(() => {
        this.state = 1 /* Inactive */;
        this.progress = 0;
        this.didStart = false;
        this.setCss(0, '0ms', false, '');
      }, 600);
      // reset set the styles on the scroll element
      // set that the refresh is actively cancelling/completing
      this.state = state;
      this.setCss(0, this.closeDuration, true, delay);
      // TODO: stop gesture
    }
    setCss(y, duration, overflowVisible, delay) {
      if (this.nativeRefresher) {
        return;
      }
      this.appliedStyles = (y > 0);
      writeTask(() => {
        if (this.scrollEl && this.backgroundContentEl) {
          const scrollStyle = this.scrollEl.style;
          const backgroundStyle = this.backgroundContentEl.style;
          scrollStyle.transform = backgroundStyle.transform = ((y > 0) ? `translateY(${y}px) translateZ(0px)` : '');
          scrollStyle.transitionDuration = backgroundStyle.transitionDuration = duration;
          scrollStyle.transitionDelay = backgroundStyle.transitionDelay = delay;
          scrollStyle.overflow = (overflowVisible ? 'hidden' : '');
        }
      });
    }
    render() {
      const mode = getIonMode(this);
      return (h(Host, { slot: "fixed", class: {
          [mode]: true,
          // Used internally for styling
          [`refresher-${mode}`]: true,
          'refresher-native': this.nativeRefresher,
          'refresher-active': this.state !== 1 /* Inactive */,
          'refresher-pulling': this.state === 2 /* Pulling */,
          'refresher-ready': this.state === 4 /* Ready */,
          'refresher-refreshing': this.state === 8 /* Refreshing */,
          'refresher-cancelling': this.state === 16 /* Cancelling */,
          'refresher-completing': this.state === 32 /* Completing */,
        } }));
    }
    get el() { return getElement(this); }
    static get watchers() { return {
      "disabled": ["disabledChanged"]
    }; }
  };
  Refresher.style = {
    ios: refresherIosCss,
    md: refresherMdCss
  };
  
  const RefresherContent = class {
    constructor(hostRef) {
      registerInstance(this, hostRef);
    }
    componentWillLoad() {
      if (this.pullingIcon === undefined) {
        const mode = getIonMode(this);
        const overflowRefresher = this.el.style.webkitOverflowScrolling !== undefined ? 'lines' : 'arrow-down';
        this.pullingIcon = config.get('refreshingIcon', mode === 'ios' && isPlatform('mobile') ? config.get('spinner', overflowRefresher) : 'circular');
      }
      if (this.refreshingSpinner === undefined) {
        const mode = getIonMode(this);
        this.refreshingSpinner = config.get('refreshingSpinner', config.get('spinner', mode === 'ios' ? 'lines' : 'circular'));
      }
    }
    render() {
      const pullingIcon = this.pullingIcon;
      const hasSpinner = pullingIcon != null && SPINNERS[pullingIcon] !== undefined;
      const mode = getIonMode(this);
      return (h(Host, { class: mode }, h("div", { class: "refresher-pulling" }, this.pullingIcon && hasSpinner &&
        h("div", { class: "refresher-pulling-icon" }, h("div", { class: "spinner-arrow-container" }, h("ion-spinner", { name: this.pullingIcon, paused: true }), mode === 'md' && this.pullingIcon === 'circular' &&
          h("div", { class: "arrow-container" }, h("ion-icon", { name: "caret-back-sharp" })))), this.pullingIcon && !hasSpinner &&
        h("div", { class: "refresher-pulling-icon" }, h("ion-icon", { icon: this.pullingIcon, lazy: false })), this.pullingText &&
        h("div", { class: "refresher-pulling-text", innerHTML: sanitizeDOMString(this.pullingText) })), h("div", { class: "refresher-refreshing" }, this.refreshingSpinner &&
        h("div", { class: "refresher-refreshing-icon" }, h("ion-spinner", { name: this.refreshingSpinner })), this.refreshingText &&
        h("div", { class: "refresher-refreshing-text", innerHTML: sanitizeDOMString(this.refreshingText) }))));
    }
    get el() { return getElement(this); }
  };
  
  export { Refresher as ion_refresher, RefresherContent as ion_refresher_content };
  