feat(toggle): boolean input directive

This commit is contained in:
Adam Bradley
2017-04-23 21:54:34 -05:00
parent 9a533f430f
commit ccfce9d034
35 changed files with 1529 additions and 464 deletions

View File

@ -0,0 +1,124 @@
import { Directive, ElementRef, EventEmitter, HostListener, Input, Output } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
/**
* @hidden
*/
@Directive({
selector: 'ion-toggle',
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: BooleanInput, multi: true }],
})
export class BooleanInput {
id: string;
_labelId: string;
_chg: Function;
_tch: Function;
_dis: Function;
@Output() ionChange: EventEmitter<any> = new EventEmitter<any>();
@Output() ionFocus: EventEmitter<any> = new EventEmitter<any>();
@Output() ionBlur: EventEmitter<any> = new EventEmitter<any>();
constructor(public _el: ElementRef) {}
/**
* @input {boolean} If true, the element is selected.
*/
@Input()
get checked(): boolean {
return this._el.nativeElement.checked;
}
set checked(val: boolean) {
this._el.nativeElement.checked = val;
}
/**
* @input {boolean} If true, the user cannot interact with this element.
*/
@Input()
get disabled(): boolean {
return this._el.nativeElement.disabled;
}
set disabled(val: boolean) {
this._el.nativeElement.disabled = val;
}
/**
* @input {string}
*/
@Input()
get value(): string {
return this._el.nativeElement.value;
}
set value(val: string) {
this._el.nativeElement.value = val;
}
/**
* @hidden
*/
@HostListener('$ionChange', ['$event'])
_onChange(ev: any) {
ev.stopPropagation();
this._chg && this._chg(ev.detail.checked);
this._tch && this._tch();
this.ionChange.emit(this);
}
/**
* @hidden
*/
@HostListener('$ionFocus', ['$event'])
_onFocus(ev: any) {
ev.stopPropagation();
this.ionFocus.emit(this);
}
/**
* @hidden
*/
@HostListener('$ionBlur', ['$event'])
_onBlur(ev: any) {
ev.stopPropagation();
this.ionBlur.emit(this);
}
/**
* @hidden
*/
writeValue(val: any) {
if (this.checked !== val) {
this.checked = val;
}
}
/**
* @hidden
*/
registerOnChange(fn: any) {
this._chg = fn;
}
/**
* @hidden
*/
registerOnTouched(fn: any) {
this._tch = fn;
}
/**
* @hidden
*/
registerOnDisabledChange(fn: any) {
this._dis = fn;
}
/**
* @hidden
*/
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}
}

View File

@ -1,7 +1,7 @@
import { Config } from '../config/config'; import { Config } from '../../../config/config';
import { DomController } from '../platform/dom-controller'; import { DomController } from '../../../platform/dom-controller';
import { NgZone } from '@angular/core'; import { NgZone } from '@angular/core';
import { Platform } from '../platform/platform'; import { Platform } from '../../../platform/platform';
/** /**
@ -27,6 +27,9 @@ export function setupCore(config: Config, plt: Platform, domCtrl: DomController,
// keep core and angular dom reads/writes *nsync // keep core and angular dom reads/writes *nsync
ionic['domCtrl'] = domCtrl; ionic['domCtrl'] = domCtrl;
// keep core and angular dom reads/writes *nsync
ionic['eventNamePrefix'] = '$';
// next tick controller created here so that it can // next tick controller created here so that it can
// be created to run outside of angular // be created to run outside of angular
ionic['nextTickCtrl'] = getNextTickController(zone, plt.userAgent().toLowerCase()); ionic['nextTickCtrl'] = getNextTickController(zone, plt.userAgent().toLowerCase());

View File

@ -15,7 +15,7 @@ ion-badge,
:host { :host {
display: inline-block; display: inline-block;
visibility: inherit !important; visibility: inherit !important;
contain: layout content; contain: content;
} }
.badge { .badge {

View File

@ -1,4 +1,4 @@
import { Component } from '../../util/decorators'; import { Component } from '../../index';
@Component({ @Component({

View File

@ -0,0 +1,3 @@
@import "../../themes/ionic.globals";
@import "./card-content";

View File

@ -0,0 +1,3 @@
@import "../../themes/ionic.globals";
@import "./card-content";

View File

@ -0,0 +1,9 @@
@import "../../themes/ionic.globals";
// Card
// --------------------------------------------------
ion-card-content,
:host {
display: block;
}

View File

@ -1,17 +1,12 @@
import { Directive, ElementRef, Renderer } from '@angular/core'; import { Component } from '../../index';
import { Config } from '../../config/config';
import { Ion } from '../ion';
/** @Component({
* @hidden tag: 'ion-card-content',
*/ styleUrls: {
@Directive({ ios: 'card-content.ios.scss',
selector: 'ion-card-content' md: 'card-content.md.scss',
}) wp: 'card-content.wp.scss'
export class CardContent extends Ion {
constructor(config: Config, elementRef: ElementRef, renderer: Renderer) {
super(config, elementRef, renderer, 'card-content');
} }
} })
export class CardContent {}

View File

@ -0,0 +1,3 @@
@import "../../themes/ionic.globals";
@import "./card-content";

View File

@ -0,0 +1,2 @@
@import "../../themes/ionic.globals";
@import "./card-header";

View File

@ -0,0 +1,2 @@
@import "../../themes/ionic.globals";
@import "./card-header";

View File

@ -0,0 +1,14 @@
@import "../../themes/ionic.globals";
// Card Header
// --------------------------------------------------
ion-card-header,
:host {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@ -1,17 +1,12 @@
import { Directive, ElementRef, Renderer } from '@angular/core'; import { Component } from '../../index';
import { Config } from '../../config/config';
import { Ion } from '../ion';
/** @Component({
* @hidden tag: 'ion-card-header',
*/ styleUrls: {
@Directive({ ios: 'card-header.ios.scss',
selector: 'ion-card-header' md: 'card-header.md.scss',
}) wp: 'card-header.wp.scss'
export class CardHeader extends Ion {
constructor(config: Config, elementRef: ElementRef, renderer: Renderer) {
super(config, elementRef, renderer, 'card-header');
} }
} })
export class CardHeader {}

View File

@ -0,0 +1,2 @@
@import "../../themes/ionic.globals";
@import "./card-header";

View File

@ -1,17 +1,12 @@
import { Directive, ElementRef, Renderer } from '@angular/core'; import { Component } from '../../index';
import { Config } from '../../config/config';
import { Ion } from '../ion';
/** @Component({
* @hidden tag: 'ion-card-title',
*/ styleUrls: {
@Directive({ ios: 'card.ios.scss',
selector: 'ion-card-title' md: 'card.md.scss',
}) wp: 'card.wp.scss'
export class CardTitle extends Ion {
constructor(config: Config, elementRef: ElementRef, renderer: Renderer) {
super(config, elementRef, renderer, 'card-title');
} }
} })
export class CardTitle {}

View File

@ -4,25 +4,15 @@
// -------------------------------------------------- // --------------------------------------------------
ion-card { ion-card,
:host {
display: block; display: block;
overflow: hidden; overflow: hidden;
} }
ion-card img { ion-card img,
:host img {
display: block; display: block;
width: 100%; width: 100%;
} }
ion-card-header {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
ion-card-content {
display: block;
}

View File

@ -1,18 +1,12 @@
import { Directive, ElementRef, Renderer } from '@angular/core'; import { Component } from '../../index';
import { Config } from '../../config/config';
import { Ion } from '../ion';
/** @Component({
* @hidden tag: 'ion-card',
*/ styleUrls: {
@Directive({ ios: 'card.ios.scss',
selector: 'ion-card' md: 'card.md.scss',
}) wp: 'card.wp.scss'
export class Card extends Ion {
constructor(config: Config, elementRef: ElementRef, renderer: Renderer) {
super(config, elementRef, renderer, 'card');
} }
} })
export class Card {}

