feat(): add ability to continue processing hardware back button events (#20613)

fixes #17824
This commit is contained in:
Liam DeBeasi
2020-04-27 12:58:37 -04:00
committed by GitHub
parent 429edb053b
commit 3821c0463a
8 changed files with 115 additions and 37 deletions

View File

@ -45,7 +45,10 @@ export class NavController {
}
// Subscribe to backButton events
platform.backButton.subscribeWithPriority(0, () => this.pop());
platform.backButton.subscribeWithPriority(0, processNextHandler => {
this.pop();
processNextHandler();
});
}
/**

View File

@ -4,7 +4,7 @@ import { BackButtonEventDetail, Platforms, getPlatforms, isPlatform } from '@ion
import { Subject, Subscription } from 'rxjs';
export interface BackButtonEmitter extends Subject<BackButtonEventDetail> {
subscribeWithPriority(priority: number, callback: () => Promise<any> | void): Subscription;
subscribeWithPriority(priority: number, callback: (processNextHandler: () => void) => Promise<any> | void): Subscription;
}
@Injectable({
@ -46,9 +46,9 @@ export class Platform {
zone.run(() => {
this.win = doc.defaultView;
this.backButton.subscribeWithPriority = function(priority, callback) {
return this.subscribe(ev => (
ev.register(priority, () => zone.run(callback))
));
return this.subscribe(ev => {
return ev.register(priority, processNextHandler => zone.run(() => callback(processNextHandler)));
});
};
proxyEvent(this.pause, doc, 'pause');

View File

@ -79,7 +79,10 @@ export class Router implements ComponentInterface {
@Listen('ionBackButton', { target: 'document' })
protected onBackButton(ev: BackButtonEvent) {
ev.detail.register(0, () => this.back());
ev.detail.register(0, processNextHandler => {
this.back();
processNextHandler();
});
}
/**

View File

@ -53,7 +53,7 @@ export interface FrameworkDelegate {
}
export interface BackButtonEventDetail {
register(priority: number, handler: () => Promise<any> | void): void;
register(priority: number, handler: (processNextHandler: () => void) => Promise<any> | void): void;
}
export interface StyleEventDetail {

View File

@ -1,10 +1,11 @@
import { BackButtonEvent } from '../interface';
type Handler = () => Promise<any> | void | null;
type Handler = (processNextHandler: () => void) => Promise<any> | void | null;
interface HandlerRegister {
priority: number;
handler: Handler;
id: number;
}
export const startHardwareBackButton = () => {
@ -16,37 +17,22 @@ export const startHardwareBackButton = () => {
return;
}
const handlers: HandlerRegister[] = [];
let index = 0;
let handlers: HandlerRegister[] = [];
const ev: BackButtonEvent = new CustomEvent('ionBackButton', {
bubbles: false,
detail: {
register(priority: number, handler: Handler) {
handlers.push({ priority, handler });
handlers.push({ priority, handler, id: index++ });
}
}
});
doc.dispatchEvent(ev);
if (handlers.length > 0) {
let selectedPriority = Number.MIN_SAFE_INTEGER;
let selectedHandler: Handler | undefined;
handlers.forEach(({ priority, handler }) => {
if (priority >= selectedPriority) {
selectedPriority = priority;
selectedHandler = handler;
}
});
busy = true;
executeAction(selectedHandler).then(() => busy = false);
}
});
};
const executeAction = async (handler: Handler | undefined) => {
const executeAction = async (handlerRegister: HandlerRegister | undefined) => {
try {
if (handler) {
const result = handler();
if (handlerRegister && handlerRegister.handler) {
const result = handlerRegister.handler(processHandlers);
if (result != null) {
await result;
}
@ -56,5 +42,28 @@ const executeAction = async (handler: Handler | undefined) => {
}
};
const processHandlers = () => {
if (handlers.length > 0) {
let selectedHandler: HandlerRegister = {
priority: Number.MIN_SAFE_INTEGER,
handler: () => undefined,
id: -1
};
handlers.forEach(handler => {
if (handler.priority >= selectedHandler.priority) {
selectedHandler = handler;
}
});
busy = true;
handlers = handlers.filter(handler => handler.id !== selectedHandler.id);
executeAction(selectedHandler).then(() => busy = false);
}
};
processHandlers();
});
};
export const OVERLAY_BACK_BUTTON_PRIORITY = 100;
export const MENU_BACK_BUTTON_PRIORITY = 99; // 1 less than overlay priority since menu is displayed behind overlays

View File

@ -0,0 +1,59 @@
import { startHardwareBackButton } from '../hardware-back-button';
describe('Hardware Back Button', () => {
beforeEach(() => startHardwareBackButton());
it('should call handler', () => {
const cbSpy = jest.fn();
document.addEventListener('ionBackButton', (ev) => {
ev.detail.register(0, cbSpy);
});
dispatchBackButtonEvent();
expect(cbSpy).toHaveBeenCalled();
});
it('should call handlers in order of priority', () => {
const cbSpy = jest.fn();
const cbSpyTwo = jest.fn();
document.addEventListener('ionBackButton', (ev) => {
ev.detail.register(100, cbSpy);
ev.detail.register(99, cbSpyTwo);
});
dispatchBackButtonEvent();
expect(cbSpy).toHaveBeenCalled();
expect(cbSpyTwo).not.toHaveBeenCalled();
});
it('should only call last handler to be added for handlers with same priority', () => {
const cbSpy = jest.fn();
const cbSpyTwo = jest.fn();
document.addEventListener('ionBackButton', (ev) => {
ev.detail.register(100, cbSpy);
ev.detail.register(100, cbSpyTwo);
});
dispatchBackButtonEvent();
expect(cbSpy).not.toHaveBeenCalled();
expect(cbSpyTwo).toHaveBeenCalled();
});
it('should call multiple callbacks', () => {
const cbSpy = (processNextHandler) => {
processNextHandler();
}
const cbSpyTwo = jest.fn();
document.addEventListener('ionBackButton', (ev) => {
ev.detail.register(100, cbSpy);
ev.detail.register(99, cbSpyTwo);
});
dispatchBackButtonEvent();
expect(cbSpyTwo).toHaveBeenCalled();
});
});
const dispatchBackButtonEvent = () => {
const ev = new Event('backbutton');
document.dispatchEvent(ev);
}

View File

@ -36,8 +36,9 @@ export class NavManager extends React.Component<NavManagerProps, NavContextState
if (document) {
document.addEventListener('ionBackButton', (e: any) => {
e.detail.register(0, () => {
e.detail.register(0, (processNextHandler: () => void) => {
this.props.history.goBack();
processNextHandler();
});
});
}

View File

@ -37,7 +37,10 @@ export default class Router extends VueRouter {
// Listen to Ionic's back button event
document.addEventListener('ionBackButton', (e: Event) => {
(e as BackButtonEvent).detail.register(0, () => this.back());
(e as BackButtonEvent).detail.register(0, processNextHandler => {
this.back();
processNextHandler();
});
});
}