Skip to content

Commit

Permalink
feat(app): hardware back button support
Browse files Browse the repository at this point in the history
  • Loading branch information
manucorporat committed Sep 2, 2018
1 parent d40d0a7 commit dfac9dc
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 10 deletions.
1 change: 1 addition & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3638,6 +3638,7 @@ export namespace Components {
}

interface IonRouter {
'goBack': () => Promise<void> | undefined;
'navChanged': (intent: number) => Promise<boolean>;
'printDebug': () => void;
/**
Expand Down
13 changes: 11 additions & 2 deletions core/src/components/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Component, Element, Prop, QueueApi } from '@stencil/core';

import { Config } from '../../interface';
import { rIC } from '../../utils/helpers';
import { isPlatform } from '../../utils/platform';

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

componentDidLoad() {
setTimeout(() => {
rIC(() => {
importTapClick(this.win);
importInputShims(this.win, this.config);
importStatusTap(this.win, this.queue);
}, 32);
importHardwareBackButton(this.win);
});
}

hostData() {
Expand All @@ -32,6 +34,13 @@ export class App {
}
}

function importHardwareBackButton(win: Window) {
if (isPlatform(win, 'hybrid')) {
// tslint:disable-next-line:no-floating-promises
import('../../utils/hardware-back-button').then(module => module.startHardwareBackButton(win));
}
}

function importStatusTap(win: Window, queue: QueueApi) {
if (isPlatform(win, 'hybrid')) {
// tslint:disable-next-line:no-floating-promises
Expand Down
7 changes: 2 additions & 5 deletions core/src/components/ripple-effect/ripple-effect.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Component, Element, Method, Prop, QueueApi } from '@stencil/core';

import { rIC } from '../../utils/helpers';

@Component({
tag: 'ion-ripple-effect',
styleUrl: 'ripple-effect.scss',
Expand All @@ -17,11 +19,6 @@ export class RippleEffect {
*/
@Method()
addRipple(pageX: number, pageY: number) {
let rIC = (this.win as any).requestIdleCallback;
if (!rIC) {
rIC = window.requestAnimationFrame;
}

rIC(() => this.prepareRipple(pageX, pageY));
}

Expand Down
13 changes: 12 additions & 1 deletion core/src/components/router/router.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, QueueApi } from '@stencil/core';

import { Config, RouteChain, RouterDirection, RouterEventDetail } from '../../interface';
import { BackButtonEvent, Config, RouteChain, RouterDirection, RouterEventDetail } from '../../interface';
import { debounce } from '../../utils/helpers';

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

@Listen('window:ionBackButton')
protected onBackButton(ev: BackButtonEvent) {
ev.detail.register(0, () => this.goBack());
}

/** Navigate to the specified URL */
@Method()
push(url: string, direction: RouterDirection = 'forward') {
Expand All @@ -91,6 +96,12 @@ export class Router {
return this.writeNavStateRoot(path, intent);
}

@Method()
goBack() {
this.win.history.back(1);
return this.waitPromise;
}

/** @hidden */
@Method()
printDebug() {
Expand Down
2 changes: 1 addition & 1 deletion core/src/components/tab/tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class Tab {
/**
* The name of the tab.
*/
@Prop() name?: string;
@Prop({ mutable: true }) name?: string;

/**
* If true, the user cannot interact with the tab. Defaults to `false`.
Expand Down
2 changes: 1 addition & 1 deletion core/src/components/tabbar/tabbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export class Tabbar {
'tab-btn-has-icon-only': hasIcon && !hasLabel,
'tab-btn-has-badge': badge !== undefined,
'tab-btn-disabled': disabled,
'tab-btn-hidden': !!tab.show
'tab-btn-hidden': !tab.show
}}
onClick={ev => {
if (!tab.disabled) {
Expand Down
3 changes: 3 additions & 0 deletions core/src/interface.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export type ComponentTags = keyof StencilIntrinsicElements;
export type ComponentRef = Function | HTMLElement | string;
export type ComponentProps<T = null> = T extends ComponentTags ? StencilIntrinsicElements[T] : {[key: string]: any};
export type CssClassMap = { [className: string]: boolean };
export type BackButtonEvent = CustomEvent<{
register(priority: number, handler: () => Promise<void> | void): void;
}>

declare global {
interface StencilGlobalHTMLAttributes {
Expand Down
43 changes: 43 additions & 0 deletions core/src/utils/hardware-back-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { BackButtonEvent } from '../interface';

type Handler = () => Promise<void> | void;

interface HandlerRegister {
priority: number;
handler: Handler;
}

export function startHardwareBackButton(win: Window) {
let busy = false;
win.addEventListener('backbutton', () => {
if (busy) {
return;
}
busy = true;
const handlers: HandlerRegister[] = [];
const ev: BackButtonEvent = new CustomEvent('ionBackButton', {
bubbles: false,
detail: {
register(priority: number, handler: Handler) {
handlers.push({ priority, handler });
}
}
});
win.dispatchEvent(ev);

if (handlers.length > 0) {
let selectedPriority = Number.MIN_SAFE_INTEGER;
let handler: Handler;
handlers.forEach(h => {
if (h.priority >= selectedPriority) {
selectedPriority = h.priority;
handler = h.handler;
}
});
const result = handler!();
if (result) {
result.then(() => busy = false);
}
}
});
}
8 changes: 8 additions & 0 deletions core/src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ export function reorderArray(array: any[], indexes: {from: number, to: number}):
return array;
}

export function rIC(callback: () => void) {
if ('requestIdleCallback' in window) {
(window as any).requestIdleCallback(callback);
} else {
setTimeout(callback, 32);
}
}

export function hasShadowDom(el: HTMLElement) {
return !!el.shadowRoot && !!(el as any).attachShadow;
}
Expand Down

0 comments on commit dfac9dc

Please sign in to comment.