Skip to content

Commit

Permalink
feat(item): two-way sliding of items
Browse files Browse the repository at this point in the history
  • Loading branch information
manucorporat committed Jun 1, 2016
1 parent 170cf8c commit 89d7d85
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 220 deletions.
232 changes: 49 additions & 183 deletions src/components/item/item-sliding-gesture.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import {DIRECTION_RIGHT} from '../../gestures/hammer';
import {DragGesture} from '../../gestures/drag-gesture';
import {ItemSliding} from './item-sliding';
import {List} from '../list/list';

import {CSS, nativeRaf, closest} from '../../util/dom';
import {closest} from '../../util/dom';

const DRAG_THRESHOLD = 20;

export class ItemSlidingGesture extends DragGesture {
canDrag: boolean = true;
data = {};
openItems: number = 0;
onTap;
onMouseOut;
canDrag: boolean = true;
preventDrag: boolean = false;
dragEnded: boolean = true;

Expand All @@ -19,54 +17,43 @@ export class ItemSlidingGesture extends DragGesture {
direction: 'x',
threshold: DRAG_THRESHOLD
});

this.onTap = (event) => this.onTapCallback(event);
this.listen();
}

this.onTap = (ev) => {
if (!isFromOptionButtons(ev.target)) {
let didClose = this.closeOpened();
if (didClose) {
console.debug('tap close sliding item');
preventDefault(ev);
}
}
};

this.onMouseOut = (ev) => {
if (ev.target.tagName === 'ION-ITEM-SLIDING') {
console.debug('tap close sliding item');
this.onDragEnd(ev);
}
};
onTapCallback(ev) {
if (isFromOptionButtons(ev.target)) {
return;
}
let didClose = this.closeOpened();
if (didClose) {
console.debug('tap close sliding item, preventDefault');
ev.preventDefault();
}
}

onDragStart(ev): boolean {
let itemContainerEle = getItemContainer(ev.target);
if (!itemContainerEle) {
let container = getContainer(ev);
if (!container) {
console.debug('onDragStart, no itemContainerEle');
return false;
}

this.closeOpened(itemContainerEle);

let openAmout = this.getOpenAmount(itemContainerEle);
let itemData = this.get(itemContainerEle);
this.preventDrag = (openAmout > 0);

// Close all item sliding containers but the selected one
this.preventDrag = container.getOpenAmount() > 0;
if (this.preventDrag) {
this.closeOpened();
console.debug('onDragStart, preventDefault');
preventDefault(ev);
return;
ev.preventDefault();
return false;
}

itemContainerEle.classList.add('active-slide');

this.set(itemContainerEle, 'offsetX', openAmout);
this.set(itemContainerEle, 'startX', ev.center[this.direction]);

// Close all item sliding containers but the selected one
this.closeOpened(container);
this.dragEnded = false;

container.startSliding(ev.center.x);

return true;
}

Expand All @@ -77,181 +64,60 @@ export class ItemSlidingGesture extends DragGesture {
return;
}

let itemContainerEle = getItemContainer(ev.target);
if (!itemContainerEle || !isActive(itemContainerEle)) {
let container = getContainer(ev);
if (!container) {
console.debug('onDrag, no itemContainerEle');
return;
}

let itemData = this.get(itemContainerEle);

if (!itemData.optsWidth) {
itemData.optsWidth = getOptionsWidth(itemContainerEle);
if (!itemData.optsWidth) {
console.debug('onDrag, no optsWidth');
return;
}
}

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)));
}

if (newX > 5 && ev.srcEvent.type.indexOf('mouse') > -1 && !itemData.hasMouseOut) {
itemContainerEle.addEventListener('mouseout', this.onMouseOut);
itemData.hasMouseOut = true;
}

nativeRaf(() => {
if (!this.dragEnded && !this.preventDrag) {
isItemActive(itemContainerEle, true);
this.open(itemContainerEle, newX, false);
}
});
container.moveSliding(ev.center.x);
}

