This commit is contained in:
Adam Bradley
2015-12-22 21:45:28 -06:00
parent f409d6add7
commit 98e4336c16
13 changed files with 534 additions and 96 deletions

View File

@ -7,6 +7,7 @@
@import @import
"components/app/app.ios", "components/app/app.ios",
"components/action-sheet/action-sheet.ios", "components/action-sheet/action-sheet.ios",
"components/alert/alert.ios",
"components/badge/badge.ios", "components/badge/badge.ios",
"components/button/button.ios", "components/button/button.ios",
"components/card/card.ios", "components/card/card.ios",

View File

@ -25,6 +25,7 @@ export * from './components/nav/nav-push'
export * from './components/nav/nav-router' export * from './components/nav/nav-router'
export * from './components/navbar/navbar' export * from './components/navbar/navbar'
export * from './components/overlay/overlay' export * from './components/overlay/overlay'
export * from './components/overlay/overlay-controller'
export * from './components/popup/popup' export * from './components/popup/popup'
export * from './components/slides/slides' export * from './components/slides/slides'
export * from './components/radio/radio' export * from './components/radio/radio'

View File

@ -0,0 +1,129 @@
@import "../../globals.ios";
@import "./alert";
// iOS Alerts
// --------------------------------------------------
$alert-ios-max-width: 270px !default;
$alert-ios-background: rgba(0,0,0,0) !default;
$alert-ios-border-radius: 13px !default;
$alert-ios-background-color: #f8f8f8 !default;
$alert-ios-head-padding: 12px 16px 20px !default;
$alert-ios-head-text-align: center !default;
$alert-ios-title-margin-top: 12px !default;
$alert-ios-title-font-weight: bold !default;
$alert-ios-title-font-size: 17px !default;
$alert-ios-sub-title-font-size: 14px !default;
$alert-ios-sub-title-text-color: #666 !default;
$alert-ios-body-padding: 0px 16px 24px !default;
$alert-ios-body-text-color: inherit !default;
$alert-ios-body-text-align: center !default;
$alert-ios-body-font-size: 13px !default;
$alert-ios-input-padding: 6px !default;
$alert-ios-input-margin-top: 24px !default;
$alert-ios-input-background-color: #fff !default;
$alert-ios-input-border: 1px solid #ccc !default;
$alert-ios-input-border-radius: 4px !default;
$alert-ios-button-min-height: 44px !default;
$alert-ios-button-font-size: 17px !default;
$alert-ios-button-border-color: #c8c7cc !default;
$alert-ios-button-activated-background-color: #e9e9e9 !default;
ion-alert {
background: $alert-ios-background;
}
.alert-wrapper {
border-radius: $alert-ios-border-radius;
background-color: $alert-ios-background-color;
max-width: $alert-ios-max-width;
overflow: hidden;
}
.alert-head {
padding: $alert-ios-head-padding;
text-align: $alert-ios-head-text-align;
}
.alert-title {
margin-top: $alert-ios-title-margin-top;
font-weight: $alert-ios-title-font-weight;
font-size: $alert-ios-title-font-size;
}
.alert-sub-title {
font-size: $alert-ios-sub-title-font-size;
color: $alert-ios-sub-title-text-color;
}
.alert-body {
padding: $alert-ios-body-padding;
color: $alert-ios-body-text-color;
text-align: $alert-ios-body-text-align;
font-size: $alert-ios-body-font-size;
}
.alert-input {
padding: $alert-ios-input-padding;
margin-top: $alert-ios-input-margin-top;
background-color: $alert-ios-input-background-color;
border: $alert-ios-input-border;
border-radius: $alert-ios-input-border-radius;
-webkit-appearance: none;
}
.alert-buttons {
:last-child {
font-weight: bold;
border-right: 0;
}
}
.alert-button {
margin: 0;
flex: 1;
border-radius: 0;
font-size: $alert-ios-button-font-size;
min-height: $alert-ios-button-min-height;
border-right: 1px solid $alert-ios-button-border-color;
&.activated {
opacity: 1;
background-color: $alert-ios-button-activated-background-color;
}
&:hover:not(.disable-hover) {
opacity: 1;
}
&:before {
position: absolute;
top: 0;
right: 0;
left: 0;
border-top: 1px solid $alert-ios-button-border-color;
content: '';
pointer-events: none;
}
}
&.hairlines {
.alert-input {
border-width: 0.55px;
}
.alert-button {
border-right-width: 0.55px;
&:before {
border-top-width: 0.55px;
}
}
}