View File

@ -1,4 +1,4 @@
import { NgModule } from '@angular/core'; import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { IonicPageModule } from '../../../../../..'; import { IonicPageModule } from '../../../../../..';
import { RootPage } from './root-page'; import { RootPage } from './root-page';
@ -9,6 +9,9 @@ import { RootPage } from './root-page';
], ],
imports: [ imports: [
IonicPageModule.forChild(RootPage) IonicPageModule.forChild(RootPage)
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
] ]
}) })
export class RootPageModule {} export class RootPageModule {}

View File

@ -0,0 +1,173 @@
export class GestureController {
private id: number = 0;
private requestedStart: { [eventId: number]: number } = {};
private disabledGestures: { [eventName: string]: Set<number> } = {};
private disabledScroll: Set<number> = new Set<number>();
private capturedID: number = null;
createGesture(gestureName: string, gesturePriority: number, disableScroll: boolean): GestureDelegate {
return new GestureDelegate(this, ++this.id, gestureName, gesturePriority, disableScroll);
}
start(gestureName: string, id: number, priority: number): boolean {
if (!this.canStart(gestureName)) {
delete this.requestedStart[id];
return false;
}
this.requestedStart[id] = priority;
return true;
}
capture(gestureName: string, id: number, priority: number): boolean {
if (!this.start(gestureName, id, priority)) {
return false;
}
let requestedStart = this.requestedStart;
let maxPriority = -10000;
for (let gestureID in requestedStart) {
maxPriority = Math.max(maxPriority, requestedStart[gestureID]);
}
if (maxPriority === priority) {
this.capturedID = id;
this.requestedStart = {};
return true;
}
delete requestedStart[id];
return false;
}
release(id: number) {
delete this.requestedStart[id];
if (this.capturedID && id === this.capturedID) {
this.capturedID = null;
}
}
disableGesture(gestureName: string, id: number) {
let set = this.disabledGestures[gestureName];
if (!set) {
set = new Set<number>();
this.disabledGestures[gestureName] = set;
}
set.add(id);
}
enableGesture(gestureName: string, id: number) {
let set = this.disabledGestures[gestureName];
if (set) {
set.delete(id);
}
}
disableScroll(id: number) {
// let isEnabled = !this.isScrollDisabled();
this.disabledScroll.add(id);
// if (this._app && isEnabled && this.isScrollDisabled()) {
// console.debug('GestureController: Disabling scrolling');
// this._app._setDisableScroll(true);
// }
}
enableScroll(id: number) {
// let isDisabled = this.isScrollDisabled();
this.disabledScroll.delete(id);
// if (this._app && isDisabled && !this.isScrollDisabled()) {
// console.debug('GestureController: Enabling scrolling');
// this._app._setDisableScroll(false);
// }
}
canStart(gestureName: string): boolean {
if (this.capturedID) {
// a gesture already captured
return false;
}
if (this.isDisabled(gestureName)) {
return false;
}
return true;
}
isCaptured(): boolean {
return !!this.capturedID;
}
isScrollDisabled(): boolean {
return this.disabledScroll.size > 0;
}
isDisabled(gestureName: string): boolean {
let disabled = this.disabledGestures[gestureName];
if (disabled && disabled.size > 0) {
return true;
}
return false;
}
}
export class GestureDelegate {
constructor(
private ctrl: GestureController,
private id: number,
private name: string,
private priority: number,
private disableScroll: boolean
) { }
canStart(): boolean {
if (!this.ctrl) {
return false;
}
return this.ctrl.canStart(this.name);
}
start(): boolean {
if (!this.ctrl) {
return false;
}
return this.ctrl.start(this.name, this.id, this.priority);
}
capture(): boolean {
if (!this.ctrl) {
return false;
}
let captured = this.ctrl.capture(this.name, this.id, this.priority);
if (captured && this.disableScroll) {
this.ctrl.disableScroll(this.id);
}
return captured;
}
release() {
if (this.ctrl) {
this.ctrl.release(this.id);
if (this.disableScroll) {
this.ctrl.enableScroll(this.id);
}
}
}
destroy() {
this.release();
this.ctrl = null;
}
}

View File

