mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
refactor(helper): move functions
This commit is contained in:
9
packages/core/src/components.d.ts
vendored
9
packages/core/src/components.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,8 +117,3 @@ export interface BlockerConfig {
|
||||
disable?: string[];
|
||||
disableScroll?: boolean;
|
||||
}
|
||||
|
||||
export const BLOCK_ALL: BlockerConfig = {
|
||||
disable: ['menu-swipe', 'goback-swipe'],
|
||||
disableScroll: true
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -45,8 +45,8 @@ export function createConfigController(configObj: any, platforms: PlatformConfig
|
||||
}
|
||||
|
||||
return {
|
||||
get: get,
|
||||
getBoolean: getBoolean,
|
||||
getNumber: getNumber
|
||||
get,
|
||||
getBoolean,
|
||||
getNumber
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
19
packages/core/src/index.d.ts
vendored
19
packages/core/src/index.d.ts
vendored
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user