mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-22 21:48:42 +08:00

committed by
Adam Bradley

parent
af2228757f
commit
5c38921595
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;
|
selectedContainer: ItemSliding = null;
|
||||||
openContainer: ItemSliding = null;
|
openContainer: ItemSliding = null;
|
||||||
|
|
||||||
constructor(public list: List, public listEle: HTMLElement) {
|
constructor(public list: List) {
|
||||||
super(listEle, {
|
super(list.getNativeElement(), {
|
||||||
direction: 'x',
|
direction: 'x',
|
||||||
threshold: DRAG_THRESHOLD
|
threshold: DRAG_THRESHOLD
|
||||||
});
|
});
|
||||||
|
@ -85,3 +85,4 @@ ion-input.item {
|
|||||||
|
|
||||||
@import "item-media";
|
@import "item-media";
|
||||||
@import "item-sliding";
|
@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 {Button} from '../button/button';
|
||||||
import {Form} from '../../util/form';
|
import {Form} from '../../util/form';
|
||||||
import {Icon} from '../icon/icon';
|
import {Icon} from '../icon/icon';
|
||||||
import {Label} from '../label/label';
|
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>' +
|
'<ng-content select="ion-select,ion-input,ion-textarea,ion-datetime,ion-range,[item-content]"></ng-content>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<ng-content select="[item-right],ion-radio,ion-toggle"></ng-content>' +
|
'<ng-content select="[item-right],ion-radio,ion-toggle"></ng-content>' +
|
||||||
|
'<ion-reorder></ion-reorder>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<ion-button-effect></ion-button-effect>',
|
'<ion-button-effect></ion-button-effect>',
|
||||||
host: {
|
host: {
|
||||||
'class': 'item'
|
'class': 'item'
|
||||||
},
|
},
|
||||||
|
directives: [forwardRef(() => ItemReorder)],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
encapsulation: ViewEncapsulation.None,
|
encapsulation: ViewEncapsulation.None,
|
||||||
})
|
})
|
||||||
@ -249,6 +252,11 @@ export class Item {
|
|||||||
private _label: Label;
|
private _label: Label;
|
||||||
private _viewLabel: boolean = true;
|
private _viewLabel: boolean = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
@Input() index: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@ -261,6 +269,7 @@ export class Item {
|
|||||||
|
|
||||||
constructor(form: Form, private _renderer: Renderer, private _elementRef: ElementRef) {
|
constructor(form: Form, private _renderer: Renderer, private _elementRef: ElementRef) {
|
||||||
this.id = form.nextId().toString();
|
this.id = form.nextId().toString();
|
||||||
|
_elementRef.nativeElement['$ionComponent'] = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -354,4 +363,11 @@ export class Item {
|
|||||||
icon.addClass('item-icon');
|
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 {Ion} from '../ion';
|
||||||
import {ItemSlidingGesture} from '../item/item-sliding-gesture';
|
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,
|
* 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({
|
@Directive({
|
||||||
selector: 'ion-list'
|
selector: 'ion-list',
|
||||||
|
host: {
|
||||||
|
'[class.reorder-enabled]': '_enableReorder',
|
||||||
|
}
|
||||||
})
|
})
|
||||||
export class List extends Ion {
|
export class List extends Ion {
|
||||||
|
private _enableReorder: boolean = false;
|
||||||
private _enableSliding: boolean = false;
|
private _enableSliding: boolean = false;
|
||||||
|
private _slidingGesture: ItemSlidingGesture;
|
||||||
|
private _reorderGesture: ItemReorderGesture;
|
||||||
|
private _lastToIndex: number = -1;
|
||||||
|
|
||||||
/**
|
@Output() ionItemReorder: EventEmitter<{ from: number, to: number }> = new EventEmitter();
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ele: HTMLElement;
|
|
||||||
|
|
||||||
/**
|
constructor(elementRef: ElementRef, private _zone: NgZone, @Optional() private _content: Content) {
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
slidingGesture: ItemSlidingGesture;
|
|
||||||
|
|
||||||
constructor(elementRef: ElementRef, private _zone: NgZone) {
|
|
||||||
super(elementRef);
|
super(elementRef);
|
||||||
this.ele = elementRef.nativeElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.slidingGesture && this.slidingGesture.destroy();
|
this._slidingGesture && this._slidingGesture.destroy();
|
||||||
this.ele = this.slidingGesture = null;
|
this._reorderGesture && this._reorderGesture.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,12 +78,10 @@ export class List extends Ion {
|
|||||||
this._enableSliding = shouldEnable;
|
this._enableSliding = shouldEnable;
|
||||||
if (shouldEnable) {
|
if (shouldEnable) {
|
||||||
console.debug('enableSlidingItems');
|
console.debug('enableSlidingItems');
|
||||||
this._zone.runOutsideAngular(() => {
|
nativeTimeout(() => this._slidingGesture = new ItemSlidingGesture(this));
|
||||||
setTimeout(() => this.slidingGesture = new ItemSlidingGesture(this, this.ele));
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.slidingGesture && this.slidingGesture.unlisten();
|
this._slidingGesture && this._slidingGesture.unlisten();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +105,96 @@ export class List extends Ion {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
closeSlidingItems() {
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
150
src/util/ui-event-manager.ts
Normal file
150
src/util/ui-event-manager.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import {ElementRef} from '@angular/core';
|
||||||
|
|
||||||
|
const MOUSE_WAIT = 2 * 1000;
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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() + MOUSE_WAIT;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.rmTouchStart && this.rmTouchStart();
|
||||||
|
this.rmTouchMove && this.rmTouchMove();
|
||||||
|
this.rmTouchEnd && this.rmTouchEnd();
|
||||||
|
|
||||||
|
this.rmMouseStart && this.rmMouseStart();
|
||||||
|
this.rmMouseMove && this.rmMouseMove();
|
||||||
|
this.rmMouseUp && this.rmMouseUp();
|
||||||
|
|
||||||
|
this.rmTouchStart = null;
|
||||||
|
this.rmTouchMove = null;
|
||||||
|
this.rmTouchEnd = null;
|
||||||
|
this.rmMouseStart = null;
|
||||||
|
this.rmMouseMove = null;
|
||||||
|
this.rmMouseUp = null;
|
||||||
|
|
||||||
|
this.pointerDown = null;
|
||||||
|
this.pointerMove = null;
|
||||||
|
this.pointerUp = null;
|
||||||
|
|
||||||
|
this.ele = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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): Function {
|
||||||
|
let submanager = new PointerEvents(
|
||||||
|
element,
|
||||||
|
pointerDown,
|
||||||
|
pointerMove,
|
||||||
|
pointerUp,
|
||||||
|
this.zoneWrapped,
|
||||||
|
option);
|
||||||
|
|
||||||
|
let removeFunc = () => submanager.destroy();
|
||||||
|
this.events.push(removeFunc);
|
||||||
|
return removeFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
listen(element: any, eventName: string, callback: any, option: any = false): Function {
|
||||||
|
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