@ -0,0 +1,365 @@
import { applyStyles, getElementReference, pointerCoordX, pointerCoordY } from '../../util/dom';
import { Component, Listen, Ionic, Prop } from '../../index';
import { GestureCallback, GestureDetail } from '../../util/interfaces';
import { GestureController, GestureDelegate } from './gesture-controller';
import { PanRecognizer } from './recognizers';
@Component({
tag: 'ion-gesture',
shadow: false
})
export class Gesture {
private $el: HTMLElement;
private detail: GestureDetail = {};
private positions: number[] = [];
private gesture: GestureDelegate;
private lastTouch = 0;
private pan: PanRecognizer;
private hasCapturedPan = false;
private hasPress = false;
private hasStartedPan = false;
private requiresMove = false;
@Prop() direction: string = 'x';
@Prop() gestureName: string = '';
@Prop() gesturePriority: number = 0;
@Prop() listenOn: string = 'child';
@Prop() maxAngle: number = 40;
@Prop() threshold: number = 20;
@Prop() type: string = 'pan';
@Prop() canStart: GestureCallback;
@Prop() onStart: GestureCallback;
@Prop() onMove: GestureCallback;
@Prop() onEnd: GestureCallback;
@Prop() onPress: GestureCallback;
@Prop() notCaptured: GestureCallback;
ionViewDidLoad() {
Ionic.controllers.gesture = (Ionic.controllers.gesture || new GestureController());
this.gesture = (<GestureController>Ionic.controllers.gesture).createGesture(this.gestureName, this.gesturePriority, false);
const types = this.type.replace(/\s/g, '').toLowerCase().split(',');
if (types.indexOf('pan') > -1) {
this.pan = new PanRecognizer(this.direction, this.threshold, this.maxAngle);
this.requiresMove = true;
}
this.hasPress = (types.indexOf('press') > -1);
if (this.pan || this.hasPress) {
Ionic.listener.enable(this, 'touchstart', true, this.listenOn);
Ionic.listener.enable(this, 'mousedown', true, this.listenOn);
Ionic.dom.write(() => {
applyStyles(getElementReference(this.$el, this.listenOn), GESTURE_INLINE_STYLES);
});
}
}
// DOWN *************************
@Listen('touchstart', { passive: true, enabled: false })
onTouchStart(ev: TouchEvent) {
this.lastTouch = now(ev);
this.enableMouse(false);
this.enableTouch(true);
this.pointerDown(ev, this.lastTouch);
}
@Listen('mousedown', { passive: true, enabled: false })
onMouseDown(ev: MouseEvent) {
const timeStamp = now(ev);
if (this.lastTouch === 0 || (this.lastTouch + MOUSE_WAIT < timeStamp)) {
this.enableMouse(true);
this.enableTouch(false);
this.pointerDown(ev, timeStamp);
}
}
private pointerDown(ev: UIEvent, timeStamp: number): boolean {
if (!this.gesture || this.hasStartedPan) {
return false;
}
const detail = this.detail;
detail.startX = detail.currentX = pointerCoordX(ev);
detail.startY = detail.currentY = pointerCoordY(ev);
detail.startTimeStamp = detail.timeStamp = timeStamp;
detail.velocityX = detail.velocityY = detail.deltaX = detail.deltaY = 0;
detail.directionX = detail.directionY = detail.velocityDirectionX = detail.velocityDirectionY = null;
detail.event = ev;
this.positions.length = 0;
if (this.canStart && this.canStart(detail) === false) {
return false;
}
this.positions.push(detail.currentX, detail.currentY, timeStamp);
// Release fallback
this.gesture.release();
// Start gesture
if (!this.gesture.start()) {
return false;
}
if (this.pan) {
this.hasStartedPan = true;
this.hasCapturedPan = false;
this.pan.start(detail.startX, detail.startY);
}
return true;
}
// MOVE *************************
@Listen('touchmove', { passive: true, enabled: false })
onTouchMove(ev: TouchEvent) {
this.lastTouch = this.detail.timeStamp = now(ev);
this.pointerMove(ev);
}
@Listen('document:mousemove', { passive: true, enabled: false })
onMoveMove(ev: TouchEvent) {
const timeStamp = now(ev);
if (this.lastTouch === 0 || (this.lastTouch + MOUSE_WAIT < timeStamp)) {
this.detail.timeStamp = timeStamp;
this.pointerMove(ev);
}
}
private pointerMove(ev: UIEvent) {
const detail = this.detail;
this.calcGestureData(ev);
if (this.pan) {
if (this.hasCapturedPan) {
Ionic.dom.write(() => {
detail.type = 'pan';
if (this.onMove) {
this.onMove(detail);
} else {
Ionic.emit(this, 'ionGestureMove', this.detail);
}
});
} else if (this.pan.detect(detail.currentX, detail.currentY)) {
if (this.pan.isGesture() !== 0) {
if (!this.tryToCapturePan(ev)) {
this.abortGesture();
}
}
}
}
}
private calcGestureData(ev: UIEvent) {
const detail = this.detail;
detail.currentX = pointerCoordX(ev);
detail.currentY = pointerCoordY(ev);
detail.deltaX = (detail.currentX - detail.startX);
detail.deltaY = (detail.currentY - detail.startY);
detail.event = ev;
// figure out which direction we're movin'
detail.directionX = detail.velocityDirectionX = (detail.deltaX > 0 ? 'left' : (detail.deltaX < 0 ? 'right' : null));
detail.directionY = detail.velocityDirectionY = (detail.deltaY > 0 ? 'up' : (detail.deltaY < 0 ? 'down' : null));
const positions = this.positions;
positions.push(detail.currentX, detail.currentY, detail.timeStamp);
var endPos = (positions.length - 1);
var startPos = endPos;
var timeRange = (detail.timeStamp - 100);
// move pointer to position measured 100ms ago
for (var i = endPos; i > 0 && positions[i] > timeRange; i -= 3) {
startPos = i;
}
if (startPos !== endPos) {
// compute relative movement between these two points
var movedX = (positions[startPos - 2] - positions[endPos - 2]);
var movedY = (positions[startPos - 1] - positions[endPos - 1]);
var factor = 16 / (positions[endPos] - positions[startPos]);
// based on XXms compute the movement to apply for each render step
detail.velocityX = movedX * factor;
detail.velocityY = movedY * factor;
detail.velocityDirectionX = (detail.velocityX > 0 ? 'left' : (detail.velocityX < 0 ? 'right' : null));
detail.velocityDirectionY = (detail.velocityY > 0 ? 'up' : (detail.velocityY < 0 ? 'down' : null));
}
}
private tryToCapturePan(ev: UIEvent): boolean {
if (this.gesture && !this.gesture.capture()) {
return false;
}
this.detail.event = ev;
if (this.onStart) {
this.onStart(this.detail);
} else {
Ionic.emit(this, 'ionGestureStart', this.detail);
}
this.hasCapturedPan = true;
return true;
}
private abortGesture() {
this.hasStartedPan = false;
this.hasCapturedPan = false;
this.gesture.release();
this.enable(false);
this.notCaptured(this.detail);
}
// END *************************
@Listen('touchend', { passive: true, enabled: false })
onTouchEnd(ev: TouchEvent) {
this.lastTouch = this.detail.timeStamp = now(ev);
this.pointerUp(ev);
this.enableTouch(false);
}
@Listen('document:mouseup', { passive: true, enabled: false })
onMouseUp(ev: TouchEvent) {
const timeStamp = now(ev);
if (this.lastTouch === 0 || (this.lastTouch + MOUSE_WAIT < timeStamp)) {
this.detail.timeStamp = timeStamp;
this.pointerUp(ev);
this.enableMouse(false);
}
}
private pointerUp(ev: UIEvent) {
const detail = this.detail;
this.gesture && this.gesture.release();
detail.event = ev;
this.calcGestureData(ev);
if (this.pan) {
if (this.hasCapturedPan) {
detail.type = 'pan';
if (this.onEnd) {
this.onEnd(detail);
} else {
Ionic.emit(this, 'ionGestureEnd', detail);
}
} else if (this.hasPress) {
this.detectPress();
} else {
if (this.notCaptured) {
this.notCaptured(detail);
} else {
Ionic.emit(this, 'ionGestureNotCaptured', detail);
}
}
} else if (this.hasPress) {
this.detectPress();
}
this.hasCapturedPan = false;
this.hasStartedPan = false;
}
private detectPress() {
const detail = this.detail;
if (Math.abs(detail.startX - detail.currentX) < 10 && Math.abs(detail.startY - detail.currentY) < 10) {
detail.type = 'press';
if (this.onPress) {
this.onPress(detail);
} else {
Ionic.emit(this, 'ionPress', detail);
}
}
}
// ENABLE LISTENERS *************************
private enableMouse(shouldEnable: boolean) {
if (this.requiresMove) {
Ionic.listener.enable(this, 'document:mousemove', shouldEnable);
}
Ionic.listener.enable(this, 'document:mouseup', shouldEnable);
}
private enableTouch(shouldEnable: boolean) {
if (this.requiresMove) {
Ionic.listener.enable(this, 'touchmove', shouldEnable);
}
Ionic.listener.enable(this, 'touchend', shouldEnable);
}
private enable(shouldEnable: boolean) {
this.enableMouse(shouldEnable);
this.enableTouch(shouldEnable);
}
ionViewWillUnload() {
this.gesture && this.gesture.destroy();
this.gesture = this.pan = this.detail = this.detail.event = null;
}
}
const GESTURE_INLINE_STYLES = {
'touch-action': 'none',
'user-select': 'none',
'-webkit-user-drag': 'none',
'-webkit-tap-highlight-color': 'rgba(0,0,0,0)'
};
const MOUSE_WAIT = 2500;
function now(ev: UIEvent) {
return ev.timeStamp || Date.now();
}

View File

