Compare commits

..

2 Commits

Author SHA1 Message Date
Manu Mtz.-Almeida
cd0ff2caad @ionic/angular 0.0.2-29 2018-03-03 16:50:27 +01:00
Manu Mtz.-Almeida
5fff315f6d Angular needs <2.7? 2018-03-03 16:48:28 +01:00
50 changed files with 2925 additions and 3209 deletions

View File

@@ -1,19 +1,12 @@
# @ionic/angular
Ionic Angular specific building blocks on top of [@ionic/core](https://www.npmjs.com/package/@ionic/core) components.
Ionic Angular specific building blocks on top of [@ionic/core](https://www.npmjs.com/package/@ionic/core) components/services.
## Related
* [Ionic Core Components](https://www.npmjs.com/package/@ionic/core)
* [Ionic Documentation](https://ionicframework.com/docs/)
* [Ionic Worldwide Slack](http://ionicworldwide.herokuapp.com/)
* [Ionic Forum](https://forum.ionicframework.com/)
* [Ionic Components](https://www.npmjs.com/package/@ionic/core)
* [Ionicons](http://ionicons.com/)
* [Ionic Worldwide Slack](http://ionicworldwide.herokuapp.com/)
* [Stencil](https://stenciljs.com/)
* [Stencil Worldwide Slack](https://stencil-worldwide.slack.com)
## License
* [MIT](https://raw.githubusercontent.com/ionic-team/ionic/master/LICENSE)

View File

@@ -5,11 +5,10 @@
"keywords": [
"ionic",
"framework",
"angular",
"mobile",
"app",
"hybrid",
"webapp",
"capacitor",
"cordova",
"progressive web app",
"pwa"
@@ -21,7 +20,7 @@
},
"scripts": {
"build": "npm run clean && npm run compile && npm run clean-generated",
"build.link": "node scripts/link-copy.js",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"clean": "node scripts/clean.js",
"clean-generated": "node ./scripts/clean-generated.js",
"compile": "./node_modules/.bin/ngc",
@@ -48,6 +47,7 @@
"@angular/router": "latest",
"@ionic/core": "^0.1.3",
"chalk": "^2.3.2",
"conventional-changelog-cli": "^1.3.16",
"execa": "^0.9.0",
"fs-extra": "^5.0.0",
"glob": "7.1.2",

View File

@@ -1,12 +1,3 @@
# npm link local development
`npm link` doesn't work as expected due to the `devDependency` on `@angular/core`. This is the work around...
npm run build.link ../ionic-conference-app
When the command above is ran from the `packages/angular` directory, it will build `@ionic/angular` and copy the `dist` directory to the correct location of another local project. In the example above, the end result is that it copies the `dist` directory to `../ionic-conference-app/node_modules/@ionic/angular/dist`. The path given should be relative to the root of this mono repo.
# Deploy
1. `npm run prepare.deploy`

View File

@@ -149,6 +149,10 @@ function runTasks(opts) {
{
title: 'Set package.json version',
task: () => execa('npm', ['run', 'set.version', opts.version], { cwd: rootDir }),
},
{
title: 'Generate CHANGELOG',
task: () => execa('npm', ['run', 'changelog'], { cwd: rootDir }),
}
);
}
@@ -161,7 +165,7 @@ function runTasks(opts) {
},
{
title: 'Tagging the latest commit',
task: () => execa('git', ['tag', `${pkg.name}-v${opts.version}`], { cwd: rootDir })
task: () => execa('git', ['tag', `v${opts.version}`], { cwd: rootDir })
},
{
title: 'Pushing to Github',

View File

@@ -1,31 +0,0 @@
const fs = require('fs-extra');
const path = require('path');
let prjDir = process.argv[2];
if (!prjDir) {
throw new Error('local path required as last argument to "npm run build.link" command');
}
prjDir = path.join(__dirname, '../../../', prjDir);
const prjIonicAngular = path.join(prjDir, 'node_modules/@ionic/angular');
const ionicAngularDir = path.join(__dirname, '..');
const ionicAngularDist = path.join(ionicAngularDir, 'dist');
const ionicAngularPkgJsonPath = path.join(ionicAngularDir, 'package.json');
const ionicAngularPkgJson = require(ionicAngularPkgJsonPath);
// make sure this local project exists
fs.emptyDirSync(prjIonicAngular);
ionicAngularPkgJson.files.push('package.json');
ionicAngularPkgJson.files.forEach(f => {
const src = path.join(ionicAngularDir, f);
const dest = path.join(prjIonicAngular, f);
console.log('copying:', src, 'to', dest);
fs.copySync(src, dest);
});
const prjReadme = path.join(prjIonicAngular, 'README.md');
fs.writeFileSync(prjReadme, '@ionic/angular copied from ' + ionicAngularDir);

View File

@@ -36,8 +36,14 @@ import { IonicWindow } from './types/interfaces';
const win = (window as IonicWindow);
const Ionic = win.Ionic;
if (Ionic) {
if (!Ionic) {
throw new Error(`ionic.js script missing from index.html`);
} else {
console.log('bypassing zone');
Ionic.ael = function ngAddEventListener(elm, eventName, cb, opts) {
console.log('add listener', !!elm.__zone_symbol__addEventListener);
if (elm.__zone_symbol__addEventListener) {
elm.__zone_symbol__addEventListener(eventName, cb, opts);
} else {
@@ -60,4 +66,5 @@ if (Ionic) {
win.requestAnimationFrame(cb);
}
};
}

View File

@@ -1,17 +1,3 @@
<a name="0.1.4-0"></a>
## [0.1.4-0](https://github.com/ionic-team/ionic/compare/v0.1.3...v0.1.4-0) (2018-03-06)
### Refactor
- Refactored navigation system
### Bug Fixes
* **testing:** do not throw error for missing Ionic global ([aa91d11](https://github.com/ionic-team/ionic/commit/aa91d11))
* **zone:** forgot to remove console.logs ([4ec3e48](https://github.com/ionic-team/ionic/commit/4ec3e48))
<a name="0.1.3"></a>
## [0.1.3](https://github.com/ionic-team/ionic/compare/v0.1.2...v0.1.3) (2018-03-03)

View File

@@ -1,49 +1,54 @@
# @ionic/core
[Ionic](https://ionicframework.com/) is an open source App Development Framework that makes it easy to build top quality Native and Progressive Web Apps with web technologies.
This contains the core stencil components for ionic based applications.
The Ionic Core package contains the Web Components that make up the reusable UI building blocks of Ionic Framework. These components are designed to be used in traditional frontend view libraries/frameworks (such as [Stencil](https://stenciljs.com/), React, Angular, or Vue), or on their own through traditional JavaScript in the browser.
## Let's get started
### 1. Install global dependencies
- stencil
- np
- jest
- tsc
- tslint
```
npm i -g stencil np jest tsc tslint
```
### 2. Clone your ionic fork
```
git@github.com:ionic-team/ionic.git
cd ionic
```
### 3. Run `npm install`
```
npm install
cd packages/core
npm install
````
Notice that ionic-core lives in `packages/core`.
### 4. Run `npm run dev`
Make sure you are inside `packages/core`
## Features
## How to contribute
* Tiny, highly optimized components built with [Stencil](https://stenciljs.com/)
* Styling for both iOS and Material Design
* No build or compiling required
* Simply add the static files to any project
* Lazy-loaded components without configuration
* Asynchronous rendering
* Theming through CSS Variables
1. `npm run dev` allows you to modify the components and have live reloading, just like another ionic app.
3. When everything looks good, run `npm run validate` to verify the tests linter and production build passes.
## How to use
## More commands
Easiest way to start using Ionic Core is by adding a script tag to the CDN:
<script src="https://unpkg.com/@ionic/core@0.1.4-0/dist/ionic.js"></script>
Any Ionic component added to the webpage will automatically load. This includes writing the component tag directly in HTML, or using JavaScript such as `document.createElement('ion-toggle')`.
Additionally, within this package is a `dist/ionic.js` file and accompanying `dist/ionic/` directory. These are the same files which are used by the CDN, and they're available in this package so they can be apart of an app's local development.
## Framework Bindings
The `@ionic/core` package can by used in simple HTML, or by vanilla JavaScript without any framework at all. Ionic also has packages that make it easiesr to integrate Ionic into a framework's traditional ecosystem and patterns. (However, at the lowest-level framework bindings are still just using Ionic Core and Web Components).
* [@ionic/angular](https://www.npmjs.com/package/@ionic/angular)
## Related
* [Ionic Documentation](https://ionicframework.com/docs/)
* [Ionic Worldwide Slack](http://ionicworldwide.herokuapp.com/)
* [Ionic Forum](https://forum.ionicframework.com/)
* [Ionicons](http://ionicons.com/)
* [Stencil](https://stenciljs.com/)
* [Stencil Worldwide Slack](https://stencil-worldwide.slack.com)
## License
* [MIT](https://raw.githubusercontent.com/ionic-team/ionic/master/LICENSE)
- `npm run build`: build ionic-core for production.
- `npm run dev`: live reloading server for ionic developement,
- `npm run test`: runs unit tests.
- `npm run clean`: cleans dist folder.
- `npm run lint`: runs typescript linter.
- `npm run lint-fix`: tries to auto-fix linter issues.
- `npm run validate`: runs tests, linter and production build.
- `npm run deploy`: publishes a new version to NPM.

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "0.1.4-0",
"version": "0.1.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,19 +1,7 @@
{
"name": "@ionic/core",
"version": "0.1.4-0",
"version": "0.1.3",
"description": "Base components for Ionic",
"keywords": [
"ionic",
"framework",
"stencil",
"mobile",
"app",
"webapp",
"capacitor",
"cordova",
"progressive web app",
"pwa"
],
"main": "dist/ionic.js",
"types": "dist/types/index.d.ts",
"collection": "dist/collection/collection-manifest.json",

View File

@@ -1,44 +1,7 @@
## Build
### 1. Clone ionic
git@github.com:ionic-team/ionic.git
cd ionic
### 2. Run `npm install`
cd packages/core
npm install
Notice that `@ionic/core` lives in `packages/core`.
### 3. Run `npm run dev`
Make sure you are inside the `packages/core` directory.
npm run dev
With the `dev` command, Ionic components will be built with [Stencil](https://stenciljs.com/), changes to source files are warched, a local http server will startup, and http://localhost:3333/ will open in a browser.
### 4. Preview
Navigate to http://localhost:3333/src/components/. Each component has small e2e apps found in the `test` directory, for example: http://localhost:3333/src/components/button/test/basic
As changes are made in an editor to source files, the e2e app will live-reload.
## How to contribute
1. `npm run dev` allows you to modify the components and have live reloading, just like another ionic app.
2. When everything looks good, run `npm run validate` to verify the tests linter and production build passes.
# Deploy
1. `npm run prepare.deploy`
2. Review/update changelog
3. Commit updates using the package name and version number as the commit message.
4. `npm run deploy`
5. :tada:
5. :tada:

View File

@@ -1,32 +1,19 @@
const fs = require('fs');
const path = require('path');
const semver = require('semver')
const pkgJsonPath = path.join(__dirname, '../package.json');
const pkgLockPath = path.join(__dirname, '../package-lock.json');
const readmePath = path.join(__dirname, '../README.md');
const packageJsonPath = path.join(__dirname, '../package.json');
const packageLockPath = path.join(__dirname, '../package-lock.json');
// get the version number from the last arg
const newVersion = process.argv[2];
semver.valid(newVersion);
if (!newVersion) {
throw new Error('invalid version number: ' + newVersion);
function getVersion() {
return process.argv[process.argv.length - 1];
}
// update the package.json
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
pkgJson.version = newVersion;
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
packageJson.version = getVersion();
// update the package-lock.json
const pkgLock = JSON.parse(fs.readFileSync(pkgLockPath, 'utf-8'));
pkgLock.version = pkgJson.version;
const packageLock = JSON.parse(fs.readFileSync(packageLockPath, 'utf-8'));
packageLock.version = packageJson.version;
// update the readme script tag
let readme = fs.readFileSync(readmePath, 'utf-8');
const cdnUrl = 'https://unpkg.com/' + pkgJson.name + '@' + pkgJson.version + '/dist/ionic.js'
readme = readme.replace(/https:\/\/unpkg.com(.*)ionic.js/, cdnUrl);
// save our changes
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n');
fs.writeFileSync(pkgLockPath, JSON.stringify(pkgLock, null, 2) + '\n');
fs.writeFileSync(readmePath, readme);
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
fs.writeFileSync(packageLockPath, JSON.stringify(packageLock, null, 2) + '\n');

View File

@@ -25,6 +25,7 @@ import {
FrameworkDelegate,
PickerColumn,
PickerOptions,
RouterDelegate,
} from './index';
import {
ActionSheetButton,
@@ -48,6 +49,9 @@ import {
import {
SelectPopoverOption,
} from './components/select-popover/select-popover';
import {
FrameworkDelegate as FrameworkDelegate2,
} from '.';
import {
DomRenderFn,
HeaderFn,
@@ -1976,7 +1980,7 @@ declare global {
import {
NavControllerBase as IonNav
Nav as IonNav
} from './components/nav/nav';
declare global {
@@ -1999,8 +2003,13 @@ declare global {
}
namespace JSXElements {
export interface IonNavAttributes extends HTMLAttributes {
delegate?: FrameworkDelegate;
lazy?: boolean;
mode?: string;
root?: any;
routerDelegate?: RouterDelegate;
swipeBackEnabled?: boolean;
useUrls?: boolean;
}
}
}
@@ -2597,9 +2606,9 @@ declare global {
}
namespace JSXElements {
export interface IonRouteAttributes extends HTMLAttributes {
component?: string;
path?: string;
props?: any;
sel?: string;
}
}
}
@@ -3193,6 +3202,7 @@ declare global {
badgeStyle?: string;
btnId?: string;
component?: any;
delegate?: FrameworkDelegate;
disabled?: boolean;
icon?: string;
name?: string;

View File

@@ -1,5 +1,3 @@
import { ViewController } from '../..';
export interface AnimationController {
create(animationBuilder?: AnimationBuilder, baseEl?: any, opts?: any): Promise<Animation>;
}
@@ -45,7 +43,7 @@ export interface Animation {
export interface AnimationBuilder {
(Animation: Animation, baseEl?: HTMLElement, opts?: any): Promise<Animation>;
(Animation: Animation, baseEl?: HTMLElement, opts?: any): Promise<Animation>;
}
@@ -56,11 +54,9 @@ export interface AnimationOptions {
direction?: string;
isRTL?: boolean;
ev?: any;
enteringView: ViewController;
leavingView: ViewController;
nav: HTMLIonNavElement;
}
export interface PlayOptions {
duration?: number;
promise?: boolean;

View File

@@ -3,7 +3,6 @@ import { CSS_PROP, CSS_VALUE_REGEX, DURATION_MIN, TRANSFORM_PROPS, TRANSITION_EN
import { transitionEnd } from './transition-end';
const raf = window.requestAnimationFrame || ((f: Function) => f());
export class Animator {
@@ -349,8 +348,8 @@ export class Animator {
// from an input event, and just having one RAF would have this code
// run within the same frame as the triggering input event, and the
// input event probably already did way too much work for one frame
raf(() => {
raf(() => {
window.requestAnimationFrame(function() {
window.requestAnimationFrame(function() {
self._playDomInspect(opts);
});
});
@@ -426,7 +425,7 @@ export class Animator {
if (self._isAsync && !this._destroyed) {
// this animation has a duration so we need another RAF
// for the CSS TRANSITION properties to kick in
raf(() => {
window.requestAnimationFrame(function() {
self._playToStep(1);
});
}
@@ -1059,7 +1058,7 @@ export class Animator {
// this animation has a duration so we need another RAF
// for the CSS TRANSITION properties to kick in
if (!this._destroyed) {
raf(() => {
window.requestAnimationFrame(() => {
this._playToStep(stepValue);
});
}

View File

@@ -1,5 +1,10 @@
import { Component, Element, Prop } from '@stencil/core';
import { Config } from '../../index';
import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
import { Config, NavEvent, OverlayController, PublicNav, PublicViewController } from '../../index';
import { getOrAppendElement } from '../../utils/helpers';
const rootNavs = new Map<number, HTMLIonNavElement>();
let backButtonActions: BackButtonAction[] = [];
@Component({
tag: 'ion-app',
@@ -16,7 +21,11 @@ export class App {
private isDevice = false;
private deviceHacks = false;
@Element() el: HTMLElement;
externalNavPromise: void | Promise<any> = null;
externalNavOccuring = false;
@Element() element: HTMLElement;
@Event() exitApp: EventEmitter<ExitAppEventDetail>;
@Prop({ context: 'config' }) config: Config;
@@ -25,6 +34,174 @@ export class App {
this.deviceHacks = this.config.getBoolean('deviceHacks', false);
}
/**
* Returns the promise set by an external navigation system
* This API is not meant for public usage and could
* change at any time
*/
@Method()
getExternalNavPromise(): void | Promise<any> {
return this.externalNavPromise;
}
/**
* Updates the Promise set by an external navigation system
* This API is not meant for public usage and could
* change at any time
*/
@Method()
setExternalNavPromise(value: null | Promise<any>): void {
this.externalNavPromise = value;
}
/**
* Returns whether an external navigation event is occuring
* This API is not meant for public usage and could
* change at any time
*/
@Method()
getExternalNavOccuring(): boolean {
return this.externalNavOccuring;
}
/**
* Updates whether an external navigation event is occuring
* This API is not meant for public usage and could
* change at any time
*/
@Method()
updateExternalNavOccuring(status: boolean) {
this.externalNavOccuring = status;
}
@Listen('body:navInit')
protected registerRootNav(event: NavEvent) {
rootNavs.set(event.target.getId(), event.target);
}
/**
* Returns an array of top level Navs
*/
@Method()
getRootNavs(): PublicNav[] {
const navs: PublicNav[] = [];
rootNavs.forEach((rootNav: PublicNav) => {
navs.push(rootNav);
});
return navs;
}
/**
* Returns whether the application is enabled or not
*/
@Method()
isEnabled(): boolean {
return true;
}
@Method()
getTopNavs(rootNavId = -1): PublicNav[] {
return getTopNavsImpl(rootNavId);
}
@Method()
getNavByIdOrName(nameOrId: number | string): PublicNav|null {
const navs = Array.from(rootNavs.values());
for (const navContainer of navs) {
const match = getNavByIdOrNameImpl(navContainer, nameOrId);
if (match) {
return match;
}
}
return null;
}
/**
* The back button event is triggered when the user presses the native
* platform's back button, also referred to as the "hardware" back button.
* This event is only used within Cordova apps running on Android and
* Windows platforms. This event is not fired on iOS since iOS doesn't come
* with a hardware back button in the same sense an Android or Windows device
* does.
*
* Registering a hardware back button action and setting a priority allows
* apps to control which action should be called when the hardware back
* button is pressed. This method decides which of the registered back button
* actions has the highest priority and should be called.
*
* @param {Function} fn Called when the back button is pressed,
* if this registered action has the highest priority.
* @param {number} priority Set the priority for this action. Only the highest priority will execute. Defaults to `0`.
* @returns {Function} A function that, when called, will unregister
* the back button action.
*/
@Method()
registerBackButtonAction(fn: Function, priority = 0): () => void {
const newAction = {
fn,
priority
};
backButtonActions.push(newAction);
return () => {
backButtonActions = backButtonActions.filter(bbAction => bbAction !== newAction);
};
}
@Listen('document:backbutton')
hardwareBackButtonPressed() {
// okay cool, we need to execute the user's custom method if they have one
const actionToExecute = backButtonActions.reduce((previous, current) => {
if (current.priority >= previous.priority) {
return current;
}
return previous;
});
actionToExecute && actionToExecute.fn && actionToExecute.fn();
// okay great, we've done the user action, now do the default actions
// check if menu exists and is open
return checkIfMenuIsOpen().then((done: boolean) => {
if (!done) {
// we need to check if there is an action-sheet, alert, loading, picker, popover or toast open
// if so, just return and don't do anything
// Why? I have no idea, but that is the existing behavior in Ionic 3
return checkIfNotModalOverlayIsOpen();
}
return done;
}).then((done: boolean) => {
if (!done) {
// if there's a modal open, close that instead
return closeModalIfOpen();
}
return done;
}).then((done: boolean) => {
// okay cool, it's time to pop a nav if possible
if (!done) {
return popEligibleView();
}
return done;
}).then((done: boolean) => {
if (!done) {
// okay, we didn't find a nav that we can pop, so we should just exit the app
// since each platform exits differently, just delegate it to the platform to
// figure out how to exit
return this.exitApp.emit();
}
return Promise.resolve();
});
}
@Listen('document:paused')
appResume(): null {
return null;
}
@Listen('document:resume')
appPaused(): null {
return null;
}
hostData() {
const mode = this.config.get('mode');
const hoverCSS = this.config.getBoolean('hoverCSS', false);
@@ -47,3 +224,149 @@ export class App {
];
}
}
export function getTopNavsImpl(rootNavId = -1) {
if (!rootNavs.size) {
return [];
}
if (rootNavId !== -1) {
return findTopNavs(rootNavs.get(rootNavId));
}
if (rootNavs.size === 1) {
return findTopNavs(rootNavs.values().next().value);
}
// fallback to just using all root navs
let activeNavs: PublicNav[] = [];
rootNavs.forEach(nav => {
activeNavs = activeNavs.concat(findTopNavs(nav));
});
return activeNavs;
}
export function findTopNavs(nav: PublicNav): PublicNav[] {
let containers: PublicNav[] = [];
const childNavs = nav.getChildNavs();
if (!childNavs || !childNavs.length) {
containers.push(nav);
} else {
childNavs.forEach(childNav => {
const topNavs = findTopNavs(childNav);
containers = containers.concat(topNavs);
});
}
return containers;
}
export function getNavByIdOrNameImpl(nav: PublicNav, id: number | string): PublicNav {
if (nav.navId === id || nav.name === id) {
return nav;
}
for (const child of nav.getChildNavs()) {
const tmp = getNavByIdOrNameImpl(child, id);
if (tmp) {
return tmp;
}
}
return null;
}
export function getHydratedController(tagName: string): Promise<HTMLElement> {
const controller = getOrAppendElement(tagName);
return (controller as any).componentOnReady();
}
export function checkIfMenuIsOpen(): Promise<boolean> {
return getHydratedController('ion-menu-controller').then((menuController: HTMLIonMenuControllerElement) => {
if (menuController.isOpen()) {
return menuController.close().then(() => {
return true;
});
}
return false;
});
}
export function checkIfNotModalOverlayIsOpen(): Promise<boolean> {
const promises: Promise<any>[] = [];
promises.push(checkIfOverlayExists('ion-action-sheet-controller'));
promises.push(checkIfOverlayExists('ion-alert-controller'));
promises.push(checkIfOverlayExists('ion-loading-controller'));
promises.push(checkIfOverlayExists('ion-picker-controller'));
promises.push(checkIfOverlayExists('ion-popover-controller'));
promises.push(checkIfOverlayExists('ion-toast-controller'));
return Promise.all(promises).then((results: boolean[]) => {
return results.every((value: boolean) => !!value);
});
}
export function checkIfOverlayExists(tagName: string): Promise<boolean> {
const overlayControllerElement = document.querySelector(tagName) as any as OverlayController;
if (!overlayControllerElement) {
return Promise.resolve(false);
}
return (overlayControllerElement as any).componentOnReady().then(() => {
return !!(overlayControllerElement.getTop());
});
}
export function closeModalIfOpen(): Promise<boolean> {
return getHydratedController('ion-modal-controller').then((modalController: HTMLIonModalControllerElement) => {
if (modalController.getTop()) {
return modalController.dismiss().then(() => {
return true;
});
}
return false;
});
}
export function popEligibleView(): Promise<boolean> {
let navToPop: PublicNav = null;
let mostRecentVC: PublicViewController = null;
rootNavs.forEach(nav => {
const topNavs = getTopNavsImpl(nav.navId);
const poppableNavs = topNavs.map(topNav => getPoppableNav(topNav)).filter(nav => !!nav).filter(nav => !!nav.last());
poppableNavs.forEach(poppable => {
const topViewController = poppable.last();
if (!mostRecentVC || topViewController.timestamp >= mostRecentVC.timestamp) {
mostRecentVC = topViewController;
navToPop = poppable;
}
});
});
if (navToPop) {
return navToPop.pop().then(() => {
return true;
});
}
return Promise.resolve(false);
}
export function getPoppableNav(nav: PublicNav): PublicNav {
if (!nav) {
return null;
}
// to be a poppable nav, a nav must a top view, plus a view that we can pop back to
if (nav.getViews.length > 1) {
return nav;
}
return getPoppableNav(nav.parent);
}
export interface ExitAppEvent extends CustomEvent {
target: HTMLIonAppElement;
detail: ExitAppEventDetail;
}
export interface ExitAppEventDetail {
}
export interface BackButtonAction {
fn: Function;
priority: number;
}

View File

@@ -5,6 +5,72 @@
<!-- Auto Generated Below -->
## Events
#### exitApp
## Methods
#### getExternalNavOccuring()
Returns whether an external navigation event is occuring
This API is not meant for public usage and could
change at any time
#### getExternalNavPromise()
Returns the promise set by an external navigation system
This API is not meant for public usage and could
change at any time
#### getNavByIdOrName()
#### getRootNavs()
Returns an array of top level Navs
#### getTopNavs()
#### isEnabled()
Returns whether the application is enabled or not
#### registerBackButtonAction()
The back button event is triggered when the user presses the native
platform's back button, also referred to as the "hardware" back button.
This event is only used within Cordova apps running on Android and
Windows platforms. This event is not fired on iOS since iOS doesn't come
with a hardware back button in the same sense an Android or Windows device
does.
Registering a hardware back button action and setting a priority allows
apps to control which action should be called when the hardware back
button is pressed. This method decides which of the registered back button
actions has the highest priority and should be called.
#### setExternalNavPromise()
Updates the Promise set by an external navigation system
This API is not meant for public usage and could
change at any time
#### updateExternalNavOccuring()
Updates whether an external navigation event is occuring
This API is not meant for public usage and could
change at any time
----------------------------------------------

View File

@@ -1,7 +1,7 @@
const PADDING_TIMER_KEY = '$ionPaddingTimer';
export default function enableScrollPadding(keyboardHeight: number) {
console.debug('Input: enableScrollPadding');
console.debug('Input: enableInputBlurring');
function onFocusin(ev: any) {
setScrollPadding(ev.target, keyboardHeight);

View File

@@ -1,5 +1,6 @@
import { Component, Element, Listen } from '@stencil/core';
import { NavResult } from '../..';
@Component({
tag: 'ion-nav-pop',
})
@@ -8,8 +9,8 @@ export class NavPop {
@Element() element: HTMLElement;
@Listen('child:click')
pop(): Promise<any> {
const nav = this.element.closest('ion-nav') as HTMLIonNavElement;
pop(): Promise<NavResult> {
const nav = this.element.closest('ion-nav');
if (nav) {
return nav.pop();
}

View File

@@ -1,5 +1,5 @@
import { Component, Element, Listen, Prop } from '@stencil/core';
// import { NavResult } from '../../index';
import { NavResult } from '../../index';
@Component({
tag: 'ion-nav-push',
@@ -12,8 +12,8 @@ export class NavPush {
@Prop() data: any;
@Listen('child:click')
push(): Promise<any> {
const nav = this.element.closest('ion-nav') as HTMLIonNavElement;
push(): Promise<NavResult> {
const nav = this.element.closest('ion-nav');
if (nav) {
const toPush = this.url || this.component;
return nav.push(toPush, this.data);

View File

@@ -1,4 +1,5 @@
import { Component, Element, Listen, Prop } from '@stencil/core';
import { NavResult } from '../../index';
@Component({
tag: 'ion-nav-set-root',
@@ -11,7 +12,7 @@ export class NavSetRoot {
@Prop() data: any;
@Listen('child:click')
push(): Promise<any> {
push(): Promise<NavResult> {
const nav = this.element.closest('ion-nav');
if (nav) {
const toPush = this.url || this.component;

View File

@@ -0,0 +1,122 @@
/* it is very important to keep this interface in sync with ./nav */
import {
Animation,
AnimationOptions,
FrameworkDelegate,
FrameworkMountingData,
Nav,
NavOptions,
PublicViewController,
ViewController,
} from '../../index';
export interface PublicNav {
push(component: any, data?: any, opts?: NavOptions): Promise<any>;
pop(opts?: NavOptions): Promise<any>;
setRoot(component: any, data?: any, opts?: NavOptions): Promise<any>;
insert(insertIndex: number, page: any, params?: any, opts?: NavOptions): Promise<any>;
insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions): Promise<any>;
popToRoot(opts?: NavOptions): Promise<any>;
popTo(indexOrViewCtrl: any, opts?: NavOptions): Promise<any>;
removeIndex(startIndex: number, removeCount?: number, opts?: NavOptions): Promise<any>;
removeView(viewController: PublicViewController, opts?: NavOptions): Promise<any>;
setPages(componentDataPairs: any[], opts?: NavOptions): Promise<any>;
getActive(): PublicViewController;
getPrevious(view?: PublicViewController): PublicViewController;
canGoBack(): boolean;
canSwipeBack(): boolean;
first(): PublicViewController;
last(): PublicViewController;
getChildNavs(): PublicNav[];
getViews(): PublicViewController[];
navId?: number;
name?: string;
element?: HTMLElement;
parent?: PublicNav;
initialized?: boolean;
}
export interface NavOptions {
animate?: boolean;
animation?: string;
direction?: string;
duration?: number;
easing?: string;
id?: string;
keyboardClose?: boolean;
progressAnimation?: boolean;
disableApp?: boolean;
event?: any;
updateUrl?: boolean;
isNavRoot?: boolean;
}
export interface TransitionInstruction {
component: any;
opts: NavOptions;
insertStart?: number;
insertViews?: ComponentDataPair[];
viewControllers?: ViewController[];
removeView?: any; // TODO make VC
removeStart?: number;
removeCount?: number;
resolve?: (hasCompleted: NavResult) => void;
reject?: (rejectReason: Error) => void;
leavingRequiresTransition?: boolean;
enteringRequiresTransition?: boolean;
requiresTransition?: boolean;
id?: number;
nav?: Nav;
delegate?: FrameworkDelegate;
animation?: Animation;
escapeHatch?: EscapeHatch;
method?: string;
mountingData?: FrameworkMountingData;
}
export interface NavResult {
successful: boolean;
mountingData: FrameworkMountingData;
}
export interface ComponentDataPair {
component: any;
data: any;
}
export interface ExternalNavData {
url: string;
method: string;
resolve: Function;
reject: Function;
}
export interface EscapeHatch {
fromExternalRouter?: boolean;
url?: string;
}
export interface Transition extends Animation {
enteringView?: ViewController;
leavingView?: ViewController;
transitionStartFunction?: Function;
transitionId?: number;
registerTransitionStart(callback: Function): void;
start(): void;
originalDestroy(): void; // this is intended to be private, don't use this bad boy
}
export interface TransitionBuilder {
(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions ): Promise<Transition>;
}
export interface PublicViewController {
id?: string;
component?: any;
instance?: any;
element?: HTMLElement;
timestamp?: number;
}

View File

@@ -1,194 +0,0 @@
import { ViewController, isViewController } from './view-controller';
import { NavControllerBase } from './nav';
import { Transition } from './transition';
export type Page2 = string | HTMLElement | ViewController;
export interface PageMeta {
page: Page2;
params?: any;
}
export function convertToView(page: any, params: any): ViewController {
if (!page) {
return null;
}
if (isViewController(page)) {
return page;
}
return new ViewController(page, params);
}
export function convertToViews(pages: any[]): ViewController[] {
return pages
.map(page => {
if (isViewController(page)) {
return page;
}
if ('page' in page) {
return convertToView(page.page, page.params);
}
return convertToView(page, undefined);
})
.filter(v => v !== null);
}
export function setZIndex(nav: NavControllerBase, enteringView: ViewController, leavingView: ViewController, direction: string) {
if (enteringView) {
leavingView = leavingView || nav.getPrevious(enteringView);
if (leavingView && isPresent(leavingView._zIndex)) {
if (direction === DIRECTION_BACK) {
enteringView._setZIndex(leavingView._zIndex - 1);
} else {
enteringView._setZIndex(leavingView._zIndex + 1);
}
} else {
enteringView._setZIndex(INIT_ZINDEX);
}
}
}
export function isTabs(nav: any): boolean {
// Tabs (ion-tabs)
return !!nav && !!nav.getSelected;
}
export function isTab(nav: any): boolean {
// Tab (ion-tab)
return !!nav && isPresent(nav._tabId);
}
export function isNav(nav: any): boolean {
// Nav (ion-nav), Tab (ion-tab), Portal (ion-portal)
return !!nav && !!nav.push && nav.getType() === 'nav';
}
export function linkToSegment(navId: string, type: string, secondaryId: string, link: NavLink): NavSegment {
const segment = <NavSegment> Object.assign({}, link);
segment.navId = navId;
segment.type = type;
segment.secondaryId = secondaryId;
return segment;
}
// internal link interface, not exposed publicly
export interface NavLink {
component?: any;
loadChildren?: string;
name?: string;
segment?: string;
segmentParts?: string[];
segmentPartsLen?: number;
staticLen?: number;
dataLen?: number;
dataKeys?: {[key: string]: boolean};
defaultHistory?: any[];
}
export interface NavResult {
hasCompleted: boolean;
requiresTransition: boolean;
enteringName?: string;
leavingName?: string;
direction?: string;
}
export interface NavSegment extends DehydratedSegment {
type: string;
navId: string;
secondaryId: string;
requiresExplicitNavPrefix?: boolean;
}
export interface DehydratedSegment {
id: string;
name: string;
component?: any;
loadChildren?: string;
data: any;
defaultHistory?: NavSegment[];
secondaryId?: string;
}
export interface DehydratedSegmentPair {
segments: DehydratedSegment[];
navGroup: NavGroup;
}
export interface NavGroup {
type: string;
navId: string;
secondaryId: string;
segmentPieces?: string[];
tabSegmentPieces?: string[];
}
export interface NavOptions {
animate?: boolean;
animation?: string;
direction?: string;
duration?: number;
easing?: string;
id?: string;
keyboardClose?: boolean;
progressAnimation?: boolean;
disableApp?: boolean;
minClickBlockDuration?: number;
ev?: any;
updateUrl?: boolean;
isNavRoot?: boolean;
}
export function isPresent(val: any): val is any { return val !== undefined && val !== null; }
export interface Page extends Function {
new (...args: any[]): any;
}
export interface TransitionResolveFn {
(hasCompleted: boolean, requiresTransition: boolean, enteringName?: string, leavingName?: string, direction?: string): void;
}
export interface TransitionRejectFn {
(rejectReason: any, transition?: Transition): void;
}
export interface TransitionDoneFn {
(hasCompleted: boolean, requiresTransition: boolean, enteringName?: string, leavingName?: string, direction?: string): void;
}
export interface TransitionInstruction {
opts: NavOptions;
insertStart?: number;
insertViews?: any[];
removeView?: ViewController;
removeStart?: number;
removeCount?: number;
resolve?: (hasCompleted: boolean) => void;
reject?: (rejectReason: string) => void;
done?: TransitionDoneFn;
leavingRequiresTransition?: boolean;
enteringRequiresTransition?: boolean;
requiresTransition?: boolean;
}
export const STATE_NEW = 1;
export const STATE_INITIALIZED = 2;
export const STATE_ATTACHED = 3;
export const STATE_DESTROYED = 4;
export const INIT_ZINDEX = 100;
export const DIRECTION_BACK = 'back';
export const DIRECTION_FORWARD = 'forward';
export const DIRECTION_SWITCH = 'switch';
export const NAV = 'nav';
export const TABS = 'tabs';

View File

@@ -0,0 +1,194 @@
import { Transition } from './nav-interfaces';
import { Animation, AnimationOptions, Config, Nav, TransitionBuilder, ViewController } from '../../index';
import { isDef } from '../../utils/helpers';
export enum State {
New,
INITIALIZED,
ATTACHED,
DESTROYED,
}
export const STATE_NEW = 1;
export const STATE_INITIALIZED = 2;
export const STATE_ATTACHED = 3;
export const STATE_DESTROYED = 4;
export const INIT_ZINDEX = 100;
export const PORTAL_Z_INDEX_OFFSET = 0;
export const DIRECTION_BACK = 'back';
export const DIRECTION_FORWARD = 'forward';
export const DIRECTION_SWITCH = 'switch';
export const NAV = 'nav';
export const TABS = 'tabs';
export let NAV_ID_START = 1000;
export let VIEW_ID_START = 2000;
let transitionIds = 0;
const activeTransitions = new Map<number, any>();
export function isViewController(object: any): boolean {
return !!(object && object.didLoad && object.willUnload);
}
export function setZIndex(nav: Nav, enteringView: ViewController, leavingView: ViewController, direction: string) {
if (enteringView) {
leavingView = leavingView || nav.getPrevious(enteringView) as ViewController;
if (leavingView && isDef(leavingView.zIndex)) {
if (direction === DIRECTION_BACK) {
updateZIndex(enteringView, leavingView.zIndex - 1);
} else {
updateZIndex(enteringView, leavingView.zIndex + 1);
}
} else {
// TODO - fix typing
updateZIndex(enteringView, INIT_ZINDEX + (nav as any).zIndexOffset);
}
}
}
export function updateZIndex(viewController: ViewController, newZIndex: number) {
if (newZIndex !== viewController.zIndex) {
viewController.zIndex = newZIndex;
viewController.element.style.zIndex = '' + newZIndex;
}
}
export function toggleHidden(element: HTMLElement, shouldBeHidden: boolean) {
element.hidden = shouldBeHidden;
}
export function canNavGoBack(nav: Nav, view?: ViewController) {
if (!nav) {
return false;
}
return !!nav.getPrevious(view);
}
export function transitionFactory(animation: Animation): Transition {
(animation as any).registerTransitionStart = (callback: Function) => {
(animation as any).transitionStartFunction = callback;
};
(animation as any).start = function() {
this.transitionStartFunction && this.transitionStartFunction();
this.transitionStartFunction = null;
transitionStartImpl(animation as Transition);
};
(animation as any).originalDestroy = animation.destroy;
(animation as any).destroy = function() {
transitionDestroyImpl(animation as Transition);
};
return animation as Transition;
}
export function transitionStartImpl(transition: Transition) {
transition.transitionStartFunction && transition.transitionStartFunction();
transition.transitionStartFunction = null;
transition.parent && (transition.parent as Transition).start();
}
export function transitionDestroyImpl(transition: Transition) {
transition.originalDestroy();
transition.parent = transition.enteringView = transition.leavingView = transition.transitionStartFunction = null;
}
export function getParentTransitionId(nav: Nav) {
nav = nav.parent;
while (nav) {
const transitionId = nav.transitionId;
if (isDef(transitionId)) {
return transitionId;
}
nav = nav.parent;
}
return -1;
}
export function getNextTransitionId() {
return transitionIds++;
}
export function destroyTransition(transitionId: number) {
const transition = activeTransitions.get(transitionId);
if (transition) {
transition.destroy();
activeTransitions.delete(transitionId);
}
}
export function getHydratedTransition(name: string, config: Config, transitionId: number, emptyTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions, defaultTransitionFactory: TransitionBuilder): Promise<Transition> {
// Let makes sure everything is hydrated and ready to animate
const componentReadyPromise: Promise<any>[] = [];
if (enteringView && (enteringView.element as any).componentOnReady) {
componentReadyPromise.push((enteringView.element as any).componentOnReady());
}
if (leavingView && (leavingView.element as any).componentOnReady) {
componentReadyPromise.push((leavingView.element as any).componentOnReady());
}
const transitionFactory = config.get(name) as TransitionBuilder || defaultTransitionFactory;
return Promise.all(componentReadyPromise)
.then(() => transitionFactory(emptyTransition, enteringView, leavingView, opts))
.then((hydratedTransition) => {
hydratedTransition.transitionId = transitionId;
if (!activeTransitions.has(transitionId)) {
// sweet, this is the root transition
activeTransitions.set(transitionId, hydratedTransition);
} else {
// we've got a parent transition going
// just append this transition to the existing one
activeTransitions.get(transitionId).add(hydratedTransition);
}
return hydratedTransition;
});
}
export function canGoBack(nav: Nav) {
return nav.views && nav.views.length > 0;
}
export function canSwipeBack(_nav: Nav) {
return true;
}
export function getFirstView(nav: Nav): ViewController {
return nav.views && nav.views.length ? nav.views[0] : null;
}
export function getLastView(nav: Nav): ViewController {
return nav.views && nav.views.length ? nav.views[nav.views.length - 1] : null;
}
export function getActiveChildNavs(nav: Nav): Nav[] {
return nav.childNavs ? nav.childNavs : [];
}
export function getViews(nav: Nav): ViewController[] {
return nav.views ? nav.views : [];
}
export function getActiveImpl(nav: Nav): ViewController {
return nav.views && nav.views.length > 0 ? nav.views[nav.views.length - 1] : null;
}
export function getPreviousImpl(nav: Nav, viewController: ViewController): ViewController {
if (!viewController) {
viewController = nav.getActive() as ViewController;
}
return nav.views[nav.views.indexOf(viewController) - 1];
}
export function getNextNavId() {
return navControllerIds++;
}
let navControllerIds = NAV_ID_START;

View File

File diff suppressed because it is too large Load Diff

View File

@@ -7,61 +7,115 @@
## Properties
#### delegate
#### lazy
boolean
#### mode
string
#### root
any
#### routerDelegate
#### swipeBackEnabled
boolean
#### useUrls
boolean
## Attributes
#### delegate
#### lazy
boolean
#### mode
string
#### root
any
#### router-delegate
#### swipe-back-enabled
boolean
#### use-urls
boolean
## Events
#### ionNavChanged
#### navInit
## Methods
#### activateFromTab()
#### canGoBack()
#### first()
#### getActive()
#### getAllChildNavs()
#### getByIndex()
#### getChildNavs()
#### getContentElement()
#### getId()
#### getPrevious()
#### getRouteId()
#### getViewById()
Return a view controller
#### getViews()
@@ -71,12 +125,18 @@ Return a view controller
#### insertPages()
#### isTransitioning()
#### last()
#### onAllTransitionsComplete()
#### pop()
#### popAll()
#### popTo()
@@ -86,6 +146,9 @@ Return a view controller
#### push()
#### reconcileFromExternalRouter()
#### removeIndex()
@@ -95,6 +158,9 @@ Return a view controller
#### setPages()
#### setParent()
#### setRoot()

View File

@@ -24,6 +24,7 @@
`;
}
}
class PageTwo extends HTMLElement {
connectedCallback() {
this.innerHTML = `
@@ -43,10 +44,12 @@
<ion-button class="next">Go to Page Two</ion-button>
</ion-nav-push>
</div>
</ion-content>
`;
}
}
class PageThree extends HTMLElement {
connectedCallback() {
this.innerHTML = `
@@ -65,9 +68,11 @@
`;
}
}
customElements.define('page-one', PageOne);
customElements.define('page-two', PageTwo);
customElements.define('page-three', PageThree);
</script>
</head>
<body>

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,384 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Nav Then Tabs</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<script src="/dist/ionic.js"></script>
<script>
class PageOne extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Page One</h1>
<ion-nav-push component="page-two">
<ion-button class="next">Go to Page Two</ion-button>
</ion-nav-push>
</ion-content>
`;
}
}
class PageTwo extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-nav root="page-two-section-one"></ion-nav>
`;
}
}
class PageTwoSectionOne extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Page Two Section One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Page Two Section One</h1>
<ion-nav-push component="page-two-section-two">
<ion-button class="next">Go to Page Two Section Two</ion-button>
</ion-nav-push>
<div>
<ion-button class="previous">Go Back</ion-button>
</div>
</ion-content>
`;
const button = this.querySelector('ion-button.previous');
button.addEventListener('click', () => {
const nav = this.closest('ion-nav');
if (nav.parent) {
nav.parent.pop();
}
});
}
}
class PageTwoSectionTwo extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Page Two Section Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Page Two Section Two</h1>
<ion-nav-push component="page-three">
<ion-button class="next">Go to Page Three</ion-button>
</ion-nav-push>
</ion-content>
`;
}
}
class PageThree extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Page Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Page Three</h1>
<ion-nav-pop>
<ion-button class="previous">Go Back</ion-button>
</ion-nav-pop>
</ion-content>
`;
}
}
class LoginPage extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Login Page</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Login Page</h1>
<ion-nav-push component="tabs-page">
<ion-button>Login</ion-button>
</ion-nav-push>
</ion-content>
`;
}
}
class TabsPage extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-tabs>
<ion-tab title="Tab One" icon="star" #tabOne>
<ion-nav root="tab-one-page-one" name="tab-one" lazy="true"></ion-nav>
</ion-tab>
<ion-tab title="Tab Two" icon="globe" #tabTwo>
<ion-nav root="tab-two-page-one" name="tab-two" lazy="true"></ion-nav>
</ion-tab>
<ion-tab title="Tab Three" icon="logo-facebook" #tabThree>
<ion-nav root="tab-three-page-one" name="tab-three" lazy="true"></ion-nav>
</ion-tab>
</ion-tabs>
`;
}
}
class TabOnePageOne extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Tab One Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Tab One Page One</h1>
<ion-nav-push component="tab-one-page-two">
<ion-button class="next">Go to Tab One Page Two</ion-button>
</ion-nav-push>
</ion-content>
`;
}
}
class TabOnePageTwo extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Tab One Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Tab One Page Two</h1>
<div>
<ion-nav-push component="tab-one-page-three">
<ion-button class="next">Go to Tab One Page Three</ion-button>
</ion-nav-push>
</div>
</ion-content>
`;
}
}
class TabOnePageThree extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Tab One Page Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Tab One Page Three</h1>
</ion-content>
`;
}
}
class TabTwoPageOne extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Tab Two Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Tab Two Page One</h1>
<ion-nav-push component="tab-two-page-two">
<ion-button class="next">Go to Tab Two Page Two</ion-button>
</ion-nav-push>
</ion-content>
`;
}
}
class TabTwoPageTwo extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Tab Two Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Tab Two Page Two</h1>
<div>
<ion-nav-push component="tab-two-page-three">
<ion-button class="next">Go to Tab Two Page Three</ion-button>
</ion-nav-push>
</div>
</ion-content>
`;
}
}
class TabTwoPageThree extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Tab Two Page Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Tab Two Page Three</h1>
</ion-content>
`;
}
}
class TabThreePageOne extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Tab Three Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Tab Three Page One</h1>
<ion-nav-push component="tab-three-page-two">
<ion-button class="next">Go to Tab Three Page Two</ion-button>
</ion-nav-push>
</ion-content>
`;
}
}
class TabThreePageTwo extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Tab Three Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Tab Three Page Two</h1>
<div>
<ion-nav-push component="tab-three-page-three">
<ion-button class="next">Go to Tab Three Page Three</ion-button>
</ion-nav-push>
</div>
</ion-content>
`;
}
}
class TabThreePageThree extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Tab Three Page Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Tab Three Page Three</h1>
</ion-content>
`;
}
}
customElements.define('login-page', LoginPage);
customElements.define('tabs-page', TabsPage);
customElements.define('tab-one-page-one', TabOnePageOne);
customElements.define('tab-one-page-two', TabOnePageTwo);
customElements.define('tab-one-page-three', TabOnePageThree);
customElements.define('tab-two-page-one', TabTwoPageOne);
customElements.define('tab-two-page-two', TabTwoPageTwo);
customElements.define('tab-two-page-three', TabTwoPageThree);
customElements.define('tab-three-page-one', TabThreePageOne);
customElements.define('tab-three-page-two', TabThreePageTwo);
customElements.define('tab-three-page-three', TabThreePageThree);
</script>
</head>
<body>
<ion-app>
<ion-nav root="login-page"></ion-nav>
</ion-app>
</body>
</html>

View File

@@ -1,110 +0,0 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Nav</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<script src="/dist/ionic.js"></script>
</head>
<body onload="initiaize()">
<ion-app>
<ion-nav root="page-one"></ion-nav>
</ion-app>
</body>
<script>
class PageOne extends HTMLElement {
async connectedCallback() {
this.innerHTML = `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Page One</h1>
<ion-button class="next">Go to Page Two</ion-button>
</ion-content>
</ion-page>`;
const button = this.querySelector('ion-button');
button.addEventListener('click', async () => {
this.closest('ion-nav').push('page-two');
});
}
}
class PageTwo extends HTMLElement {
async connectedCallback() {
this.innerHTML = `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Page Two</h1>
<ion-button class="next">Go to Page Three</ion-button>
<ion-button class="previous">Go Back</ion-button>
</ion-content>
</ion-page>`;
const previousButton = this.querySelector('ion-button.previous');
previousButton.addEventListener('click', async () => {
await this.closest('ion-nav').pop();
});
const nextButton = this.querySelector('ion-button.next');
nextButton.addEventListener('click', async () => {
await this.closest('ion-nav').push('page-three');
});
}
}
class PageThree extends HTMLElement {
async connectedCallback() {
this.innerHTML = `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Page Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Page Three</h1>
<ion-button class="previous">Go Back</ion-button>
</ion-content>
</ion-page>`;
const previousButton = this.querySelector('ion-button.previous');
previousButton.addEventListener('click', async () => {
await this.closest('ion-nav').pop();
});
}
}
customElements.define('page-one', PageOne);
customElements.define('page-two', PageTwo);
customElements.define('page-three', PageThree);
async function initiaize() {
const nav = document.querySelector('ion-nav');
await nav.componentOnReady();
nav.root = 'page-one';
setInterval(() => {
if (nav.root === 'page-one') {
nav.root = 'page-two';
} else if ( nav.root === 'page-two') {
nav.root = 'page-three';
} else {
nav.root = 'page-one';
}
}, 1000);
}
</script>
</html>

View File

@@ -1,49 +0,0 @@
import { isPresent } from './nav-util';
import { Transition } from './transition';
import { NavControllerBase } from './nav';
export class TransitionController {
private _ids = 0;
private _trns = new Map<number, Transition>();
// constructor(public plt: Platform, private _config: Config) {}
getRootTrnsId(nav: NavControllerBase): number {
nav = nav.parent;
while (nav) {
if (isPresent(nav._trnsId)) {
return nav._trnsId;
}
nav = nav.parent;
}
return null;
}
nextId() {
return this._ids++;
}
register(trnsId: number, trns: Transition) {
trns.trnsId = trnsId;
const parent = this._trns.get(trnsId);
if (!parent) {
// we haven't created the root transition yet
this._trns.set(trnsId, trns);
} else {
// we already have a root transition created
// add this new transition as a child to the root
parent.parent = trns;
}
}
destroy(trnsId: number) {
const trans = this._trns.get(trnsId);
if (trans) {
trans.destroy();
this._trns.delete(trnsId);
}
}
}

View File

@@ -1,57 +0,0 @@
import { ViewController } from './view-controller';
import { Animation, AnimationBuilder } from '../..';
/**
* @hidden
*
* - play
* - Add before classes - DOM WRITE
* - Remove before classes - DOM WRITE
* - Add before inline styles - DOM WRITE
* - set inline FROM styles - DOM WRITE
* - RAF
* - read toolbar dimensions - DOM READ
* - write content top/bottom padding - DOM WRITE
* - set css transition duration/easing - DOM WRITE
* - RAF
* - set inline TO styles - DOM WRITE
*/
export class Transition {
_trnsStart: Function;
trnsId: number;
ani: Animation;
parent: Transition;
constructor(
private animationCtrl: HTMLIonAnimationControllerElement,
private builder: AnimationBuilder,
public enteringView: ViewController,
public leavingView: ViewController,
private opts: any
) {}
registerStart(trnsStart: Function) {
this._trnsStart = trnsStart;
}
init() {
return this.animationCtrl.create(this.builder, null, this.opts).then((ani) => {
this.ani = ani;
});
}
start() {
this._trnsStart && this._trnsStart();
this._trnsStart = null;
// bubble up start
this.parent && this.parent.start();
}
destroy() {
this.ani && this.ani.destroy();
this.ani = this._trnsStart = null;
}
}

View File

@@ -1,4 +1,5 @@
import { Animation, AnimationOptions } from '../../../index';
import { AnimationOptions, Transition, ViewController } from '../../../index';
import { canNavGoBack } from '../nav-utils';
import { isDef } from '../../../utils/helpers';
const DURATION = 500;
@@ -10,38 +11,41 @@ const CENTER = '0%';
const OFF_OPACITY = 0.8;
const SHOW_BACK_BTN_CSS = 'show-back-button';
export default function iosTransitionAnimation(Animation: Animation, _: HTMLElement, opts: AnimationOptions): Promise<Animation> {
export function buildIOSTransition(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions): Promise<Transition> {
// Cool we're all hydrated, and can do deep selector
rootTransition.enteringView = enteringView;
rootTransition.leavingView = leavingView;
const isRTL = opts.isRTL;
const isRTL = document.dir === 'rtl';
const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';
const OFF_LEFT = isRTL ? '31%' : '-31%';
const enteringEl = opts.enteringView ? opts.enteringView.element : undefined;
const leavingEl = opts.leavingView ? opts.leavingView.element : undefined;
const nav = opts.nav;
const rootTransition = new Animation();
rootTransition.duration(isDef(opts.duration) ? opts.duration : DURATION);
rootTransition.easing(isDef(opts.easing) ? opts.easing : EASING);
rootTransition.addElement(enteringEl);
rootTransition.addElement(enteringView.element);
rootTransition.beforeRemoveClass('hide-page');
if (leavingEl && nav) {
const navDecor = new Animation();
navDecor.addElement(nav.el).duringAddClass('show-decor');
rootTransition.add(navDecor);
if (leavingView) {
const navEl = leavingView.element.closest('ion-nav');
if (navEl) {
const navDecor = rootTransition.create();
navDecor.addElement(navEl).duringAddClass('show-decor');
rootTransition.add(navDecor);
}
}
const backDirection = (opts.direction === 'back');
// setting up enter view
if (enteringEl) {
const contentEl = enteringEl.querySelector('ion-content');
const headerEls = enteringEl.querySelectorAll('ion-header > *:not(ion-toolbar),ion-footer > *');
const enteringToolBarEle = enteringEl.querySelector('ion-toolbar');
const enteringContent = new Animation();
if (enteringView) {
const contentEl = enteringView.element.querySelector('ion-content');
const headerEls = enteringView.element.querySelectorAll('ion-header > *:not(ion-toolbar),ion-footer > *');
const enteringToolBarEle = enteringView.element.querySelector('ion-toolbar');
const enteringContent = rootTransition.create();
if (!contentEl && !enteringToolBarEle && headerEls.length === 0) {
enteringContent.addElement(enteringEl.querySelector('ion-page,ion-nav,ion-tabs'));
enteringContent.addElement(enteringView.element.querySelector('ion-page,ion-nav,ion-tabs'));
} else {
enteringContent.addElement(contentEl);
enteringContent.addElement(headerEls);
@@ -59,23 +63,24 @@ export default function iosTransitionAnimation(Animation: Animation, _: HTMLElem
enteringContent
.beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
}
if (enteringToolBarEle) {
const enteringToolBar = new Animation();
const enteringToolBar = rootTransition.create();
enteringToolBar.addElement(enteringToolBarEle);
rootTransition.add(enteringToolBar);
const enteringTitle = new Animation();
const enteringTitle = rootTransition.create();
enteringTitle.addElement(enteringToolBarEle.querySelector('ion-title'));
const enteringToolBarItems = new Animation();
const enteringToolBarItems = rootTransition.create();
enteringToolBarItems.addElement(enteringToolBarEle.querySelectorAll('ion-buttons,[menuToggle]'));
const enteringToolBarBg = new Animation();
const enteringToolBarBg = rootTransition.create();
enteringToolBarBg.addElement(enteringToolBarEle.querySelector('.toolbar-background'));
const enteringBackButton = new Animation();
const enteringBackButton = rootTransition.create();
enteringBackButton.addElement(enteringToolBarEle.querySelector('.back-button'));
enteringToolBar
@@ -90,7 +95,7 @@ export default function iosTransitionAnimation(Animation: Animation, _: HTMLElem
if (backDirection) {
enteringTitle.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true);
if (opts.enteringView.enableBack()) {
if (canNavGoBack(enteringView.nav, enteringView)) {
// back direction, entering page has a back button
enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS).fromTo(OPACITY, 0.01, 1, true);
}
@@ -102,14 +107,15 @@ export default function iosTransitionAnimation(Animation: Animation, _: HTMLElem
.beforeClearStyles([OPACITY])
.fromTo(OPACITY, 0.01, 1, true);
if (opts.enteringView.enableBack()) {
if (canNavGoBack(enteringView.nav, enteringView)) {
// forward direction, entering page has a back button
enteringBackButton
.beforeAddClass(SHOW_BACK_BTN_CSS)
.fromTo(OPACITY, 0.01, 1, true);
const enteringBackBtnText = new Animation();
const enteringBackBtnText = rootTransition.create();
enteringBackBtnText.addElement(enteringToolBarEle.querySelector('.back-button .button-text'));
enteringBackBtnText.fromTo(TRANSLATEX, (isRTL ? '-100px' : '100px'), '0px');
@@ -122,11 +128,11 @@ export default function iosTransitionAnimation(Animation: Animation, _: HTMLElem
}
// setup leaving view
if (leavingEl) {
if (leavingView) {
const leavingContent = new Animation();
leavingContent.addElement(leavingEl.querySelector('ion-content'));
leavingContent.addElement(leavingEl.querySelectorAll('ion-header > *:not(ion-toolbar),ion-footer > *'));
const leavingContent = rootTransition.create();
leavingContent.addElement(leavingView.element.querySelector('ion-content'));
leavingContent.addElement(leavingView.element.querySelectorAll('ion-header > *:not(ion-toolbar),ion-footer > *'));
rootTransition.add(leavingContent);
if (backDirection) {
@@ -143,21 +149,21 @@ export default function iosTransitionAnimation(Animation: Animation, _: HTMLElem
.fromTo(OPACITY, 1, OFF_OPACITY, true);
}
const leavingToolBarEle = leavingEl.querySelector('ion-toolbar');
const leavingToolBarEle = leavingView.element.querySelector('ion-toolbar');
if (leavingToolBarEle) {
const leavingToolBar = new Animation();
const leavingToolBar = rootTransition.create();
leavingToolBar.addElement(leavingToolBarEle);
const leavingTitle = new Animation();
const leavingTitle = rootTransition.create();
leavingTitle.addElement(leavingToolBarEle.querySelector('ion-title'));
const leavingToolBarItems = new Animation();
const leavingToolBarItems = rootTransition.create();
leavingToolBarItems.addElement(leavingToolBarEle.querySelectorAll('ion-buttons,[menuToggle]'));
const leavingToolBarBg = new Animation();
const leavingToolBarBg = rootTransition.create();
leavingToolBarBg.addElement(leavingToolBarEle.querySelector('.toolbar-background'));
const leavingBackButton = new Animation();
const leavingBackButton = rootTransition.create();
leavingBackButton.addElement(leavingToolBarEle.querySelector('.back-button'));
leavingToolBar
@@ -183,7 +189,7 @@ export default function iosTransitionAnimation(Animation: Animation, _: HTMLElem
.beforeClearStyles([OPACITY])
.fromTo(OPACITY, 1, 0.01, true);
const leavingBackBtnText = new Animation();
const leavingBackBtnText = rootTransition.create();
leavingBackBtnText.addElement(leavingToolBarEle.querySelector('.back-button .button-text'));
leavingBackBtnText.fromTo(TRANSLATEX, CENTER, (isRTL ? -115 : 115) + 'px');
leavingToolBar.add(leavingBackBtnText);

View File

@@ -1,4 +1,5 @@
import { Animation, AnimationOptions } from '../../../index';
import { AnimationOptions, Transition, ViewController } from '../../../index';
import { canNavGoBack } from '../nav-utils';
import { isDef } from '../../../utils/helpers';
const TRANSLATEY = 'translateY';
@@ -6,18 +7,18 @@ const OFF_BOTTOM = '40px';
const CENTER = '0px';
const SHOW_BACK_BTN_CSS = 'show-back-button';
export default function mdTransitionAnimation(Animation: Animation, _: HTMLElement, opts: AnimationOptions): Promise<Animation> {
export function buildMdTransition(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions): Promise<Transition> {
const enteringEl = opts.enteringView ? opts.enteringView.element : undefined;
const leavingEl = opts.leavingView ? opts.leavingView.element : undefined;
const ionPageElement = getIonPageElement(enteringEl);
rootTransition.enteringView = enteringView;
rootTransition.leavingView = leavingView;
const ionPageElement = getIonPageElement(enteringView.element);
const rootTransition = new Animation();
rootTransition.addElement(ionPageElement);
rootTransition.beforeRemoveClass('hide-page');
const backDirection = (opts.direction === 'back');
if (enteringEl) {
if (enteringView) {
// animate the component itself
if (backDirection) {
@@ -34,14 +35,14 @@ export default function mdTransitionAnimation(Animation: Animation, _: HTMLEleme
const enteringToolbarEle = ionPageElement.querySelector('ion-toolbar');
if (enteringToolbarEle) {
const enteringToolBar = new Animation();
const enteringToolBar = rootTransition.create();
enteringToolBar.addElement(enteringToolbarEle);
rootTransition.add(enteringToolBar);
const enteringBackButton = new Animation();
const enteringBackButton = rootTransition.create();
enteringBackButton.addElement(enteringToolbarEle.querySelector('.back-button'));
rootTransition.add(enteringBackButton);
if (opts.enteringView.enableBack()) {
if (canNavGoBack(enteringView.nav, enteringView)) {
enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS);
} else {
enteringBackButton.beforeRemoveClass(SHOW_BACK_BTN_CSS);
@@ -50,11 +51,11 @@ export default function mdTransitionAnimation(Animation: Animation, _: HTMLEleme
}
// setup leaving view
if (leavingEl && backDirection) {
if (leavingView && backDirection) {
// leaving content
rootTransition.duration(opts.duration || 200).easing('cubic-bezier(0.47,0,0.745,0.715)');
const leavingPage = new Animation();
leavingPage.addElement(getIonPageElement(leavingEl));
const leavingPage = rootTransition.create();
leavingPage.addElement(getIonPageElement(leavingView.element));
rootTransition.add(leavingPage.fromTo(TRANSLATEY, CENTER, OFF_BOTTOM).fromTo('opacity', 1, 0));
}

View File

@@ -1,326 +1,137 @@
import { FrameworkDelegate, Nav, PublicViewController } from '../../index';
import { STATE_ATTACHED, STATE_DESTROYED, STATE_INITIALIZED, STATE_NEW } from './nav-utils';
import { NavOptions, STATE_ATTACHED, STATE_DESTROYED, STATE_INITIALIZED, STATE_NEW } from './nav-util';
import { NavControllerBase } from './nav';
import { assert } from '../../utils/helpers';
import {
assert,
normalizeUrl
} from '../../utils/helpers';
/**
* @name ViewController
* @description
* Access various features and information about the current view.
* @usage
* ```ts
* import { Component } from '@angular/core';
* import { ViewController } from 'ionic-angular';
*
* @Component({...})
* export class MyPage{
*
* constructor(public viewCtrl: ViewController) {}
*
* }
* ```
*/
export class ViewController {
export class ViewController implements PublicViewController {
private _cntDir: any;
private _isHidden = false;
private _leavingOpts: NavOptions;
private _detached: boolean;
_nav: NavControllerBase;
_zIndex: number;
_state: number = STATE_NEW;
_cssClass: string;
/** @hidden */
id: string;
/** @hidden */
isOverlay = false;
data: any;
element: HTMLElement;
/** @hidden */
// @Output() private _emitter: EventEmitter<any> = new EventEmitter();
instance: any;
state: number;
nav: Nav;
overlay: boolean;
zIndex: number;
dismissProxy: any;
timestamp: number;
fromExternalRouter: boolean;
url: string;
constructor(
public component: any,
public data?: any,
rootCssClass: string = DEFAULT_CSS_CLASS
) {
// component could be anything, never use it directly
// it could be a string, a HTMLElement
// passed in data could be NavParams, but all we care about is its data object
// this.data = (data instanceof NavParams ? data.data : (isPresent(data) ? data : {}));
this._cssClass = rootCssClass;
onDidDismiss: (data: any, role: string) => void;
onWillDismiss: (data: any, role: string) => void;
constructor(public component: any, data: any, fromExternalRouter: boolean, url: string) {
initializeNewViewController(this, data, fromExternalRouter, url);
}
/**
* @hidden
*/
init() {
if (this.element) {
return;
}
const component = this.component;
this.element = (typeof component === 'string')
? document.createElement(component)
: component;
willLeave(unload: boolean): void {
willLeaveImpl(unload, this);
}
_setNav(navCtrl: NavControllerBase) {
this._nav = navCtrl;
didLeave(): void {
didLeaveImpl(this);
}
/**
* @hidden
*/
getNav(): NavControllerBase {
return this._nav;
willEnter(): void {
callLifeCycleFunction(this.instance, 'ionViewWillEnter');
}
/**
* @hidden
*/
getTransitionName(_direction: string): string {
return this._nav && this._nav.config && this._nav.config.get('pageTransition') || 'md';
didEnter(): void {
didEnterImpl(this);
}
/**
* @hidden
*/
setLeavingOpts(opts: NavOptions) {
this._leavingOpts = opts;
willLoad(): void {
willLoadImpl(this);
}
/**
* Check to see if you can go back in the navigation stack.
* @returns {boolean} Returns if it's possible to go back from this Page.
*/
enableBack(): boolean {
// update if it's possible to go back from this nav item
if (!this._nav) {
return false;
}
// the previous view may exist, but if it's about to be destroyed
// it shouldn't be able to go back to
const previousItem = this._nav.getPrevious(this);
return !!(previousItem);
didLoad(): void {
didLoadImpl(this);
}
/**
* @hidden
*/
get name(): string {
const component = this.component;
if (typeof component === 'string') {
return component;
}
if (component.tagName) {
return component.tagName;
}
return this.element ? this.element.tagName : 'unknown';
willUnload(): void {
willUnloadImpl(this);
}
/**
* @hidden
* DOM WRITE
*/
_domShow(shouldShow: boolean) {
// using hidden element attribute to display:none and not render views
// doing checks to make sure we only update the DOM when actually needed
// if it should render, then the hidden attribute should not be on the element
if (this.element && shouldShow === this._isHidden) {
this._isHidden = !shouldShow;
// ******** DOM WRITE ****************
if (shouldShow) {
this.element.removeAttribute('hidden');
} else {
this.element.setAttribute('hidden', '');
}
}
destroy(delegate?: FrameworkDelegate): Promise<any> {
return destroy(this, delegate);
}
/**
* @hidden
*/
getZIndex(): number {
return this._zIndex;
}
/**
* @hidden
* DOM WRITE
*/
_setZIndex(zIndex: number) {
if (zIndex !== this._zIndex) {
this._zIndex = zIndex;
const pageEl = this.element;
if (pageEl) {
const el = pageEl as HTMLElement;
// ******** DOM WRITE ****************
el.style.zIndex = zIndex + '';
}
}
}
_preLoad() {
assert(this._state === STATE_INITIALIZED, 'view state must be INITIALIZED');
this._lifecycle('PreLoad');
}
/**
* @hidden
* The view has loaded. This event only happens once per view will be created.
* This event is fired before the component and his children have been initialized.
*/
_willLoad() {
assert(this._state === STATE_INITIALIZED, 'view state must be INITIALIZED');
this._lifecycle('WillLoad');
}
/**
* @hidden
* The view has loaded. This event only happens once per view being
* created. If a view leaves but is cached, then this will not
* fire again on a subsequent viewing. This method is a good place
* to put your setup code for the view; however, it is not the
* recommended method to use when a view becomes active.
*/
_didLoad() {
assert(this._state === STATE_ATTACHED, 'view state must be ATTACHED');
this._lifecycle('DidLoad');
}
/**
* @hidden
* The view is about to enter and become the active view.
*/
_willEnter() {
assert(this._state === STATE_ATTACHED, 'view state must be ATTACHED');
if (this._detached) {
// ensure this has been re-attached to the change detector
// TODO
// this._cmp.changeDetectorRef.reattach();
this._detached = false;
}
// this.willEnter.emit(null);
this._lifecycle('WillEnter');
}
/**
* @hidden
* The view has fully entered and is now the active view. This
* will fire, whether it was the first load or loaded from the cache.
*/
_didEnter() {
assert(this._state === STATE_ATTACHED, 'view state must be ATTACHED');
// this._nb && this._nb.didEnter();
// this.didEnter.emit(null);
this._lifecycle('DidEnter');
}
/**
* @hidden
* The view is about to leave and no longer be the active view.
*/
_willLeave(_willUnload: boolean) {
// this.willLeave.emit(null);
this._lifecycle('WillLeave');
}
/**
* @hidden
* The view has finished leaving and is no longer the active view. This
* will fire, whether it is cached or unloaded.
*/
_didLeave() {
// this.didLeave.emit(null);
this._lifecycle('DidLeave');
// when this is not the active page
// we no longer need to detect changes
if (!this._detached) {
// TODO
// this._cmp.changeDetectorRef.detach();
this._detached = true;
}
}
/**
* @hidden
*/
_willUnload() {
// this.willUnload.emit(null);
this._lifecycle('WillUnload');
}
/**
* @hidden
* DOM WRITE
*/
_destroy() {
assert(this._state !== STATE_DESTROYED, 'view state must be ATTACHED');
const element = this.element;
if (element) {
// completely destroy this component. boom.
// TODO
// this._cmp.destroy();
element.remove();
}
this._nav = this._cntDir = this._leavingOpts = null;
this._state = STATE_DESTROYED;
}
/**
* Get the index of the current component in the current navigation stack.
* @returns {number} Returns the index of this page within its `NavController`.
*/
get index(): number {
return (this._nav ? this._nav.indexOf(this) : -1);
}
/**
* @hidden
*/
_lifecycleTest(_lifecycle: string): Promise<any> {
// const instance = this.instance;
// const methodName = 'ionViewCan' + lifecycle;
// if (instance && instance[methodName]) {
// try {
// const result = instance[methodName]();
// if (result instanceof Promise) {
// return result;
// } else {
// // Any value but explitic false, should be true
// return Promise.resolve(result !== false);
// }
// } catch (e) {
// return Promise.reject(`${this.name} ${methodName} error: ${e.message}`);
// }
// }
return Promise.resolve(true);
}
_lifecycle(lifecycle: string) {
const event = new CustomEvent(`ionView${lifecycle}`, {
bubbles: false,
cancelable: false
});
this.element.dispatchEvent(event);
}
}
export function isViewController(viewCtrl: any): viewCtrl is ViewController {
return !!(viewCtrl && (<ViewController>viewCtrl)._didLoad && (<ViewController>viewCtrl)._willUnload);
export function callLifecycle(instance: any, methodName: string) {
instance && instance[methodName] && instance[methodName]();
}
const DEFAULT_CSS_CLASS = 'ion-page';
export function destroy(viewController: ViewController, delegate?: FrameworkDelegate): Promise<any> {
assert(viewController.state !== STATE_DESTROYED, 'view state must be attached');
return delegate ? delegate.removeViewFromDom(viewController.nav.element, viewController.element) : Promise.resolve().then(() => {
if (viewController.component) {
// TODO - consider removing classes and styles as thats what we do in ionic-angular
}
viewController.id = viewController.data = viewController.element = viewController.instance = viewController.nav = viewController.dismissProxy = null;
viewController.state = STATE_DESTROYED;
});
}
export function callLifeCycleFunction(instance: any, functionName: string) {
instance && instance[functionName] && instance[functionName]();
}
export function willLeaveImpl(unload: boolean, viewController: ViewController) {
callLifeCycleFunction(viewController.instance, 'ionViewWillLeave');
if (unload && viewController.onWillDismiss) {
viewController.onWillDismiss(this.dismissProxy.data, this.dismissProxy.proxy);
viewController.onWillDismiss = null;
}
}
export function didLeaveImpl(viewController: ViewController) {
callLifeCycleFunction(viewController.instance, 'ionViewDidLeave');
// TODO, maybe need to do something framework specific here... figure this out later
// for example, disconnecting from change detection
}
export function willEnterImpl(viewController: ViewController) {
assert(viewController.state === STATE_ATTACHED, 'view state must be ATTACHED');
// TODO, maybe need to do something framework specific here... figure this out later
// for example, connecting to change detection
callLifeCycleFunction(viewController.instance, 'ionViewWillEnter');
}
export function didEnterImpl(viewController: ViewController) {
assert(viewController.state === STATE_ATTACHED, 'view state must be ATTACHED');
// TODO - navbar didEnter here
callLifeCycleFunction(viewController.instance, 'ionViewDidEnter');
}
export function willLoadImpl(viewController: ViewController) {
assert(viewController.state === STATE_INITIALIZED, 'view state must be INITIALIZED');
callLifeCycleFunction(viewController.instance, 'ionViewWillLoad');
}
export function willUnloadImpl(viewController: ViewController) {
callLifeCycleFunction(viewController.instance, 'ionViewWillUnLoad');
viewController.onDidDismiss && viewController.onDidDismiss(viewController.dismissProxy.data, viewController.dismissProxy.role);
viewController.onDidDismiss = viewController.dismissProxy = null;
}
export function didLoadImpl(viewController: ViewController) {
assert(viewController.state === STATE_ATTACHED, 'view state must be ATTACHED');
callLifeCycleFunction(viewController.instance, 'ionViewDidLoad');
}
export function initializeNewViewController(viewController: ViewController, data: any, fromExternalRouter: boolean, url: string) {
viewController.timestamp = Date.now();
viewController.state = STATE_NEW;
viewController.data = data || {};
viewController.fromExternalRouter = fromExternalRouter;
viewController.url = url && normalizeUrl(url);
}

View File

@@ -1,47 +1,6 @@
# ion-range
# ion-range-knob
The Range slider lets users select from a range of values by moving
the slider knob. It can accept dual knobs, but by default one knob
controls the value of the range.
### Range Labels
Labels can be placed on either side of the range by adding the
`slot="start"` or `slot="end"` to the element. The element doesn't have to
be an `ion-label`, it can be added to any element to place it to the
left or right of the range. See [usage](#usage) below for examples.
### Usage
```html
<ion-list>
<ion-item>
<ion-range color="danger" pin="true"></ion-range>
</ion-item>
<ion-item>
<ion-range min="-200" max="200" color="secondary">
<ion-label slot="start">-200</ion-label>
<ion-label slot="end">200</ion-label>
</ion-range>
</ion-item>
<ion-item>
<ion-range min="20" max="80" step="2" >
<ion-icon small slot="start" name="sunny"></ion-icon>
<ion-icon slot="end" name="sunny"></ion-icon>
</ion-range>
</ion-item>
<ion-item>
<ion-range min="1000" max="2000" step="100" snaps="true" color="secondary" ></ion-range>
</ion-item>
<ion-item>
<ion-range dualKnobs="true" min="21" max="72" step="3" snaps="true"></ion-range>
</ion-item>
</ion-list>
```
<!-- Auto Generated Below -->

View File

@@ -7,11 +7,6 @@
## Properties
#### component
string
#### path
string
@@ -22,13 +17,13 @@ string
any
#### sel
string
## Attributes
#### component
string
#### path
string
@@ -39,6 +34,11 @@ string
any
#### sel
string
----------------------------------------------

View File

@@ -5,7 +5,9 @@ import { Component, Prop } from '@stencil/core';
tag: 'ion-route'
})
export class Route {
@Prop() path = '';
@Prop() component: string;
@Prop() path: string;
@Prop() sel: string;
@Prop() props: any = {};
}

View File

@@ -143,7 +143,7 @@ export function readRoutes(root: Element): RouterEntries {
.filter(el => el.tagName === 'ION-ROUTE')
.map(el => ({
path: parsePath(el.path),
id: el.component,
id: el.sel,
props: el.props,
subroutes: readRoutes(el)
}));

View File

@@ -106,30 +106,33 @@
<ion-app>
<ion-router>
<ion-route path="/" component="tab-one"> </ion-route>
<ion-route path="/" sel="tab1"> </ion-route>
<ion-route path="/two" component="tab-two">
<ion-route component="page-one"> </ion-route>
<ion-route path="/second-page" component="page-two"> </ion-route>
<ion-route path="/three-page" component="page-three"> </ion-route>
<ion-route path="/two" sel="tab2">
<ion-route path="/" sel="page-one"> </ion-route>
<ion-route path="/second-page" sel="page-two"> </ion-route>
<ion-route path="/thee-page" sel="page-three"> </ion-route>
</ion-route>
<ion-route path="/three" component="tab3"> </ion-route>
<ion-route path="/four" component="tab4"> </ion-route>
<ion-route path="/three" sel="tab-three"> </ion-route>
<ion-route path="/four" sel="tab4">
<ion-route path="/" sel="page-two"> </ion-route>
</ion-route>
</ion-router>
<ion-tabs>
<ion-tab component="tab-one"
<ion-tab name="tab1"
title="Plain List"
icon="star"></ion-tab>
icon="star"
component="tab-one"></ion-tab>
<ion-tab component="tab-two"
<ion-tab name="tab2"
title="Schedule"
icon="globe"></ion-tab>
icon="globe"
component="tab-two"></ion-tab>
<ion-tab name="tab3"
<ion-tab
title="Stopwatch"
icon="logo-facebook"
component="tab-three"></ion-tab>

View File

@@ -37,72 +37,5 @@ export class StatusTap {
return scroll.scrollToTop(this.duration);
});
});
/**
* The back button event is triggered when the user presses the native
* platform's back button, also referred to as the "hardware" back button.
* This event is only used within Cordova apps running on Android and
* Windows platforms. This event is not fired on iOS since iOS doesn't come
* with a hardware back button in the same sense an Android or Windows device
* does.
*
* Registering a hardware back button action and setting a priority allows
* apps to control which action should be called when the hardware back
* button is pressed. This method decides which of the registered back button
* actions has the highest priority and should be called.
*
* @param {Function} fn Called when the back button is pressed,
* if this registered action has the highest priority.
* @param {number} priority Set the priority for this action. Only the highest priority will execute. Defaults to `0`.
* @returns {Function} A function that, when called, will unregister
* the back button action.
*/
// @Method()
// registerBackButtonAction(fn: Function, priority = 0): () => void {
// const newAction = {
// fn,
// priority
// };
// backButtonActions.push(newAction);
// return () => {
// backButtonActions = backButtonActions.filter(bbAction => bbAction !== newAction);
// };
// }
// @Listen('document:backbutton')
// hardwareBackButtonPressed() {
// // check if menu exists and is open
// return checkIfMenuIsOpen().then((done: boolean) => {
// if (!done) {
// // we need to check if there is an action-sheet, alert, loading, picker, popover or toast open
// // if so, just return and don't do anything
// // Why? I have no idea, but that is the existing behavior in Ionic 3
// return checkIfNotModalOverlayIsOpen();
// }
// return done;
// }).then((done: boolean) => {
// if (!done) {
// // if there's a modal open, close that instead
// return closeModalIfOpen();
// }
// return done;
// }).then((done: boolean) => {
// // okay cool, it's time to pop a nav if possible
// if (!done) {
// return popEligibleView();
// }
// return done;
// }).then((done: boolean) => {
// if (!done) {
// // okay, we didn't find a nav that we can pop, so we should just exit the app
// // since each platform exits differently, just delegate it to the platform to
// // figure out how to exit
// return this.exitApp.emit();
// }
// return Promise.resolve();
// });
// }
}
}

View File

@@ -48,7 +48,7 @@ ion-tab-button {
}
}
.tab-btn-disabled {
.tab-disabled {
pointer-events: none;
> .tab-cover {
@@ -101,6 +101,7 @@ ion-tab-button {
justify-content: center;
}
.tab-hidden,
.layout-icon-hide .tab-button-icon,
.layout-title-hide .tab-button-text {
display: none;

View File

@@ -57,7 +57,6 @@ export class TabButton {
'role': 'tab',
'id': tab.btnId,
'aria-selected': selected,
'hidden': !tab.show,
class: {
'tab-selected': selected,
'has-title': hasTitle,
@@ -65,7 +64,8 @@ export class TabButton {
'has-title-only': hasTitleOnly,
'has-icon-only': hasIconOnly,
'has-badge': hasBadge,
'tab-btn-disabled': tab.disabled,
'tab-disabled': tab.disabled,
'tab-hidden': tab.hidden,
'focused': this.keyFocus
}
};

View File

@@ -81,6 +81,11 @@ any
The component to display inside of the tab.
#### delegate
#### disabled
boolean
@@ -163,6 +168,11 @@ any
The component to display inside of the tab.
#### delegate
#### disabled
boolean

View File

@@ -4,7 +4,16 @@ ion-tab {
@include position(0, null, null, 0);
position: absolute;
z-index: -1;
display: none;
width: 100%;
height: 100%;
contain: layout size style;
}
ion-tab.show-tab {
z-index: $z-index-page-container;
display: block;
}

View File

@@ -1,5 +1,7 @@
import { Component, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
import { asyncRaf } from '../../utils/helpers';
import { FrameworkDelegate } from '../..';
import { asyncRaf, getIonApp, getNavAsChildIfExists } from '../../utils/helpers';
@Component({
@@ -13,6 +15,8 @@ export class Tab {
@State() init = false;
@Prop() delegate: FrameworkDelegate;
@Prop({ mutable: true }) active = false;
@Prop() btnId: string;
@@ -99,23 +103,45 @@ export class Tab {
private prepareLazyLoaded(): Promise<any> {
if (!this.loaded && this.component) {
this.loaded = true;
return attachViewToDom(this.el, this.component).then(() => asyncRaf());
const promise = (this.delegate)
? this.delegate.attachViewToDom(this.el, this.component)
: attachViewToDom(this.el, this.component);
return promise.then(() => asyncRaf());
}
return Promise.resolve();
}
private showTab(): Promise<any|void> {
this.active = true;
return Promise.resolve();
const nav = getNavAsChildIfExists(this.el);
if (!nav) {
return Promise.resolve();
}
// the tab's nav has been initialized externally
return getIonApp().then((ionApp) => {
const externalNavPromise = ionApp ? ionApp.getExternalNavPromise() : null;
if (externalNavPromise) {
return (externalNavPromise as any).then(() => {
ionApp.setExternalNavPromise(null);
});
}
// the tab's nav has not been initialized externally, so
// check if we need to initiailize it
return nav.componentOnReady()
.then(() => nav.activateFromTab(this.selected));
});
}
hostData() {
const hidden = !this.active || !this.selected;
return {
'aria-hidden': hidden,
'aria-labelledby': this.btnId,
'role': 'tabpanel',
'hidden': !this.active,
'class': {
'ion-page': !this.component
class: {
'show-tab': this.active
}
};
}

View File

@@ -1,5 +1,7 @@
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core';
import { Config, NavOutlet } from '../../index';
import { Config, NavEventDetail, NavOutlet } from '../../index';
import { getIonApp } from '../../utils/helpers';
@Component({
@@ -10,6 +12,7 @@ export class Tabs implements NavOutlet {
private ids = -1;
private transitioning = false;
private tabsId: number = (++tabIds);
initialized = false;
@Element() el: HTMLElement;
@@ -64,17 +67,29 @@ export class Tabs implements NavOutlet {
* Emitted when the tab changes.
*/
@Event() ionChange: EventEmitter;
@Event() ionNavChanged: EventEmitter<any>;
@Event() ionNavChanged: EventEmitter<NavEventDetail>;
componentWillLoad() {
componentDidLoad() {
this.loadConfig('tabsPlacement', 'bottom');
this.loadConfig('tabsLayout', 'icon-top');
this.loadConfig('tabsHighlight', true);
}
componentDidLoad() {
return this.initTabs()
.then(() => this.initSelect());
const promises: Promise<any>[] = [];
promises.push(this.initTabs());
promises.push(getIonApp());
return Promise.all(promises).then(([_, ionApp]) => {
if (ionApp) {
return (ionApp as HTMLIonAppElement).getExternalNavOccuring();
}
return false;
}).then((externalNavOccuring: boolean) => {
if (!externalNavOccuring) {
return this.initSelect();
}
return null;
}).then(() => {
this.initialized = true;
});
}
componentDidUnload() {
@@ -83,6 +98,7 @@ export class Tabs implements NavOutlet {
}
@Listen('ionTabbarClick')
// @Listen('ionSelect')
protected tabChange(ev: CustomEvent) {
const selectedTab = ev.detail as HTMLIonTabElement;
this.select(selectedTab);

View File

@@ -5,6 +5,7 @@
<title>Tab - Basic</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<script src="/dist/ionic.js"></script>
<script src="/test-components/build/app.js"></script>
</head>
<body>
@@ -12,40 +13,40 @@
<ion-tabs>
<ion-tab title="Plain List" icon="star" path="">
<ion-header>
<ion-toolbar>
<ion-title>Tab One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab One
</ion-content>
<ion-header>
<ion-toolbar>
<ion-title>Tab One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab One
</ion-content>
</ion-tab>
</ion-tab>
<ion-tab title="Schedule" icon="globe" path="tab2">
<ion-header>
<ion-toolbar>
<ion-title>Tab Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab Two
</ion-content>
<ion-header>
<ion-toolbar>
<ion-title>Tab Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab Two
</ion-content>
</ion-tab>
</ion-tab>
<ion-tab title="Stopwatch" icon="logo-facebook" path="tab3">
<ion-header>
<ion-toolbar>
<ion-title>Tab Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab Three
</ion-content>
<ion-header>
<ion-toolbar>
<ion-title>Tab Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab Three
</ion-content>
</ion-tab>

View File

@@ -7,6 +7,7 @@ import { now, pointerCoord } from '../../utils/helpers';
})
export class TapClick {
private app: HTMLIonAppElement;
private lastTouch = -MOUSE_WAIT * 10;
private lastActivated = 0;
private cancelled = false;
@@ -21,9 +22,16 @@ export class TapClick {
@Element() el: HTMLElement;
componentDidLoad() {
if (this.isServer) {
return;
}
this.app = this.el.closest('ion-app');
}
@Listen('body:click', {passive: false, capture: true})
onBodyClick(ev: Event) {
if (this.cancelled) {
if (this.cancelled || this.shouldCancel()) {
ev.preventDefault();
ev.stopPropagation();
}
@@ -79,8 +87,11 @@ export class TapClick {
if (this.activatableEle) {
return;
}
this.cancelled = false;
this.setActivatedElement(getActivatableTarget(ev.target), ev);
this.cancelled = this.shouldCancel();
if (!this.cancelled) {
this.setActivatedElement(getActivatableTarget(ev.target), ev);
}
}
private pointerUp(ev: UIEvent) {
@@ -156,6 +167,15 @@ export class TapClick {
activatableEle.classList.remove(ACTIVATED);
}
}
private shouldCancel(): boolean {
if (!this.app.isEnabled()) {
console.debug('click prevent: appDisabled');
return true;
}
return false;
}
}
function getActivatableTarget(el: HTMLElement): any {

View File

@@ -61,6 +61,7 @@ export {
export * from './components/modal/modal';
export { ModalController } from './components/modal-controller/modal-controller';
export * from './components/nav/nav';
export * from './components/nav/nav-interfaces';
export { ViewController } from './components/nav/view-controller';
export { Note } from './components/note/note';
export { PickerColumnCmp } from './components/picker-column/picker-column';