From c28aa53478771f2ffcab191a049adbc9d777bd54 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Sat, 28 May 2016 12:16:20 +0200 Subject: [PATCH] feat(item): two-way sliding of items closes #5073 --- src/components/item/item-sliding-gesture.ts | 270 ++++------------ src/components/item/item-sliding.scss | 62 +++- src/components/item/item-sliding.ts | 324 +++++++++++++++++++- src/components/item/item.ts | 7 + src/components/item/test/sliding/index.ts | 32 +- src/components/item/test/sliding/main.html | 144 ++++++--- src/components/list/list.ts | 32 +- src/config/directives.ts | 3 +- 8 files changed, 588 insertions(+), 286 deletions(-) diff --git a/src/components/item/item-sliding-gesture.ts b/src/components/item/item-sliding-gesture.ts index cf0b8e2e649..8d150c3fc1e 100644 --- a/src/components/item/item-sliding-gesture.ts +++ b/src/components/item/item-sliding-gesture.ts @@ -1,221 +1,103 @@ -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; +const MAX_ATTACK_ANGLE = 20; export class ItemSlidingGesture extends DragGesture { - canDrag: boolean = true; - data = {}; - openItems: number = 0; onTap: any; - onMouseOut: any; - preventDrag: boolean = false; - dragEnded: boolean = true; + selectedContainer: ItemSliding = null; + openContainer: ItemSliding = null; constructor(public list: List, public listEle: HTMLElement) { super(listEle, { direction: 'x', threshold: DRAG_THRESHOLD }); - this.listen(); + } - this.onTap = (ev: UIEvent) => { - if (!isFromOptionButtons(ev.target)) { - let didClose = this.closeOpened(); - if (didClose) { - console.debug('tap close sliding item'); - preventDefault(ev); - } - } - }; - - this.onMouseOut = (ev: any) => { - if (ev.target.tagName === 'ION-ITEM-SLIDING') { - console.debug('tap close sliding item'); - this.onDragEnd(ev); - } - }; + onTapCallback(ev: any) { + if (isFromOptionButtons(ev.target)) { + return; + } + let didClose = this.closeOpened(); + if (didClose) { + console.debug('tap close sliding item, preventDefault'); + ev.preventDefault(); + } } onDragStart(ev: any): boolean { - let itemContainerEle = getItemContainer(ev.target); - if (!itemContainerEle) { - console.debug('onDragStart, no itemContainerEle'); + let angle = Math.abs(ev.angle); + if (angle > MAX_ATTACK_ANGLE && Math.abs(angle - 180) > MAX_ATTACK_ANGLE) { + this.closeOpened(); return false; } - this.closeOpened(itemContainerEle); + if (this.selectedContainer) { + console.debug('onDragStart, another container is already selected'); + return false; + } - let openAmout = this.getOpenAmount(itemContainerEle); - let itemData = this.get(itemContainerEle); - this.preventDrag = (openAmout > 0); + let container = getContainer(ev); + if (!container) { + console.debug('onDragStart, no itemContainerEle'); + return false; + } - if (this.preventDrag) { + // Close open container if it is not the selected one. + if (container !== this.openContainer) { this.closeOpened(); - console.debug('onDragStart, preventDefault'); - preventDefault(ev); - return; } - itemContainerEle.classList.add('active-slide'); - - this.set(itemContainerEle, 'offsetX', openAmout); - this.set(itemContainerEle, 'startX', ev.center[this.direction]); - - this.dragEnded = false; + // Close all item sliding containers but the selected one + this.selectedContainer = container; + this.openContainer = container; + container.startSliding(ev.center.x); return true; } onDrag(ev: any): boolean { - if (this.dragEnded || this.preventDrag || Math.abs(ev.deltaY) > 30) { - console.debug('onDrag preventDrag, dragEnded:', this.dragEnded, 'preventDrag:', this.preventDrag, 'ev.deltaY:', Math.abs(ev.deltaY)); - this.preventDrag = true; - return; - } - - let itemContainerEle = getItemContainer(ev.target); - if (!itemContainerEle || !isActive(itemContainerEle)) { - 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; - } + if (this.selectedContainer) { + this.selectedContainer.moveSliding(ev.center.x); + ev.preventDefault(); } - - 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); - } - }); + return; } onDragEnd(ev: any) { - this.preventDrag = false; - this.dragEnded = true; - - let itemContainerEle = getItemContainer(ev.target); - if (!itemContainerEle || !isActive(itemContainerEle)) { - 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; - } - } - - itemContainerEle.removeEventListener('mouseout', this.onMouseOut); - itemData.hasMouseOut = false; - - nativeRaf(() => { - this.open(itemContainerEle, restingPoint, true); - }); - } - - closeOpened(doNotCloseEle?: HTMLElement) { - 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: any, openAmount: number, isFinal: boolean) { - 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 { + if (this.selectedContainer) { + let openAmount = this.selectedContainer.endSliding(ev.velocityX); + this.selectedContainer = null; + + // TODO: I am not sure listening for a tap event is the best idea + // we should try mousedown/touchstart + if (openAmount === 0) { + this.openContainer = null; this.off('tap', this.onTap); + this.onTap = null; + } else if (!this.onTap) { + this.onTap = (event: any) => this.onTapCallback(event); + this.on('tap', this.onTap); } } } - getOpenAmount(itemContainerEle: any) { - return this.get(itemContainerEle).openAmount || 0; - } - - get(itemContainerEle: any) { - return this.data[itemContainerEle && itemContainerEle.$ionSlide] || {}; - } - - set(itemContainerEle: any, key: any, value: any) { - if (!this.data[itemContainerEle.$ionSlide]) { - this.data[itemContainerEle.$ionSlide] = {}; + closeOpened(): boolean { + if (this.openContainer) { + this.openContainer.close(); + this.openContainer = null; + this.selectedContainer = null; + this.off('tap', this.onTap); + this.onTap = null; + return true; } - this.data[itemContainerEle.$ionSlide][key] = value; + return false; } unlisten() { @@ -224,34 +106,14 @@ export class ItemSlidingGesture extends DragGesture { } } -function isItemActive(ele: any, isActive: boolean) { - ele.classList[isActive ? 'add' : 'remove']('active-slide'); - ele.classList[isActive ? 'add' : 'remove']('active-options'); -} - -function preventDefault(ev: any) { - console.debug('sliding item preventDefault', ev.type); - ev.preventDefault(); -} - -function getItemContainer(ele: any) { - return closest(ele, 'ion-item-sliding', true); -} - -function isFromOptionButtons(ele: any) { - return !!closest(ele, 'ion-item-options', true); -} - -function getOptionsWidth(itemContainerEle: any) { - let optsEle = itemContainerEle.querySelector('ion-item-options'); - if (optsEle) { - return optsEle.offsetWidth; +function getContainer(ev: any): ItemSliding { + let ele = closest(ev.target, 'ion-item-sliding', true); + if (ele) { + return ele['$ionComponent']; } + return null; } -function isActive(itemContainerEle: any) { - return itemContainerEle.classList.contains('active-slide'); +function isFromOptionButtons(ele: HTMLElement): boolean { + return !!closest(ele, 'ion-item-options', true); } - - -const DRAG_THRESHOLD = 20; diff --git a/src/components/item/item-sliding.scss b/src/components/item/item-sliding.scss index 93d0b41d5a1..1893e923446 100644 --- a/src/components/item/item-sliding.scss +++ b/src/components/item/item-sliding.scss @@ -4,7 +4,6 @@ // -------------------------------------------------- // The hidden right-side buttons that can be exposed under a list item with dragging. - ion-item-sliding { position: relative; display: block; @@ -22,23 +21,35 @@ ion-item-options { z-index: $z-index-item-options; display: none; + justify-content: flex-end; + height: 100%; + font-size: 14px; + visibility: hidden; } +ion-item-options[side=left] { + right: auto; + left: 0; + + justify-content: flex-start; +} + ion-item-options .button { margin: 0; + padding: 0 .7em; height: 100%; border-radius: 0; box-shadow: none; + + box-sizing: content-box; } ion-item-options:not([icon-left]) .button-icon-left { - font-size: 14px; - .button-inner { flex-direction: column; } @@ -60,15 +71,54 @@ ion-item-sliding.active-slide { opacity: 1; transition: all 300ms cubic-bezier(.36, .66, .04, 1); - pointer-events: none; - + pointer-events: all; } 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; } } + +// Item Swipeable Animation +// -------------------------------------------------- + +button[swipeable] { + flex-shrink: 0; + + transition-duration: 0; + transition-property: none; + transition-timing-function: cubic-bezier(.65, .05, .36, 1); +} + +ion-item-sliding.active-swipe-right, +ion-item-sliding.active-swipe-left { + ion-item-options { + width: 100%; + } +} + +ion-item-sliding.active-swipe-right button[swipeable] { + order: 1; + + padding-left: 90%; + + transition-duration: .6s; + transition-property: padding-left; +} + +ion-item-sliding.active-swipe-left button[swipeable] { + order: -1; + + padding-right: 90%; + + transition-duration: .6s; + transition-property: padding-right; +} diff --git a/src/components/item/item-sliding.ts b/src/components/item/item-sliding.ts index 647f2b75285..44b1e6824f4 100644 --- a/src/components/item/item-sliding.ts +++ b/src/components/item/item-sliding.ts @@ -1,6 +1,61 @@ -import {Component, ElementRef, Optional, ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ContentChildren, ContentChild, Directive, ElementRef, EventEmitter, HostBinding, Input, Optional, Output, QueryList, Renderer, ViewEncapsulation} from '@angular/core'; import {List} from '../list/list'; +import {Ion} from '../ion'; +import {Item} from './item'; +import {isPresent} from '../../util/util'; +import {CSS} from '../../util/dom'; + +const SWIPE_FACTOR = 1.1; +const ELASTIC_FACTOR = 0.55; + +export const enum SideFlags { + None = 0, + Left = 1 << 0, + Right = 1 << 1, + Both = Left | Right +} + +/** + * @private + */ +@Directive({ + selector: 'ion-item-options', +}) +export class ItemOptions extends Ion { + @Input() side: string; + @Output() ionSwipe: EventEmitter = new EventEmitter(); + + constructor(elementRef: ElementRef, private _renderer: Renderer) { + super(elementRef); + } + + /** + * @private + */ + setCssStyle(property: string, value: string) { + this._renderer.setElementStyle(this.elementRef.nativeElement, property, value); + } + + /** + * @private + */ + getSides(): SideFlags { + if (isPresent(this.side) && this.side === 'left') { + return SideFlags.Left; + } else { + return SideFlags.Right; + } + } + +} + +const enum SlidingState { + Disabled = 0, + Enabled = 1, + Right = 2, + Left = 3 +} /** @@ -11,14 +66,42 @@ import {List} from '../list/list'; * 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 `` element. * - * ### Button Layout - * If an icon is placed with text in the option button, by default it will - * display the icon on top of the text. This can be changed to display the icon - * to the left of the text by setting `icon-left` as an attribute on the - * `` element. + * ### 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 + * in the right side (sliding from left to right) by setting the `side` attribute + * on the `ion-item-options` element. Up to 2 `ion-item-options` can used at the same time + * in order to reveal two different sets of buttons depending the swipping direction. * * ```html - * + * + * + * + + * + * + * + * ``` + * + * ### Listening for events (ionDrag) and (ionSwipe) + * It's possible to know the current relative position of the sliding item by subscribing + * to the (ionDrag)` event. + * + * ```html + * + * + * + + * * + * + * + * ``` * * @usage * ```html * - * + * * * Item * @@ -38,6 +135,10 @@ import {List} from '../list/list'; * * * + + * + * + * * * * ``` @@ -53,13 +154,210 @@ import {List} from '../list/list'; '' + '', changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, + encapsulation: ViewEncapsulation.None }) export class ItemSliding { + private _openAmount: number = 0; + private _startX: number = 0; + private _optsWidthRightSide: number = 0; + private _optsWidthLeftSide: number = 0; + private _sides: SideFlags; + private _timer: number = null; + private _leftOptions: ItemOptions; + private _rightOptions: ItemOptions; + private _optsDirty: boolean = true; + private _state: SlidingState = SlidingState.Disabled; + slidingPercent: number = 0; - constructor(@Optional() private _list: List, elementRef: ElementRef) { + @ContentChild(Item) private item: Item; + + + /** + * @output {event} Expression to evaluate when the sliding position changes. + * It reports the relative position. + * + * ```ts + * ondrag(percent) { + * if (percent > 0) { + * // positive + * console.log('right side'); + * } else { + * // negative + * console.log('left side'); + * } + * if (Math.abs(percent) > 1) { + * console.log('overscroll'); + * } + * } + * ``` + * + */ + @Output() ionDrag: EventEmitter = new EventEmitter(); + + constructor(@Optional() private _list: List, private _renderer: Renderer, private _elementRef: ElementRef) { _list.enableSlidingItems(true); - elementRef.nativeElement.$ionSlide = ++slideIds; + _elementRef.nativeElement.$ionComponent = this; + } + + + /** + * @private + */ + @ContentChildren(ItemOptions) + set _itemOptions(itemOptions: QueryList) { + let sides = 0; + for (var item of itemOptions.toArray()) { + var side = item.getSides(); + if (side === SideFlags.Left) { + this._leftOptions = item; + } else { + this._rightOptions = item; + } + sides |= item.getSides(); + } + this._optsDirty = true; + this._sides = sides; + } + + /** + * @private + */ + startSliding(startX: number) { + if (this._openAmount === 0) { + this._optsDirty = true; + this._setState(SlidingState.Enabled); + } + this._startX = startX + this._openAmount; + this.item.setCssStyle(CSS.transition, 'none'); + } + + /** + * @private + */ + moveSliding(x: number): number { + this.calculateOptsWidth(); + + let openAmount = this._startX - x; + switch (this._sides) { + case SideFlags.Right: openAmount = Math.max(0, openAmount); break; + case SideFlags.Left: openAmount = Math.min(0, openAmount); break; + case SideFlags.Both: break; + default: return; + } + + if (openAmount > this._optsWidthRightSide) { + var optsWidth = this._optsWidthRightSide; + openAmount = optsWidth + (openAmount - optsWidth) * ELASTIC_FACTOR; + + } else if (openAmount < -this._optsWidthLeftSide) { + var optsWidth = -this._optsWidthLeftSide; + openAmount = optsWidth + (openAmount - optsWidth) * ELASTIC_FACTOR; + } + + this._setOpenAmount(openAmount, false); + return openAmount; + } + + /** + * @private + */ + endSliding(velocity: number): number { + let restingPoint = (this._openAmount > 0) + ? this._optsWidthRightSide + : -this._optsWidthLeftSide; + + // Check if the drag didn't clear the buttons mid-point + // and we aren't moving fast enough to swipe open + let isOnResetZone = Math.abs(this._openAmount) < Math.abs(restingPoint / 2); + let isMovingSlow = Math.abs(velocity) < 0.3; + let isDirection = (this._openAmount > 0) === (velocity > 0); + if (isOnResetZone && (isMovingSlow || isDirection)) { + restingPoint = 0; + } + + this.fireSwipeEvent(); + this._setOpenAmount(restingPoint, true); + return restingPoint; + } + + fireSwipeEvent() { + if (this.slidingPercent > SWIPE_FACTOR) { + this._rightOptions.ionSwipe.emit(this); + } else if (this.slidingPercent < -SWIPE_FACTOR) { + this._leftOptions.ionSwipe.emit(this); + } + } + + calculateOptsWidth() { + if (this._optsDirty) { + if (this._rightOptions) { + this._optsWidthRightSide = this._rightOptions.width(); + } + if (this._leftOptions) { + this._optsWidthLeftSide = this._leftOptions.width(); + } + this._optsDirty = false; + } + } + + /** + * @private + */ + private _setOpenAmount(openAmount: number, isFinal: boolean) { + if (this._timer) { + clearTimeout(this._timer); + this._timer = null; + } + this._openAmount = openAmount; + + let didEnd = openAmount === 0; + if (didEnd) { + // TODO: refactor. there must exist a better way + // if sliding ended, we wait 400ms until animation finishes + this._timer = setTimeout(() => { + this._setState(SlidingState.Disabled); + this._timer = null; + }, 400); + this.slidingPercent = 0; + + } else if (openAmount > 0) { + this._setState(SlidingState.Right); + this.slidingPercent = openAmount / this._optsWidthRightSide; + } else if (openAmount < 0) { + this._setState(SlidingState.Left); + this.slidingPercent = openAmount / this._optsWidthLeftSide; + } + if (!isFinal) { + this.setClass('active-swipe-right', this.slidingPercent > SWIPE_FACTOR); + this.setClass('active-swipe-left', this.slidingPercent < -SWIPE_FACTOR); + } else { + this.item.setCssStyle(CSS.transition, ''); + } + + this.ionDrag.emit(this.slidingPercent); + this.item.setCssStyle(CSS.transform, (didEnd ? '' : 'translate3d(' + -openAmount + 'px,0,0)')); + } + + private _setState(state: SlidingState) { + if (state !== this._state) { + this._state = state; + this.setClass('active-slide', state !== SlidingState.Disabled); + this.setClass('active-options-right', state === SlidingState.Right); + this.setClass('active-options-left', state === SlidingState.Left); + } + } + + /** + * @private + */ + setClass(className: string, add: boolean) { + this._renderer.setElementClass(this._elementRef.nativeElement, className, add); + } + /** + * @private + */ + getOpenAmount(): number { + return this._openAmount; } /** @@ -97,9 +395,7 @@ export class ItemSliding { * ``` */ close() { - this._list.closeSlidingItems(); + this._setOpenAmount(0, true); } } - -let slideIds = 0; diff --git a/src/components/item/item.ts b/src/components/item/item.ts index 53c8643931c..254d66abbc5 100644 --- a/src/components/item/item.ts +++ b/src/components/item/item.ts @@ -108,6 +108,13 @@ export class Item { this._renderer.setElementClass(this._elementRef.nativeElement, cssClass, shouldAdd); } + /** + * @private + */ + setCssStyle(property: string, value: string) { + this._renderer.setElementStyle(this._elementRef.nativeElement, property, value); + } + /** * @private */ diff --git a/src/components/item/test/sliding/index.ts b/src/components/item/test/sliding/index.ts index d9571ea7dbe..aa677228a12 100644 --- a/src/components/item/test/sliding/index.ts +++ b/src/components/item/test/sliding/index.ts @@ -1,5 +1,5 @@ import {Component, ViewChild} from '@angular/core'; -import {ionicBootstrap, App, Alert, NavController, List, ItemSliding} from '../../../../../src'; +import {ionicBootstrap, App, Alert, NavController, List, ItemSliding, Toast} from '../../../../../src'; @Component({ @@ -8,7 +8,7 @@ import {ionicBootstrap, App, Alert, NavController, List, ItemSliding} from '../. class E2EPage { @ViewChild('myList', {read: List}) list: List; - items = []; + items: number[] = []; shouldShow: boolean = true; constructor(private app: App, private nav: NavController) { @@ -21,7 +21,14 @@ class E2EPage { this.list.closeSlidingItems(); } - didClick(item) { + unread(item: ItemSliding) { + if (item) { + item.close(); + } + console.log('UNREAD', item); + } + + didClick(item: ItemSliding) { console.log('Clicked, ion-item'); let alert = Alert.create({ @@ -31,7 +38,7 @@ class E2EPage { this.nav.present(alert); } - archive(item) { + archive(item: ItemSliding) { console.log('Archive, ion-item-options button', item); let alert = Alert.create({ @@ -46,7 +53,7 @@ class E2EPage { this.nav.present(alert); } - del(item) { + del(item: ItemSliding) { console.log('Delete ion-item-options button', item); let alert = Alert.create({ @@ -61,6 +68,21 @@ class E2EPage { this.nav.present(alert); } + download(item: ItemSliding) { + item.setClass('downloading', true); + setTimeout(() => { + const toast = Toast.create({ + message: 'Item was downloaded!' + }); + this.nav.present(toast); + item.setClass('downloading', false); + item.close(); + setTimeout(() => { + toast.dismiss(); + }, 2000); + }, 1500); + } + reload() { window.location.reload(); } diff --git a/src/components/item/test/sliding/main.html b/src/components/item/test/sliding/main.html index 15495fc7c3a..e68549341e9 100644 --- a/src/components/item/test/sliding/main.html +++ b/src/components/item/test/sliding/main.html @@ -1,3 +1,4 @@ + Sliding Items @@ -11,10 +12,8 @@ @@ -22,51 +21,76 @@