onDragEnd(ev) {
this.preventDrag = false;
this.dragEnded = true;

let itemContainerEle = getItemContainer(ev.target);
if (!itemContainerEle || !isActive(itemContainerEle)) {
let container = getContainer(ev);
if (!container) {
console.debug('onDragEnd, no 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 & DIRECTION_RIGHT || Math.abs(ev.velocityX) < 0.3) {
restingPoint = 0;
}
let openAmount = container.endSliding(ev.velocityX);
if (openAmount === 0) {
this.off('tap', this.onTap);
} else {
this.on('tap', this.onTap);
}

itemContainerEle.removeEventListener('mouseout', this.onMouseOut);
itemData.hasMouseOut = false;

nativeRaf(() => {
this.open(itemContainerEle, restingPoint, true);
});
}

closeOpened(doNotCloseEle?: HTMLElement) {
closeOpened(doNotClose?: ItemSliding): boolean {
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;
}
let openItemElements = this.listEle.querySelectorAll('.active-slide');
for (var i = 0; i < openItemElements.length; i++) {
var component = openItemElements[i]['$ionComponent'];
if (component && component !== doNotClose) {
component.close();
didClose = true;
}
}
return didClose;
}

open(itemContainerEle, openAmount, isFinal) {
let slidingEle = itemContainerEle.querySelector('ion-item,[ion-item]');
if (!slidingEle) {
console.debug('open, no slidingEle, openAmount:', openAmount);
return;
}

this.set(itemContainerEle, 'openAmount', openAmount);

clearTimeout(this.get(itemContainerEle).timerId);

if (openAmount) {
this.openItems++;

} else {
let timerId = setTimeout(() => {
if (slidingEle.style[CSS.transform] === '') {
isItemActive(itemContainerEle, false);
this.openItems--;
}
}, 400);
this.set(itemContainerEle, 'timerId', timerId);
}

slidingEle.style[CSS.transition] = (isFinal ? '' : 'none');
slidingEle.style[CSS.transform] = (openAmount ? 'translate3d(' + -openAmount + 'px,0,0)' : '');

if (isFinal) {
if (openAmount) {
isItemActive(itemContainerEle, true);
this.on('tap', this.onTap);

} else {
this.off('tap', this.onTap);
}
}
}

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 isItemActive(ele, isActive) {
ele.classList[isActive ? 'add' : 'remove']('active-slide');
ele.classList[isActive ? 'add' : 'remove']('active-options');
}

function preventDefault(ev) {
console.debug('sliding item preventDefault', ev.type);
ev.preventDefault();
}

function getItemContainer(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 getContainer(ev): ItemSliding {
let ele = closest(ev.target, 'ion-item-sliding', true);
if (ele) {
return ele['$ionComponent'];
}
return null;
}

function isActive(itemContainerEle) {
return itemContainerEle.classList.contains('active-slide');
function isFromOptionButtons(ele: HTMLElement): boolean {
return !!closest(ele, 'ion-item-options', true);
}


const DRAG_THRESHOLD = 20;
21 changes: 19 additions & 2 deletions src/components/item/item-sliding.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ ion-item-options {
visibility: hidden;
}

ion-item-options[side=left] {
right: auto;
left: 0;
}

ion-item-options .button {
margin: 0;

Expand All @@ -50,6 +55,15 @@ ion-item-options:not([icon-left]) .button-icon-left {
}
}

ion-item-sliding.active-swipe > ion-item-options[swipeable] {
>[swipeable] {
width: 220px;
}
>:not([swipeable]) {
display: none;
}
}

ion-item-sliding.active-slide {

.item,
Expand All @@ -61,14 +75,17 @@ ion-item-sliding.active-slide {
transition: all 300ms cubic-bezier(.36, .66, .04, 1);

pointer-events: none;

}

ion-item-options {
display: flex;
}

&.active-options ion-item-options {
&.active-options-left ion-item-options[side=left] {
visibility: visible;
}

&.active-options-right ion-item-options:not([side=left]) {
visibility: visible;
}
}
Loading

0 comments on commit 89d7d85

Please sign in to comment.