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
|
// 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';
|
import { Subject, Subscription } from 'rxjs';
|
||||||
|
|
||||||
export interface BackButtonEmitter extends Subject<BackButtonEventDetail> {
|
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({
|
@Injectable({
|
||||||
@ -46,9 +46,9 @@ export class Platform {
|
|||||||
zone.run(() => {
|
zone.run(() => {
|
||||||
this.win = doc.defaultView;
|
this.win = doc.defaultView;
|
||||||
this.backButton.subscribeWithPriority = function(priority, callback) {
|
this.backButton.subscribeWithPriority = function(priority, callback) {
|
||||||
return this.subscribe(ev => (
|
return this.subscribe(ev => {
|
||||||
ev.register(priority, () => zone.run(callback))
|
return ev.register(priority, processNextHandler => zone.run(() => callback(processNextHandler)));
|
||||||
));
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
proxyEvent(this.pause, doc, 'pause');
|
proxyEvent(this.pause, doc, 'pause');
|
||||||
|
@ -79,7 +79,10 @@ export class Router implements ComponentInterface {
|
|||||||
|
|
||||||
@Listen('ionBackButton', { target: 'document' })
|
@Listen('ionBackButton', { target: 'document' })
|
||||||
protected onBackButton(ev: BackButtonEvent) {
|
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 {
|
export interface BackButtonEventDetail {
|
||||||
register(priority: number, handler: () => Promise<any> | void): void;
|
register(priority: number, handler: (processNextHandler: () => void) => Promise<any> | void): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StyleEventDetail {
|
export interface StyleEventDetail {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { BackButtonEvent } from '../interface';
|
import { BackButtonEvent } from '../interface';
|
||||||
|
|
||||||
type Handler = () => Promise<any> | void | null;
|
type Handler = (processNextHandler: () => void) => Promise<any> | void | null;
|
||||||
|
|
||||||
interface HandlerRegister {
|
interface HandlerRegister {
|
||||||
priority: number;
|
priority: number;
|
||||||
handler: Handler;
|
handler: Handler;
|
||||||
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const startHardwareBackButton = () => {
|
export const startHardwareBackButton = () => {
|
||||||
@ -16,44 +17,52 @@ export const startHardwareBackButton = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlers: HandlerRegister[] = [];
|
let index = 0;
|
||||||
|
let handlers: HandlerRegister[] = [];
|
||||||
const ev: BackButtonEvent = new CustomEvent('ionBackButton', {
|
const ev: BackButtonEvent = new CustomEvent('ionBackButton', {
|
||||||
bubbles: false,
|
bubbles: false,
|
||||||
detail: {
|
detail: {
|
||||||
register(priority: number, handler: Handler) {
|
register(priority: number, handler: Handler) {
|
||||||
handlers.push({ priority, handler });
|
handlers.push({ priority, handler, id: index++ });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
doc.dispatchEvent(ev);
|
doc.dispatchEvent(ev);
|
||||||
|
|
||||||
if (handlers.length > 0) {
|
const executeAction = async (handlerRegister: HandlerRegister | undefined) => {
|
||||||
let selectedPriority = Number.MIN_SAFE_INTEGER;
|
try {
|
||||||
let selectedHandler: Handler | undefined;
|
if (handlerRegister && handlerRegister.handler) {
|
||||||
handlers.forEach(({ priority, handler }) => {
|
const result = handlerRegister.handler(processHandlers);
|
||||||
if (priority >= selectedPriority) {
|
if (result != null) {
|
||||||
selectedPriority = priority;
|
await result;
|
||||||
selectedHandler = handler;
|
}
|
||||||
}
|
}
|
||||||
});
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
busy = true;
|
|
||||||
executeAction(selectedHandler).then(() => busy = false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const executeAction = async (handler: Handler | undefined) => {
|
|
||||||
try {
|
|
||||||
if (handler) {
|
|
||||||
const result = handler();
|
|
||||||
if (result != null) {
|
|
||||||
await result;
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
} 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;
|
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) {
|
if (document) {
|
||||||
document.addEventListener('ionBackButton', (e: any) => {
|
document.addEventListener('ionBackButton', (e: any) => {
|
||||||
e.detail.register(0, () => {
|
e.detail.register(0, (processNextHandler: () => void) => {
|
||||||
this.props.history.goBack();
|
this.props.history.goBack();
|
||||||
|
processNextHandler();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,10 @@ export default class Router extends VueRouter {
|
|||||||
|
|
||||||
// Listen to Ionic's back button event
|
// Listen to Ionic's back button event
|
||||||
document.addEventListener('ionBackButton', (e: 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