@ -0,0 +1,66 @@
export class PanRecognizer {
private startX: number;
private startY: number;
private dirty: boolean = false;
private threshold: number;
private maxCosine: number;
private angle = 0;
private isPan = 0;
constructor(private direction: string, threshold: number, maxAngle: number) {
const radians = maxAngle * (Math.PI / 180);
this.maxCosine = Math.cos(radians);
this.threshold = threshold * threshold;
}
start(x: number, y: number) {
this.startX = x;
this.startY = y;
this.angle = 0;
this.isPan = 0;
this.dirty = true;
}
detect(x: number, y: number): boolean {
if (!this.dirty) {
return false;
}
const deltaX = (x - this.startX);
const deltaY = (y - this.startY);
const distance = deltaX * deltaX + deltaY * deltaY;
if (distance >= this.threshold) {
var angle = Math.atan2(deltaY, deltaX);
var cosine = (this.direction === 'y')
? Math.sin(angle)
: Math.cos(angle);
this.angle = angle;
if (cosine > this.maxCosine) {
this.isPan = 1;
} else if (cosine < -this.maxCosine) {
this.isPan = -1;
} else {
this.isPan = 0;
}
this.dirty = false;
return true;
}
return false;
}
isGesture(): number {
return this.isPan;
}
}

View File

