refactor(helper): move functions

This commit is contained in:
Manu Mtz.-Almeida
2018-03-10 01:41:09 +01:00
parent 6b0327f2ef
commit 7dcefe345c
23 changed files with 205 additions and 398 deletions

View File

@@ -33,14 +33,13 @@ import {
AlertButton,
AlertInput,
} from './components/alert/alert';
import {
ElementRef,
Side,
} from './utils/helpers';
import {
GestureCallback,
GestureDetail,
} from './components/gesture/gesture';
import {
Side,
} from './utils/helpers';
import {
PickerButton,
PickerColumn as PickerColumn2,
@@ -1062,7 +1061,7 @@ declare global {
}
namespace JSXElements {
export interface IonGestureAttributes extends HTMLAttributes {
attachTo?: ElementRef;
attachTo?: string|HTMLElement;
autoBlockAll?: boolean;
canStart?: GestureCallback;
direction?: string;

View File

@@ -6,7 +6,7 @@ export interface AnimationController {
export interface Animation {
new (): any;
parent: Animation;
parent: Animation|undefined;
hasChildren: boolean;
addElement(el: Node|Node[]|NodeList): Animation;
add(childAnimation: Animation): Animation;
@@ -30,7 +30,8 @@ export interface Animation {
afterStyles(styles: { [property: string]: any; }): Animation;
afterClearStyles(propertyNames: string[]): Animation;
play(opts?: PlayOptions): void;
syncPlay(): void;
playSync(): void;
playAsync(opts?: PlayOptions): Promise<Animation>;
reverse(shouldReverse?: boolean): Animation;
stop(stepValue?: number): void;
progressStart(): void;
@@ -39,7 +40,6 @@ export interface Animation {
onFinish(callback: (animation?: Animation) => void, opts?: {oneTimeCallback?: boolean, clearExistingCallacks?: boolean}): Animation;
destroy(): void;
isRoot(): boolean;
create(): Animation;
hasCompleted: boolean;
}

View File

@@ -1,8 +1,28 @@
import { AnimationOptions, EffectProperty, EffectState, PlayOptions } from './animation-interface';
import { CSS_PROP, CSS_VALUE_REGEX, DURATION_MIN, TRANSFORM_PROPS, TRANSITION_END_FALLBACK_PADDING_MS } from './constants';
import { CSS_PROP, CSS_VALUE_REGEX, DURATION_MIN, TRANSITION_END_FALLBACK_PADDING_MS } from './constants';
import { transitionEnd } from './transition-end';
export const TRANSFORM_PROPS: {[key: string]: number} = {
'translateX': 1,
'translateY': 1,
'translateZ': 1,
'scale': 1,
'scaleX': 1,
'scaleY': 1,
'scaleZ': 1,
'rotate': 1,
'rotateX': 1,
'rotateY': 1,
'rotateZ': 1,
'skewX': 1,
'skewY': 1,
'perspective': 1
};
const raf = window.requestAnimationFrame || ((f: Function) => f());
export class Animator {
@@ -50,7 +70,6 @@ export class Animator {
this._addEl(el);
}
}
return this;
}
@@ -117,7 +136,7 @@ export class Animator {
/**
* Set the easing for this animation.
*/
easing(name: string) {
easing(name: string): Animator {
this._easingName = name;
return this;
}
@@ -125,7 +144,7 @@ export class Animator {
/**
* Set the easing for this reversed animation.
*/
easingReverse(name: string) {
easingReverse(name: string): Animator {
this._reversedEasingName = name;
return this;
}
@@ -356,7 +375,15 @@ export class Animator {
});
}
syncPlay() {
playAsync(opts?: PlayOptions): Promise<Animator> {
return new Promise(resolve => {
this.onFinish(resolve, {oneTimeCallback: true, clearExistingCallacks: true });
this.play(opts);
return this;
});
}
playSync() {
// If the animation was already invalidated (it did finish), do nothing
if (!this._destroyed) {
const opts = { duration: 0 };
@@ -371,7 +398,7 @@ export class Animator {
* DOM WRITE
* RECURSION
*/
_playInit(opts: PlayOptions|undefined) {
private _playInit(opts: PlayOptions|undefined) {
// always default that an animation does not tween
// a tween requires that an Animation class has an element
// and that it has at least one FROM/TO effect
@@ -1212,7 +1239,7 @@ export class Animator {
* NO DOM
*/
_transEl(): HTMLElement|null {
// get the lowest level element that has an Animation
// get the lowest level element that has an Animator
for (let i = 0; i < this._childAnimationTotal; i++) {
const targetEl = this._childAnimations[i]._transEl();
if (targetEl) {
@@ -1222,8 +1249,4 @@ export class Animator {
return (this._hasTweenEffect && this._hasDur && this._elements && this._elementTotal > 0 ? this._elements[0] : null);
}
create() {
return new Animator();
}
}

View File

@@ -1,5 +1,5 @@
export let CSS_PROP = function(docEle: HTMLElement) {
export const CSS_PROP = function(docEle: HTMLElement) {
// transform
const transformProp = [
'webkitTransform',
@@ -25,27 +25,6 @@ export let CSS_PROP = function(docEle: HTMLElement) {
}(document.documentElement);
export let TRANSFORM_PROPS: {[key: string]: number} = {
'translateX': 1,
'translateY': 1,
'translateZ': 1,
'scale': 1,
'scaleX': 1,
'scaleY': 1,
'scaleZ': 1,
'rotate': 1,
'rotateX': 1,
'rotateY': 1,
'rotateZ': 1,
'skewX': 1,
'skewY': 1,
'perspective': 1
};
export let CSS_VALUE_REGEX = /(^-?\d*\.?\d*)(.*)/;
export let DURATION_MIN = 32;
export let TRANSITION_END_FALLBACK_PADDING_MS = 400;
export const CSS_VALUE_REGEX = /(^-?\d*\.?\d*)(.*)/;
export const DURATION_MIN = 32;
export const TRANSITION_END_FALLBACK_PADDING_MS = 400;

View File

@@ -1,6 +1,5 @@
import { Component, Element, Listen, Method, Prop } from '@stencil/core';
import { Config, DomController } from '../../index';
import { getPageElement } from '../../utils/helpers';
@Component({
tag: 'ion-content',
@@ -141,3 +140,27 @@ export class Content {
];
}
}
function getParentElement(el: any) {
if (el.parentElement ) {
// normal element with a parent element
return el.parentElement;
}
if (el.parentNode && el.parentNode.host) {
// shadow dom's document fragment
return el.parentNode.host;
}
return null;
}
function getPageElement(el: HTMLElement) {
const tabs = el.closest('ion-tabs');
if (tabs) {
return tabs;
}
const page = el.closest('ion-app,ion-page,.ion-page,page-inner');
if (page) {
return page;
}
return getParentElement(el);
}

View File

@@ -1,8 +1,8 @@
import { isArray, isBlank, isString } from '../../utils/helpers';
export function isBlank(val: any): val is null { return val === undefined || val === null; }
export function renderDatetime(template: string, value: DatetimeData, locale: LocaleData) {
if (isBlank(value)) {
if (value === undefined) {
return '';
}
@@ -234,7 +234,7 @@ export function parseDate(val: any): DatetimeData {
export function updateDate(existingData: DatetimeData, newData: any): boolean {
if (newData && newData !== '') {
if (isString(newData)) {
if (typeof newData === 'string') {
// new date is a string, and hopefully in the ISO format
// convert it to our DatetimeData if a valid ISO
newData = parseDate(newData);
@@ -399,14 +399,14 @@ export function convertToArrayOfStrings(input: string | string[] | undefined | n
return null;
}
if (isString(input)) {
if (typeof input === 'string') {
// convert the string to an array of strings
// auto remove any [] characters
input = input.replace(/\[|\]/g, '').split(',');
}
let values: string[];
if (isArray(input)) {
if (Array.isArray(input)) {
// trim up each string value
values = input.map(val => val.toString().trim());
}
@@ -424,14 +424,14 @@ export function convertToArrayOfStrings(input: string | string[] | undefined | n
* an array of numbers, and clean up any user input
*/
export function convertToArrayOfNumbers(input: any[] | string | number, type: string): number[] {
if (isString(input)) {
if (typeof input === 'string') {
// convert the string to an array of strings
// auto remove any whitespace and [] characters
input = input.replace(/\[|\]|\s/g, '').split(',');
}
let values: number[];
if (isArray(input)) {
if (Array.isArray(input)) {
// ensure each value is an actual number in the returned array
values = input
.map((num: any) => parseInt(num, 10))

View File

@@ -12,13 +12,14 @@ import {
dateValueRange,
daysInMonth,
getValueFromFormat,
isBlank,
parseDate,
parseTemplate,
renderDatetime,
renderTextFormat,
updateDate
} from './datetime-util';
import { clamp, isBlank, isObject } from '../../utils/helpers';
import { clamp } from '../../utils/helpers';
import { Picker, PickerColumn, PickerController, PickerOptions } from '../../index';
@@ -590,7 +591,7 @@ export class Datetime {
hasValue(): boolean {
const val = this.datetimeValue;
return val
&& isObject(val)
&& typeof val === 'object'
&& Object.keys(val).length > 0;
}

View File

@@ -117,8 +117,3 @@ export interface BlockerConfig {
disable?: string[];
disableScroll?: boolean;
}
export const BLOCK_ALL: BlockerConfig = {
disable: ['menu-swipe', 'goback-swipe'],
disableScroll: true
};

View File

@@ -1,9 +1,13 @@
import { Component, Event, EventEmitter, EventListenerEnable, Listen, Prop, Watch } from '@stencil/core';
import { ElementRef, assert, now, updateDetail } from '../../utils/helpers';
import { BlockerDelegate, DomController, GestureDelegate } from '../../index';
import { BLOCK_ALL } from '../gesture-controller/gesture-controller-utils';
import { assert, now } from '../../utils/helpers';
import { BlockerConfig, BlockerDelegate, DomController, GestureDelegate } from '../../index';
import { PanRecognizer } from './recognizers';
export const BLOCK_ALL: BlockerConfig = {
disable: ['menu-swipe', 'goback-swipe'],
disableScroll: true
};
@Component({
tag: 'ion-gesture'
@@ -27,7 +31,7 @@ export class Gesture {
@Prop({ context: 'enableListener' }) enableListener: EventListenerEnable;
@Prop() disabled = false;
@Prop() attachTo: ElementRef = 'child';
@Prop() attachTo: string|HTMLElement = 'child';
@Prop() autoBlockAll = false;
@Prop() disableScroll = false;
@Prop() direction = 'x';
@@ -478,7 +482,26 @@ export interface GestureDetail {
data?: any;
}
export interface GestureCallback {
(detail?: GestureDetail): boolean|void;
}
function updateDetail(ev: any, detail: any) {
// get X coordinates for either a mouse click
// or a touch depending on the given event
let x = 0;
let y = 0;
if (ev) {
const changedTouches = ev.changedTouches;
if (changedTouches && changedTouches.length > 0) {
const touch = changedTouches[0];
x = touch.clientX;
y = touch.clientY;
} else if (ev.pageX !== undefined) {
x = ev.pageX;
y = ev.pageY;
}
}
detail.currentX = x;
detail.currentY = y;
}

View File

@@ -1,7 +1,6 @@
import { Component, Element, Event, EventEmitter, Method, State } from '@stencil/core';
import { GestureDetail } from '../../index';
import { swipeShouldReset } from '../../utils/helpers';
import { ItemOptions } from '../item-options/item-options';
@@ -288,3 +287,22 @@ export class ItemSliding {
);
}
}
/** @hidden */
export function swipeShouldReset(isResetDirection: boolean, isMovingFast: boolean, isOnResetZone: boolean): boolean {
// The logic required to know when the sliding item should close (openAmount=0)
// depends on three booleans (isCloseDirection, isMovingFast, isOnCloseZone)
// and it ended up being too complicated to be written manually without errors
// so the truth table is attached below: (0=false, 1=true)
// isCloseDirection | isMovingFast | isOnCloseZone || shouldClose
// 0 | 0 | 0 || 0
// 0 | 0 | 1 || 1
// 0 | 1 | 0 || 0
// 0 | 1 | 1 || 0
// 1 | 0 | 0 || 0
// 1 | 0 | 1 || 1
// 1 | 1 | 0 || 1
// 1 | 1 | 1 || 1
// The resulting expression was generated by resolving the K-map (Karnaugh map):
return (!isMovingFast && isOnResetZone) || (isResetDirection && isMovingFast);
}

View File

@@ -1,6 +1,5 @@
import { Component, Event, EventEmitter, Prop} from '@stencil/core';
import { Config } from '../..';
import { focusOutActiveElement, getDocument, getWindow, hasFocusedTextInput } from '../../utils/helpers';
import { KEY_TAB } from './keys';
let v2KeyboardWillShowHandler: () => void = null;
@@ -75,11 +74,11 @@ export function onCloseImpl(keyboardController: KeyboardController, callback: Fu
}
export function componentDidLoadImpl(keyboardController: KeyboardController) {
focusOutline(getDocument(), keyboardController.config.get('focusOutline'));
focusOutline(document, keyboardController.config.get('focusOutline'));
if (keyboardController.config.getBoolean('keyboardResizes', false)) {
listenV2(getWindow(), keyboardController);
listenV2(window, keyboardController);
} else {
listenV1(getWindow(), keyboardController);
listenV1(window, keyboardController);
}
}
@@ -183,5 +182,31 @@ export function focusOutline(doc: Document, value: boolean) {
doc.addEventListener('keydown', keyDownHandler);
}
function hasFocusedTextInput() {
const activeElement = document.activeElement;
if (isTextInput(activeElement) && activeElement.parentElement) {
return activeElement.parentElement.querySelector(':focus') === activeElement;
}
return false;
}
const NON_TEXT_INPUT_REGEX = /^(radio|checkbox|range|file|submit|reset|color|image|button)$/i;
function isTextInput(el: any) {
return !!el &&
(el.tagName === 'TEXTAREA'
|| el.contentEditable === 'true'
|| (el.tagName === 'INPUT' && !(NON_TEXT_INPUT_REGEX.test(el.type))));
}
function focusOutActiveElement() {
const activeElement = document.activeElement as HTMLElement;
activeElement && activeElement.blur && activeElement.blur();
}
const KEYBOARD_CLOSE_POLLING = 150;
const KEYBOARD_POLLING_CHECKS_MAX = 100;

View File

@@ -1,6 +1,6 @@
import { Component, Element, Event, EventEmitter, EventListenerEnable, Listen, Method, Prop, State, Watch } from '@stencil/core';
import { Animation, Config, GestureDetail } from '../../index';
import { Side, assert, checkEdgeSide, isRightSide } from '../../utils/helpers';
import { Side, assert, isRightSide } from '../../utils/helpers';
@Component({
tag: 'ion-menu',
@@ -248,19 +248,13 @@ export class Menu {
}
private startAnimation(shouldOpen: boolean, animated: boolean): Promise<Animation> {
let done;
const promise = new Promise<Animation>(resolve => done = resolve);
const ani = this.animation
.onFinish(done, {oneTimeCallback: true, clearExistingCallacks: true })
.reverse(!shouldOpen);
const ani = this.animation.reverse(!shouldOpen);
if (animated) {
ani.play();
return ani.playAsync();
} else {
ani.syncPlay();
ani.playSync();
return Promise.resolve(ani);
}
return promise;
}
private canSwipe(): boolean {
@@ -477,6 +471,14 @@ function computeDelta(deltaX: number, isOpen: boolean, isRightSide: boolean): nu
return Math.max(0, (isOpen !== isRightSide) ? -deltaX : deltaX);
}
function checkEdgeSide(posX: number, isRightSide: boolean, maxEdgeStart: number): boolean {
if (isRightSide) {
return posX >= window.innerWidth - maxEdgeStart;
} else {
return posX <= maxEdgeStart;
}
}
const SHOW_MENU = 'show-menu';
const SHOW_BACKDROP = 'show-backdrop';
const MENU_CONTENT_OPEN = 'menu-content-open';

View File

@@ -19,7 +19,7 @@ import {
import { ViewController, isViewController } from './view-controller';
import { AnimationOptions, Config, DomController, GestureDetail, NavOutlet } from '../..';
import { assert, isBlank, isNumber } from '../../utils/helpers';
import { assert } from '../../utils/helpers';
import { TransitionController } from './transition-controller';
import { Transition } from './transition';
@@ -136,7 +136,7 @@ export class NavControllerBase implements NavOutlet {
if (isViewController(indexOrViewCtrl)) {
config.removeView = indexOrViewCtrl;
config.removeStart = 1;
} else if (isNumber(indexOrViewCtrl)) {
} else if (typeof indexOrViewCtrl === 'number') {
config.removeStart = indexOrViewCtrl + 1;
}
return this._queueTrns(config, done);
@@ -186,7 +186,7 @@ export class NavControllerBase implements NavOutlet {
@Method()
setPages(pages: any[], opts?: NavOptions, done?: TransitionDoneFn): Promise<any> {
if (isBlank(opts)) {
if (!opts) {
opts = {};
}
// if animation wasn't set to true then default it to NOT animate

View File

@@ -1,8 +1,6 @@
import { Component, Element, Prop, State, Watch } from '@stencil/core';
import { DomController, GestureDetail } from '../../index';
import { clamp, reorderArray } from '../../utils/helpers';
import { hapticSelectionChanged, hapticSelectionEnd, hapticSelectionStart} from '../../utils/haptic';
import { CSS_PROP } from '../animation-controller/constants';
const AUTO_SCROLL_MARGIN = 60;
const SCROLL_JUMP = 10;
@@ -148,7 +146,7 @@ export class ReorderGroup {
// // Get coordinate
const top = this.containerTop - scroll;
const bottom = this.containerBottom - scroll;
const currentY = clamp(top, ev.currentY, bottom);
const currentY = Math.max(top, Math.min(ev.currentY, bottom));
const deltaY = scroll + currentY - ev.startY;
const normalizedY = currentY - top;
const toIndex = this.itemIndexForTop(normalizedY);
@@ -161,7 +159,7 @@ export class ReorderGroup {
}
// Update selected item position
(selectedItem.style as any)[CSS_PROP.transformProp] = `translateY(${deltaY}px)`;
selectedItem.style.transform = `translateY(${deltaY}px)`;
}
private onDragEnd() {
@@ -182,9 +180,8 @@ export class ReorderGroup {
this.containerEl.insertBefore(selectedItem, ref);
const len = children.length;
const transform = CSS_PROP.transformProp;
for (let i = 0; i < len; i++) {
children[i].style[transform] = '';
children[i].style['transform'] = '';
}
const reorderInactive = () => {
@@ -223,7 +220,6 @@ export class ReorderGroup {
private reorderMove(fromIndex: number, toIndex: number) {
const itemHeight = this.selectedItemHeight;
const children = this.containerEl.children;
const transform = CSS_PROP.transformProp;
for (let i = 0; i < children.length; i++) {
const style = (children[i] as any).style;
let value = '';
@@ -232,7 +228,7 @@ export class ReorderGroup {
} else if (i < fromIndex && i >= toIndex) {
value = `translateY(${itemHeight}px)`;
}
style[transform] = value;
style['transform'] = value;
}
}
@@ -302,3 +298,11 @@ function findReorderItem(node: HTMLElement, container: HTMLElement): HTMLElement
}
return null;
}
export function reorderArray(array: any[], indexes: {from: number, to: number}): any[] {
const element = array[indexes.from];
array.splice(indexes.from, 1);
array.splice(indexes.to, 0, element);
return array;
}

View File

@@ -30,9 +30,9 @@ export class StatusTap {
return null;
}
return el.closest('ion-scroll');
}).then(([scroll]: HTMLIonScrollElement[]) => {
return scroll.componentOnReady();
}).then((scroll: HTMLIonScrollElement) => {
})
.then(scroll => scroll.componentOnReady())
.then(scroll => {
return domControllerAsync(this.dom.write, () => {
return scroll.scrollToTop(this.duration);
});

View File

@@ -45,8 +45,8 @@ export function createConfigController(configObj: any, platforms: PlatformConfig
}
return {
get: get,
getBoolean: getBoolean,
getNumber: getNumber
get,
getBoolean,
getNumber
};
}

View File

@@ -1,15 +0,0 @@
export function setupEvents(win: Window, doc: Document) {
win.addEventListener('statusTap', () => {
const centerElm = doc.elementFromPoint(win.innerWidth / 2, win.innerHeight / 2);
if (centerElm) {
const scrollElm = centerElm.closest('ion-scroll');
if (scrollElm) {
scrollElm.componentOnReady(() => scrollElm.scrollToTop(300));
}
}
});
}

View File

@@ -2,7 +2,6 @@ import 'ionicons';
import { createConfigController } from './config-controller';
import { createDomControllerClient } from './dom-controller';
import { PLATFORM_CONFIGS, detectPlatforms, readQueryParam } from './platform-configs';
import { setupEvents } from './events';
const Ionic = (window as any).Ionic = (window as any).Ionic || {};
@@ -31,8 +30,6 @@ Ionic.config = Context.config = createConfigController(
Context.platforms
);
setupEvents(window, document);
// first see if the mode was set as an attribute on <html>
// which could have been set by the user, or by prerendering
// otherwise get the mode via config settings, and fallback to md

View File

@@ -72,7 +72,7 @@ export { RadioGroup } from './components/radio-group/radio-group';
export { Radio, HTMLIonRadioElementEvent } from './components/radio/radio';
export { Range, RangeEvent } from './components/range/range';
export { RangeKnob } from './components/range-knob/range-knob';
export { ReorderGroup } from './components/reorder-group/reorder-group';
export { ReorderGroup, reorderArray } from './components/reorder-group/reorder-group';
export {
RouteNode,
RouteTree,
@@ -109,6 +109,7 @@ export { PlatformConfig } from './global/platform-configs';
export * from './components';
export { DomController, RafCallback } from './global/dom-controller';
export { FrameworkDelegate, FrameworkMountingData } from './utils/dom-framework-delegate';
export interface Config {
get: (key: string, fallback?: any) => any;
@@ -136,22 +137,6 @@ export interface OverlayDismissEventDetail {
role?: string;
}
export interface FrameworkDelegate {
attachViewToDom(elementOrContainerToMountTo: any, elementOrComponentToMount: any, propsOrDataObj?: any, classesToAdd?: string[], escapeHatch?: any): Promise<FrameworkMountingData>;
removeViewFromDom(elementOrContainerToUnmountFrom: any, elementOrComponentToUnmount: any, escapeHatch?: any): Promise<void>;
}
export interface RouterDelegate {
pushUrlState(urlSegment: string, stateObject?: any, title?: string): Promise<any>;
popUrlState(): Promise<any>;
}
export interface FrameworkMountingData {
element: HTMLElement;
component: any;
data: any;
}
declare global {
namespace JSXElements {

View File

@@ -1,11 +1,21 @@
import { FrameworkDelegate, FrameworkMountingData, } from '../index';
import { isString } from './helpers';
export interface FrameworkDelegate {
attachViewToDom(elementOrContainerToMountTo: any, elementOrComponentToMount: any, propsOrDataObj?: any, classesToAdd?: string[], escapeHatch?: any): Promise<FrameworkMountingData>;
removeViewFromDom(elementOrContainerToUnmountFrom: any, elementOrComponentToUnmount: any, escapeHatch?: any): Promise<void>;
}
export interface FrameworkMountingData {
element: HTMLElement;
component: any;
data: any;
}
export class DomFrameworkDelegate implements FrameworkDelegate {
attachViewToDom(parentElement: HTMLElement, tagOrElement: string | HTMLElement, data: any = {}, classesToAdd: string[] = []): Promise<FrameworkMountingData> {
return new Promise((resolve) => {
const usersElement = (isString(tagOrElement) ? document.createElement(tagOrElement) : tagOrElement);
const usersElement = (typeof tagOrElement === 'string' ? document.createElement(tagOrElement) : tagOrElement);
Object.assign(usersElement, data);
if (classesToAdd.length) {

View File

@@ -1,16 +0,0 @@
import { RouterDelegate } from '../index';
export class DomRouterDelegate implements RouterDelegate {
pushUrlState(urlSegment: string, stateObject: any = null, title = ''): Promise<any> {
history.pushState(stateObject, title, urlSegment);
return Promise.resolve();
}
popUrlState() {
history.back();
return Promise.resolve();
}
}

View File

@@ -1,4 +1,3 @@
import { Animation } from '../index';
import { EventEmitter } from '@stencil/core';
export function clamp(min: number, n: number, max: number) {
@@ -7,43 +6,6 @@ export function clamp(min: number, n: number, max: number) {
export function isDef(v: any): boolean { return v !== undefined && v !== null; }
export function isUndef(v: any): boolean { return v === undefined || v === null; }
export function isArray(v: any): v is Array<any> { return Array.isArray(v); }
export function isObject(v: any): v is Object { return v !== null && typeof v === 'object'; }
export function isBoolean(v: any): v is (boolean) { return typeof v === 'boolean'; }
export function isString(v: any): v is (string) { return typeof v === 'string'; }
export function isNumber(v: any): v is (number) { return typeof v === 'number'; }
export function isFunction(v: any): v is (Function) { return typeof v === 'function'; }
export function isStringOrNumber(v: any): v is (string | number) { return isString(v) || isNumber(v); }
export function isBlank(val: any): val is null { return val === undefined || val === null; }
/** @hidden */
export function isCheckedProperty(a: any, b: any): boolean {
if (a === undefined || a === null || a === '') {
return (b === undefined || b === null || b === '');
} else if (a === true || a === 'true') {
return (b === true || b === 'true');
} else if (a === false || a === 'false') {
return (b === false || b === 'false');
} else if (a === 0 || a === '0') {
return (b === 0 || b === '0');
}
// not using strict comparison on purpose
return (a == b); // tslint:disable-line
}
export function assert(actual: any, reason: string) {
if (!actual) {
const message = 'ASSERT: ' + reason;
@@ -63,11 +25,6 @@ export function autoFocus(containerEl: HTMLElement): HTMLElement|null {
return null;
}
export function toDashCase(str: string) {
return str.replace(/([A-Z])/g, (g) => '-' + g[0].toLowerCase());
}
export function now(ev: UIEvent) {
return ev.timeStamp || Date.now();
}
@@ -87,93 +44,8 @@ export function pointerCoord(ev: any): {x: number, y: number} {
}
return {x: 0, y: 0};
}
export function updateDetail(ev: any, detail: any) {
// get X coordinates for either a mouse click
// or a touch depending on the given event
let x = 0;
let y = 0;
if (ev) {
const changedTouches = ev.changedTouches;
if (changedTouches && changedTouches.length > 0) {
const touch = changedTouches[0];
x = touch.clientX;
y = touch.clientY;
} else if (ev.pageX !== undefined) {
x = ev.pageX;
y = ev.pageY;
}
}
detail.currentX = x;
detail.currentY = y;
}
export type ElementRef = 'child' | 'parent' | 'body' | 'document' | 'window';
export function getElementReference(el: any, ref: ElementRef) {
if (ref === 'child') {
return el.firstElementChild;
}
if (ref === 'parent') {
return getParentElement(el) || el;
}
if (ref === 'body') {
return el.ownerDocument.body;
}
if (ref === 'document') {
return el.ownerDocument;
}
if (ref === 'window') {
return el.ownerDocument.defaultView;
}
return el;
}
export function getParentElement(el: any) {
if (el.parentElement ) {
// normal element with a parent element
return el.parentElement;
}
if (el.parentNode && el.parentNode.host) {
// shadow dom's document fragment
return el.parentNode.host;
}
return null;
}
export function getPageElement(el: HTMLElement) {
const tabs = el.closest('ion-tabs');
if (tabs) {
return tabs;
}
const page = el.closest('ion-app,ion-page,.ion-page,page-inner');
if (page) {
return page;
}
return getParentElement(el);
}
export function applyStyles(el: HTMLElement, styles: {[styleProp: string]: string|number}) {
const styleProps = Object.keys(styles);
if (el) {
for (let i = 0; i < styleProps.length; i++) {
(el.style as any)[styleProps[i]] = styles[styleProps[i]];
}
}
}
/** @hidden */
export type Side = 'left' | 'right' | 'start' | 'end';
export function checkEdgeSide(posX: number, isRightSide: boolean, maxEdgeStart: number): boolean {
if (isRightSide) {
return posX >= window.innerWidth - maxEdgeStart;
} else {
return posX <= maxEdgeStart;
}
}
/**
* @hidden
* Given a side, return if it should be on the right
@@ -193,85 +65,6 @@ export function isRightSide(side: Side, defaultRight = false): boolean {
}
}
/** @hidden */
export function swipeShouldReset(isResetDirection: boolean, isMovingFast: boolean, isOnResetZone: boolean): boolean {
// The logic required to know when the sliding item should close (openAmount=0)
// depends on three booleans (isCloseDirection, isMovingFast, isOnCloseZone)
// and it ended up being too complicated to be written manually without errors
// so the truth table is attached below: (0=false, 1=true)
// isCloseDirection | isMovingFast | isOnCloseZone || shouldClose
// 0 | 0 | 0 || 0
// 0 | 0 | 1 || 1
// 0 | 1 | 0 || 0
// 0 | 1 | 1 || 0
// 1 | 0 | 0 || 0
// 1 | 0 | 1 || 1
// 1 | 1 | 0 || 1
// 1 | 1 | 1 || 1
// The resulting expression was generated by resolving the K-map (Karnaugh map):
return (!isMovingFast && isOnResetZone) || (isResetDirection && isMovingFast);
}
export function getOrAppendElement(tagName: string): Element {
const element = document.querySelector(tagName);
if (element) {
return element;
}
const tmp = document.createElement(tagName);
document.body.appendChild(tmp);
return tmp;
}
export function getWindow() {
return window;
}
export function getDocument() {
return document;
}
export function getActiveElement(): HTMLElement {
return getDocument()['activeElement'] as HTMLElement;
}
export function focusOutActiveElement() {
const activeElement = getActiveElement();
activeElement && activeElement.blur && activeElement.blur();
}
export function isTextInput(el: any) {
return !!el &&
(el.tagName === 'TEXTAREA'
|| el.contentEditable === 'true'
|| (el.tagName === 'INPUT' && !(NON_TEXT_INPUT_REGEX.test(el.type))));
}
export const NON_TEXT_INPUT_REGEX = /^(radio|checkbox|range|file|submit|reset|color|image|button)$/i;
export function hasFocusedTextInput() {
const activeElement = getActiveElement();
if (isTextInput(activeElement) && activeElement.parentElement) {
return activeElement.parentElement.querySelector(':focus') === activeElement;
}
return false;
}
/**
* @private
*/
export function reorderArray(array: any[], indexes: {from: number, to: number}): any[] {
const element = array[indexes.from];
array.splice(indexes.from, 1);
array.splice(indexes.to, 0, element);
return array;
}
export function playAnimationAsync(animation: Animation): Promise<Animation> {
return new Promise(resolve => {
animation.onFinish(resolve);
animation.play();
});
}
export function domControllerAsync(domControllerFunction: Function, callback?: Function): Promise<any> {
return new Promise((resolve) => {
domControllerFunction(() => {
@@ -304,41 +97,3 @@ export function debounce(func: Function, wait = 0) {
timer = setTimeout(func, wait, ...args);
};
}
export function asyncRaf(): Promise<number> {
return new Promise(resolve => requestAnimationFrame(resolve));
}
export function getNavAsChildIfExists(element: HTMLElement): HTMLIonNavElement|null {
for (let i = 0; i < element.children.length; i++) {
if (element.children[i].tagName.toLowerCase() === 'ion-nav') {
return element.children[i] as any as HTMLIonNavElement;
}
}
return null;
}
export function normalizeUrl(url: string): string {
url = url.trim();
if (url.charAt(0) !== '/') {
// ensure first char is a /
url = '/' + url;
}
if (url.length > 1 && url.charAt(url.length - 1) === '/') {
// ensure last char is not a /
url = url.substr(0, url.length - 1);
}
return url;
}
export function isParentTab(element: HTMLElement) {
return element.parentElement.tagName.toLowerCase() === 'ion-tab';
}
export function getIonApp(): Promise<HTMLIonAppElement|null> {
const appElement = document.querySelector('ion-app');
if (!appElement) {
return Promise.resolve(null);
}
return appElement.componentOnReady();
}

View File

@@ -1,5 +1,4 @@
import { Animation, AnimationBuilder } from '..';
import { playAnimationAsync } from './helpers';
let lastId = 1;
@@ -70,7 +69,7 @@ export function overlayAnimation(
if (!animate) {
animation.duration(0);
}
return playAnimationAsync(animation);
return animation.playAsync();
}).then((animation) => {
animation.destroy();
overlay.animation = undefined;