View File

@ -0,0 +1,80 @@
@import "../../globals.core";
// Alerts
// --------------------------------------------------
$alert-min-width: 250px !default;
$alert-max-height: 90% !default;
$alert-button-line-height: 20px !default;
$alert-button-font-size: 14px !default;
$alert-button-margin-right: 8px !default;
ion-alert {
position: absolute;
z-index: $z-index-overlay;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
input {
width: 100%;
}
}
.alert-wrapper {
z-index: $z-index-overlay-wrapper;
min-width: $alert-min-width;
max-height: $alert-max-height;
display: flex;
flex-direction: column;
opacity: 0;
}
.alert-title {
margin: 0;
padding: 0;
}
.alert-sub-title {
margin: 5px 0 0 0;
padding: 0;
font-weight: normal;
}
.alert-body {
overflow: auto;
&:empty {
padding: 0;
}
}
.alert-input {
@include placeholder();
border: 0;
background: inherit;
padding: 10px 0;
}
.alert-buttons {
display: flex;
flex-direction: row;
}
.alert-button {
display: block;
margin: 0;
line-height: $alert-button-line-height;
font-size: $alert-button-font-size;
margin-right: $alert-button-margin-right;
}

View File

@ -1,25 +1,217 @@
import {Component, ElementRef, Injectable, Renderer} from 'angular2/core'; import {Component, ElementRef, Renderer} from 'angular2/core';
import {NgClass, NgIf, NgFor, FORM_DIRECTIVES} from 'angular2/common'; import {NgClass, NgIf, NgFor, FORM_DIRECTIVES} from 'angular2/common';
import {OverlayController} from '../overlay/overlay-controller';
import {Config} from '../../config/config';
import {Animation} from '../../animations/animation';
import {NavParams} from '../nav/nav-controller'; import {NavParams} from '../nav/nav-controller';
import {ViewController} from '../nav/view-controller';
import {Animation} from '../../animations/animation';
import {Button} from '../button/button'; import {Button} from '../button/button';
import {extend} from '../../util/util'; import {extend, isDefined} from '../../util/util';
@Injectable() export class Alert extends ViewController {
export class Alert {
constructor(private _ctrl: OverlayController, private _config: Config) { constructor(opts={}) {
super(null, AlertCmp, opts);
this.data.inputs = this.data.inputs || [];
let buttons = this.data.buttons || [];
this.data.buttons = [];
for (let button of buttons) {
this.addButton(button);
} }
static create() { this.enterAnimationKey = 'alertEnter';
let alert = new Alert(); this.leaveAnimationKey = 'alertLeave';
}
return alert; setTitle(title) {
this.data.title = title;
}
setSubTitle(subTitle) {
this.data.subTitle = subTitle;
}
setBodyText(text) {
this.data.text = text;
}
addInput(input) {
input.value = isDefined(input.value) ? input.value : '';
this.data.inputs.push(input);
}
addButton(button) {
if (typeof button === 'string') {
button = {
text: button
};
}
this.data.buttons.push(button);
}
close() {
let index = this._nav.indexOf(this);
this._nav.remove(index, { animateFirst: true });
}
onClose(handler) {
this.data.onClose = handler;
}
static create(opts={}) {
return new Alert(opts);
} }
} }
@Component({
selector: 'ion-alert',
template:
'<div (click)="close()" tappable disable-activated class="backdrop" role="presentation"></div>' +
'<div class="alert-wrapper">' +
'<div class="alert-head">' +
'<h2 class="alert-title" *ngIf="d.title">{{d.title}}</h2>' +
'<h3 class="alert-sub-title" *ngIf="d.subTitle">{{d.subTitle}}</h3>' +
'</div>' +
'<div class="alert-body" *ngIf="d.text">{{d.text}}</div>' +
'<div class="alert-body alert-inputs" *ngIf="d.inputs.length">' +
'<div class="alert-input-wrapper" *ngFor="#i of d.inputs">' +
'<div class="alert-input-title" *ngIf="i.title">{{i.title}}</div>' +
'<input [placeholder]="i.placeholder" [type]="i.input" [value]="i.value" class="alert-input">' +
'</div>' +
'</div>' +
'<div class="alert-buttons">' +
'<button *ngFor="#b of d.buttons" (click)="click(b)" [ngClass]="b.cssClass" class="alert-button">' +
'{{b.text}}' +
'</button>' +
'</div>' +
'</div>',
host: {
'role': 'dialog'
},
directives: [NgClass, NgIf, NgFor]
})
class AlertCmp {
constructor(
private _viewCtrl: ViewController,
elementRef: ElementRef,
params: NavParams,
renderer: Renderer
) {
this.d = params.data;
if (this.d.cssClass) {
renderer.setElementClass(elementRef, this.d.cssClass, true);
}
}
click(button, ev) {
let shouldClose = true;
if (button.handler) {
// a handler has been provided, run it
if (button.handler() === false) {
// if the return value is a false then do not close
shouldClose = false;
}
}
if (shouldClose) {
this.close();
}
}
close() {
this._viewCtrl.close();
}
onPageDidLeave() {
let values = this.d.inputs.map(i => i.value);
this.d.onClose && this.d.onClose(values);
}
}
/**
* Animations for alerts
*/
class AlertPopIn extends Animation {
constructor(enteringView, leavingView, opts) {
super(null, opts);
let ele = enteringView.pageRef().nativeElement;
let backdrop = new Animation(ele.querySelector('.backdrop'));
let wrapper = new Animation(ele.querySelector('.alert-wrapper'));
wrapper.fromTo('opacity', '0.01', '1').fromTo('scale', '1.1', '1');
backdrop.fromTo('opacity', '0.01', '0.3');
this
.easing('ease-in-out')
.duration(200)
.add(backdrop, wrapper);
}
}
Animation.register('alert-pop-in', AlertPopIn);
class AlertPopOut extends Animation {
constructor(enteringView, leavingView, opts) {
super(null, opts);
let ele = leavingView.pageRef().nativeElement;
let backdrop = new Animation(ele.querySelector('.backdrop'));
let wrapper = new Animation(ele.querySelector('.alert-wrapper'));
wrapper.fromTo('opacity', '1', '0').fromTo('scale', '1', '0.9');
backdrop.fromTo('opacity', '0.3', '0');
this
.easing('ease-in-out')
.duration(200)
.add(backdrop, wrapper);
}
}
Animation.register('alert-pop-out', AlertPopOut);
class AlertMdPopIn extends Animation {
constructor(enteringView, leavingView, opts) {
super(null, opts);
let ele = enteringView.pageRef().nativeElement;
let backdrop = new Animation(ele.querySelector('.backdrop'));
let wrapper = new Animation(ele.querySelector('.alert-wrapper'));
wrapper.fromTo('opacity', '0.01', '1').fromTo('scale', '1.1', '1');
backdrop.fromTo('opacity', '0.01', '0.5');
this
.easing('ease-in-out')
.duration(200)
.add(backdrop, wrapper);
}
}
Animation.register('alert-md-pop-in', AlertMdPopIn);
class AlertMdPopOut extends Animation {
constructor(enteringView, leavingView, opts) {
super(null, opts);
let ele = leavingView.pageRef().nativeElement;
let backdrop = new Animation(ele.querySelector('.backdrop'));
let wrapper = new Animation(ele.querySelector('.alert-wrapper'));
wrapper.fromTo('opacity', '1', '0').fromTo('scale', '1', '0.9');
backdrop.fromTo('opacity', '0.5', '0');
this
.easing('ease-in-out')
.duration(200)
.add(backdrop, wrapper);
}
}
Animation.register('alert-md-pop-out', AlertMdPopOut);

