Skip to content

Commit dfac9dc

Browse files
committed
feat(app): hardware back button support
1 parent d40d0a7 commit dfac9dc

File tree

9 files changed

+82
-10
lines changed

9 files changed

+82
-10
lines changed

core/src/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3638,6 +3638,7 @@ export namespace Components {
36383638
}
36393639

36403640
interface IonRouter {
3641+
'goBack': () => Promise<void> | undefined;
36413642
'navChanged': (intent: number) => Promise<boolean>;
36423643
'printDebug': () => void;
36433644
/**

core/src/components/app/app.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Component, Element, Prop, QueueApi } from '@stencil/core';
22

33
import { Config } from '../../interface';
4+
import { rIC } from '../../utils/helpers';
45
import { isPlatform } from '../../utils/platform';
56

67
@Component({
@@ -16,11 +17,12 @@ export class App {
1617
@Prop({ context: 'queue' }) queue!: QueueApi;
1718

1819
componentDidLoad() {
19-
setTimeout(() => {
20+
rIC(() => {
2021
importTapClick(this.win);
2122
importInputShims(this.win, this.config);
2223
importStatusTap(this.win, this.queue);
23-
}, 32);
24+
importHardwareBackButton(this.win);
25+
});
2426
}
2527

2628
hostData() {
@@ -32,6 +34,13 @@ export class App {
3234
}
3335
}
3436

37+
function importHardwareBackButton(win: Window) {
38+
if (isPlatform(win, 'hybrid')) {
39+
// tslint:disable-next-line:no-floating-promises
40+
import('../../utils/hardware-back-button').then(module => module.startHardwareBackButton(win));
41+
}
42+
}
43+
3544
function importStatusTap(win: Window, queue: QueueApi) {
3645
if (isPlatform(win, 'hybrid')) {
3746
// tslint:disable-next-line:no-floating-promises

core/src/components/ripple-effect/ripple-effect.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Component, Element, Method, Prop, QueueApi } from '@stencil/core';
22

3+
import { rIC } from '../../utils/helpers';
4+
35
@Component({
46
tag: 'ion-ripple-effect',
57
styleUrl: 'ripple-effect.scss',
@@ -17,11 +19,6 @@ export class RippleEffect {
1719
*/
1820
@Method()
1921
addRipple(pageX: number, pageY: number) {
20-
let rIC = (this.win as any).requestIdleCallback;
21-
if (!rIC) {
22-
rIC = window.requestAnimationFrame;
23-
}
24-
2522
rIC(() => this.prepareRipple(pageX, pageY));
2623
}
2724

core/src/components/router/router.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, QueueApi } from '@stencil/core';
22

3-
import { Config, RouteChain, RouterDirection, RouterEventDetail } from '../../interface';
3+
import { BackButtonEvent, Config, RouteChain, RouterDirection, RouterEventDetail } from '../../interface';
44
import { debounce } from '../../utils/helpers';
55

66
import { RouterIntent } from './utils/constants';
@@ -80,6 +80,11 @@ export class Router {
8080
return this.writeNavStateRoot(path, direction);
8181
}
8282

83+
@Listen('window:ionBackButton')
84+
protected onBackButton(ev: BackButtonEvent) {
85+
ev.detail.register(0, () => this.goBack());
86+
}
87+
8388
/** Navigate to the specified URL */
8489
@Method()
8590
push(url: string, direction: RouterDirection = 'forward') {
@@ -91,6 +96,12 @@ export class Router {
9196
return this.writeNavStateRoot(path, intent);
9297
}
9398

99+
@Method()
100+
goBack() {
101+
this.win.history.back(1);
102+
return this.waitPromise;
103+
}
104+
94105
/** @hidden */
95106
@Method()
96107
printDebug() {

core/src/components/tab/tab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class Tab {
5757
/**
5858
* The name of the tab.
5959
*/
60-
@Prop() name?: string;
60+
@Prop({ mutable: true }) name?: string;
6161

6262
/**
6363
* If true, the user cannot interact with the tab. Defaults to `false`.

core/src/components/tabbar/tabbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export class Tabbar {
124124
'tab-btn-has-icon-only': hasIcon && !hasLabel,
125125
'tab-btn-has-badge': badge !== undefined,
126126
'tab-btn-disabled': disabled,
127-
'tab-btn-hidden': !!tab.show
127+
'tab-btn-hidden': !tab.show
128128
}}
129129
onClick={ev => {
130130
if (!tab.disabled) {

core/src/interface.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ export type ComponentTags = keyof StencilIntrinsicElements;
4040
export type ComponentRef = Function | HTMLElement | string;
4141
export type ComponentProps<T = null> = T extends ComponentTags ? StencilIntrinsicElements[T] : {[key: string]: any};
4242
export type CssClassMap = { [className: string]: boolean };
43+
export type BackButtonEvent = CustomEvent<{
44+
register(priority: number, handler: () => Promise<void> | void): void;
45+
}>
4346

4447
declare global {
4548
interface StencilGlobalHTMLAttributes {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { BackButtonEvent } from '../interface';
2+
3+
type Handler = () => Promise<void> | void;
4+
5+
interface HandlerRegister {
6+
priority: number;
7+
handler: Handler;
8+
}
9+
10+
export function startHardwareBackButton(win: Window) {
11+
let busy = false;
12+
win.addEventListener('backbutton', () => {
13+
if (busy) {
14+
return;
15+
}
16+
busy = true;
17+
const handlers: HandlerRegister[] = [];
18+
const ev: BackButtonEvent = new CustomEvent('ionBackButton', {
19+
bubbles: false,
20+
detail: {
21+
register(priority: number, handler: Handler) {
22+
handlers.push({ priority, handler });
23+
}
24+
}
25+
});
26+
win.dispatchEvent(ev);
27+
28+
if (handlers.length > 0) {
29+
let selectedPriority = Number.MIN_SAFE_INTEGER;
30+
let handler: Handler;
31+
handlers.forEach(h => {
32+
if (h.priority >= selectedPriority) {
33+
selectedPriority = h.priority;
34+
handler = h.handler;
35+
}
36+
});
37+
const result = handler!();
38+
if (result) {
39+
result.then(() => busy = false);
40+
}
41+
}
42+
});
43+
}

core/src/utils/helpers.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ export function reorderArray(array: any[], indexes: {from: number, to: number}):
99
return array;
1010
}
1111

12+
export function rIC(callback: () => void) {
13+
if ('requestIdleCallback' in window) {
14+
(window as any).requestIdleCallback(callback);
15+
} else {
16+
setTimeout(callback, 32);
17+
}
18+
}
19+
1220
export function hasShadowDom(el: HTMLElement) {
1321
return !!el.shadowRoot && !!(el as any).attachShadow;
1422
}

0 commit comments

Comments
 (0)