diff --git a/angular/src/directives/proxies.ts b/angular/src/directives/proxies.ts index 529ac88d23a..24846b4c9f6 100644 --- a/angular/src/directives/proxies.ts +++ b/angular/src/directives/proxies.ts @@ -1324,6 +1324,13 @@ called when the async operation has completed. * Emitted when the user begins to start pulling down. */ ionStart: EventEmitter>; + /** + * Emitted after the pull gesture has ended and the refresher has returned to +the INACTIVE state. It doesn't matter where the pull gesture ended; whether +the user pulled far enough for a refresh, let go in the middle of a pull, +or reversed the pull and released with the content at the top. + */ + ionEnd: EventEmitter>; } @@ -1343,7 +1350,7 @@ export class IonRefresher { constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { c.detach(); this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionRefresh', 'ionPull', 'ionStart']); + proxyOutputs(this, this.el, ['ionRefresh', 'ionPull', 'ionStart', 'ionEnd']); } } diff --git a/core/api.txt b/core/api.txt index ce9578268f2..9d42918f3b9 100644 --- a/core/api.txt +++ b/core/api.txt @@ -1006,6 +1006,7 @@ ion-refresher,prop,snapbackDuration,string,'280ms',false,false ion-refresher,method,cancel,cancel() => Promise ion-refresher,method,complete,complete() => Promise ion-refresher,method,getProgress,getProgress() => Promise +ion-refresher,event,ionEnd,void,true ion-refresher,event,ionPull,void,true ion-refresher,event,ionRefresh,RefresherEventDetail,true ion-refresher,event,ionStart,void,true diff --git a/core/src/components.d.ts b/core/src/components.d.ts index fa3c05f3e6b..e193d4aafbb 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -5758,6 +5758,10 @@ declare namespace LocalJSX { * If `true`, the refresher will be hidden. */ "disabled"?: boolean; + /** + * Emitted after the pull gesture has ended and the refresher has returned to the INACTIVE state. It doesn't matter where the pull gesture ended; whether the user pulled far enough for a refresh, let go in the middle of a pull, or reversed the pull and released with the content at the top. + */ + "onIonEnd"?: (event: CustomEvent) => void; /** * Emitted while the user is pulling down the content and exposing the refresher. */ diff --git a/core/src/components/refresher/readme.md b/core/src/components/refresher/readme.md index 65bdcd972d6..0411fb45abc 100644 --- a/core/src/components/refresher/readme.md +++ b/core/src/components/refresher/readme.md @@ -304,11 +304,12 @@ export default defineComponent({ ## Events -| Event | Description | Type | -| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | -| `ionPull` | Emitted while the user is pulling down the content and exposing the refresher. | `CustomEvent` | -| `ionRefresh` | Emitted when the user lets go of the content and has pulled down further than the `pullMin` or pulls the content down and exceeds the pullMax. Updates the refresher state to `refreshing`. The `complete()` method should be called when the async operation has completed. | `CustomEvent` | -| `ionStart` | Emitted when the user begins to start pulling down. | `CustomEvent` | +| Event | Description | Type | +| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | +| `ionEnd` | Emitted after the pull gesture has ended and the refresher has returned to the INACTIVE state. It doesn't matter where the pull gesture ended; whether the user pulled far enough for a refresh, let go in the middle of a pull, or reversed the pull and released with the content at the top. | `CustomEvent` | +| `ionPull` | Emitted while the user is pulling down the content and exposing the refresher. | `CustomEvent` | +| `ionRefresh` | Emitted when the user lets go of the content and has pulled down further than the `pullMin` or pulls the content down and exceeds the pullMax. Updates the refresher state to `refreshing`. The `complete()` method should be called when the async operation has completed. | `CustomEvent` | +| `ionStart` | Emitted when the user begins to start pulling down. | `CustomEvent` | ## Methods diff --git a/core/src/components/refresher/refresher.tsx b/core/src/components/refresher/refresher.tsx index d31eabf929b..03314576321 100644 --- a/core/src/components/refresher/refresher.tsx +++ b/core/src/components/refresher/refresher.tsx @@ -133,6 +133,14 @@ export class Refresher implements ComponentInterface { */ @Event() ionStart!: EventEmitter; + /** + * Emitted after the pull gesture has ended and the refresher has returned to + * the INACTIVE state. It doesn't matter where the pull gesture ended; whether + * the user pulled far enough for a refresh, let go in the middle of a pull, + * or reversed the pull and released with the content at the top. + */ + @Event() ionEnd!: EventEmitter; + private async checkNativeRefresher() { const useNativeRefresher = await shouldUseNativeRefresher(this.el, getIonMode(this)); if (useNativeRefresher && !this.nativeRefresher) { @@ -159,6 +167,10 @@ export class Refresher implements ComponentInterface { await translateElement(el, undefined, 300); } else { await transitionEndAsync(this.el.querySelector('.refresher-refreshing-icon'), 200); + // TODO Does something have to happen here to emit the ionEnd event? + // And if so, can it happen anywhere in resetNativeRefresher()? + // Or does something have to happen in the onEnd gesture events that + // are created within the setup[iOS/MD]NativeRefresher functions? } this.didRefresh = false; @@ -640,13 +652,16 @@ export class Refresher implements ComponentInterface { if (this.state === RefresherState.Ready) { // they pulled down far enough, so it's ready to refresh this.beginRefresh(); - } else if (this.state === RefresherState.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(); + } else if (this.state === RefresherState.Inactive) { + // they pulled down, but reversed the gesture and released + // when deltaY was <= 0 (i.e. getProgress was at 0). + this.ionEnd.emit(); } } @@ -669,6 +684,15 @@ export class Refresher implements ComponentInterface { // create fallback timer incase something goes wrong with transitionEnd event setTimeout(() => { + + // The comment above setTimeout() is worrisome because I don't want this to + // emit more than once (though that wouldn't be a big deal). I don't know + // enough about transitionEnd to know if that's possible and this + // conditional might not be necessary. + if (this.state !== RefresherState.Inactive) { + this.ionEnd.emit(); + } + this.state = RefresherState.Inactive; this.progress = 0; this.didStart = false; diff --git a/core/src/components/refresher/test/spec/index.html b/core/src/components/refresher/test/spec/index.html index 0132030c1b2..82a879379d7 100644 --- a/core/src/components/refresher/test/spec/index.html +++ b/core/src/components/refresher/test/spec/index.html @@ -92,6 +92,10 @@ refresher.addEventListener('ionStart', () => { console.log('ionStart'); }); + + refresher.addEventListener('ionEnd', () => { + console.log('ionEnd'); + }); refresher.addEventListener('ionRefresh', async function () { console.log('Loading data...'); diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index f265a759a9f..5cb7248f319 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -603,7 +603,8 @@ export const IonRefresher = /*@__PURE__*/ defineContainer('ion 'disabled', 'ionRefresh', 'ionPull', - 'ionStart' + 'ionStart', + 'ionEnd' ]);