mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 10:01:59 +08:00
feat(): add ability to continue processing hardware back button events (#20613)
fixes #17824
This commit is contained in:
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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');
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
2
core/src/interface.d.ts
vendored
2
core/src/interface.d.ts
vendored
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
@ -54,6 +40,29 @@ const executeAction = async (handler: Handler | undefined) => {
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
59
core/src/utils/test/hardware-back-button.spec.ts
Normal file
59
core/src/utils/test/hardware-back-button.spec.ts
Normal 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);
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user