Max Lynch

- + -

Adam Bradley

-

- I think I figured out how to get more Mountain Dew -

+

LEFT side - no icons

+

I think I figured out how to get more Mountain Dew

- - + + +
+ + + + +

RIGHT/LEFT side - icons

+

I think I figured out how to get more Mountain Dew

+
+ + + + + + -
- + + -

Ben Sperry

-

- I like paper -

+

RIGHT/LEFT side - icons (item-left)

+

I think I figured out how to get more Mountain Dew

- - - + + + + + + +
- + + One Line w/ Icon, div only text - - - + + @@ -74,45 +98,76 @@

Ben Sperry

One Line w/ Avatar, div only text
- - -
- + -

Two Lines w/ Thumbnail, H2 Header

+

DOWNLOAD

Paragraph text.

- - + + +
- + + + + + +

ion-item-sliding without options (no sliding)

+

Paragraph text.

+
+
+ + +

Normal ion-item (no sliding)

+

Paragraph text.

+
+ + + + + + + +

ng-for {{data}}

- +

- +

@@ -121,4 +176,17 @@

ng-for {{data}}

img { height: 100px; } + #download-spinner { + display: none; + } + + svg circle { + stroke: white; + } + .downloading #download-spinner { + display: block; + } + .downloading .download-hide { + display: none; + } diff --git a/src/components/list/list.ts b/src/components/list/list.ts index dbd44a3fb78..2abc477af6a 100644 --- a/src/components/list/list.ts +++ b/src/components/list/list.ts @@ -69,20 +69,19 @@ export class List extends Ion { * @param {boolean} shouldEnable whether the item-sliding should be enabled or not */ enableSlidingItems(shouldEnable: boolean) { - if (this._enableSliding !== shouldEnable) { - this._enableSliding = shouldEnable; - - if (shouldEnable) { - console.debug('enableSlidingItems'); - this._zone.runOutsideAngular(() => { - setTimeout(() => { - this.slidingGesture = new ItemSlidingGesture(this, this.ele); - }); - }); - - } else { - this.slidingGesture && this.slidingGesture.unlisten(); - } + if (this._enableSliding === shouldEnable) { + return; + } + + this._enableSliding = shouldEnable; + if (shouldEnable) { + console.debug('enableSlidingItems'); + this._zone.runOutsideAngular(() => { + setTimeout(() => this.slidingGesture = new ItemSlidingGesture(this, this.ele)); + }); + + } else { + this.slidingGesture && this.slidingGesture.unlisten(); } } @@ -119,11 +118,8 @@ export class List extends Ion { selector: 'ion-list-header' }) export class ListHeader { - private _id: string; - constructor(private _renderer: Renderer, private _elementRef: ElementRef, @Attribute('id') id: string) { - this._id = id; - } + constructor(private _renderer: Renderer, private _elementRef: ElementRef, @Attribute('id') private _id: string) { } public get id(): string { return this._id; diff --git a/src/config/directives.ts b/src/config/directives.ts index f069ec469bf..eaa2091cdd7 100644 --- a/src/config/directives.ts +++ b/src/config/directives.ts @@ -19,7 +19,7 @@ import {Tabs} from '../components/tabs/tabs'; import {Tab} from '../components/tabs/tab'; import {List, ListHeader} from '../components/list/list'; import {Item} from '../components/item/item'; -import {ItemSliding} from '../components/item/item-sliding'; +import {ItemSliding, ItemOptions} from '../components/item/item-sliding'; import {VirtualScroll} from '../components/virtual-scroll/virtual-scroll'; import {VirtualItem, VirtualHeader, VirtualFooter} from '../components/virtual-scroll/virtual-item'; import {Toolbar, ToolbarTitle, ToolbarItem} from '../components/toolbar/toolbar'; @@ -137,6 +137,7 @@ export const IONIC_DIRECTIVES: any[] = [ ListHeader, Item, ItemSliding, + ItemOptions, VirtualScroll, VirtualItem, VirtualHeader,