diff --git a/ionic/components/item/item-sliding-gesture.ts b/ionic/components/item/item-sliding-gesture.ts
new file mode 100644
index 0000000000..ab0128a1b5
--- /dev/null
+++ b/ionic/components/item/item-sliding-gesture.ts
@@ -0,0 +1,187 @@
+import {Hammer} from 'ionic/gestures/hammer';
+import {DragGesture} from 'ionic/gestures/drag-gesture';
+import {List} from '../list/list';
+
+import * as util from 'ionic/util';
+import {CSS, raf, closest} from 'ionic/util/dom';
+
+
+export class ItemSlidingGesture extends DragGesture {
+ constructor(list: List, listEle) {
+ super(listEle, {
+ direction: 'x',
+ threshold: list.width()
+ });
+
+ 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();
+ }
+ }
+ });
+ }
+
+ onDragStart(ev) {
+ let itemContainerEle = getItemConatiner(ev.target);
+ if (!itemContainerEle) return;
+
+ this.closeOpened(ev, itemContainerEle);
+
+ let openAmout = this.getOpenAmount(itemContainerEle);
+ let itemData = this.getData(itemContainerEle);
+
+ if (openAmout) {
+ return ev.preventDefault();
+ }
+
+ itemContainerEle.classList.add('active-slide');
+
+ this.setData(itemContainerEle, 'offsetX', openAmout);
+ this.setData(itemContainerEle, 'startX', ev.center[this.direction]);
+ }
+
+ onDrag(ev) {
+ let itemContainerEle = getItemConatiner(ev.target);
+ if (!itemContainerEle || !isActive(itemContainerEle)) return;
+
+ let itemData = this.getData(itemContainerEle);
+
+ if (!itemData.optsWidth) {
+ itemData.optsWidth = getOptionsWidth(itemContainerEle);
+ if (!itemData.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)));
+ }
+
+ this.open(itemContainerEle, newX, false);
+ }
+
+ onDragEnd(ev) {
+ 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.getData(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;
+ }
+ }
+
+ this.setData(itemContainerEle, 'opened', restingPoint > 0);
+
+ 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, animate) {
+ let slidingEle = itemContainerEle.querySelector('ion-item');
+ if (!slidingEle) return;
+
+ this.setData(itemContainerEle, 'openAmount', openAmount);
+
+ clearTimeout(this.getData(itemContainerEle).timerId);
+
+ if (openAmount > 0) {
+ this.openItems++;
+
+ } else {
+ let timerId = setTimeout(() => {
+ if (slidingEle.style[CSS.transform] === '') {
+ itemContainerEle.classList.remove('active-slide');
+ this.openItems--;
+ }
+ }, 400);
+ this.setData(itemContainerEle, 'timerId', timerId);
+ }
+
+ slidingEle.style[CSS.transform] = (openAmount === 0 ? '' : 'translate3d(' + -openAmount + 'px,0,0)');
+ slidingEle.style[CSS.transition] = (animate ? '' : 'none');
+ }
+
+ getOpenAmount(itemContainerEle) {
+ return this.getData(itemContainerEle).openAmount || 0;
+ }
+
+ getData(itemContainerEle) {
+ return this.data[itemContainerEle && itemContainerEle.$ionSlide] || {};
+ }
+
+ setData(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');
+}
diff --git a/ionic/components/item/item-sliding.scss b/ionic/components/item/item-sliding.scss
new file mode 100644
index 0000000000..f27705b017
--- /dev/null
+++ b/ionic/components/item/item-sliding.scss
@@ -0,0 +1,36 @@
+
+/**
+ * The hidden right-side buttons that can be exposed under a list item
+ * with dragging.
+ */
+
+ion-item-sliding {
+ display: block;
+ position: relative;
+ overflow: hidden;
+
+ .item {
+ position: static;
+ }
+}
+
+ion-item-options {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: $z-index-item-options;
+ height: 100%;
+}
+
+ion-item-sliding.active-slide {
+
+ .item {
+ position: relative;
+ z-index: $z-index-item-options + 1;
+ }
+
+ ion-item-options {
+ display: flex;
+ }
+}
diff --git a/ionic/components/item/item-sliding.ts b/ionic/components/item/item-sliding.ts
index ea04bacbfd..4e7fcdae43 100644
--- a/ionic/components/item/item-sliding.ts
+++ b/ionic/components/item/item-sliding.ts
@@ -1,31 +1,7 @@
-import {Component, Directive, ElementRef, NgIf, Host, Optional, Renderer, NgZone} from 'angular2/angular2';
+import {Component, ElementRef, Optional} from 'angular2/angular2';
-import {Gesture} from 'ionic/gestures/gesture';
-import {DragGesture} from 'ionic/gestures/drag-gesture';
-import {Hammer} from 'ionic/gestures/hammer';
-import {List} from 'ionic/components/list/list';
+import {List} from '../list/list';
-import * as util from 'ionic/util';
-
-import {CSS, raf} from 'ionic/util/dom';
-
-
-
-@Directive({
- selector: 'ion-item-options > button,ion-item-options > [button]',
- host: {
- '(click)': 'clicked($event)'
- }
-})
-export class ItemSlidingOptionButton {
- constructor(elementRef: ElementRef) {
- }
- clicked(event) {
- // Don't allow the click to propagate
- event.preventDefault();
- event.stopPropagation();
- }
-}
/**
* @name ionItem
@@ -36,238 +12,35 @@ export class ItemSlidingOptionButton {
* @usage
* ```html
*
- *
- * {{item.title}}
- *
- * {{item.note}}
- *
+ *
+ *
+ * {{item.title}}
+ *
+ *
+ *
+ *
+ *
*
*
- * ```
+ * ```
*/
@Component({
selector: 'ion-item-sliding',
- inputs: [
- 'sliding'
- ],
template:
- '' +
- '' +
- '' +
- '' +
- '' +
- ''+
- '' +
- ''
+ '' +
+ ''
})
export class ItemSliding {
- /**
- * TODO
- * @param {ElementRef} elementRef A reference to the component's DOM element.
- */
- constructor(elementRef: ElementRef, renderer: Renderer, @Optional() @Host() list: List, zone: NgZone) {
- this._zone = zone;
- renderer.setElementClass(elementRef, 'item', true);
- renderer.setElementAttribute(elementRef, 'tappable', '');
- this._isOpen = false;
- this._isSlideActive = false;
- this._isTransitioning = false;
- this._transform = '';
-
- this.list = list;
-
- this.elementRef = elementRef;
- this.swipeButtons = {};
- this.optionButtons = {};
+ constructor(@Optional() private list: List, elementRef: ElementRef) {
+ list.enableSlidingItems(true);
+ elementRef.nativeElement.$ionSlide = ++slideIds;
}
- onInit() {
- let ele = this.elementRef.nativeElement;
- this.itemSlidingContent = ele.querySelector('ion-item-sliding-content');
- this.itemOptions = ele.querySelector('ion-item-options');
- this.openAmount = 0;
- this._zone.runOutsideAngular(() => {
- this.gesture = new ItemSlideGesture(this, this.itemSlidingContent, this._zone);
- });
+ close() {
+ this.list.closeSlidingItems();
}
- onDestroy() {
- this.gesture && this.gesture.unlisten();
- this.itemSlidingContent = this.itemOptionsContent = null;
- }
-
- close(andStopDrag) {
- this.openAmount = 0;
-
- // Enable it once, it'll get disabled on the next drag
- raf(() => {
- this.enableAnimation();
- if (this.itemSlidingContent) {
- this.itemSlidingContent.style[CSS.transform] = 'translateX(0)';
- }
- });
- }
-
- open(amt) {
- let el = this.itemSlidingContent;
- this.openAmount = amt || 0;
-
- if (this.list) {
- this.list.setOpenItem(this);
- }
-
- if (amt === '') {
- el.style[CSS.transform] = '';
- } else {
- el.style[CSS.transform] = 'translateX(' + -amt + 'px)';
- }
- }
-
- isOpen() {
- return this.openAmount > 0;
- }
-
- getOpenAmt() {
- return this.openAmount;
- }
-
- disableAnimation() {
- this.itemSlidingContent.style[CSS.transition] = 'none';
- }
-
- enableAnimation() {
- // Clear the explicit transition, allow for CSS one to take over
- this.itemSlidingContent.style[CSS.transition] = '';
- }
-
- /**
- * User did a touchstart
- */
- didTouch() {
- if (this.isOpen()) {
- this.close();
- this.didClose = true;
-
- } else {
- let openItem = this.list.getOpenItem();
- if (openItem && openItem !== this) {
- this.didClose = true;
- }
- if (this.list) {
- this.list.closeOpenItem();
- }
- }
- }
}
-class ItemSlideGesture extends DragGesture {
- constructor(item: ItemSliding, el: Element, zone) {
- super(el, {
- direction: 'x',
- threshold: el.offsetWidth
- });
- this.item = item;
- this.canDrag = true;
- this.listen();
-
- zone.runOutsideAngular(() => {
- let touchStart = (e) => {
- this.item.didTouch();
- raf(() => {
- this.item.itemOptionsWidth = this.item.itemOptions && this.item.itemOptions.offsetWidth || 0;
- })
- };
- el.addEventListener('touchstart', touchStart);
- el.addEventListener('mousedown', touchStart);
-
- let touchEnd = (e) => {
- // If we have a touch end and the item is closing,
- // prevent default to stop a click from triggering
- if(this.item.didClose) {
- e.preventDefault();
- }
- this.item.didClose = false;
- };
- el.addEventListener('touchend', touchEnd);
- el.addEventListener('mouseup', touchEnd);
- el.addEventListener('mouseout', touchEnd);
- el.addEventListener('mouseleave', touchEnd);
- el.addEventListener('touchcancel', touchEnd);
- });
- }
-
- onDragStart(ev) {
- if (this.item.didClose) { return; }
-
- if (!this.item.itemOptionsWidth) { return; }
-
- this.slide = {};
-
- this.slide.offsetX = this.item.getOpenAmt();
- this.slide.startX = ev.center[this.direction];
- this.slide.started = true;
-
- this.item.disableAnimation();
- }
-
- onDrag(ev) {
- if (!this.slide || !this.slide.started) return;
-
- this.slide.x = ev.center[this.direction];
- this.slide.delta = this.slide.x - this.slide.startX;
-
- let newX = Math.max(0, this.slide.offsetX - this.slide.delta);
-
- let buttonsWidth = this.item.itemOptionsWidth;
-
- if (newX > this.item.itemOptionsWidth) {
- // Calculate the new X position, capped at the top of the buttons
- newX = -Math.min(-buttonsWidth, -buttonsWidth + (((this.slide.delta + buttonsWidth) * 0.4)));
- }
-
- this.item.open(newX);
- }
-
- onDragEnd(ev) {
- if (!this.slide || !this.slide.started) return;
-
- let buttonsWidth = this.item.itemOptionsWidth;
-
- // 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
- var restingPoint = this.item.itemOptionsWidth;
-
- // Check if the drag didn't clear the buttons mid-point
- // and we aren't moving fast enough to swipe open
- if (this.item.openAmount < (buttonsWidth / 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;
- }
- }
-
- raf(() => {
- if (restingPoint === 0) {
- // Reset to zero
- this.item.open('');
- var buttons = this.item.itemOptions;
- clearTimeout(this.hideButtonsTimeout);
- this.hideButtonsTimeout = setTimeout(() => {
- buttons && buttons.classList.add('invisible');
- }, 250);
-
- } else {
- this.item.open(restingPoint);
- }
- this.item.enableAnimation();
-
- this.slide = null;
- });
- }
-}
+let slideIds = 0;
diff --git a/ionic/components/item/item.scss b/ionic/components/item/item.scss
index a047c5bccd..61ffdc307f 100644
--- a/ionic/components/item/item.scss
+++ b/ionic/components/item/item.scss
@@ -134,24 +134,6 @@ ion-input.item {
align-items: flex-start;
}
-/**
- * The hidden right-side buttons that can be exposed under a list item
- * with dragging.
- */
-ion-item-sliding-content {
- display: block;
- z-index: $z-index-item-options + 1;
- flex: 1;
-}
-ion-item-options {
- display: flex;
- position: absolute;
- top: 0;
- right: 0;
- z-index: $z-index-item-options;
- height: 100%;
-}
-
// TEMP hack for https://github.com/angular/angular/issues/4582
[item-right] {
diff --git a/ionic/components/item/modes/ios.scss b/ionic/components/item/modes/ios.scss
index 1de425d967..6215e46607 100644
--- a/ionic/components/item/modes/ios.scss
+++ b/ionic/components/item/modes/ios.scss
@@ -163,26 +163,6 @@ ion-note {
}
}
-
- ion-item-sliding.item {
- padding-left: 0;
- padding-right: 0;
- }
-
- ion-item-sliding-content {
- background-color: $item-ios-sliding-content-bg;
- padding-right: ($item-ios-padding-right / 2);
- padding-left: ($item-ios-padding-left / 2);
- display: flex;
- min-height: 42px;
- justify-content: center;
-
- transition: $item-ios-sliding-transition;
-
- // To allow the hairlines through
- margin-top: 1px;
- margin-bottom: 1px;
- }
ion-item-options {
button, [button] {
min-height: calc(100% - 2px);
@@ -204,8 +184,7 @@ ion-note {
.item.activated,
a.item.activated,
-button.item.activated,
-.item.activated ion-item-sliding-content {
+button.item.activated {
background-color: $item-ios-activated-background-color;
transition-duration: 0ms;
}
@@ -219,25 +198,13 @@ button.item {
.list,
ion-card {
button[ion-item]:not([detail-none]),
- a[ion-item]:not([detail-none]),
- [detail-push]:not(ion-item-sliding) {
+ a[ion-item]:not([detail-none]) {
@include ios-detail-push-icon($item-ios-detail-push-color);
background-repeat: no-repeat;
background-position: right ($item-ios-padding-right - 2) center;
background-size: 14px 14px;
margin-right: 32px;
}
-
- ion-item-sliding[detail-push] {
-
- ion-item-sliding-content {
- @include ios-detail-push-icon($item-ios-detail-push-color);
- background-repeat: no-repeat;
- background-position: right ($item-ios-padding-right - 2) center;
- background-size: 14px 14px;
- margin-right: 32px;
- }
- }
}
// Hairlines for iOS need to be set at 0.55px to show on iPhone 6 and 6 Plus
@@ -252,11 +219,6 @@ ion-card {
}
}
- ion-item-sliding-content {
- margin-top: 0.55px;
- margin-bottom: 0.55px;
- }
-
ion-header + .item {
border-top-width: 0.55px;
diff --git a/ionic/components/item/modes/md.scss b/ionic/components/item/modes/md.scss
index 4afc28a385..9f38858d62 100644
--- a/ionic/components/item/modes/md.scss
+++ b/ionic/components/item/modes/md.scss
@@ -207,24 +207,6 @@ ion-note {
box-shadow: none;
}
- ion-item-sliding.item {
- padding-left: 0;
- padding-right: 0;
- }
- ion-item-sliding-content {
- background-color: $item-md-sliding-content-bg;
- padding-right: ($item-md-padding-right / 2);
- padding-left: ($item-md-padding-left / 2);
- display: flex;
- min-height: 42px;
- justify-content: center;
-
- transition: $item-md-sliding-transition;
-
- // To allow the hairlines through
- margin-top: 1px;
- margin-bottom: 1px;
- }
ion-item-options {
button, [button] {
height: calc(100% - 2px);
@@ -246,15 +228,13 @@ ion-note {
.item,
a.item,
-button.item,
-.item ion-item-sliding-content {
- transition: background-color $button-md-transition-duration $button-md-animation-curve;
+button.item {
+ transition: background-color $button-md-transition-duration $button-md-animation-curve, transform 300ms;
}
.item.activated,
a.item.activated,
-button.item.activated,
-.item.activated ion-item-sliding-content {
+button.item.activated {
background-color: $item-md-activated-background-color;
box-shadow: none;
}
diff --git a/ionic/components/item/test/sliding/index.ts b/ionic/components/item/test/sliding/index.ts
index edf8bf5718..96d33e8aca 100644
--- a/ionic/components/item/test/sliding/index.ts
+++ b/ionic/components/item/test/sliding/index.ts
@@ -1,29 +1,35 @@
-import {App} from 'ionic/ionic';
+import {App, IonicApp} from 'ionic/ionic';
@App({
templateUrl: 'main.html'
})
class E2EApp {
- constructor() {
+ constructor(private app: IonicApp) {
setTimeout(() => {
this.shouldShow = true;
}, 10);
}
+ closeOpened() {
+ this.app.getComponent('myList').closeSlidingItems();
+ }
+
getItems() {
- console.log('getItems');
return [0,1];
}
- didClick(e) {
- console.log('CLICK', e.defaultPrevented, e)
+ didClick(ev, item) {
+ console.log('CLICK', ev.defaultPrevented, ev)
}
- archive(e) {
- console.log('Accept', e);
+ archive(ev, item) {
+ console.log('Archive', ev, item);
+ item.close();
}
- del(e) {
- console.log('Delete', e);
+
+ del(ev, item) {
+ console.log('Delete', ev, item);
+ item.close();
}
}
diff --git a/ionic/components/item/test/sliding/main.html b/ionic/components/item/test/sliding/main.html
index 5f13ed5840..7b3fd83541 100644
--- a/ionic/components/item/test/sliding/main.html
+++ b/ionic/components/item/test/sliding/main.html
@@ -1,77 +1,99 @@
Sliding Items
-
+
+
+
-
-
Max Lynch
-
- Hey do you want to go to the game tonight?
-
-
-
-
-
-
+
+
+
Max Lynch
+
+ Hey do you want to go to the game tonight?
+
+
+
+
+
+
+
-
-
Adam Bradley
-
- I think I figured out how to get more Mountain Dew
-
-
-
-
-
-
+
+
+
Adam Bradley
+
+ I think I figured out how to get more Mountain Dew
+
+
+
+
+
+
+
-
-
Ben Sperry
-
- I like paper
-
-
-
-
-
-
+
+
+
Ben Sperry
+
+ I like paper
+
+
+
+
+
+
+
-
-
- One Line w/ Icon, div only text
-
-
-
-
+
+
+
+ One Line w/ Icon, div only text
+
+
+
+
+
-
-
-
-
- One Line w/ Avatar, div only text
-
-
-
-
+
+
+
+
+
+ One Line w/ Avatar, div only text
+
+
+
+
+
-
-
-
-
-