@ -1,37 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule, ModuleWithProviders, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { IconModule } from '../icon/icon.module';
import { Tab } from './tab';
import { TabButton } from './tab-button';
import { TabHighlight } from './tab-highlight';
import { Tabs } from './tabs';
/** @hidden */
@NgModule({
imports: [
CommonModule,
IconModule
],
declarations: [
Tab,
TabButton,
TabHighlight,
Tabs
],
exports: [
Tab,
TabButton,
TabHighlight,
Tabs
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class TabsModule {
public static forRoot(): ModuleWithProviders {
return {
ngModule: TabsModule, providers: []
};
}
}

View File

@ -1,7 +1,6 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { Toggle } from '../../../../../../';
@Component({ @Component({
templateUrl: 'root-page.html' templateUrl: 'root-page.html'
@ -38,20 +37,20 @@ export class RootPage {
this.grapeCtrl.enabled ? this.grapeCtrl.disable() : this.grapeCtrl.enable(); this.grapeCtrl.enabled ? this.grapeCtrl.disable() : this.grapeCtrl.enable();
} }
appleChange(toggle: Toggle) { appleChange(toggle: any) {
console.log('appleChange', toggle); console.log('appleChange', toggle);
} }
bananaChange(toggle: Toggle) { bananaChange(toggle: any) {
console.log('bananaChange', toggle); console.log('bananaChange', toggle);
} }
kiwiChange(toggle: Toggle) { kiwiChange(toggle: any) {
console.log('kiwiChange', toggle); console.log('kiwiChange', toggle);
this.kiwiValue = toggle.checked; this.kiwiValue = toggle.checked;
} }
strawberryChange(toggle: Toggle) { strawberryChange(toggle: any) {
console.log('strawberryChange', toggle); console.log('strawberryChange', toggle);
this.strawberryValue = toggle.checked; this.strawberryValue = toggle.checked;
} }

View File

@ -1,29 +0,0 @@
import { Toggle } from '../toggle';
import { mockConfig, mockPlatform, mockHaptic, mockElementRef, mockGestureController, mockRenderer, mockItem, mockForm, mockChangeDetectorRef, mockZone } from '../../../util/mock-providers';
import { commonInputTest, BOOLEAN_CORPUS } from '../../../util/input-tester';
describe('Toggle', () => {
it('should pass common test', () => {
const platform = mockPlatform();
const config = mockConfig();
const elementRef = mockElementRef();
const renderer = mockRenderer();
const item: any = mockItem();
const form = mockForm();
const haptic = mockHaptic();
const cd = mockChangeDetectorRef();
const gesture = mockGestureController();
const zone = mockZone();
const toggle = new Toggle(form, config, platform, elementRef, renderer, haptic, item, gesture, null, cd, zone);
commonInputTest(toggle, {
defaultValue: false,
corpus: BOOLEAN_CORPUS,
});
});
});

View File

@ -1,53 +0,0 @@
import { GestureController, GESTURE_PRIORITY_TOGGLE, GESTURE_TOGGLE } from '../../gestures/gesture-controller';
import { DomController } from '../../platform/dom-controller';
import { PanGesture } from '../../gestures/pan-gesture';
import { Platform } from '../../platform/platform';
import { pointerCoord } from '../../util/dom';
import { Toggle } from './toggle';
/**
* @hidden
*/
export class ToggleGesture extends PanGesture {
constructor(
plt: Platform,
public toggle: Toggle,
gestureCtrl: GestureController,
domCtrl: DomController
) {
super(
plt,
toggle.getNativeElement(), {
threshold: 0,
zone: false,
domController: domCtrl,
gesture: gestureCtrl.createGesture({
name: GESTURE_TOGGLE,
priority: GESTURE_PRIORITY_TOGGLE
})
});
}
canStart(ev: any): boolean {
return true;
}
onDragStart(ev: any) {
ev.preventDefault();
this.toggle._onDragStart(pointerCoord(ev).x);
}
onDragMove(ev: any) {
ev.preventDefault();
this.toggle._onDragMove(pointerCoord(ev).x);
}
onDragEnd(ev: any) {
ev.preventDefault();
this.toggle._onDragEnd(pointerCoord(ev).x);
}
}

View File

@ -1,4 +1,6 @@
@import "../../themes/ionic.globals.ios"; @import "../../themes/ionic.globals.ios";
@import "./toggle";
// iOS Toggle // iOS Toggle
// -------------------------------------------------- // --------------------------------------------------

View File

@ -1,4 +1,6 @@
@import "../../themes/ionic.globals.md"; @import "../../themes/ionic.globals.md";
@import "./toggle";
// Material Design Toggle // Material Design Toggle
// -------------------------------------------------- // --------------------------------------------------

View File

@ -0,0 +1,35 @@
@import "../../themes/ionic.globals";
// Toggle
// --------------------------------------------------
:host {
display: inline-block;
visibility: inherit !important;
contain: content;
}
.toggle-cover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
background: transparent;
font-family: inherit;
font-style: inherit;
font-variant: inherit;
line-height: 1;
text-transform: none;
cursor: pointer;
outline: none;
}

View File

@ -1,238 +1,147 @@
import { NgZone, AfterViewInit, ChangeDetectorRef, Component, ElementRef, HostListener, Input, OnDestroy, Optional, Renderer, ViewEncapsulation } from '@angular/core'; import { BooleanInputComponent, GestureDetail } from '../../util/interfaces';
import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { Component, h, Ionic, Listen, Prop, Watch } from '../../index';
import { Config } from '../../config/config';
import { DomController } from '../../platform/dom-controller';
import { Form, IonicTapInput } from '../../util/form';
import { GestureController } from '../../gestures/gesture-controller';
import { Haptic } from '../../tap-click/haptic';
import { assert, isTrueProperty } from '../../util/util';
import { BaseInput } from '../../util/base-input';
import { Item } from '../item/item';
import { KEY_ENTER, KEY_SPACE } from '../../platform/key';
import { Platform } from '../../platform/platform';
import { ToggleGesture } from './toggle-gesture';
/**
* @name Toggle
* @description
* A toggle technically is the same thing as an HTML checkbox input,
* except it looks different and is easier to use on a touch device.
* Toggles can also have colors assigned to them, by adding any color
* attribute.
*
* See the [Angular 2 Docs](https://angular.io/docs/ts/latest/guide/forms.html)
* for more info on forms and inputs.
*
* @usage
* ```html
*
* <ion-list>
*
* <ion-item>
* <ion-label>Pepperoni</ion-label>
* <ion-toggle [(ngModel)]="pepperoni"></ion-toggle>
* </ion-item>
*
* <ion-item>
* <ion-label>Sausage</ion-label>
* <ion-toggle [(ngModel)]="sausage" disabled="true"></ion-toggle>
* </ion-item>
*
* <ion-item>
* <ion-label>Mushrooms</ion-label>
* <ion-toggle [(ngModel)]="mushrooms"></ion-toggle>
* </ion-item>
*
* </ion-list>
* ```
*
* @demo /docs/demos/src/toggle/
* @see {@link /docs/components#toggle Toggle Component Docs}
*/
@Component({ @Component({
selector: 'ion-toggle', tag: 'ion-toggle',
template: styleUrls: {
'<div class="toggle-icon">' + ios: 'toggle.ios.scss',
'<div class="toggle-inner"></div>' + md: 'toggle.md.scss',
'</div>' + wp: 'toggle.wp.scss'
'<button role="checkbox" ' + }
'type="button" ' +
'ion-button="item-cover" ' +
'[id]="id" ' +
'[attr.aria-checked]="_value" ' +
'[attr.aria-labelledby]="_labelId" ' +
'[attr.aria-disabled]="_disabled" ' +
'class="item-cover" disable-activated>' +
'</button>',
host: {
'[class.toggle-disabled]': '_disabled',
'[class.toggle-checked]': '_value',
'[class.toggle-activated]': '_activated',
},
providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: Toggle, multi: true } ],
encapsulation: ViewEncapsulation.None,
}) })
export class Toggle extends BaseInput<boolean> implements IonicTapInput, AfterViewInit, OnDestroy { export class Toggle implements BooleanInputComponent {
activated: boolean;
hasFocus: boolean;
id: string;
labelId: string;
startX: number;
_activated: boolean = false; @Prop() checked: boolean;
_startX: number; @Prop() disabled: boolean;
_msPrv: number = 0; @Prop() value: string;
_gesture: ToggleGesture;
/**
* @input {boolean} If true, the element is selected. @Watch('checked')
*/ changed(val: boolean) {
@Input() Ionic.emit(this, 'ionChange', { checked: val });
get checked(): boolean {
return this.value;
} }
set checked(val: boolean) {
this.value = val; canStart() {
return !this.disabled;
} }
constructor(
form: Form, onDragStart(detail: GestureDetail) {
config: Config, this.startX = detail.startX;
private _plt: Platform, this.fireFocus();
elementRef: ElementRef,
renderer: Renderer,
private _haptic: Haptic,
@Optional() item: Item,
private _gestureCtrl: GestureController,
private _domCtrl: DomController,
private _cd: ChangeDetectorRef,
private _zone: NgZone,
) {
super(config, elementRef, renderer, 'toggle', false, form, item, null);
} }
/**
* @hidden
*/
ngAfterViewInit() {
this._initialize();
this._gesture = new ToggleGesture(this._plt, this, this._gestureCtrl, this._domCtrl);
this._gesture.listen();
}
/** onDragMove(detail: GestureDetail) {
* @hidden if (this.checked) {
*/ if (detail.currentX + 15 < this.startX) {
_inputCheckHasValue() {} this.checked = false;
this.activated = true;
/** this.startX = detail.currentX;
* @hidden
*/
_inputNormalize(val: any): boolean {
return isTrueProperty(val);
}
/**
* @hidden
*/
_onDragStart(startX: number) {
assert(startX, 'startX must be valid');
console.debug('toggle, _onDragStart', startX);
this._zone.run(() => {
this._startX = startX;
this._fireFocus();
this._activated = true;
});
}
/**
* @hidden
*/
_onDragMove(currentX: number) {
if (!this._startX) {
assert(false, '_startX must be valid');
return;
}
let dirty = false;
let value: boolean;
let activated: boolean;
if (this._value) {
if (currentX + 15 < this._startX) {
dirty = true;
value = false;
activated = true;
} }
} else if (currentX - 15 > this._startX) { } else if (detail.currentX - 15 > this.startX) {
dirty = true; this.checked = true;
value = true; this.activated = (detail.currentX < this.startX + 5);
activated = (currentX < this._startX + 5); this.startX = detail.currentX;
} }
if (dirty) {
this._zone.run(() => {
this.value = value;
this._startX = currentX;
this._activated = activated;
this._haptic.selection();
});
}
} }
/**
* @hidden
*/
_onDragEnd(endX: number) {
if (!this._startX) {
assert(false, '_startX must be valid');
return;
}
console.debug('toggle, _onDragEnd', endX);
this._zone.run(() => { onDragEnd(detail: GestureDetail) {
if (this._value) { if (this.checked) {
if (this._startX + 4 > endX) { if (detail.startX + 4 > detail.currentX) {
this.value = false; this.checked = false;
this._haptic.selection(); }
} else if (detail.startX - 4 < detail.currentX) {
this.checked = true;
}
this.activated = false;
this.fireBlur();
this.startX = null;
}
@Listen('keydown.space')
onSpace(ev: KeyboardEvent) {
this.toggle();
ev.stopPropagation();
ev.preventDefault();
}
toggle() {
if (!this.disabled) {
this.checked = !this.checked;
this.fireFocus();
}
}
fireFocus() {
if (!this.hasFocus) {
this.hasFocus = true;
Ionic.emit(this, 'ionFocus');
}
}
fireBlur() {
if (this.hasFocus) {
this.hasFocus = false;
Ionic.emit(this, 'ionBlur');
}
}
render() {
return h(this,
h('ion-gesture', Ionic.theme(this, 'toggle', {
class: {
'toggle-activated': this.activated,
'toggle-checked': this.checked,
'toggle-disabled': this.disabled,
},
props: {
'canStart': this.canStart.bind(this),
'onStart': this.onDragStart.bind(this),
'onMove': this.onDragMove.bind(this),
'onEnd': this.onDragEnd.bind(this),
'onPress': this.toggle.bind(this),
'gestureName': 'toggle',
'gesturePriority': 30,
'type': 'pan,press',
'direction': 'x',
'threshold': 20,
'listenOn': 'parent'
} }
}),
} else if (this._startX - 4 < endX) { [
this.value = true; h('div.toggle-icon',
this._haptic.selection(); h('div.toggle-inner')
} ),
h('div.toggle-cover', {
this._activated = false; attrs: {
this._fireBlur(); 'id': this.id,
this._startX = null; 'aria-checked': this.checked ? 'true' : false,
}); 'aria-disabled': this.disabled ? 'true' : false,
} 'aria-labelledby': this.labelId,
'role': 'checkbox',
/** 'tabindex': 0
* @hidden }
*/ })
@HostListener('keyup', ['$event']) _keyup(ev: KeyboardEvent) { ]
if (ev.keyCode === KEY_SPACE || ev.keyCode === KEY_ENTER) { )
console.debug(`toggle, keyup: ${ev.keyCode}`); );
ev.preventDefault();
ev.stopPropagation();
this.value = !this.value;
}
}
/**
* @hidden
*/
initFocus() {
this._elementRef.nativeElement.querySelector('button').focus();
}
/**
* @hidden
*/
ngOnDestroy() {
super.ngOnDestroy();
this._gesture && this._gesture.destroy();
} }
} }

View File

@ -1,4 +1,6 @@
@import "../../themes/ionic.globals.wp"; @import "../../themes/ionic.globals.wp";
@import "./toggle";
// Windows Toggle // Windows Toggle
// -------------------------------------------------- // --------------------------------------------------

View File

@ -12,12 +12,7 @@ export { AlertCmp } from './components/alert/alert-component';
export { App } from './components/app/app'; export { App } from './components/app/app';
export { Avatar } from './components/avatar/avatar'; export { Avatar } from './components/avatar/avatar';
export { Backdrop } from './components/backdrop/backdrop'; export { Backdrop } from './components/backdrop/backdrop';
export { Badge } from './components/badge/badge';
export { Button } from './components/button/button'; export { Button } from './components/button/button';
export { Card } from './components/card/card';
export { CardContent } from './components/card/card-content';
export { CardHeader } from './components/card/card-header';
export { CardTitle } from './components/card/card-title';
export { Checkbox } from './components/checkbox/checkbox'; export { Checkbox } from './components/checkbox/checkbox';
export { Chip } from './components/chip/chip'; export { Chip } from './components/chip/chip';
export { Content, ScrollEvent } from './components/content/content'; export { Content, ScrollEvent } from './components/content/content';
@ -102,7 +97,6 @@ export { Toast } from './components/toast/toast';
export { ToastCmp } from './components/toast/toast-component'; export { ToastCmp } from './components/toast/toast-component';
export { ToastController } from './components/toast/toast-controller'; export { ToastController } from './components/toast/toast-controller';
export { ToastOptions } from './components/toast/toast-options'; export { ToastOptions } from './components/toast/toast-options';
export { Toggle } from './components/toggle/toggle';
export { Footer } from './components/toolbar/toolbar-footer'; export { Footer } from './components/toolbar/toolbar-footer';
export { Header } from './components/toolbar/toolbar-header'; export { Header } from './components/toolbar/toolbar-header';
export { Toolbar } from './components/toolbar/toolbar'; export { Toolbar } from './components/toolbar/toolbar';
@ -115,6 +109,7 @@ export { VirtualFooter } from './components/virtual-scroll/virtual-footer';
export { VirtualHeader } from './components/virtual-scroll/virtual-header'; export { VirtualHeader } from './components/virtual-scroll/virtual-header';
export { VirtualItem } from './components/virtual-scroll/virtual-item'; export { VirtualItem } from './components/virtual-scroll/virtual-item';
export { VirtualScroll } from './components/virtual-scroll/virtual-scroll'; export { VirtualScroll } from './components/virtual-scroll/virtual-scroll';
export { BooleanInput } from './bindings/angular/components/boolean-input';
/** /**
@ -130,6 +125,7 @@ export { NavController } from './navigation/nav-controller';
export { NavControllerBase } from './navigation/nav-controller-base'; export { NavControllerBase } from './navigation/nav-controller-base';
export { NavParams } from './navigation/nav-params'; export { NavParams } from './navigation/nav-params';
export { NavLink, NavOptions, DeepLinkConfig, DeepLinkMetadata, DeepLinkMetadataFactory } from './navigation/nav-util'; export { NavLink, NavOptions, DeepLinkConfig, DeepLinkMetadata, DeepLinkMetadataFactory } from './navigation/nav-util';
export { setupCore } from './bindings/angular/providers/ionic-core';
export { TapClick, setupTapClick, isActivatable } from './tap-click/tap-click'; export { TapClick, setupTapClick, isActivatable } from './tap-click/tap-click';
export { UrlSerializer, DeepLinkConfigToken } from './navigation/url-serializer'; export { UrlSerializer, DeepLinkConfigToken } from './navigation/url-serializer';
export { ViewController } from './navigation/view-controller'; export { ViewController } from './navigation/view-controller';
@ -169,3 +165,22 @@ export { IonicGestureConfig } from './gestures/gesture-config';
export { IonicModule, IonicPageModule, provideLocationStrategy } from './module'; export { IonicModule, IonicPageModule, provideLocationStrategy } from './module';
/**
* Core
*/
import * as interfaces from './util/interfaces';
export declare const Component: interfaces.ComponentDecorator;
export declare const h: interfaces.Hyperscript;
export declare const Ionic: interfaces.Ionic;
export declare const Listen: interfaces.ListenDecorator;
export declare const Prop: interfaces.PropDecorator;
export declare const Watch: interfaces.WatchDecorator;

View File

@ -1,7 +1,7 @@
/** /**
* Import Angular * Import Angular
*/ */
import { ANALYZE_FOR_ENTRY_COMPONENTS, APP_INITIALIZER, ComponentFactoryResolver, Inject, Injector, ModuleWithProviders, NgModule, NgZone, Optional } from '@angular/core'; import { ANALYZE_FOR_ENTRY_COMPONENTS, APP_INITIALIZER, ComponentFactoryResolver, CUSTOM_ELEMENTS_SCHEMA, Inject, Injector, ModuleWithProviders, NgModule, NgZone, Optional } from '@angular/core';
import { APP_BASE_HREF, Location, LocationStrategy, HashLocationStrategy, PathLocationStrategy, PlatformLocation } from '@angular/common'; import { APP_BASE_HREF, Location, LocationStrategy, HashLocationStrategy, PathLocationStrategy, PlatformLocation } from '@angular/common';
import { DOCUMENT, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser'; import { DOCUMENT, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@ -31,6 +31,7 @@ import { ModuleLoader, provideModuleLoader, setupPreloading, LAZY_LOADED_TOKEN }
import { NgModuleLoader } from './util/ng-module-loader'; import { NgModuleLoader } from './util/ng-module-loader';
import { Platform, setupPlatform } from './platform/platform'; import { Platform, setupPlatform } from './platform/platform';
import { PlatformConfigToken, providePlatformConfigs } from './platform/platform-registry'; import { PlatformConfigToken, providePlatformConfigs } from './platform/platform-registry';
import { setupCore } from './bindings/angular/providers/ionic-core';
import { TapClick, setupTapClick } from './tap-click/tap-click'; import { TapClick, setupTapClick } from './tap-click/tap-click';
import { registerModeConfigs } from './config/mode-registry'; import { registerModeConfigs } from './config/mode-registry';
import { TransitionController } from './transitions/transition-controller'; import { TransitionController } from './transitions/transition-controller';
@ -49,12 +50,7 @@ import { IonicApp } from './components/app/app-root';
import { OverlayPortal } from './components/app/overlay-portal'; import { OverlayPortal } from './components/app/overlay-portal';
import { Avatar } from './components/avatar/avatar'; import { Avatar } from './components/avatar/avatar';
import { Backdrop } from './components/backdrop/backdrop'; import { Backdrop } from './components/backdrop/backdrop';
import { Badge } from './components/badge/badge';
import { Button } from './components/button/button'; import { Button } from './components/button/button';
import { Card } from './components/card/card';
import { CardContent } from './components/card/card-content';
import { CardHeader } from './components/card/card-header';
import { CardTitle } from './components/card/card-title';
import { Checkbox } from './components/checkbox/checkbox'; import { Checkbox } from './components/checkbox/checkbox';
import { Chip } from './components/chip/chip'; import { Chip } from './components/chip/chip';
import { Content } from './components/content/content'; import { Content } from './components/content/content';
@ -128,7 +124,6 @@ import { Tabs } from './components/tabs/tabs';
import { Thumbnail } from './components/thumbnail/thumbnail'; import { Thumbnail } from './components/thumbnail/thumbnail';
import { ToastCmp } from './components/toast/toast-component'; import { ToastCmp } from './components/toast/toast-component';
import { ToastController } from './components/toast/toast-controller'; import { ToastController } from './components/toast/toast-controller';
import { Toggle } from './components/toggle/toggle';
import { Footer } from './components/toolbar/toolbar-footer'; import { Footer } from './components/toolbar/toolbar-footer';
import { Header } from './components/toolbar/toolbar-header'; import { Header } from './components/toolbar/toolbar-header';
import { Toolbar } from './components/toolbar/toolbar'; import { Toolbar } from './components/toolbar/toolbar';
@ -140,6 +135,7 @@ import { VirtualFooter } from './components/virtual-scroll/virtual-footer';
import { VirtualHeader } from './components/virtual-scroll/virtual-header'; import { VirtualHeader } from './components/virtual-scroll/virtual-header';
import { VirtualItem } from './components/virtual-scroll/virtual-item'; import { VirtualItem } from './components/virtual-scroll/virtual-item';
import { VirtualScroll } from './components/virtual-scroll/virtual-scroll'; import { VirtualScroll } from './components/virtual-scroll/virtual-scroll';
import { BooleanInput } from './bindings/angular/components/boolean-input';
/** /**
* @name IonicModule * @name IonicModule
@ -190,12 +186,7 @@ import { VirtualScroll } from './components/virtual-scroll/virtual-scroll';
OverlayPortal, OverlayPortal,
Avatar, Avatar,
Backdrop, Backdrop,
Badge,
Button, Button,
Card,
CardContent,
CardHeader,
CardTitle,
Checkbox, Checkbox,
Chip, Chip,
Col, Col,
@ -263,7 +254,6 @@ import { VirtualScroll } from './components/virtual-scroll/virtual-scroll';
TextInput, TextInput,
Thumbnail, Thumbnail,
ToastCmp, ToastCmp,
Toggle,
Footer, Footer,
Header, Header,
Toolbar, Toolbar,
@ -274,7 +264,8 @@ import { VirtualScroll } from './components/virtual-scroll/virtual-scroll';
VirtualFooter, VirtualFooter,
VirtualHeader, VirtualHeader,
VirtualItem, VirtualItem,
VirtualScroll VirtualScroll,
BooleanInput
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -293,12 +284,7 @@ import { VirtualScroll } from './components/virtual-scroll/virtual-scroll';
OverlayPortal, OverlayPortal,
Avatar, Avatar,
Backdrop, Backdrop,
Badge,
Button, Button,
Card,
CardContent,
CardHeader,
CardTitle,
Checkbox, Checkbox,
Chip, Chip,
Col, Col,
@ -366,7 +352,6 @@ import { VirtualScroll } from './components/virtual-scroll/virtual-scroll';
TextInput, TextInput,
Thumbnail, Thumbnail,
ToastCmp, ToastCmp,
Toggle,
Footer, Footer,
Header, Header,
Toolbar, Toolbar,
@ -377,7 +362,8 @@ import { VirtualScroll } from './components/virtual-scroll/virtual-scroll';
VirtualFooter, VirtualFooter,
VirtualHeader, VirtualHeader,
VirtualItem, VirtualItem,
VirtualScroll VirtualScroll,
BooleanInput
], ],
entryComponents: [ entryComponents: [
ActionSheetCmp, ActionSheetCmp,
@ -389,7 +375,8 @@ import { VirtualScroll } from './components/virtual-scroll/virtual-scroll';
PopoverCmp, PopoverCmp,
SelectPopover, SelectPopover,
ToastCmp ToastCmp
] ],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}) })
export class IonicModule { export class IonicModule {
@ -418,6 +405,7 @@ export class IonicModule {
// useFactory: ionic app initializers // useFactory: ionic app initializers
{ provide: APP_INITIALIZER, useFactory: registerModeConfigs, deps: [ Config ], multi: true }, { provide: APP_INITIALIZER, useFactory: registerModeConfigs, deps: [ Config ], multi: true },
{ provide: APP_INITIALIZER, useFactory: setupCore, deps: [ Config, Platform, DomController, NgZone ], multi: true },
{ provide: APP_INITIALIZER, useFactory: setupProvideEvents, deps: [ Platform, DomController ], multi: true }, { provide: APP_INITIALIZER, useFactory: setupProvideEvents, deps: [ Platform, DomController ], multi: true },
{ provide: APP_INITIALIZER, useFactory: setupTapClick, deps: [ Config, Platform, DomController, App, NgZone, GestureController ], multi: true }, { provide: APP_INITIALIZER, useFactory: setupTapClick, deps: [ Config, Platform, DomController, App, NgZone, GestureController ], multi: true },
{ provide: APP_INITIALIZER, useFactory: setupPreloading, deps: [ Config, DeepLinkConfigToken, ModuleLoader, NgZone ], multi: true }, { provide: APP_INITIALIZER, useFactory: setupPreloading, deps: [ Config, DeepLinkConfigToken, ModuleLoader, NgZone ], multi: true },

View File

@ -1,19 +0,0 @@
export const Component: ComponentDecorator = function(opts?: ComponentOptions): (target: any) => any {
return function() {};
};
export interface ComponentDecorator {
(opts?: ComponentOptions): any;
}
export interface ComponentOptions {
tag: string;
styleUrls?: string[] | ModeStyles;
}
export interface ModeStyles {
[modeName: string]: string | string[];
}

View File

@ -113,3 +113,89 @@ export interface PointerCoordinates {
x?: number; x?: number;
y?: number; y?: number;
} }
export function pointerCoordX(ev: any): number {
// get X coordinates for either a mouse click
// or a touch depending on the given event
if (ev) {
var changedTouches = ev.changedTouches;
if (changedTouches && changedTouches.length > 0) {
return changedTouches[0].clientX;
}
if (ev.pageX !== undefined) {
return ev.pageX;
}
}
return 0;
}
export function pointerCoordY(ev: any): number {
// get Y coordinates for either a mouse click
// or a touch depending on the given event
if (ev) {
var changedTouches = ev.changedTouches;
if (changedTouches && changedTouches.length > 0) {
return changedTouches[0].clientY;
}
if (ev.pageY !== undefined) {
return ev.pageY;
}
}
return 0;
}
export function getElementReference(elm: any, ref: string) {
if (ref === 'child') {
return elm.firstElementChild;
}
if (ref === 'parent') {
if (elm.parentElement ) {
// normal element with a parent element
return elm.parentElement;
}
if (elm.parentNode && elm.parentNode.host) {
// shadow dom's document fragment
return elm.parentNode.host;
}
}
if (ref === 'body') {
return elm.ownerDocument.body;
}
if (ref === 'document') {
return elm.ownerDocument;
}
if (ref === 'window') {
return elm.ownerDocument.defaultView;
}
return elm;
}
export function getKeyCodeByName(keyName: string) {
if (keyName === 'enter') {
return 13;
}
if (keyName === 'escape') {
return 27;
}
if (keyName === 'space') {
return 32;
}
if (keyName === 'tab') {
return 9;
}
return null;
}
export function applyStyles(elm: HTMLElement, styles: {[styleProp: string]: string|number}) {
const styleProps = Object.keys(styles);
for (var i = 0; i < styleProps.length; i++) {
(<any>elm.style)[styleProps[i]] = styles[styleProps[i]];
}
}

424
src/util/interfaces.ts Normal file
View File

@ -0,0 +1,424 @@
export interface Ionic {
emit: EventEmit;
listener: {
enable: EventListenerEnable;
};
theme: IonicTheme;
controllers: {
gesture?: any;
};
dom: DomControllerApi;
}
export interface EventEmit {
(instance: any, eventName: string, data?: any): void;
}
export interface EventListenerEnable {
(instance: any, eventName: string, enabled: boolean, listenOn?: string): void;
}
export interface EventListenerCallback {
(ev?: any): void;
}
export interface GestureDetail {
type?: string;
event?: UIEvent;
startX?: number;
startY?: number;
startTimeStamp?: number;
currentX?: number;
currentY?: number;
velocityX?: number;
velocityY?: number;
deltaX?: number;
deltaY?: number;
directionX?: 'left'|'right';
directionY?: 'up'|'down';
velocityDirectionX?: 'left'|'right';
velocityDirectionY?: 'up'|'down';
timeStamp?: number;
}
export interface GestureCallback {
(detail?: GestureDetail): boolean|void;
}
export interface IonicGlobal {
staticDir?: string;
components?: LoadComponents;
loadComponents?: {(bundleId: string): void};
config?: Object;
configCtrl?: ConfigApi;
domCtrl?: DomControllerApi;
nextTickCtrl?: NextTickApi;
eventNamePrefix?: string;
}
export interface NextTickApi {
nextTick: NextTick;
}
export interface NextTick {
(cb: Function): void;
}
export interface DomRead {
(cb: Function): void;
}
export interface DomWrite {
(cb: Function): void;
}
export interface DomControllerApi {
read: DomRead;
write: DomWrite;
}
export interface RafCallback {
(timeStamp?: number): void;
}
export interface RequestAnimationFrame {
(cb: RafCallback): void;
}
export interface LoadComponents {
[tag: string]: any[];
}
export interface ComponentModeData {
/**
* tag name (ion-badge)
*/
[0]: string;
/**
* component class name (Badge)
*/
[1]: string;
/**
* listeners
*/
[2]: ComponentListenersData[];
/**
* watchers
*/
[3]: ComponentWatchersData[];
/**
* shadow
*/
[4]: boolean;
/**
* mode name (ios, md, wp)
*/
[5]: string;
/**
* component mode styles
*/
[6]: string;
/**
* import component function
*/
[7]: ComponentModeImporterFn;
}
export interface ComponentListenersData {
/**
* methodName
*/
[0]: string;
/**
* eventName
*/
[1]: string;
/**
* capture
*/
[2]: number;
/**
* passive
*/
[3]: number;
/**
* enabled
*/
[4]: number;
}
export interface ComponentWatchersData {
[methodName: string]: any;
}
export interface ComponentModeImporterFn {
(importer: any, h: Hyperscript, Ionic: Ionic): void;
}
export interface ComponentDecorator {
(opts?: ComponentOptions): any;
}
export interface ComponentOptions {
tag: string;
styleUrls?: string[] | ModeStyles;
shadow?: boolean;
}
export interface ModeStyles {
[modeName: string]: string | string[];
}
export interface PropDecorator {
(opts?: PropOptions): any;
}
export interface PropOptions {
type?: number;
}
export interface Props {
[propName: string]: PropOptions;
}
export interface ListenDecorator {
(eventName: string, opts?: ListenOpts): any;
}
export interface ComponentMetaListeners {
[methodName: string]: ListenOpts;
}
export interface ListenOpts {
eventName?: string;
capture?: boolean;
passive?: boolean;
enabled?: boolean;
}
export interface WatchDecorator {
(propName: string): any;
}
export interface WatchOpts {
fn: string;
}
export interface Watchers {
[propName: string]: WatchOpts;
}
export interface IonicTheme {
(instance: any, cssClassName: string, vnodeData: VNodeData): VNodeData;
}
export interface ConfigApi {
get: (key: string, fallback?: any) => any;
getBoolean: (key: string, fallback?: boolean) => boolean;
getNumber: (key: string, fallback?: number) => number;
}
export interface ComponentMeta {
tag?: string;
props?: Props;
listeners?: ComponentMetaListeners;
watchers?: Watchers;
shadow?: boolean;
obsAttrs?: string[];
hostCss?: string;
componentModule?: any;
modes: {[modeName: string]: ComponentMode};
}
export interface ComponentMode {
bundleId?: string;
styles?: string;
styleUrls?: string[];
styleElm?: HTMLElement;
}
export interface Component {
ionViewDidLoad?: {(): void};
ionViewWillUnload?: {(): void};
render?: {(): VNode};
mode?: string;
color?: string;
$el?: ProxyElement;
$meta?: ComponentMeta;
$listeners?: ComponentActiveListeners;
$root?: HTMLElement | ShadowRoot;
$vnode?: VNode;
[memberName: string]: any;
}
export interface ComponentActiveListeners {
[eventName: string]: Function;
}
export interface BaseInputComponent extends Component {
disabled: boolean;
hasFocus: boolean;
value: string;
fireFocus: {(): void};
fireBlur: {(): void};
}
export interface BooleanInputComponent extends BaseInputComponent {
checked: boolean;
toggle: {(ev: UIEvent): void};
}
export interface ComponentModule {
new (): Component;
}
export interface ComponentRegistry {
[tag: string]: ComponentMeta;
}
export interface ProxyElement extends HTMLElement {
connectedCallback: {(): void};
attributeChangedCallback: {(attrName: string, oldVal: string, newVal: string, namespace: string): void};
disconnectedCallback: {(): void};
$queued?: boolean;
$instance?: Component;
[memberName: string]: any;
}
export interface RendererApi {
(oldVnode: VNode | Element, vnode: VNode): VNode;
}
export type Key = string | number;
export interface Hyperscript {
(sel: any): VNode;
(sel: Node, data: VNodeData): VNode;
(sel: any, data: VNodeData): VNode;
(sel: any, text: string): VNode;
(sel: any, children: Array<any>): VNode;
(sel: any, data: VNodeData, text: string): VNode;
(sel: any, data: VNodeData, children: Array<any|string>): VNode;
(sel: any, data: VNodeData, children: any): VNode;
}
export interface VNode {
sel: string | undefined;
vdata: VNodeData | undefined;
vchildren: Array<VNode | string> | undefined;
elm: Node | undefined;
vtext: string | undefined;
vkey: Key;
}
export interface VNodeData {
props?: any;
attrs?: any;
class?: any;
style?: any;
dataset?: any;
on?: any;
attachData?: any;
vkey?: Key;
vns?: string; // for SVGs
[key: string]: any; // for any other 3rd party module
}
export interface PlatformApi {
registerComponent: (tag: string, data: any[]) => ComponentMeta;
getComponentMeta: (tag: string) => ComponentMeta;
loadComponent: (bundleId: string, cb: Function) => void;
nextTick: NextTick;
domRead: DomRead;
domWrite: DomWrite;
isElement: (node: Node) => node is Element;
isText: (node: Node) => node is Text;
isComment: (node: Node) => node is Comment;
$createElement<K extends keyof HTMLElementTagNameMap>(tagName: K): HTMLElementTagNameMap[K];
$createElementNS: (namespaceURI: string, qualifiedName: string) => Element;
$createTextNode: (text: string) => Text;
$createComment: (text: string) => Comment;
$insertBefore: (parentNode: Node, newNode: Node, referenceNode: Node | null) => void;
$removeChild: (parentNode: Node, childNode: Node) => void;
$appendChild: (parentNode: Node, childNode: Node) => void;
$parentNode: (node: Node) => Node;
$nextSibling: (node: Node) => Node;
$tagName: (elm: Element) => string;
$setTextContent: (node: Node, text: string | null) => void;
$getTextContent: (node: Node) => string | null;
$getAttribute: (elm: Element, attrName: string) => string;
$attachShadow: (elm: Element, cmpMode: ComponentMode, cmpModeId: string) => ShadowRoot;
}
export interface ServerInitConfig {
staticDir: string;
config?: Object;
}