View File

@ -1,4 +1,4 @@
import {App, Alert} from 'ionic/ionic'; import {App, Alert, OverlayController} from 'ionic/ionic';
@App({ @App({
@ -6,7 +6,7 @@ import {App, Alert} from 'ionic/ionic';
}) })
class E2EApp { class E2EApp {
constructor() { constructor(private overlay: OverlayController) {
this.alertOpen = false; this.alertOpen = false;
this.confirmOpen = false; this.confirmOpen = false;
this.confirmResult = ''; this.confirmResult = '';
@ -15,12 +15,56 @@ class E2EApp {
} }
doAlert() { doAlert() {
debugger; let alert = Alert.create({
let alert = Alert.create(); title: 'Alert!',
subTitle: 'My alert subtitle',
bodyText: 'My alert body text',
buttons: ['Ok']
});
alert.onClose(() => {
this.alertOpen = false;
});
this.overlay.push(alert).then(() => {
this.alertOpen = true;
});
} }
doPrompt() { doPrompt() {
let alert = Alert.create();
alert.setTitle('Prompt!');
alert.addInput({
label: 'Input Label',
placeholder: 'Placeholder'
});
alert.addButton({
text: 'Cancel',
handler: () => {
console.log('500ms delayed prompt close');
setTimeout(() => {
console.log('Prompt close');
alert.close();
}, 500);
return false;
}
});
alert.addButton({
text: 'Ok',
handler: () => {
console.log('Prompt Ok');
}
});
alert.onClose(data => {
this.promptOpen = false;
});
this.overlay.push(alert).then(() => {
this.promptOpen = true;
});
} }
doConfirm() { doConfirm() {

View File

@ -233,7 +233,7 @@ export class NavController extends Ion {
push(componentType, params={}, opts={}, callback) { push(componentType, params={}, opts={}, callback) {
if (!componentType) { if (!componentType) {
let errMsg = 'invalid componentType to push'; let errMsg = 'invalid componentType to push';
console.error(errMsg) console.error(errMsg);
return Promise.reject(errMsg); return Promise.reject(errMsg);
} }
@ -245,13 +245,27 @@ export class NavController extends Ion {
return Promise.reject('nav controller actively transitioning'); return Promise.reject('nav controller actively transitioning');
} }
this.setTransitioning(true, 500);
let promise = null; let promise = null;
if (!callback) { if (!callback) {
promise = new Promise(res => { callback = res; }); promise = new Promise(res => { callback = res; });
} }
// create a new ViewController
let enteringView = new ViewController(this, componentType, params);
enteringView.pageType = opts.pageType;
enteringView.handle = opts.handle || null;
this.pushView(enteringView, opts, callback);
return promise;
}
/**
* @private
*/
pushView(enteringView, opts, callback) {
this.setTransitioning(true, 500);
// do not animate if this is the first in the stack // do not animate if this is the first in the stack
if (!this._views.length && !opts.animateFirst) { if (!this._views.length && !opts.animateFirst) {
opts.animate = false; opts.animate = false;
@ -260,6 +274,10 @@ export class NavController extends Ion {
// default the direction to "forward" // default the direction to "forward"
opts.direction = opts.direction || 'forward'; opts.direction = opts.direction || 'forward';
if (!opts.animation) {
opts.animation = this.config.get(enteringView.enterAnimationKey);
}
// the active view is going to be the leaving one (if one exists) // the active view is going to be the leaving one (if one exists)
let leavingView = this.getActive() || new ViewController(); let leavingView = this.getActive() || new ViewController();
leavingView.shouldCache = (isBoolean(opts.cacheLeavingView) ? opts.cacheLeavingView : true); leavingView.shouldCache = (isBoolean(opts.cacheLeavingView) ? opts.cacheLeavingView : true);
@ -268,25 +286,16 @@ export class NavController extends Ion {
leavingView.willUnload(); leavingView.willUnload();
} }
// create a new ViewController
let enteringView = new ViewController(this, componentType, params);
enteringView.shouldDestroy = false;
enteringView.shouldCache = false;
enteringView.pageType = opts.pageType;
enteringView.handle = opts.handle || null;
// add the view to the stack // add the view to the stack
this._add(enteringView); this._add(enteringView);
if (this.router) { if (this.router) {
// notify router of the state change // notify router of the state change
this.router.stateChange('push', enteringView, params); this.router.stateChange('push', enteringView, enteringView.params);
} }
// start the transition // start the transition
this._transition(enteringView, leavingView, opts, callback); this._transition(enteringView, leavingView, opts, callback);
return promise;
} }
/** /**
@ -333,6 +342,10 @@ export class NavController extends Ion {
leavingView.willUnload(); leavingView.willUnload();
} }
if (!opts.animation) {
opts.animation = this.config.get(leavingView.leaveAnimationKey);
}
// the entering view is now the new last view // the entering view is now the new last view
// Note: we might not have an entering view if this is the // Note: we might not have an entering view if this is the
// only view on the history stack. // only view on the history stack.
@ -655,9 +668,6 @@ export class NavController extends Ion {
return done(enteringView); return done(enteringView);
} }
if (!opts.animation) {
opts.animation = this.config.get('pageTransition');
}
if (this.config.get('animate') === false) { if (this.config.get('animate') === false) {
opts.animate = false; opts.animate = false;
} }
@ -909,7 +919,7 @@ export class NavController extends Ion {
let providers = this.providers.concat(Injector.resolve([ let providers = this.providers.concat(Injector.resolve([
provide(ViewController, {useValue: viewCtrl}), provide(ViewController, {useValue: viewCtrl}),
provide(NavParams, {useValue: viewCtrl.params}) provide(NavParams, {useValue: viewCtrl.getNavParams()})
])); ]));
let location = this.elementRef; let location = this.elementRef;

View File

@ -1,6 +1,5 @@
import {NavParams} from './nav-controller'; import {NavParams} from './nav-controller';
/** /**
* @name ViewController * @name ViewController
* @description * @description
@ -18,14 +17,26 @@ import {NavParams} from './nav-controller';
*/ */
export class ViewController { export class ViewController {
constructor(navCtrl, componentType, params = {}) { constructor(navCtrl, componentType, data={}) {
this.navCtrl = navCtrl; this.setNav(navCtrl);
this.componentType = componentType; this.componentType = componentType;
this.params = new NavParams(params); this.data = data;
this.instance = {}; this.instance = {};
this.state = 0; this.state = 0;
this._destroys = []; this._destroys = [];
this._loaded = false; this._loaded = false;
this.shouldDestroy = false;
this.shouldCache = false;
this.enterAnimationKey = 'pageTransition';
this.leaveAnimationKey = 'pageTransition';
}
setNav(navCtrl) {
this._nav = navCtrl;
}
getNavParams() {
return new NavParams(this.data);
} }
/** /**
@ -35,8 +46,8 @@ export class ViewController {
*/ */
enableBack() { enableBack() {
// update if it's possible to go back from this nav item // update if it's possible to go back from this nav item
if (this.navCtrl) { if (this._nav) {
let previousItem = this.navCtrl.getPrevious(this); let previousItem = this._nav.getPrevious(this);
// the previous view may exist, but if it's about to be destroyed // the previous view may exist, but if it's about to be destroyed
// it shouldn't be able to go back to // it shouldn't be able to go back to
return !!(previousItem && !previousItem.shouldDestroy); return !!(previousItem && !previousItem.shouldDestroy);
@ -74,7 +85,7 @@ export class ViewController {
* @returns {Number} Returns the index of this page within its NavController. * @returns {Number} Returns the index of this page within its NavController.
*/ */
get index() { get index() {
return (this.navCtrl ? this.navCtrl.indexOf(this) : -1); return (this._nav ? this._nav.indexOf(this) : -1);
} }
/** /**

View File

@ -1,63 +1,31 @@
import {Animation} from '../../animations/animation'; import {Injectable} from 'angular2/core';
import {extend} from '../../util';
import {ViewController} from '../nav/view-controller';
import {Config} from '../../config/config';
import {IonicApp} from '../app/app';
/** @Injectable()
* @private
*/
export class OverlayController { export class OverlayController {
open(componentType, params = {}, opts = {}) { constructor(private _config: Config) {}
if (!this.nav) {
console.error('<ion-overlay></ion-overlay> required in root template (app.html) to use: ' + opts.pageType);
return Promise.reject();
}
let resolve, reject; push(overlayView, opts={}) {
let promise = new Promise((res, rej) => { resolve = res; reject = rej; }); overlayView.setNav(this._nav);
opts.animation = opts.enterAnimation;
opts.animateFirst = true; opts.animateFirst = true;
this.nav.push(componentType, params, opts).then(viewCtrl => { return new Promise(resolve => {
if (viewCtrl && viewCtrl.instance) { this._nav.pushView(overlayView, opts, resolve);
let self = this;
function escape(ev) {
if (ev.keyCode == 27 && self.nav.last() === viewCtrl) {
viewCtrl.instance.close();
}
}
viewCtrl.instance.close = (data, closeOpts={}) => {
extend(opts, closeOpts);
opts.animation = opts.leaveAnimation;
viewCtrl.instance.onClose && viewCtrl.instance.onClose(data);
this.nav.pop(opts);
document.removeEventListener('keyup', escape, true);
};
document.addEventListener('keyup', escape, true);
resolve(viewCtrl.instance);
} else {
reject();
}
}, rejectReason => {
console.error(rejectReason);
}); });
return promise;
} }
getByType(overlayType) { pop(opts={}) {
let overlay = this.nav.getByType(overlayType); opts.animateFirst = true;
return overlay && overlay.instance; return this._nav.pop(opts);
} }
getByHandle(handle, overlayType) { setNav(nav) {
let overlay = this.nav.getByHandle(handle); this._nav = nav;
return overlay && overlay.instance;
} }
} }

View File

@ -35,7 +35,7 @@ export class OverlayNav extends NavController {
} }
this.initZIndex = 1000; this.initZIndex = 1000;
overlayCtrl.nav = this; overlayCtrl.setNav(this);
} }
} }

View File

@ -186,6 +186,9 @@ export class Config {
get(key) { get(key) {
if (!isDefined(this._c[key])) { if (!isDefined(this._c[key])) {
if (!isDefined(key)) {
throw 'config key is not defined';
}
// if the value was already set this will all be skipped // if the value was already set this will all be skipped
// if there was no user config then it'll check each of // if there was no user config then it'll check each of

View File

@ -11,6 +11,9 @@ Config.setModeConfig('ios', {
actionSheetCancelIcon: '', actionSheetCancelIcon: '',
actionSheetDestructiveIcon: '', actionSheetDestructiveIcon: '',
alertEnter: 'alert-pop-in',
alertLeave: 'alert-pop-out',
backButtonText: 'Back', backButtonText: 'Back',
backButtonIcon: 'ion-ios-arrow-back', backButtonIcon: 'ion-ios-arrow-back',
@ -24,9 +27,6 @@ Config.setModeConfig('ios', {
pageTransition: 'ios-transition', pageTransition: 'ios-transition',
pageTransitionDelay: 16, pageTransitionDelay: 16,
popupEnter: 'popup-pop-in',
popupLeave: 'popup-pop-out',
tabbarPlacement: 'bottom', tabbarPlacement: 'bottom',
}); });
@ -40,6 +40,9 @@ Config.setModeConfig('md', {
actionSheetCancelIcon: 'ion-md-close', actionSheetCancelIcon: 'ion-md-close',
actionSheetDestructiveIcon: 'ion-md-trash', actionSheetDestructiveIcon: 'ion-md-trash',
alertEnter: 'alert-md-pop-in',
alertLeave: 'alert-md-pop-out',
backButtonText: '', backButtonText: '',
backButtonIcon: 'ion-md-arrow-back', backButtonIcon: 'ion-md-arrow-back',
@ -53,9 +56,6 @@ Config.setModeConfig('md', {
pageTransition: 'md-transition', pageTransition: 'md-transition',
pageTransitionDelay: 120, pageTransitionDelay: 120,
popupEnter: 'popup-md-pop-in',
popupLeave: 'popup-md-pop-out',
tabbarHighlight: true, tabbarHighlight: true,
tabbarPlacement: 'top', tabbarPlacement: 'top',

View File

@ -18,7 +18,6 @@ export * from './util/keyboard'
export * from './animations/animation' export * from './animations/animation'
export * from './translation/translate' export * from './translation/translate'
export * from './translation/translate_pipe' export * from './translation/translate_pipe'