mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-22 13:32:54 +08:00
Merge branch '2.0' into layout-refactor
# Conflicts: # src/components/tabs/test/advanced/index.ts
This commit is contained in:
84
CHANGELOG.md
84
CHANGELOG.md
@ -1,3 +1,61 @@
|
||||
<a name="2.0.0-beta.9"></a>
|
||||
# [2.0.0-beta.9](https://github.com/driftyco/ionic/compare/v2.0.0-beta.8...v2.0.0-beta.9) (2016-06-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **backButton:** register back button actions ([84f37cf](https://github.com/driftyco/ionic/commit/84f37cf))
|
||||
* **item:** add the ability to show a forward arrow on md and wp modes ([c41f24d](https://github.com/driftyco/ionic/commit/c41f24d))
|
||||
* **item:** two-way sliding of items ([c28aa53](https://github.com/driftyco/ionic/commit/c28aa53)), closes [#5073](https://github.com/driftyco/ionic/issues/5073)
|
||||
* **item-sliding:** two-way item sliding gestures ([5d873ff](https://github.com/driftyco/ionic/commit/5d873ff))
|
||||
* **modal:** background click and escape key dismiss (#6831) ([e5473b6](https://github.com/driftyco/ionic/commit/e5473b6)), closes [#6738](https://github.com/driftyco/ionic/issues/6738)
|
||||
* **navPop:** add nav pop method on the app instance ([9f293e8](https://github.com/driftyco/ionic/commit/9f293e8))
|
||||
* **popover:** background dismiss, escape dismiss ([1d78f78](https://github.com/driftyco/ionic/commit/1d78f78)), closes [#6817](https://github.com/driftyco/ionic/issues/6817)
|
||||
* **range:** range can be disabled ([ccd926b](https://github.com/driftyco/ionic/commit/ccd926b))
|
||||
* **select:** add placeholder as an input for select ([461ba11](https://github.com/driftyco/ionic/commit/461ba11)), closes [#6862](https://github.com/driftyco/ionic/issues/6862)
|
||||
* **tabs:** track tab selecting history, create previousTab() method ([d98f3c9](https://github.com/driftyco/ionic/commit/d98f3c9))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **button:** check for icon and add css after content checked ([f7b2ea2](https://github.com/driftyco/ionic/commit/f7b2ea2)), closes [#6662](https://github.com/driftyco/ionic/issues/6662)
|
||||
* **click-block:** click block is now showing on all screns. ([761a1f6](https://github.com/driftyco/ionic/commit/761a1f6))
|
||||
* **click-block:** fix for the click block logic ([9b78aeb](https://github.com/driftyco/ionic/commit/9b78aeb))
|
||||
* **datetime:** add styling for datetime with different labels ([adcd2fc](https://github.com/driftyco/ionic/commit/adcd2fc)), closes [#6764](https://github.com/driftyco/ionic/issues/6764)
|
||||
* **decorators:** change to match angular style guide ([9315c68](https://github.com/driftyco/ionic/commit/9315c68))
|
||||
* **item:** change ion-item-swiping to use .item-wrapper css instead ([31f62e7](https://github.com/driftyco/ionic/commit/31f62e7))
|
||||
* **item:** encode hex value in the detail arrow so it works on firefox ([03986d4](https://github.com/driftyco/ionic/commit/03986d4)), closes [#6830](https://github.com/driftyco/ionic/issues/6830)
|
||||
* **item:** improve open/close logic, update demos ([db9fa7e](https://github.com/driftyco/ionic/commit/db9fa7e))
|
||||
* **item:** item-options width calculated correctly ([64af0c8](https://github.com/driftyco/ionic/commit/64af0c8))
|
||||
* **item:** sliding item supports dynamic options + tests ([14d29e6](https://github.com/driftyco/ionic/commit/14d29e6)), closes [#5192](https://github.com/driftyco/ionic/issues/5192)
|
||||
* **item:** sliding item's width must be 100% ([efcdd20](https://github.com/driftyco/ionic/commit/efcdd20))
|
||||
* **menu:** push/overlay working correctly in landscape ([0c88589](https://github.com/driftyco/ionic/commit/0c88589))
|
||||
* **menu:** swiping menu distinguishes between opening and closing direction ([29791f8](https://github.com/driftyco/ionic/commit/29791f8)), closes [#5511](https://github.com/driftyco/ionic/issues/5511)
|
||||
* **Menu:** fix right overlay menu when rotating device ([07d55c5](https://github.com/driftyco/ionic/commit/07d55c5))
|
||||
* **modal:** add status bar padding to modal ([181129b](https://github.com/driftyco/ionic/commit/181129b))
|
||||
* **modal:** change modal display so you can scroll the entire height ([01bbc94](https://github.com/driftyco/ionic/commit/01bbc94)), closes [#6839](https://github.com/driftyco/ionic/issues/6839)
|
||||
* **navigation:** keep the click block up longer if the keyboard is open (#6884) ([d6b7d5d](https://github.com/driftyco/ionic/commit/d6b7d5d))
|
||||
* **popover:** allow target element to be positioned at left:0 ([ea450d4](https://github.com/driftyco/ionic/commit/ea450d4)), closes [#6896](https://github.com/driftyco/ionic/issues/6896)
|
||||
* **popover:** hide arrow if no event was passed ([8350df0](https://github.com/driftyco/ionic/commit/8350df0)), closes [#6796](https://github.com/driftyco/ionic/issues/6796)
|
||||
* **range:** bar height for ios should be 1px, add disabled for wp ([f2a9f2d](https://github.com/driftyco/ionic/commit/f2a9f2d))
|
||||
* **range:** stop sliding after releasing mouse outside the window ([9b2e934](https://github.com/driftyco/ionic/commit/9b2e934)), closes [#6802](https://github.com/driftyco/ionic/issues/6802)
|
||||
* **scrollView:** ensure scroll element exists for event listeners ([1188730](https://github.com/driftyco/ionic/commit/1188730))
|
||||
* **searchbar:** add opacity so the searchbar doesn't show when it's moved over ([b5f93f9](https://github.com/driftyco/ionic/commit/b5f93f9))
|
||||
* **searchbar:** only trigger the input event on clear if there is a value ([99fdcc0](https://github.com/driftyco/ionic/commit/99fdcc0)), closes [#6382](https://github.com/driftyco/ionic/issues/6382)
|
||||
* **searchbar:** position elements when the value changes not after content checked ([31c7e59](https://github.com/driftyco/ionic/commit/31c7e59))
|
||||
* **searchbar:** set a negative tabindex for the cancel button ([614ace4](https://github.com/driftyco/ionic/commit/614ace4))
|
||||
* **searchbar:** use the contrast color for the background in a toolbar ([b4028c6](https://github.com/driftyco/ionic/commit/b4028c6)), closes [#6379](https://github.com/driftyco/ionic/issues/6379)
|
||||
* **tabs:** reduce padding on tabs for ios ([fd9cdc7](https://github.com/driftyco/ionic/commit/fd9cdc7)), closes [#6679](https://github.com/driftyco/ionic/issues/6679)
|
||||
* **tap:** export isActivatable as a const so its transpiled correctly ([ce3da97](https://github.com/driftyco/ionic/commit/ce3da97))
|
||||
* **toast:** close toasts when two or more are open (#6814) ([8ff2476](https://github.com/driftyco/ionic/commit/8ff2476)), closes [(#6814](https://github.com/(/issues/6814)
|
||||
* **toast:** toast will now be enabled (#6904) ([c068828](https://github.com/driftyco/ionic/commit/c068828))
|
||||
* **virtualScroll:** detect changes in individual nodes ([f049521](https://github.com/driftyco/ionic/commit/f049521)), closes [#6137](https://github.com/driftyco/ionic/issues/6137)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **virtualScroll:** improve UIWebView virtual scroll ([ff1daa6](https://github.com/driftyco/ionic/commit/ff1daa6))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-beta.8"></a>
|
||||
# [2.0.0-beta.8](https://github.com/driftyco/ionic/compare/v2.0.0-beta.7...v2.0.0-beta.8) (2016-06-06)
|
||||
|
||||
@ -36,7 +94,7 @@ All Ionic component events have been renamed to start with `ion`. This is to pre
|
||||
- `change` -> `ionChange`
|
||||
- **DateTime**
|
||||
- `change` -> `ionChange`
|
||||
- `cancel` -> `ionCancel`
|
||||
- `cancel` -> `ionCancel`
|
||||
- **InfiniteScroll**
|
||||
- `infinite` -> `ionInfinite`
|
||||
- **Menu**
|
||||
@ -88,13 +146,13 @@ All Ionic component events have been renamed to start with `ion`. This is to pre
|
||||
```
|
||||
npm install --save ionic-angular@2.0.0-beta.8
|
||||
```
|
||||
|
||||
|
||||
_or_ modify the following line to use `beta.8` in your `package.json` and then run `npm install`:
|
||||
|
||||
|
||||
```
|
||||
"ionic-angular": "^2.0.0-beta.8",
|
||||
```
|
||||
|
||||
|
||||
**This is the way to update Ionic to any version, more information can be found in the [docs](http://ionicframework.com/docs/v2/resources/using-npm/).**
|
||||
|
||||
2. Replace all instances of `@Page` with `@Component`:
|
||||
@ -179,29 +237,29 @@ All Ionic component events have been renamed to start with `ion`. This is to pre
|
||||
5. Rename any uses of the lifecycle events, for example:
|
||||
|
||||
```
|
||||
onPageDidEnter() {
|
||||
onPageDidEnter() {
|
||||
console.log("Entered page!");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
becomes
|
||||
|
||||
|
||||
```
|
||||
ionViewDidEnter() {
|
||||
ionViewDidEnter() {
|
||||
console.log("Entered page!");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
The full list of lifecycle name changes is in the [section above](https://github.com/driftyco/ionic/blob/2.0/CHANGELOG.md#ionic-lifecycle-events-renamed).
|
||||
|
||||
|
||||
6. Rename any Ionic events, for example:
|
||||
|
||||
```
|
||||
<ion-slides (slideChangeStart)="onSlideChangeStart($event)">
|
||||
```
|
||||
|
||||
|
||||
becomes
|
||||
|
||||
|
||||
```
|
||||
<ion-slides (ionWillChange)="onSlideChangeStart($event)">
|
||||
```
|
||||
@ -1144,6 +1202,6 @@ Enjoy!
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
The Web Animations polyfill is no longer shipped with the framework and may cause build errors.
|
||||
The Web Animations polyfill is no longer shipped with the framework and may cause build errors.
|
||||
|
||||
Projects will need to be [updated accordingly](https://github.com/driftyco/ionic-conference-app/commit/2ed59e6fd275c4616792c7b2e5aa9da4a20fb188).
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": "true",
|
||||
"name": "ionic2",
|
||||
"version": "2.0.0-beta.8",
|
||||
"version": "2.0.0-beta.9",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -102,4 +102,4 @@
|
||||
"path": "node_modules/ionic-cz-conventional-changelog"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ In the root of the package are ES5 sources in the CommonJS module format, their
|
||||
Usually, the only import required by the user is `ionic-angular`, as everything from Ionic is exported by the package:
|
||||
|
||||
```
|
||||
import {App, Page} from 'ionic-angular';
|
||||
import {App} from 'ionic-angular';
|
||||
```
|
||||
|
||||
### Bundles
|
||||
|
@ -185,6 +185,13 @@ export class Content extends Ion {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getScrollElement(): HTMLElement {
|
||||
return this._scrollEle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
|
@ -143,7 +143,7 @@ export class Icon {
|
||||
css += this._name;
|
||||
}
|
||||
|
||||
if (this.mode === 'ios' && !this.isActive) {
|
||||
if (this.mode === 'ios' && !this.isActive && css.indexOf('logo') < 0) {
|
||||
css += '-outline';
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ export class NativeInput {
|
||||
}
|
||||
|
||||
@HostListener('input', ['$event'])
|
||||
private _change(ev) {
|
||||
private _change(ev: any) {
|
||||
this.valueChange.emit(ev.target.value);
|
||||
}
|
||||
|
||||
@ -40,9 +40,9 @@ export class NativeInput {
|
||||
var self = this;
|
||||
|
||||
self.focusChange.emit(true);
|
||||
|
||||
function docTouchEnd(ev) {
|
||||
var tapped: HTMLElement = ev.target;
|
||||
|
||||
function docTouchEnd(ev: TouchEvent) {
|
||||
var tapped = <HTMLElement>ev.target;
|
||||
if (tapped && self.element()) {
|
||||
if (tapped.tagName !== 'INPUT' && tapped.tagName !== 'TEXTAREA' && !tapped.classList.contains('input-cover')) {
|
||||
self.element().blur();
|
||||
@ -178,7 +178,7 @@ export class NativeInput {
|
||||
|
||||
}
|
||||
|
||||
function cloneInput(focusedInputEle, addCssClass) {
|
||||
function cloneInput(focusedInputEle: any, addCssClass: string) {
|
||||
let clonedInputEle = focusedInputEle.cloneNode(true);
|
||||
clonedInputEle.classList.add('cloned-input');
|
||||
clonedInputEle.classList.add(addCssClass);
|
||||
@ -191,7 +191,7 @@ function cloneInput(focusedInputEle, addCssClass) {
|
||||
return clonedInputEle;
|
||||
}
|
||||
|
||||
function removeClone(focusedInputEle, queryCssClass) {
|
||||
function removeClone(focusedInputEle: any, queryCssClass: string) {
|
||||
let clonedInputEle = focusedInputEle.parentElement.querySelector('.' + queryCssClass);
|
||||
if (clonedInputEle) {
|
||||
clonedInputEle.parentNode.removeChild(clonedInputEle);
|
||||
|
148
src/components/item/item-reorder-gesture.ts
Normal file
148
src/components/item/item-reorder-gesture.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import {Item} from './item';
|
||||
import {List} from '../list/list';
|
||||
import {UIEventManager} from '../../util/ui-event-manager';
|
||||
import {closest, Coordinates, pointerCoord, CSS, nativeRaf} from '../../util/dom';
|
||||
|
||||
|
||||
const AUTO_SCROLL_MARGIN = 60;
|
||||
const SCROLL_JUMP = 10;
|
||||
const ITEM_REORDER_ACTIVE = 'reorder-active';
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export class ItemReorderGesture {
|
||||
private selectedItem: Item = null;
|
||||
private offset: Coordinates;
|
||||
private lastToIndex: number;
|
||||
private lastYcoord: number;
|
||||
private emptyZone: boolean;
|
||||
|
||||
private itemHeight: number;
|
||||
private windowHeight: number;
|
||||
|
||||
private events: UIEventManager = new UIEventManager(false);
|
||||
|
||||
constructor(public list: List) {
|
||||
let element = this.list.getNativeElement();
|
||||
this.events.pointerEvents(element,
|
||||
(ev: any) => this.onDragStart(ev),
|
||||
(ev: any) => this.onDragMove(ev),
|
||||
(ev: any) => this.onDragEnd(ev));
|
||||
}
|
||||
|
||||
private onDragStart(ev: any): boolean {
|
||||
let itemEle = ev.target;
|
||||
if (itemEle.nodeName !== 'ION-REORDER') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let item = itemEle['$ionComponent'];
|
||||
if (!item) {
|
||||
console.error('item does not contain ion component');
|
||||
return false;
|
||||
}
|
||||
ev.preventDefault();
|
||||
|
||||
// Preparing state
|
||||
this.offset = pointerCoord(ev);
|
||||
this.offset.y += this.list.scrollContent(0);
|
||||
this.selectedItem = item;
|
||||
this.itemHeight = item.height();
|
||||
this.lastToIndex = item.index;
|
||||
this.windowHeight = window.innerHeight - AUTO_SCROLL_MARGIN;
|
||||
item.setCssClass(ITEM_REORDER_ACTIVE, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private onDragMove(ev: any) {
|
||||
if (!this.selectedItem) {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
|
||||
// Get coordinate
|
||||
var coord = pointerCoord(ev);
|
||||
|
||||
// Scroll if we reach the scroll margins
|
||||
let scrollPosition = this.scroll(coord);
|
||||
|
||||
// Update selected item position
|
||||
let ydiff = Math.round(coord.y - this.offset.y + scrollPosition);
|
||||
this.selectedItem.setCssStyle(CSS.transform, `translateY(${ydiff}px)`);
|
||||
|
||||
// Only perform hit test if we moved at least 30px from previous position
|
||||
if (Math.abs(coord.y - this.lastYcoord) < 30) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hit test
|
||||
let overItem = this.itemForCoord(coord);
|
||||
if (!overItem) {
|
||||
this.emptyZone = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Move surrounding items if needed
|
||||
let toIndex = overItem.index;
|
||||
if (toIndex !== this.lastToIndex || this.emptyZone) {
|
||||
let fromIndex = this.selectedItem.index;
|
||||
this.lastToIndex = overItem.index;
|
||||
this.lastYcoord = coord.y;
|
||||
this.emptyZone = false;
|
||||
nativeRaf(() => {
|
||||
this.list.reorderMove(fromIndex, toIndex, this.itemHeight);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onDragEnd(ev: any) {
|
||||
if (!this.selectedItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
nativeRaf(() => {
|
||||
let toIndex = this.lastToIndex;
|
||||
let fromIndex = this.selectedItem.index;
|
||||
this.selectedItem.setCssClass(ITEM_REORDER_ACTIVE, false);
|
||||
this.selectedItem = null;
|
||||
this.list.reorderEmit(fromIndex, toIndex);
|
||||
});
|
||||
}
|
||||
|
||||
private itemForCoord(coord: Coordinates): Item {
|
||||
let element = <any>document.elementFromPoint(this.offset.x - 100, coord.y);
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
element = closest(element, 'ion-item', true);
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
let item = <Item>(<any>element)['$ionComponent'];
|
||||
if (!item) {
|
||||
console.error('item does not have $ionComponent');
|
||||
return null;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
private scroll(coord: Coordinates): number {
|
||||
let scrollDiff = 0;
|
||||
if (coord.y < AUTO_SCROLL_MARGIN) {
|
||||
scrollDiff = -SCROLL_JUMP;
|
||||
} else if (coord.y > this.windowHeight) {
|
||||
scrollDiff = SCROLL_JUMP;
|
||||
}
|
||||
return this.list.scrollContent(scrollDiff);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
destroy() {
|
||||
this.events.unlistenAll();
|
||||
this.events = null;
|
||||
this.list = null;
|
||||
}
|
||||
}
|
48
src/components/item/item-reorder.scss
Normal file
48
src/components/item/item-reorder.scss
Normal file
@ -0,0 +1,48 @@
|
||||
|
||||
// Item reorder
|
||||
// --------------------------------------------------
|
||||
|
||||
ion-reorder {
|
||||
display: none;
|
||||
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
max-width: 40px;
|
||||
height: 100%;
|
||||
|
||||
font-size: 1.6em;
|
||||
|
||||
pointer-events: all;
|
||||
touch-action: manipulation;
|
||||
|
||||
ion-icon {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.reorder-enabled {
|
||||
|
||||
ion-item {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
ion-reorder {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
ion-item.reorder-active {
|
||||
z-index: 4;
|
||||
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, .5);
|
||||
opacity: .8;
|
||||
transition: none;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
ion-reorder {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
17
src/components/item/item-reorder.ts
Normal file
17
src/components/item/item-reorder.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import {Component, ElementRef, Inject, forwardRef} from '@angular/core';
|
||||
import {Item} from './item';
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ion-reorder',
|
||||
template: `<ion-icon name="menu"></ion-icon>`
|
||||
})
|
||||
export class ItemReorder {
|
||||
constructor(
|
||||
@Inject(forwardRef(() => Item)) item: Item,
|
||||
elementRef: ElementRef) {
|
||||
elementRef.nativeElement['$ionComponent'] = item;
|
||||
}
|
||||
}
|
@ -12,8 +12,8 @@ export class ItemSlidingGesture extends DragGesture {
|
||||
selectedContainer: ItemSliding = null;
|
||||
openContainer: ItemSliding = null;
|
||||
|
||||
constructor(public list: List, public listEle: HTMLElement) {
|
||||
super(listEle, {
|
||||
constructor(public list: List) {
|
||||
super(list.getNativeElement(), {
|
||||
direction: 'x',
|
||||
threshold: DRAG_THRESHOLD
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ ion-item-sliding.active-slide {
|
||||
opacity: 1;
|
||||
transition: all 300ms cubic-bezier(.36, .66, .04, 1);
|
||||
|
||||
pointer-events: all;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
ion-item-options {
|
||||
|
@ -16,13 +16,40 @@ export const enum SideFlags {
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @name ItemOptions
|
||||
* @description
|
||||
* The option buttons for an `ion-item-sliding`. These buttons can be placed either on the left or right side.
|
||||
* You can combind the `(ionSiwpe)` event plus the `expandable` directive to create a full swipe action for the item.
|
||||
*
|
||||
* @usage
|
||||
*
|
||||
* ```html
|
||||
* <ion-item-sliding>
|
||||
* <ion-item>
|
||||
* Item 1
|
||||
* </ion-item>
|
||||
* <ion-item-options side="right" (ionSwipe)="saveItem(item)">
|
||||
* <button expandable (click)="saveItem(item)">
|
||||
* <ion-icon name="star"></ion-icon>
|
||||
* </button>
|
||||
* </ion-item-options>
|
||||
* </ion-item-sliding>
|
||||
*```
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'ion-item-options',
|
||||
})
|
||||
export class ItemOptions {
|
||||
|
||||
/**
|
||||
* @input {string} the side the option button should be on. Defaults to right
|
||||
* If you have multiple `ion-item-options`, a side must be provided for each.
|
||||
*/
|
||||
@Input() side: string;
|
||||
|
||||
/**
|
||||
* @output {event} Expression to evaluate when the item has been fully swiped.
|
||||
*/
|
||||
@Output() ionSwipe: EventEmitter<ItemSliding> = new EventEmitter();
|
||||
|
||||
constructor(private _elementRef: ElementRef, private _renderer: Renderer) {
|
||||
@ -46,6 +73,9 @@ export class ItemOptions {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
width() {
|
||||
return this._elementRef.nativeElement.offsetWidth;
|
||||
}
|
||||
@ -62,12 +92,30 @@ const enum SlidingState {
|
||||
|
||||
/**
|
||||
* @name ItemSliding
|
||||
*
|
||||
* @description
|
||||
* A sliding item is a list item that can be swiped to reveal buttons. It requires
|
||||
* an [Item](../Item) component as a child and a [List](../../list/List) component as
|
||||
* a parent. All buttons to reveal can be placed in the `<ion-item-options>` element.
|
||||
*
|
||||
* @usage
|
||||
* ```html
|
||||
* <ion-list>
|
||||
* <ion-item-sliding #item>
|
||||
* <ion-item>
|
||||
* Item
|
||||
* </ion-item>
|
||||
* <ion-item-options side="left">
|
||||
* <button (click)="favorite(item)">Favorite</button>
|
||||
* <button danger (click)="share(item)">Share</button>
|
||||
* </ion-item-options>
|
||||
|
||||
* <ion-item-options side="right">
|
||||
* <button (click)="unread(item)">Unread</button>
|
||||
* </ion-item-options>
|
||||
* </ion-item-sliding>
|
||||
* </ion-list>
|
||||
* ```
|
||||
*
|
||||
* ### Swipe Direction
|
||||
* By default, the buttons are revealed when the sliding item is swiped from right to left,
|
||||
* so the buttons are placed in the right side. But it's also possible to reveal them
|
||||
@ -83,7 +131,7 @@ const enum SlidingState {
|
||||
* </button>
|
||||
* </ion-item-options>
|
||||
|
||||
* <ion-item-options>
|
||||
* <ion-item-options side="left">
|
||||
* <button (click)="archive(item)">
|
||||
* <ion-icon name="archive"></ion-icon>
|
||||
* Archive
|
||||
@ -96,19 +144,12 @@ const enum SlidingState {
|
||||
* to the (ionDrag)` event.
|
||||
*
|
||||
* ```html
|
||||
* <ion-item-options side="right">
|
||||
* <button (click)="archive(item)">
|
||||
* <ion-icon name="archive"></ion-icon>
|
||||
* Archive
|
||||
* </button>
|
||||
* </ion-item-options>
|
||||
|
||||
* <ion-item-options>
|
||||
* <button (click)="archive(item)">
|
||||
* <ion-icon name="archive"></ion-icon>
|
||||
* Archive
|
||||
* </button>
|
||||
* </ion-item-options>
|
||||
* <ion-item-sliding (ionDrag)="logDrag($event)">
|
||||
* <ion-item>Item</ion-item>
|
||||
* <ion-item-options>
|
||||
* <button>Favorite</button>
|
||||
* </ion-item-options>
|
||||
* </ion-item-sliding>
|
||||
* ```
|
||||
*
|
||||
* ### Button Layout
|
||||
@ -118,32 +159,15 @@ const enum SlidingState {
|
||||
* `<ion-item-options>` element.
|
||||
*
|
||||
* ```html
|
||||
* <ion-item-sliding (ionDrag)="ondrag($event)">
|
||||
* <ion-item>Item</ion-item>
|
||||
* <ion-item-options>
|
||||
* <button>Favorite</button>
|
||||
* </ion-item-options>
|
||||
* </ion-item-sliding>
|
||||
* <ion-item-options icon-left>
|
||||
* <button (click)="archive(item)">
|
||||
* <ion-icon name="archive"></ion-icon>
|
||||
* Archive
|
||||
* </button>
|
||||
* </ion-item-options>
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* @usage
|
||||
* ```html
|
||||
* <ion-list>
|
||||
* <ion-item-sliding #item>
|
||||
* <ion-item>
|
||||
* Item
|
||||
* </ion-item>
|
||||
* <ion-item-options>
|
||||
* <button (click)="favorite(item)">Favorite</button>
|
||||
* <button danger (click)="share(item)">Share</button>
|
||||
* </ion-item-options>
|
||||
|
||||
* <ion-item-options side="right">
|
||||
* <button (click)="unread(item)">Unread</button>
|
||||
* </ion-item-options>
|
||||
* </ion-item-sliding>
|
||||
* </ion-list>
|
||||
* ```
|
||||
*
|
||||
* @demo /docs/v2/demos/item-sliding/
|
||||
* @see {@link /docs/v2/components#lists List Component Docs}
|
||||
@ -169,8 +193,15 @@ export class ItemSliding {
|
||||
private _rightOptions: ItemOptions;
|
||||
private _optsDirty: boolean = true;
|
||||
private _state: SlidingState = SlidingState.Disabled;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* */
|
||||
slidingPercent: number = 0;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* */
|
||||
@ContentChild(Item) private item: Item;
|
||||
|
||||
|
||||
@ -290,6 +321,9 @@ export class ItemSliding {
|
||||
return restingPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* */
|
||||
fireSwipeEvent() {
|
||||
if (this.slidingPercent > SWIPE_FACTOR) {
|
||||
this._rightOptions.ionSwipe.emit(this);
|
||||
@ -298,6 +332,9 @@ export class ItemSliding {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* */
|
||||
calculateOptsWidth() {
|
||||
nativeRaf(() => {
|
||||
if (this._optsDirty) {
|
||||
@ -382,7 +419,7 @@ export class ItemSliding {
|
||||
/**
|
||||
* Close the sliding item. Items can also be closed from the [List](../../list/List).
|
||||
*
|
||||
* The sliding item can be closed by garbbing a reference to `ItemSliding`. In the
|
||||
* The sliding item can be closed by grabbing a reference to `ItemSliding`. In the
|
||||
* below example, the template reference variable `slidingItem` is placed on the element
|
||||
* and passed to the `share` method.
|
||||
*
|
||||
@ -437,4 +474,4 @@ function shouldClose(isCloseDirection: boolean, isMovingFast: boolean, isOnClose
|
||||
let shouldClose = (!isMovingFast && isOnCloseZone) || (isCloseDirection && isMovingFast);
|
||||
return shouldClose;
|
||||
}
|
||||
|
||||
|
||||
|
@ -85,3 +85,4 @@ ion-input.item {
|
||||
|
||||
@import "item-media";
|
||||
@import "item-sliding";
|
||||
@import "item-reorder";
|
||||
|
@ -1,9 +1,10 @@
|
||||
import {Component, ContentChildren, forwardRef, ViewChild, ContentChild, Renderer, ElementRef, ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
|
||||
import {Component, ContentChildren, forwardRef, Input, ViewChild, ContentChild, Renderer, ElementRef, ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {Button} from '../button/button';
|
||||
import {Form} from '../../util/form';
|
||||
import {Icon} from '../icon/icon';
|
||||
import {Label} from '../label/label';
|
||||
import {ItemReorder} from './item-reorder';
|
||||
|
||||
|
||||
/**
|
||||
@ -235,11 +236,13 @@ import {Label} from '../label/label';
|
||||
'<ng-content select="ion-select,ion-input,ion-textarea,ion-datetime,ion-range,[item-content]"></ng-content>' +
|
||||
'</div>' +
|
||||
'<ng-content select="[item-right],ion-radio,ion-toggle"></ng-content>' +
|
||||
'<ion-reorder></ion-reorder>' +
|
||||
'</div>' +
|
||||
'<ion-button-effect></ion-button-effect>',
|
||||
host: {
|
||||
'class': 'item'
|
||||
},
|
||||
directives: [forwardRef(() => ItemReorder)],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
@ -249,6 +252,11 @@ export class Item {
|
||||
private _label: Label;
|
||||
private _viewLabel: boolean = true;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@Input() index: number;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ -261,6 +269,7 @@ export class Item {
|
||||
|
||||
constructor(form: Form, private _renderer: Renderer, private _elementRef: ElementRef) {
|
||||
this.id = form.nextId().toString();
|
||||
_elementRef.nativeElement['$ionComponent'] = this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -354,4 +363,11 @@ export class Item {
|
||||
icon.addClass('item-icon');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
height(): number {
|
||||
return this._elementRef.nativeElement.offsetHeight;
|
||||
}
|
||||
}
|
||||
|
1
src/components/item/test/reorder/e2e.ts
Normal file
1
src/components/item/test/reorder/e2e.ts
Normal file
@ -0,0 +1 @@
|
||||
|
30
src/components/item/test/reorder/index.ts
Normal file
30
src/components/item/test/reorder/index.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {Component, ChangeDetectorRef} from '@angular/core';
|
||||
import {ionicBootstrap} from '../../../../../src';
|
||||
|
||||
|
||||
@Component({
|
||||
templateUrl: 'main.html'
|
||||
})
|
||||
class E2EPage {
|
||||
items: any[] = [];
|
||||
isReordering: boolean = false;
|
||||
|
||||
constructor(private d: ChangeDetectorRef) {
|
||||
let nu = 30;
|
||||
for (let i = 0; i < nu; i++) {
|
||||
this.items.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.isReordering = !this.isReordering;
|
||||
}
|
||||
|
||||
reorder(indexes: any) {
|
||||
let element = this.items[indexes.from];
|
||||
this.items.splice(indexes.from, 1);
|
||||
this.items.splice(indexes.to, 0, element);
|
||||
}
|
||||
}
|
||||
|
||||
ionicBootstrap(E2EPage);
|
22
src/components/item/test/reorder/main.html
Normal file
22
src/components/item/test/reorder/main.html
Normal file
@ -0,0 +1,22 @@
|
||||
<ion-toolbar primary>
|
||||
<ion-title>Reorder items</ion-title>
|
||||
<ion-buttons end>
|
||||
<button (click)="toggle()">
|
||||
Edit
|
||||
</button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
|
||||
<ion-content>
|
||||
|
||||
<ion-list [reorder]="isReordering" (ionItemReorder)="reorder($event)">
|
||||
<ion-item *ngFor="let item of items; let index=index"
|
||||
[index]="index"
|
||||
[style.background]="'rgb('+(255-item*4)+','+(255-item*4)+','+(255-item*4)+')'"
|
||||
[style.height]="item*2+35+'px'">
|
||||
{{item}}
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
</ion-content>
|
||||
|
@ -1,7 +1,11 @@
|
||||
import {Directive, ElementRef, Renderer, Attribute, NgZone} from '@angular/core';
|
||||
import {Directive, ElementRef, EventEmitter, Renderer, Input, Optional, Output, Attribute, NgZone} from '@angular/core';
|
||||
|
||||
import {Content} from '../content/content';
|
||||
import {Ion} from '../ion';
|
||||
import {ItemSlidingGesture} from '../item/item-sliding-gesture';
|
||||
import {ItemReorderGesture} from '../item/item-reorder-gesture';
|
||||
import {isTrueProperty} from '../../util/util';
|
||||
import {nativeTimeout} from '../../util/dom';
|
||||
|
||||
/**
|
||||
* The List is a widely used interface element in almost any mobile app,
|
||||
@ -20,32 +24,30 @@ import {ItemSlidingGesture} from '../item/item-sliding-gesture';
|
||||
*
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'ion-list'
|
||||
selector: 'ion-list',
|
||||
host: {
|
||||
'[class.reorder-enabled]': '_enableReorder',
|
||||
}
|
||||
})
|
||||
export class List extends Ion {
|
||||
private _enableReorder: boolean = false;
|
||||
private _enableSliding: boolean = false;
|
||||
private _slidingGesture: ItemSlidingGesture;
|
||||
private _reorderGesture: ItemReorderGesture;
|
||||
private _lastToIndex: number = -1;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ele: HTMLElement;
|
||||
@Output() ionItemReorder: EventEmitter<{ from: number, to: number }> = new EventEmitter();
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
slidingGesture: ItemSlidingGesture;
|
||||
|
||||
constructor(elementRef: ElementRef, private _zone: NgZone) {
|
||||
constructor(elementRef: ElementRef, private _zone: NgZone, @Optional() private _content: Content) {
|
||||
super(elementRef);
|
||||
this.ele = elementRef.nativeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this.slidingGesture && this.slidingGesture.destroy();
|
||||
this.ele = this.slidingGesture = null;
|
||||
this._slidingGesture && this._slidingGesture.destroy();
|
||||
this._reorderGesture && this._reorderGesture.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,12 +78,10 @@ export class List extends Ion {
|
||||
this._enableSliding = shouldEnable;
|
||||
if (shouldEnable) {
|
||||
console.debug('enableSlidingItems');
|
||||
this._zone.runOutsideAngular(() => {
|
||||
setTimeout(() => this.slidingGesture = new ItemSlidingGesture(this, this.ele));
|
||||
});
|
||||
nativeTimeout(() => this._slidingGesture = new ItemSlidingGesture(this));
|
||||
|
||||
} else {
|
||||
this.slidingGesture && this.slidingGesture.unlisten();
|
||||
this._slidingGesture && this._slidingGesture.unlisten();
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,7 +105,96 @@ export class List extends Ion {
|
||||
* ```
|
||||
*/
|
||||
closeSlidingItems() {
|
||||
this.slidingGesture && this.slidingGesture.closeOpened();
|
||||
this._slidingGesture && this._slidingGesture.closeOpened();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
reorderEmit(fromIndex: number, toIndex: number) {
|
||||
this.reorderReset();
|
||||
if (fromIndex !== toIndex) {
|
||||
this._zone.run(() => {
|
||||
this.ionItemReorder.emit({
|
||||
from: fromIndex,
|
||||
to: toIndex,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
scrollContent(scroll: number) {
|
||||
let scrollTop = this._content.getScrollTop() + scroll;
|
||||
if (scroll !== 0) {
|
||||
this._content.scrollTo(0, scrollTop, 0);
|
||||
}
|
||||
return scrollTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
reorderReset() {
|
||||
let children = this.elementRef.nativeElement.children;
|
||||
let len = children.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
children[i].style.transform = '';
|
||||
}
|
||||
this._lastToIndex = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
reorderMove(fromIndex: number, toIndex: number, itemHeight: number) {
|
||||
if (this._lastToIndex === -1) {
|
||||
this._lastToIndex = fromIndex;
|
||||
}
|
||||
let lastToIndex = this._lastToIndex;
|
||||
this._lastToIndex = toIndex;
|
||||
|
||||
let children = this.elementRef.nativeElement.children;
|
||||
if (toIndex >= lastToIndex) {
|
||||
for (var i = lastToIndex; i <= toIndex; i++) {
|
||||
if (i !== fromIndex) {
|
||||
children[i].style.transform = (i > fromIndex)
|
||||
? `translateY(${-itemHeight}px)` : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toIndex <= lastToIndex) {
|
||||
for (var i = toIndex; i <= lastToIndex; i++) {
|
||||
if (i !== fromIndex) {
|
||||
children[i].style.transform = (i < fromIndex)
|
||||
? `translateY(${itemHeight}px)` : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
get reorder(): boolean {
|
||||
return this._enableReorder;
|
||||
}
|
||||
|
||||
set reorder(val: boolean) {
|
||||
let enabled = isTrueProperty(val);
|
||||
if (this._enableReorder === enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._enableReorder = enabled;
|
||||
if (enabled) {
|
||||
console.debug('enableReorderItems');
|
||||
nativeTimeout(() => this._reorderGesture = new ItemReorderGesture(this));
|
||||
|
||||
} else {
|
||||
this._reorderGesture && this._reorderGesture.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1752,6 +1752,13 @@ export class NavController extends Ion {
|
||||
return this._views.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
isSwipeBackEnabled(): boolean {
|
||||
return this._sbEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root `NavController`.
|
||||
* @returns {NavController}
|
||||
|
@ -191,7 +191,6 @@ export class Nav extends NavController implements AfterViewInit {
|
||||
get swipeBackEnabled(): boolean {
|
||||
return this._sbEnabled;
|
||||
}
|
||||
|
||||
set swipeBackEnabled(val: boolean) {
|
||||
this._sbEnabled = isTrueProperty(val);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import {Key} from '../../util/key';
|
||||
import {NavParams} from '../nav/nav-params';
|
||||
import {ViewController} from '../nav/view-controller';
|
||||
import {raf, cancelRaf, CSS, pointerCoord} from '../../util/dom';
|
||||
import {UIEventManager} from '../../util/ui-event-manager';
|
||||
|
||||
|
||||
/**
|
||||
@ -98,12 +99,6 @@ export class Picker extends ViewController {
|
||||
'[style.min-width]': 'col.columnWidth',
|
||||
'[class.picker-opts-left]': 'col.align=="left"',
|
||||
'[class.picker-opts-right]': 'col.align=="right"',
|
||||
'(touchstart)': 'pointerStart($event)',
|
||||
'(touchmove)': 'pointerMove($event)',
|
||||
'(touchend)': 'pointerEnd($event)',
|
||||
'(mousedown)': 'pointerStart($event)',
|
||||
'(mousemove)': 'pointerMove($event)',
|
||||
'(body:mouseup)': 'pointerEnd($event)'
|
||||
}
|
||||
})
|
||||
class PickerColumnCmp {
|
||||
@ -114,7 +109,6 @@ class PickerColumnCmp {
|
||||
optHeight: number;
|
||||
velocity: number;
|
||||
pos: number[] = [];
|
||||
msPrv: number = 0;
|
||||
startY: number = null;
|
||||
rafId: number;
|
||||
bounceFrom: number;
|
||||
@ -123,10 +117,11 @@ class PickerColumnCmp {
|
||||
rotateFactor: number;
|
||||
lastIndex: number;
|
||||
receivingEvents: boolean = false;
|
||||
events: UIEventManager = new UIEventManager();
|
||||
|
||||
@Output() ionChange: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
constructor(config: Config, private _sanitizer: DomSanitizationService) {
|
||||
constructor(config: Config, private elementRef: ElementRef, private _sanitizer: DomSanitizationService) {
|
||||
this.rotateFactor = config.getNumber('pickerRotateFactor', 0);
|
||||
}
|
||||
|
||||
@ -141,16 +136,22 @@ class PickerColumnCmp {
|
||||
|
||||
// set the scroll position for the selected option
|
||||
this.setSelected(this.col.selectedIndex, 0);
|
||||
|
||||
// Listening for pointer events
|
||||
this.events.pointerEventsRef(this.elementRef,
|
||||
(ev: any) => this.pointerStart(ev),
|
||||
(ev: any) => this.pointerMove(ev),
|
||||
(ev: any) => this.pointerEnd(ev)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.events.unlistenAll();
|
||||
}
|
||||
|
||||
pointerStart(ev: UIEvent) {
|
||||
pointerStart(ev: UIEvent): boolean {
|
||||
console.debug('picker, pointerStart', ev.type, this.startY);
|
||||
|
||||
if (this.isPrevented(ev)) {
|
||||
// do not both with mouse events if a touch event already fired
|
||||
return;
|
||||
}
|
||||
|
||||
// cancel any previous raf's that haven't fired yet
|
||||
cancelRaf(this.rafId);
|
||||
|
||||
@ -175,6 +176,7 @@ class PickerColumnCmp {
|
||||
|
||||
this.minY = (minY * this.optHeight * -1);
|
||||
this.maxY = (maxY * this.optHeight * -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
pointerMove(ev: UIEvent) {
|
||||
@ -185,10 +187,6 @@ class PickerColumnCmp {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isPrevented(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentY = pointerCoord(ev).y;
|
||||
this.pos.push(currentY, Date.now());
|
||||
|
||||
@ -213,10 +211,6 @@ class PickerColumnCmp {
|
||||
}
|
||||
|
||||
pointerEnd(ev: UIEvent) {
|
||||
if (this.isPrevented(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.receivingEvents) {
|
||||
return;
|
||||
}
|
||||
@ -410,22 +404,6 @@ class PickerColumnCmp {
|
||||
}
|
||||
}
|
||||
|
||||
isPrevented(ev: UIEvent): boolean {
|
||||
let now = Date.now();
|
||||
if (ev.type.indexOf('touch') > -1) {
|
||||
// this is a touch event, so prevent mouse events for a while
|
||||
this.msPrv = now + 2000;
|
||||
|
||||
} else if (this.msPrv > now && ev.type.indexOf('mouse') > -1) {
|
||||
// this is a mouse event, and a touch event already happend recently
|
||||
// prevent the calling method from continuing
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -4,7 +4,9 @@ import {NG_VALUE_ACCESSOR} from '@angular/common';
|
||||
import {Form} from '../../util/form';
|
||||
import {isTrueProperty, isNumber, isString, isPresent, clamp} from '../../util/util';
|
||||
import {Item} from '../item/item';
|
||||
import {UIEventManager} from '../../util/ui-event-manager';
|
||||
import {pointerCoord, Coordinates, raf} from '../../util/dom';
|
||||
import {Debouncer} from '../../util/debouncer';
|
||||
|
||||
|
||||
const RANGE_VALUE_ACCESSOR = new Provider(
|
||||
@ -212,9 +214,9 @@ export class Range {
|
||||
private _max: number = 100;
|
||||
private _step: number = 1;
|
||||
private _snaps: boolean = false;
|
||||
private _removes: Function[] = [];
|
||||
private _mouseRemove: Function;
|
||||
|
||||
private _debouncer: Debouncer = new Debouncer(0);
|
||||
private _events: UIEventManager = new UIEventManager();
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ -293,6 +295,17 @@ export class Range {
|
||||
this._pin = isTrueProperty(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @input {number} If true, a pin with integer value is shown when the knob is pressed. Defaults to `false`.
|
||||
*/
|
||||
@Input()
|
||||
get debounce(): number {
|
||||
return this._debouncer.wait;
|
||||
}
|
||||
set debounce(val: number) {
|
||||
this._debouncer.wait = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @input {boolean} Show two knobs. Defaults to `false`.
|
||||
*/
|
||||
@ -346,8 +359,10 @@ export class Range {
|
||||
this._renderer.setElementStyle(this._bar.nativeElement, 'right', barR);
|
||||
|
||||
// add touchstart/mousedown listeners
|
||||
this._renderer.listen(this._slider.nativeElement, 'touchstart', this.pointerDown.bind(this));
|
||||
this._mouseRemove = this._renderer.listen(this._slider.nativeElement, 'mousedown', this.pointerDown.bind(this));
|
||||
this._events.pointerEventsRef(this._slider,
|
||||
this.pointerDown.bind(this),
|
||||
this.pointerMove.bind(this),
|
||||
this.pointerUp.bind(this));
|
||||
|
||||
this.createTicks();
|
||||
}
|
||||
@ -355,12 +370,12 @@ export class Range {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
pointerDown(ev: UIEvent) {
|
||||
pointerDown(ev: UIEvent): boolean {
|
||||
// TODO: we could stop listening for events instead of checking this._disabled.
|
||||
// since there are a lot of events involved, this solution is
|
||||
// enough for the moment
|
||||
if (this._disabled) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
console.debug(`range, ${ev.type}`);
|
||||
|
||||
@ -368,11 +383,6 @@ export class Range {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
if (ev.type === 'touchstart') {
|
||||
// if this was a touchstart, then let's remove the mousedown
|
||||
this._mouseRemove && this._mouseRemove();
|
||||
}
|
||||
|
||||
// get the start coordinates
|
||||
this._start = pointerCoord(ev);
|
||||
|
||||
@ -398,25 +408,11 @@ export class Range {
|
||||
// update the ratio for the active knob
|
||||
this.updateKnob(this._start, rect);
|
||||
|
||||
// ensure past listeners have been removed
|
||||
this.clearListeners();
|
||||
|
||||
// update the active knob's position
|
||||
this._active.position();
|
||||
this._pressed = this._active.pressed = true;
|
||||
|
||||
// add a move listener depending on touch/mouse
|
||||
let renderer = this._renderer;
|
||||
let removes = this._removes;
|
||||
|
||||
if (ev.type === 'touchstart') {
|
||||
removes.push(renderer.listen(this._slider.nativeElement, 'touchmove', this.pointerMove.bind(this)));
|
||||
removes.push(renderer.listen(this._slider.nativeElement, 'touchend', this.pointerUp.bind(this)));
|
||||
|
||||
} else {
|
||||
removes.push(renderer.listenGlobal('body', 'mousemove', this.pointerMove.bind(this)));
|
||||
removes.push(renderer.listenGlobal('window', 'mouseup', this.pointerUp.bind(this)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -440,9 +436,6 @@ export class Range {
|
||||
this._active.position();
|
||||
this._pressed = this._active.pressed = true;
|
||||
|
||||
} else {
|
||||
// ensure listeners have been removed
|
||||
this.clearListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@ -464,21 +457,7 @@ export class Range {
|
||||
|
||||
// clear the start coordinates and active knob
|
||||
this._start = this._active = null;
|
||||
|
||||
// ensure listeners have been removed
|
||||
this.clearListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
clearListeners() {
|
||||
this._pressed = this._knobs.first.pressed = this._knobs.last.pressed = false;
|
||||
|
||||
for (var i = 0; i < this._removes.length; i++) {
|
||||
this._removes[i]();
|
||||
}
|
||||
this._removes.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -519,9 +498,10 @@ export class Range {
|
||||
this.value = newVal;
|
||||
}
|
||||
|
||||
this.onChange(this.value);
|
||||
|
||||
this.ionChange.emit(this);
|
||||
this._debouncer.debounce(() => {
|
||||
this.onChange(this.value);
|
||||
this.ionChange.emit(this);
|
||||
});
|
||||
}
|
||||
|
||||
this.updateBar();
|
||||
@ -695,7 +675,7 @@ export class Range {
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this._form.deregister(this);
|
||||
this.clearListeners();
|
||||
this._events.unlistenAll();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-range [(ngModel)]="singleValue" danger pin="true" (ionChange)="rangeChange($event)"></ion-range>
|
||||
<ion-range [(ngModel)]="singleValue" danger pin="true" (ionChange)="rangeChange($event)" [debounce]="100"></ion-range>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
|
@ -4,6 +4,7 @@ import {Content} from '../content/content';
|
||||
import {Icon} from '../icon/icon';
|
||||
import {isTrueProperty} from '../../util/util';
|
||||
import {CSS, pointerCoord, transitionEnd} from '../../util/dom';
|
||||
import {PointerEvents, UIEventManager} from '../../util/ui-event-manager';
|
||||
|
||||
|
||||
/**
|
||||
@ -95,15 +96,10 @@ import {CSS, pointerCoord, transitionEnd} from '../../util/dom';
|
||||
export class Refresher {
|
||||
private _appliedStyles: boolean = false;
|
||||
private _didStart: boolean;
|
||||
private _lastStart: number = 0;
|
||||
private _lastCheck: number = 0;
|
||||
private _isEnabled: boolean = true;
|
||||
private _mDown: Function;
|
||||
private _mMove: Function;
|
||||
private _mUp: Function;
|
||||
private _tStart: Function;
|
||||
private _tMove: Function;
|
||||
private _tEnd: Function;
|
||||
private _events: UIEventManager = new UIEventManager(false);
|
||||
private _pointerEvents: PointerEvents;
|
||||
|
||||
/**
|
||||
* The current state which the refresher is in. The refresher's states include:
|
||||
@ -155,7 +151,7 @@ export class Refresher {
|
||||
* will automatically go into the `refreshing` state. By default, the pull
|
||||
* maximum will be the result of `pullMin + 60`.
|
||||
*/
|
||||
@Input() pullMax: number = null;
|
||||
@Input() pullMax: number = this.pullMin + 60;
|
||||
|
||||
/**
|
||||
* @input {number} How many milliseconds it takes to close the refresher. Default is `280`.
|
||||
@ -202,8 +198,7 @@ export class Refresher {
|
||||
constructor(
|
||||
@Host() private _content: Content,
|
||||
private _zone: NgZone,
|
||||
elementRef: ElementRef
|
||||
) {
|
||||
elementRef: ElementRef) {
|
||||
_content.addCssClass('has-refresher');
|
||||
|
||||
// deprecated warning
|
||||
@ -222,31 +217,29 @@ export class Refresher {
|
||||
private _onStart(ev: TouchEvent): any {
|
||||
// if multitouch then get out immediately
|
||||
if (ev.touches && ev.touches.length > 1) {
|
||||
return 1;
|
||||
return false;
|
||||
}
|
||||
if (this.state !== STATE_INACTIVE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let scrollHostScrollTop = this._content.getContentDimensions().scrollTop;
|
||||
// if the scrollTop is greater than zero then it's
|
||||
// not possible to pull the content down yet
|
||||
if (scrollHostScrollTop > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let coord = pointerCoord(ev);
|
||||
console.debug('Pull-to-refresh, onStart', ev.type, 'y:', coord.y);
|
||||
|
||||
let now = Date.now();
|
||||
if (this._lastStart + 100 > now) {
|
||||
return 2;
|
||||
}
|
||||
this._lastStart = now;
|
||||
|
||||
if ( ev.type === 'mousedown' && !this._mMove) {
|
||||
this._mMove = this._content.addMouseMoveListener( this._onMove.bind(this) );
|
||||
}
|
||||
|
||||
this.startY = this.currentY = coord.y;
|
||||
this.progress = 0;
|
||||
|
||||
if (!this.pullMax) {
|
||||
this.pullMax = (this.pullMin + 60);
|
||||
}
|
||||
this.state = STATE_PULLING;
|
||||
return true;
|
||||
}
|
||||
|
||||
private _onMove(ev: TouchEvent): any {
|
||||
private _onMove(ev: TouchEvent) {
|
||||
// this method can get called like a bazillion times per second,
|
||||
// so it's built to be as efficient as possible, and does its
|
||||
// best to do any DOM read/writes only when absolutely necessary
|
||||
@ -396,12 +389,6 @@ export class Refresher {
|
||||
|
||||
// reset on any touchend/mouseup
|
||||
this.startY = null;
|
||||
if (this._mMove) {
|
||||
// we don't want to always listen to mousemoves
|
||||
// remove it if we're still listening
|
||||
this._mMove();
|
||||
this._mMove = null;
|
||||
}
|
||||
}
|
||||
|
||||
private _beginRefresh() {
|
||||
@ -463,10 +450,8 @@ export class Refresher {
|
||||
this.state = state;
|
||||
this._setCss(0, '', true, delay);
|
||||
|
||||
if (this._mMove) {
|
||||
// always remove the mousemove event
|
||||
this._mMove();
|
||||
this._mMove = null;
|
||||
if (this._pointerEvents) {
|
||||
this._pointerEvents.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -481,43 +466,13 @@ export class Refresher {
|
||||
}
|
||||
|
||||
private _setListeners(shouldListen: boolean) {
|
||||
const self = this;
|
||||
const content = self._content;
|
||||
|
||||
this._events.unlistenAll();
|
||||
this._pointerEvents = null;
|
||||
if (shouldListen) {
|
||||
// add listener outside of zone
|
||||
// touch handlers
|
||||
self._zone.runOutsideAngular(function() {
|
||||
if (!self._tStart) {
|
||||
self._tStart = content.addTouchStartListener( self._onStart.bind(self) );
|
||||
}
|
||||
if (!self._tMove) {
|
||||
self._tMove = content.addTouchMoveListener( self._onMove.bind(self) );
|
||||
}
|
||||
if (!self._tEnd) {
|
||||
self._tEnd = content.addTouchEndListener( self._onEnd.bind(self) );
|
||||
}
|
||||
|
||||
// mouse handlers
|
||||
// mousemove does not get added until mousedown fires
|
||||
if (!self._mDown) {
|
||||
self._mDown = content.addMouseDownListener( self._onStart.bind(self) );
|
||||
}
|
||||
if (!self._mUp) {
|
||||
self._mUp = content.addMouseUpListener( self._onEnd.bind(self) );
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
// unregister event listeners from content element
|
||||
self._mDown && self._mDown();
|
||||
self._mMove && self._mMove();
|
||||
self._mUp && self._mUp();
|
||||
self._tStart && self._tStart();
|
||||
self._tMove && self._tMove();
|
||||
self._tEnd && self._tEnd();
|
||||
|
||||
self._mDown = self._mMove = self._mUp = self._tStart = self._tMove = self._tEnd = null;
|
||||
this._pointerEvents = this._events.pointerEvents(this._content.getScrollElement(),
|
||||
this._onStart.bind(this),
|
||||
this._onMove.bind(this),
|
||||
this._onEnd.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ import {NgControl} from '@angular/common';
|
||||
|
||||
import {Config} from '../../config/config';
|
||||
import {isPresent} from '../../util/util';
|
||||
import {Debouncer} from '../../util/debouncer';
|
||||
|
||||
|
||||
/**
|
||||
@ -46,10 +47,10 @@ import {isPresent} from '../../util/util';
|
||||
})
|
||||
export class Searchbar {
|
||||
private _value: string|number = '';
|
||||
private _tmr: any;
|
||||
private _shouldBlur: boolean = true;
|
||||
private _isActive: boolean = false;
|
||||
private _searchbarInput: ElementRef;
|
||||
private _debouncer: Debouncer = new Debouncer(250);
|
||||
|
||||
/**
|
||||
* @input {string} Set the the cancel button text. Default: `"Cancel"`.
|
||||
@ -64,7 +65,13 @@ export class Searchbar {
|
||||
/**
|
||||
* @input {number} How long, in milliseconds, to wait to trigger the `input` event after each keystroke. Default `250`.
|
||||
*/
|
||||
@Input() debounce: number = 250;
|
||||
@Input()
|
||||
get debounce(): number {
|
||||
return this._debouncer.wait;
|
||||
}
|
||||
set debounce(val: number) {
|
||||
this._debouncer.wait = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @input {string} Set the input's placeholder. Default `"Search"`.
|
||||
@ -268,13 +275,11 @@ export class Searchbar {
|
||||
*/
|
||||
inputChanged(ev: any) {
|
||||
let value = ev.target.value;
|
||||
|
||||
clearTimeout(this._tmr);
|
||||
this._tmr = setTimeout(() => {
|
||||
this._debouncer.debounce(() => {
|
||||
this._value = value;
|
||||
this.onChange(this._value);
|
||||
this.ionInput.emit(ev);
|
||||
}, Math.round(this.debounce));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -201,6 +201,17 @@ export class Tab extends NavController {
|
||||
this._isShown = isTrueProperty(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @input {boolean} Whether it's possible to swipe-to-go-back on this tab or not.
|
||||
*/
|
||||
@Input()
|
||||
get swipeBackEnabled(): boolean {
|
||||
return this._sbEnabled;
|
||||
}
|
||||
set swipeBackEnabled(val: boolean) {
|
||||
this._sbEnabled = isTrueProperty(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @output {Tab} Method to call when the current tab is selected
|
||||
*/
|
||||
@ -222,6 +233,10 @@ export class Tab extends NavController {
|
||||
|
||||
parent.add(this);
|
||||
|
||||
if (parentTabs.rootNav) {
|
||||
this._sbEnabled = parentTabs.rootNav.isSwipeBackEnabled();
|
||||
}
|
||||
|
||||
this._panelId = 'tabpanel-' + this.id;
|
||||
this._btnId = 'tab-' + this.id;
|
||||
}
|
||||
|
@ -324,7 +324,7 @@ class Tab3Page1 {
|
||||
|
||||
|
||||
@Component({
|
||||
template: `<ion-nav [root]="root"></ion-nav>`
|
||||
template: '<ion-nav [root]="root" swipeBackEnabled="false"></ion-nav>'
|
||||
})
|
||||
class E2EApp {
|
||||
root = SignIn;
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
<ion-tabs preloadTabs="false" (ionChange)="onTabChange()">
|
||||
<ion-tab tabTitle="Recents" tabIcon="call" [root]="tab1Root" [rootParams]="params"></ion-tab>
|
||||
<ion-tab tabTitle="Recents" tabIcon="call" [root]="tab1Root" [rootParams]="params" swipeBackEnabled="true"></ion-tab>
|
||||
<ion-tab tabTitle="Favorites" tabIcon="star" [root]="tab2Root"></ion-tab>
|
||||
<ion-tab tabTitle="Settings" tabIcon="settings" [root]="tab3Root"></ion-tab>
|
||||
<ion-tab tabTitle="Chat" tabIcon="chatbubbles" (ionSelect)="chat()"></ion-tab>
|
||||
|
@ -235,7 +235,7 @@ export class Tab3 {
|
||||
<ion-tabs #content (ionChange)="onChange($event)">
|
||||
<ion-tab tabTitle="Plain List" tabIcon="star" [root]="root1" (ionSelect)="onSelect($event)"></ion-tab>
|
||||
<ion-tab tabTitle="Schedule" tabIcon="globe" [root]="root2"></ion-tab>
|
||||
<ion-tab tabTitle="Stopwatch" tabIcon="stopwatch" [root]="root3"></ion-tab>
|
||||
<ion-tab tabTitle="Stopwatch" tabIcon="logo-facebook" [root]="root3"></ion-tab>
|
||||
<ion-tab tabTitle="Messages" tabIcon="chatboxes" [root]="root1"></ion-tab>
|
||||
<ion-tab tabTitle="My Profile" tabIcon="person" [root]="root2"></ion-tab>
|
||||
</ion-tabs>
|
||||
|
@ -5,6 +5,7 @@ import {Form} from '../../util/form';
|
||||
import {isTrueProperty} from '../../util/util';
|
||||
import {Item} from '../item/item';
|
||||
import {pointerCoord} from '../../util/dom';
|
||||
import {UIEventManager} from '../../util/ui-event-manager';
|
||||
|
||||
|
||||
const TOGGLE_VALUE_ACCESSOR = new Provider(
|
||||
@ -87,6 +88,7 @@ export class Toggle implements ControlValueAccessor {
|
||||
private _startX: number;
|
||||
private _msPrv: number = 0;
|
||||
private _fn: Function;
|
||||
private _events: UIEventManager = new UIEventManager();
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -113,27 +115,14 @@ export class Toggle implements ControlValueAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private pointerDown(ev: UIEvent) {
|
||||
if (this._isPrevented(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
private pointerDown(ev: UIEvent): boolean {
|
||||
this._startX = pointerCoord(ev).x;
|
||||
this._activated = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private pointerMove(ev: UIEvent) {
|
||||
if (this._startX) {
|
||||
if (this._isPrevented(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentX = pointerCoord(ev).x;
|
||||
console.debug('toggle, pointerMove', ev.type, currentX);
|
||||
|
||||
@ -152,16 +141,8 @@ export class Toggle implements ControlValueAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private pointerUp(ev: UIEvent) {
|
||||
if (this._startX) {
|
||||
|
||||
if (this._isPrevented(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let endX = pointerCoord(ev).x;
|
||||
|
||||
if (this.checked) {
|
||||
@ -188,9 +169,7 @@ export class Toggle implements ControlValueAccessor {
|
||||
this.onChange(this._checked);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
||||
private _setChecked(isChecked: boolean) {
|
||||
if (isChecked !== this._checked) {
|
||||
this._checked = isChecked;
|
||||
@ -256,6 +235,11 @@ export class Toggle implements ControlValueAccessor {
|
||||
*/
|
||||
ngAfterContentInit() {
|
||||
this._init = true;
|
||||
this._events.pointerEventsRef(this._elementRef,
|
||||
(ev: any) => this.pointerDown(ev),
|
||||
(ev: any) => this.pointerMove(ev),
|
||||
(ev: any) => this.pointerUp(ev)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -263,20 +247,7 @@ export class Toggle implements ControlValueAccessor {
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this._form.deregister(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private _isPrevented(ev: UIEvent) {
|
||||
if (ev.type.indexOf('touch') > -1) {
|
||||
this._msPrv = Date.now() + 2000;
|
||||
|
||||
} else if (this._msPrv > Date.now() && ev.type.indexOf('mouse') > -1) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return true;
|
||||
}
|
||||
this._events.unlistenAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
25
src/util/debouncer.ts
Normal file
25
src/util/debouncer.ts
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
export class Debouncer {
|
||||
private timer: number = null;
|
||||
callback: Function;
|
||||
|
||||
constructor(public wait: number) { }
|
||||
|
||||
debounce(callback: Function) {
|
||||
this.callback = callback;
|
||||
this.schedule();
|
||||
}
|
||||
|
||||
schedule() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
if (this.wait <= 0) {
|
||||
this.callback();
|
||||
} else {
|
||||
this.timer = setTimeout(this.callback, this.wait);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
170
src/util/ui-event-manager.ts
Normal file
170
src/util/ui-event-manager.ts
Normal file
@ -0,0 +1,170 @@
|
||||
import {ElementRef} from '@angular/core';
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export class PointerEvents {
|
||||
private rmTouchStart: Function = null;
|
||||
private rmTouchMove: Function = null;
|
||||
private rmTouchEnd: Function = null;
|
||||
|
||||
private rmMouseStart: Function = null;
|
||||
private rmMouseMove: Function = null;
|
||||
private rmMouseUp: Function = null;
|
||||
|
||||
private lastTouchEvent: number = 0;
|
||||
|
||||
mouseWait: number = 2 * 1000;
|
||||
|
||||
constructor(private ele: any,
|
||||
private pointerDown: any,
|
||||
private pointerMove: any,
|
||||
private pointerUp: any,
|
||||
private zone: boolean,
|
||||
private option: any) {
|
||||
|
||||
this.rmTouchStart = listenEvent(ele, 'touchstart', zone, option, (ev: any) => this.handleTouchStart(ev));
|
||||
this.rmMouseStart = listenEvent(ele, 'mousedown', zone, option, (ev: any) => this.handleMouseDown(ev));
|
||||
}
|
||||
|
||||
private handleTouchStart(ev: any) {
|
||||
this.lastTouchEvent = Date.now() + this.mouseWait;
|
||||
if (!this.pointerDown(ev)) {
|
||||
return;
|
||||
}
|
||||
if (!this.rmTouchMove) {
|
||||
this.rmTouchMove = listenEvent(this.ele, 'touchmove', this.zone, this.option, this.pointerMove);
|
||||
}
|
||||
if (!this.rmTouchEnd) {
|
||||
this.rmTouchEnd = listenEvent(this.ele, 'touchend', this.zone, this.option, (ev: any) => this.handleTouchEnd(ev));
|
||||
}
|
||||
}
|
||||
|
||||
private handleMouseDown(ev: any) {
|
||||
if (this.lastTouchEvent > Date.now()) {
|
||||
console.debug('mousedown event dropped because of previous touch');
|
||||
return;
|
||||
}
|
||||
if (!this.pointerDown(ev)) {
|
||||
return;
|
||||
}
|
||||
if (!this.rmMouseMove) {
|
||||
this.rmMouseMove = listenEvent(window, 'mousemove', this.zone, this.option, this.pointerMove);
|
||||
}
|
||||
if (!this.rmMouseUp) {
|
||||
this.rmMouseUp = listenEvent(window, 'mouseup', this.zone, this.option, (ev: any) => this.handleMouseUp(ev));
|
||||
}
|
||||
}
|
||||
|
||||
private handleTouchEnd(ev: any) {
|
||||
this.rmTouchMove && this.rmTouchMove();
|
||||
this.rmTouchMove = null;
|
||||
this.rmTouchEnd && this.rmTouchEnd();
|
||||
this.rmTouchEnd = null;
|
||||
|
||||
this.pointerUp(ev);
|
||||
}
|
||||
|
||||
private handleMouseUp(ev: any) {
|
||||
this.rmMouseMove && this.rmMouseMove();
|
||||
this.rmMouseMove = null;
|
||||
this.rmMouseUp && this.rmMouseUp();
|
||||
this.rmMouseUp = null;
|
||||
|
||||
this.pointerUp(ev);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.rmTouchMove && this.rmTouchMove();
|
||||
this.rmTouchEnd && this.rmTouchEnd();
|
||||
this.rmTouchMove = null;
|
||||
this.rmTouchEnd = null;
|
||||
|
||||
this.rmMouseMove && this.rmMouseMove();
|
||||
this.rmMouseUp && this.rmMouseUp();
|
||||
this.rmMouseMove = null;
|
||||
this.rmMouseUp = null;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.rmTouchStart && this.rmTouchStart();
|
||||
this.rmTouchStart = null;
|
||||
|
||||
this.rmMouseStart && this.rmMouseStart();
|
||||
this.rmMouseStart = null;
|
||||
|
||||
this.stop();
|
||||
|
||||
this.pointerDown = null;
|
||||
this.pointerMove = null;
|
||||
this.pointerUp = null;
|
||||
|
||||
this.ele = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export class UIEventManager {
|
||||
private events: Function[] = [];
|
||||
|
||||
constructor(public zoneWrapped: boolean = true) {}
|
||||
|
||||
listenRef(ref: ElementRef, eventName: string, callback: any, option?: any): Function {
|
||||
return this.listen(ref.nativeElement, eventName, callback, option);
|
||||
}
|
||||
|
||||
pointerEventsRef(ref: ElementRef, pointerStart: any, pointerMove: any, pointerEnd: any, option?: any): Function {
|
||||
return this.pointerEvents(ref.nativeElement, pointerStart, pointerMove, pointerEnd, option);
|
||||
}
|
||||
|
||||
pointerEvents(element: any, pointerDown: any, pointerMove: any, pointerUp: any, option: any = false): PointerEvents {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
let submanager = new PointerEvents(
|
||||
element,
|
||||
pointerDown,
|
||||
pointerMove,
|
||||
pointerUp,
|
||||
this.zoneWrapped,
|
||||
option);
|
||||
|
||||
let removeFunc = () => submanager.destroy();
|
||||
this.events.push(removeFunc);
|
||||
return submanager;
|
||||
}
|
||||
|
||||
listen(element: any, eventName: string, callback: any, option: any = false): Function {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
let removeFunc = listenEvent(element, eventName, this.zoneWrapped, option, callback);
|
||||
this.events.push(removeFunc);
|
||||
return removeFunc;
|
||||
}
|
||||
|
||||
unlistenAll() {
|
||||
for (let event of this.events) {
|
||||
event();
|
||||
}
|
||||
this.events.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function listenEvent(ele: any, eventName: string, zoneWrapped: boolean, option: any, callback: any): Function {
|
||||
let rawEvent = ('__zone_symbol__addEventListener' in ele && !zoneWrapped);
|
||||
if (rawEvent) {
|
||||
ele.__zone_symbol__addEventListener(eventName, callback, option);
|
||||
return () => ele.__zone_symbol__removeEventListener(eventName, callback);
|
||||
} else {
|
||||
ele.addEventListener(eventName, callback, option);
|
||||
return () => ele.removeEventListener(eventName, callback);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user