import {Hammer} from 'ionic/gestures/hammer'; import {DragGesture} from 'ionic/gestures/drag-gesture'; import {List} from '../list/list'; import {CSS, raf, closest} from 'ionic/util/dom'; export class ItemSlidingGesture extends DragGesture { constructor(list: List, listEle) { super(listEle, { direction: 'x', threshold: 40 }); this.data = {}; this.openItems = 0; this.list = list; this.listEle = listEle; this.canDrag = true; this.listen(); this.on('tap', ev => { if (!isFromOptionButtons(ev.target)) { let didClose = this.closeOpened(); if (didClose) { ev.preventDefault(); } } }); this.mouseOut = (ev) => { this.onDragEnd(ev); }; } onDragStart(ev) { let itemContainerEle = getItemConatiner(ev.target); if (!itemContainerEle) return; this.closeOpened(ev, itemContainerEle); let openAmout = this.getOpenAmount(itemContainerEle); let itemData = this.get(itemContainerEle); this.preventDrag = (openAmout > 0); if (this.preventDrag) { this.closeOpened(ev); return ev.preventDefault(); } itemContainerEle.classList.add('active-slide'); this.set(itemContainerEle, 'offsetX', openAmout); this.set(itemContainerEle, 'startX', ev.center[this.direction]); if (ev.srcEvent.type.indexOf('mouse') > -1) { ev.target.addEventListener('mouseout', this.mouseOut); } } onDrag(ev) { let itemContainerEle = getItemConatiner(ev.target); if (!itemContainerEle || !isActive(itemContainerEle) || this.preventDrag) return; let itemData = this.get(itemContainerEle); if (!itemData.optsWidth) { itemData.optsWidth = getOptionsWidth(itemContainerEle); if (!itemData.optsWidth) return; } itemContainerEle.classList.add('active-slide'); itemContainerEle.classList.add('active-options'); let x = ev.center[this.direction]; let delta = x - itemData.startX; let newX = Math.max(0, itemData.offsetX - delta); if (newX > itemData.optsWidth) { // Calculate the new X position, capped at the top of the buttons newX = -Math.min(-itemData.optsWidth, -itemData.optsWidth + (((delta + itemData.optsWidth) * 0.4))); } this.open(itemContainerEle, newX, false); } onDragEnd(ev) { this.preventDrag = false; let itemContainerEle = getItemConatiner(ev.target); if (!itemContainerEle || !isActive(itemContainerEle)) return; // If we are currently dragging, we want to snap back into place // The final resting point X will be the width of the exposed buttons let itemData = this.get(itemContainerEle); var restingPoint = itemData.optsWidth; // Check if the drag didn't clear the buttons mid-point // and we aren't moving fast enough to swipe open if (this.getOpenAmount(itemContainerEle) < (restingPoint / 2)) { // If we are going left but too slow, or going right, go back to resting if (ev.direction & Hammer.DIRECTION_RIGHT) { // Left restingPoint = 0; } else if (Math.abs(ev.velocityX) < 0.3) { // Right restingPoint = 0; } } ev.target.removeEventListener('mouseout', this.mouseOut); raf(() => { this.open(itemContainerEle, restingPoint, true); }); } closeOpened(ev, doNotCloseEle) { let didClose = false; if (this.openItems) { let openItemElements = this.listEle.querySelectorAll('.active-slide'); for (let i = 0; i < openItemElements.length; i++) { if (openItemElements[i] !== doNotCloseEle) { this.open(openItemElements[i], 0, true); didClose = true; } } } return didClose; } open(itemContainerEle, openAmount, isFinal) { let slidingEle = itemContainerEle.querySelector('ion-item,[ion-item]'); if (!slidingEle) return; this.set(itemContainerEle, 'openAmount', openAmount); clearTimeout(this.get(itemContainerEle).timerId); if (openAmount > 0) { this.openItems++; } else { let timerId = setTimeout(() => { if (slidingEle.style[CSS.transform] === '') { itemContainerEle.classList.remove('active-slide'); itemContainerEle.classList.remove('active-options'); this.openItems--; } }, 400); this.set(itemContainerEle, 'timerId', timerId); } slidingEle.style[CSS.transition] = (isFinal ? '' : 'none'); slidingEle.style[CSS.transform] = (openAmount === 0 ? '' : 'translate3d(' + -openAmount + 'px,0,0)'); } getOpenAmount(itemContainerEle) { return this.get(itemContainerEle).openAmount || 0; } get(itemContainerEle) { return this.data[itemContainerEle && itemContainerEle.$ionSlide] || {}; } set(itemContainerEle, key, value) { if (!this.data[itemContainerEle.$ionSlide]) { this.data[itemContainerEle.$ionSlide] = {}; } this.data[itemContainerEle.$ionSlide][key] = value; } unlisten() { super.unlisten(); this.listEle = null; } } function getItemConatiner(ele) { return closest(ele, 'ion-item-sliding', true); } function isFromOptionButtons(ele) { return !!closest(ele, 'ion-item-options', true); } function getOptionsWidth(itemContainerEle) { let optsEle = itemContainerEle.querySelector('ion-item-options'); if (optsEle) { return optsEle.offsetWidth; } } function isActive(itemContainerEle) { return itemContainerEle.classList.contains('active-slide'); }