mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 19:57:22 +08:00
feat(item): initial checkin of item sliding
This commit is contained in:
69
packages/core/src/components/item-sliding/item-options.tsx
Normal file
69
packages/core/src/components/item-sliding/item-options.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { Component, h, Ionic, Prop } from '@stencil/core';
|
||||
|
||||
import { isRightSide, Side } from '../../utils/util';
|
||||
|
||||
|
||||
/**
|
||||
* @name ItemOptions
|
||||
* @description
|
||||
* The option buttons for an `ion-item-sliding`. These buttons can be placed either on the left or right side.
|
||||
* You can combine the `(ionSwipe)` event plus the `expandable` directive to create a full swipe action for the item.
|
||||
*
|
||||
* @usage
|
||||
*
|
||||
* ```html
|
||||
* <ion-item-sliding>
|
||||
* <ion-item>
|
||||
* Item 1
|
||||
* </ion-item>
|
||||
* <ion-item-options side="right" (ionSwipe)="saveItem(item)">
|
||||
* <ion-button expandable (click)="saveItem(item)">
|
||||
* <ion-icon name="star"></ion-icon>
|
||||
* </ion-button>
|
||||
* </ion-item-options>
|
||||
* </ion-item-sliding>
|
||||
*```
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-item-options'
|
||||
})
|
||||
export class ItemOptions {
|
||||
$el: HTMLElement;
|
||||
|
||||
/**
|
||||
* @input {string} The side the option button should be on. Defaults to `"right"`.
|
||||
* If you have multiple `ion-item-options`, a side must be provided for each.
|
||||
*/
|
||||
@Prop() side: Side = 'right';
|
||||
|
||||
/**
|
||||
* @output {event} Emitted when the item has been fully swiped.
|
||||
*/
|
||||
// @Output() ionSwipe: EventEmitter<ItemSliding> = new EventEmitter<ItemSliding>();
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
isRightSide(): boolean {
|
||||
const isRTL = document.dir === 'rtl';
|
||||
return isRightSide(this.side, isRTL, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @output {event} Emitted when the item has been fully swiped.
|
||||
*/
|
||||
ionSwipe(itemSliding: any) {
|
||||
Ionic.emit(itemSliding, 'ionSwipe');
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
width(): number {
|
||||
return this.$el.offsetWidth;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <slot></slot>;
|
||||
}
|
||||
}
|
171
packages/core/src/components/item-sliding/item-sliding.scss
Normal file
171
packages/core/src/components/item-sliding/item-sliding.scss
Normal file
@ -0,0 +1,171 @@
|
||||
@import "../../themes/ionic.globals";
|
||||
|
||||
// Item Sliding
|
||||
// --------------------------------------------------
|
||||
// The hidden right-side buttons that can be exposed under a list item with dragging.
|
||||
|
||||
ion-item-sliding {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ion-item-sliding .item {
|
||||
position: static;
|
||||
}
|
||||
|
||||
ion-item-options {
|
||||
position: absolute;
|
||||
z-index: $z-index-item-options;
|
||||
display: none;
|
||||
|
||||
height: 100%;
|
||||
|
||||
font-size: 14px;
|
||||
visibility: hidden;
|
||||
|
||||
@include multi-dir() {
|
||||
// scss-lint:disable PropertySpelling
|
||||
top: 0;
|
||||
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@include ltr() {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@include rtl() {
|
||||
justify-content: flex-start;
|
||||
|
||||
&:not([side=right]) {
|
||||
justify-content: flex-end;
|
||||
|
||||
// scss-lint:disable PropertySpelling
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ion-item-options[side=left] {
|
||||
@include multi-dir() {
|
||||
// scss-lint:disable PropertySpelling
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
@include ltr() {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@include rtl() {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
ion-item-options .button {
|
||||
@include margin(0);
|
||||
@include padding(0, .7em);
|
||||
@include border-radius(0);
|
||||
|
||||
height: 100%;
|
||||
|
||||
box-shadow: none;
|
||||
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
ion-item-options:not([icon-left]) .button:not([icon-only]), // deprecated
|
||||
ion-item-options:not([icon-start]) .button:not([icon-only]) {
|
||||
.button-inner {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
ion-icon {
|
||||
@include padding(null, 0, .3em, 0);
|
||||
}
|
||||
}
|
||||
|
||||
ion-item-sliding.active-slide {
|
||||
@include rtl() {
|
||||
&.active-options-left ion-item-options:not([side=right]) {
|
||||
width: 100%;
|
||||
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.item,
|
||||
.item.activated {
|
||||
position: relative;
|
||||
z-index: $z-index-item-options + 1;
|
||||
|
||||
opacity: 1;
|
||||
transition: transform 500ms cubic-bezier(.36, .66, .04, 1);
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
ion-item-options {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&.active-options-left ion-item-options[side=left],
|
||||
&.active-options-right ion-item-options:not([side=left]) {
|
||||
width: 100%;
|
||||
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
// Item Expandable Animation
|
||||
// --------------------------------------------------
|
||||
|
||||
button[expandable] {
|
||||
flex-shrink: 0;
|
||||
|
||||
transition-duration: 0;
|
||||
transition-property: none;
|
||||
transition-timing-function: cubic-bezier(.65, .05, .36, 1);
|
||||
}
|
||||
|
||||
ion-item-sliding.active-swipe-right button[expandable] {
|
||||
transition-duration: .6s;
|
||||
transition-property: padding-left;
|
||||
|
||||
@include multi-dir() {
|
||||
// scss-lint:disable PropertySpelling
|
||||
padding-left: 90%;
|
||||
}
|
||||
|
||||
@include ltr() {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
@include rtl() {
|
||||
order: -1;
|
||||
}
|
||||
}
|
||||
|
||||
ion-item-sliding.active-swipe-left button[expandable] {
|
||||
transition-duration: .6s;
|
||||
transition-property: padding-right;
|
||||
|
||||
@include multi-dir() {
|
||||
// scss-lint:disable PropertySpelling
|
||||
padding-right: 90%;
|
||||
}
|
||||
|
||||
@include ltr() {
|
||||
order: -1;
|
||||
}
|
||||
|
||||
@include rtl() {
|
||||
order: 1;
|
||||
}
|
||||
}
|
521
packages/core/src/components/item-sliding/item-sliding.tsx
Normal file
521
packages/core/src/components/item-sliding/item-sliding.tsx
Normal file
@ -0,0 +1,521 @@
|
||||
import { Component, h, Ionic, State } from '@stencil/core';
|
||||
|
||||
import { GestureDetail, HostElement } from '../../utils/interfaces';
|
||||
import { swipeShouldReset } from '../../utils/util';
|
||||
|
||||
// import { ItemOptions } from './item-options';
|
||||
|
||||
const SWIPE_MARGIN = 30;
|
||||
const ELASTIC_FACTOR = 0.55;
|
||||
|
||||
const ITEM_SIDE_FLAG_NONE = 0;
|
||||
const ITEM_SIDE_FLAG_LEFT = 1 << 0;
|
||||
const ITEM_SIDE_FLAG_RIGHT = 1 << 1;
|
||||
const ITEM_SIDE_FLAG_BOTH = ITEM_SIDE_FLAG_LEFT | ITEM_SIDE_FLAG_RIGHT;
|
||||
|
||||
|
||||
const enum SlidingState {
|
||||
Disabled = 1 << 1,
|
||||
Enabled = 1 << 2,
|
||||
Right = 1 << 3,
|
||||
Left = 1 << 4,
|
||||
|
||||
SwipeRight = 1 << 5,
|
||||
SwipeLeft = 1 << 6,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @name ItemSliding
|
||||
* @description
|
||||
* A sliding item is a list item that can be swiped to reveal buttons. It requires
|
||||
* 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 `<ion-item-options>` element.
|
||||
*
|
||||
* @usage
|
||||
* ```html
|
||||
* <ion-list>
|
||||
* <ion-item-sliding #item>
|
||||
* <ion-item>
|
||||
* Item
|
||||
* </ion-item>
|
||||
* <ion-item-options side="left">
|
||||
* <ion-button (click)="favorite(item)">Favorite</ion-button>
|
||||
* <ion-button color="danger" (click)="share(item)">Share</ion-button>
|
||||
* </ion-item-options>
|
||||
*
|
||||
* <ion-item-options side="right">
|
||||
* <ion-button (click)="unread(item)">Unread</ion-button>
|
||||
* </ion-item-options>
|
||||
* </ion-item-sliding>
|
||||
* </ion-list>
|
||||
* ```
|
||||
*
|
||||
* ### 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
|
||||
* <ion-item-options side="right">
|
||||
* <ion-button (click)="archive(item)">
|
||||
* <ion-icon name="archive"></ion-icon>
|
||||
* Archive
|
||||
* </ion-button>
|
||||
* </ion-item-options>
|
||||
*
|
||||
* <ion-item-options side="left">
|
||||
* <ion-button (click)="archive(item)">
|
||||
* <ion-icon name="archive"></ion-icon>
|
||||
* Archive
|
||||
* </ion-button>
|
||||
* </ion-item-options>
|
||||
* ```
|
||||
*
|
||||
* ### 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
|
||||
* <ion-item-sliding (ionDrag)="logDrag($event)">
|
||||
* <ion-item>Item</ion-item>
|
||||
* <ion-item-options>
|
||||
* <ion-button>Favorite</ion-button>
|
||||
* </ion-item-options>
|
||||
* </ion-item-sliding>
|
||||
* ```
|
||||
*
|
||||
* ### 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-start` as an attribute on the
|
||||
* `<ion-item-options>` element.
|
||||
*
|
||||
* ```html
|
||||
* <ion-item-options icon-start>
|
||||
* <ion-button (click)="archive(item)">
|
||||
* <ion-icon name="archive"></ion-icon>
|
||||
* Archive
|
||||
* </ion-button>
|
||||
* </ion-item-options>
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* ### Expandable Options
|
||||
*
|
||||
* Options can be expanded to take up the full width of the item if you swipe past
|
||||
* a certain point. This can be combined with the `ionSwipe` event to call methods
|
||||
* on the class.
|
||||
*
|
||||
* ```html
|
||||
*
|
||||
* <ion-item-sliding (ionSwipe)="delete(item)">
|
||||
* <ion-item>Item</ion-item>
|
||||
* <ion-item-options>
|
||||
* <ion-button expandable (click)="delete(item)">Delete</ion-button>
|
||||
* </ion-item-options>
|
||||
* </ion-item-sliding>
|
||||
* ```
|
||||
*
|
||||
* We can call `delete` by either clicking the button, or by doing a full swipe on the item.
|
||||
*
|
||||
* @demo /docs/demos/src/item-sliding/
|
||||
* @see {@link /docs/components#lists List Component Docs}
|
||||
* @see {@link ../Item Item API Docs}
|
||||
* @see {@link ../../list/List List API Docs}
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-item-sliding',
|
||||
styleUrl: 'item-sliding.scss',
|
||||
// TODO REMOVE
|
||||
styleUrls: {
|
||||
ios: 'item-sliding.scss',
|
||||
md: 'item-sliding.scss',
|
||||
wp: 'item-sliding.scss'
|
||||
}
|
||||
})
|
||||
export class ItemSliding {
|
||||
$el: HTMLElement;
|
||||
item: HostElement;
|
||||
|
||||
openAmount: number = 0;
|
||||
startX: number = 0;
|
||||
optsWidthRightSide: number = 0;
|
||||
optsWidthLeftSide: number = 0;
|
||||
sides: number;
|
||||
tmr: number = null;
|
||||
|
||||
// TODO file with item sliding interfaces & item options implement
|
||||
// leftOptions: ItemOptions;
|
||||
// rightOptions: ItemOptions;
|
||||
leftOptions: any;
|
||||
rightOptions: any;
|
||||
|
||||
optsDirty: boolean = true;
|
||||
|
||||
@State() state: SlidingState = SlidingState.Disabled;
|
||||
|
||||
preSelectedContainer: ItemSliding = null;
|
||||
selectedContainer: ItemSliding = null;
|
||||
openContainer: ItemSliding = null;
|
||||
firstCoordX: number;
|
||||
firstTimestamp: number;
|
||||
|
||||
/**
|
||||
* @output {event} Emitted when the sliding position changes.
|
||||
* It reports the relative position.
|
||||
*
|
||||
* ```ts
|
||||
* onDrag(slidingItem) {
|
||||
* let percent = slidingItem.getSlidingPercent();
|
||||
* if (percent > 0) {
|
||||
* // positive
|
||||
* console.log('right side');
|
||||
* } else {
|
||||
* // negative
|
||||
* console.log('left side');
|
||||
* }
|
||||
* if (Math.abs(percent) > 1) {
|
||||
* console.log('overscroll');
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
ionDrag() {
|
||||
Ionic.emit(this, 'ionDrag');
|
||||
}
|
||||
|
||||
ionViewDidLoad() {
|
||||
const options = this.$el.querySelectorAll('ion-item-options') as NodeListOf<HostElement>;
|
||||
|
||||
let sides = 0;
|
||||
|
||||
// Reset left and right options in case they were removed
|
||||
this.leftOptions = this.rightOptions = null;
|
||||
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
let option = options[i].$instance;
|
||||
|
||||
if (option.isRightSide()) {
|
||||
this.rightOptions = option;
|
||||
sides |= ITEM_SIDE_FLAG_RIGHT;
|
||||
} else {
|
||||
this.leftOptions = option;
|
||||
sides |= ITEM_SIDE_FLAG_LEFT;
|
||||
}
|
||||
}
|
||||
this.optsDirty = true;
|
||||
this.sides = sides;
|
||||
|
||||
this.item = this.$el.querySelector('ion-item') as HostElement;
|
||||
}
|
||||
|
||||
canStart(gesture: GestureDetail): boolean {
|
||||
if (this.selectedContainer) {
|
||||
return false;
|
||||
}
|
||||
// Get swiped sliding container
|
||||
let container = this;
|
||||
|
||||
// Close open container if it is not the selected one.
|
||||
if (container !== this.openContainer) {
|
||||
this.closeOpened();
|
||||
}
|
||||
|
||||
this.preSelectedContainer = container;
|
||||
this.firstCoordX = gesture.currentX;
|
||||
this.firstTimestamp = Date.now();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
onDragStart(gesture: GestureDetail) {
|
||||
this.selectedContainer = this.openContainer = this.preSelectedContainer;
|
||||
this.selectedContainer.startSliding(gesture.currentX);
|
||||
}
|
||||
|
||||
onDragMove(gesture: GestureDetail) {
|
||||
this.selectedContainer && this.selectedContainer.moveSliding(gesture.currentX);
|
||||
}
|
||||
|
||||
onDragEnd(gesture: GestureDetail) {
|
||||
this.selectedContainer.endSliding(gesture.velocityX);
|
||||
this.selectedContainer = null;
|
||||
this.preSelectedContainer = null;
|
||||
}
|
||||
|
||||
closeOpened(): boolean {
|
||||
this.selectedContainer = null;
|
||||
|
||||
if (this.openContainer) {
|
||||
this.openContainer.close();
|
||||
this.openContainer = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
getOpenAmount(): number {
|
||||
return this.openAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
getSlidingPercent(): number {
|
||||
const openAmount = this.openAmount;
|
||||
if (openAmount > 0) {
|
||||
return openAmount / this.optsWidthRightSide;
|
||||
} else if (openAmount < 0) {
|
||||
return openAmount / this.optsWidthLeftSide;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
startSliding(startX: number) {
|
||||
if (this.tmr) {
|
||||
clearTimeout(this.tmr);
|
||||
this.tmr = null;
|
||||
}
|
||||
if (this.openAmount === 0) {
|
||||
this.optsDirty = true;
|
||||
this.setState(SlidingState.Enabled);
|
||||
}
|
||||
this.startX = startX + this.openAmount;
|
||||
this.item.style.transition = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
moveSliding(x: number): number {
|
||||
if (this.optsDirty) {
|
||||
this.calculateOptsWidth();
|
||||
return 0;
|
||||
}
|
||||
|
||||
let openAmount = (this.startX - x);
|
||||
|
||||
switch (this.sides) {
|
||||
case ITEM_SIDE_FLAG_RIGHT: openAmount = Math.max(0, openAmount); break;
|
||||
case ITEM_SIDE_FLAG_LEFT: openAmount = Math.min(0, openAmount); break;
|
||||
case ITEM_SIDE_FLAG_BOTH: break;
|
||||
case ITEM_SIDE_FLAG_NONE: return 0;
|
||||
default: console.warn('invalid ItemSideFlags value', this.sides); break;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
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
|
||||
const isResetDirection = (this.openAmount > 0) === !(velocity < 0);
|
||||
const isMovingFast = Math.abs(velocity) > 0.3;
|
||||
const isOnCloseZone = Math.abs(this.openAmount) < Math.abs(restingPoint / 2);
|
||||
if (swipeShouldReset(isResetDirection, isMovingFast, isOnCloseZone)) {
|
||||
restingPoint = 0;
|
||||
}
|
||||
|
||||
this.setOpenAmount(restingPoint, true);
|
||||
this.fireSwipeEvent();
|
||||
return restingPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
* Emit the ionSwipe event on the child options
|
||||
*/
|
||||
fireSwipeEvent() {
|
||||
if (this.state & SlidingState.SwipeRight) {
|
||||
this.rightOptions.ionSwipe(this);
|
||||
} else if (this.state & SlidingState.SwipeLeft) {
|
||||
this.leftOptions.ionSwipe(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
calculateOptsWidth() {
|
||||
if (!this.optsDirty) {
|
||||
return;
|
||||
}
|
||||
this.optsWidthRightSide = 0;
|
||||
if (this.rightOptions) {
|
||||
this.optsWidthRightSide = this.rightOptions.width();
|
||||
this.optsWidthRightSide == 0 && console.warn('optsWidthRightSide should not be zero');
|
||||
}
|
||||
|
||||
this.optsWidthLeftSide = 0;
|
||||
if (this.leftOptions) {
|
||||
this.optsWidthLeftSide = this.leftOptions.width();
|
||||
this.optsWidthLeftSide == 0 && console.warn('optsWidthLeftSide should not be zero');
|
||||
}
|
||||
this.optsDirty = false;
|
||||
}
|
||||
|
||||
setOpenAmount(openAmount: number, isFinal: boolean) {
|
||||
if (this.tmr) {
|
||||
clearTimeout(this.tmr);
|
||||
this.tmr = null;
|
||||
}
|
||||
this.openAmount = openAmount;
|
||||
|
||||
if (isFinal) {
|
||||
this.item.style.transition = '';
|
||||
|
||||
} else {
|
||||
if (openAmount > 0) {
|
||||
var state = (openAmount >= (this.optsWidthRightSide + SWIPE_MARGIN))
|
||||
? SlidingState.Right | SlidingState.SwipeRight
|
||||
: SlidingState.Right;
|
||||
|
||||
this.setState(state);
|
||||
|
||||
} else if (openAmount < 0) {
|
||||
var state = (openAmount <= (-this.optsWidthLeftSide - SWIPE_MARGIN))
|
||||
? SlidingState.Left | SlidingState.SwipeLeft
|
||||
: SlidingState.Left;
|
||||
|
||||
this.setState(state);
|
||||
}
|
||||
}
|
||||
if (openAmount === 0) {
|
||||
this.setState(SlidingState.Disabled);
|
||||
this.tmr = setTimeout(() => {
|
||||
this.tmr = null;
|
||||
this.setState(SlidingState.Disabled);
|
||||
}, 600);
|
||||
this.item.style.transform = '';
|
||||
return;
|
||||
}
|
||||
|
||||
this.item.style.transform = `translate3d(${-openAmount}px,0,0)`;
|
||||
this.ionDrag();
|
||||
}
|
||||
|
||||
private setState(state: SlidingState) {
|
||||
console.log('setState',
|
||||
this.state + '\n',
|
||||
'active-slide', (this.state !== SlidingState.Disabled),
|
||||
'active-options-right', !!(this.state & SlidingState.Right),
|
||||
'active-options-left', !!(this.state & SlidingState.Left),
|
||||
'active-swipe-right', !!(this.state & SlidingState.SwipeRight),
|
||||
'active-swipe-left', !!(this.state & SlidingState.SwipeLeft)
|
||||
);
|
||||
|
||||
if (state === this.state) {
|
||||
return;
|
||||
}
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the sliding item. Items can also be closed from the [List](../../list/List).
|
||||
*
|
||||
* The sliding item can be closed by grabbing a reference to `ItemSliding`. In the
|
||||
* below example, the template reference variable `slidingItem` is placed on the element
|
||||
* and passed to the `share` method.
|
||||
*
|
||||
* ```html
|
||||
* <ion-list>
|
||||
* <ion-item-sliding #slidingItem>
|
||||
* <ion-item>
|
||||
* Item
|
||||
* </ion-item>
|
||||
* <ion-item-options>
|
||||
* <ion-button (click)="share(slidingItem)">Share</ion-button>
|
||||
* </ion-item-options>
|
||||
* </ion-item-sliding>
|
||||
* </ion-list>
|
||||
* ```
|
||||
*
|
||||
* ```ts
|
||||
* import { Component } from '@angular/core';
|
||||
* import { ItemSliding } from 'ionic-angular';
|
||||
*
|
||||
* @Component({...})
|
||||
* export class MyClass {
|
||||
* constructor() { }
|
||||
*
|
||||
* share(slidingItem: ItemSliding) {
|
||||
* slidingItem.close();
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
close() {
|
||||
this.setOpenAmount(0, true);
|
||||
}
|
||||
|
||||
hostData() {
|
||||
console.log('hostData',
|
||||
this.state + '\n',
|
||||
'active-slide', (this.state !== SlidingState.Disabled),
|
||||
'active-options-right', !!(this.state & SlidingState.Right),
|
||||
'active-options-left', !!(this.state & SlidingState.Left),
|
||||
'active-swipe-right', !!(this.state & SlidingState.SwipeRight),
|
||||
'active-swipe-left', !!(this.state & SlidingState.SwipeLeft)
|
||||
);
|
||||
|
||||
return {
|
||||
class: {
|
||||
'item-wrapper': true,
|
||||
'active-slide': (this.state !== SlidingState.Disabled),
|
||||
'active-options-right': !!(this.state & SlidingState.Right),
|
||||
'active-options-left': !!(this.state & SlidingState.Left),
|
||||
'active-swipe-right': !!(this.state & SlidingState.SwipeRight),
|
||||
'active-swipe-left': !!(this.state & SlidingState.SwipeLeft)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
||||
<ion-gesture props={{
|
||||
'canStart': this.canStart.bind(this),
|
||||
'onStart': this.onDragStart.bind(this),
|
||||
'onMove': this.onDragMove.bind(this),
|
||||
'onEnd': this.onDragEnd.bind(this),
|
||||
'gestureName': 'item-swipe',
|
||||
'gesturePriority': -10,
|
||||
'type': 'pan',
|
||||
'direction': 'x',
|
||||
'maxAngle': 20,
|
||||
'threshold': 5,
|
||||
'attachTo': 'parent'
|
||||
}}>
|
||||
<slot></slot>
|
||||
</ion-gesture>
|
||||
);
|
||||
}
|
||||
}
|
309
packages/core/src/components/item-sliding/test/basic.html
Normal file
309
packages/core/src/components/item-sliding/test/basic.html
Normal file
@ -0,0 +1,309 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ionic Item Sliding</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<script src="/dist/ionic.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-content>
|
||||
|
||||
<div padding>
|
||||
<ion-button block onclick="toggleSliding()">Toggle sliding</ion-button>
|
||||
<ion-button block onclick="changeDynamic()">Change Dynamic Options</ion-button>
|
||||
<ion-button block onclick="closeOpened()">Close Opened Items</ion-button>
|
||||
</div>
|
||||
|
||||
<ion-list #myList>
|
||||
|
||||
<ion-item-sliding>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h2>No Options</h2>
|
||||
<p>Should not error or swipe without options</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-sliding>
|
||||
|
||||
<ion-item-sliding id="item6">
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
One Line, dynamic option and text
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item-options *ngIf="showOptions">
|
||||
<ion-button color="primary">
|
||||
<ion-icon name="more"></ion-icon>
|
||||
{{ moreText }}
|
||||
</ion-button>
|
||||
<ion-button color="secondary" onclick="archive('item6')">
|
||||
<ion-icon name="archive"></ion-icon>
|
||||
{{ archiveText }}
|
||||
</ion-button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
|
||||
<ion-item-sliding id="item6">
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
Two options, one dynamic option and text
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item-options side="left">
|
||||
<ion-button color="primary">
|
||||
<ion-icon slot="icon-only" name="more"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item-options>
|
||||
<ion-item-options side="right" *ngIf="showOptions">
|
||||
<ion-button color="primary">
|
||||
<ion-icon name="more"></ion-icon>
|
||||
{{ moreText }}
|
||||
</ion-button>
|
||||
<ion-button color="secondary" onclick="archive('item6')">
|
||||
<ion-icon name="archive"></ion-icon>
|
||||
{{ archiveText }}
|
||||
</ion-button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
|
||||
<ion-item-sliding id="item1"00>
|
||||
<ion-item href="#">
|
||||
<ion-label>
|
||||
<h2>HubStruck Notifications</h2>
|
||||
<p>A new message from a repo in your network</p>
|
||||
<p>Oceanic Next has joined your network</p>
|
||||
</ion-label>
|
||||
<ion-note slot="end">
|
||||
10:45 AM
|
||||
</ion-note>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-options side="left">
|
||||
<ion-button onclick="noclose(item100)">
|
||||
No close
|
||||
</ion-button>
|
||||
</ion-item-options>
|
||||
<ion-item-options side="right">
|
||||
<ion-button color="danger" onclick="unread('item100')">
|
||||
<ion-icon slot="icon-only" name="trash"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button onclick="unread('item100')">
|
||||
<ion-icon slot="icon-only" name="star"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item-options>
|
||||
|
||||
</ion-item-sliding>
|
||||
|
||||
<ion-item-sliding id="item0">
|
||||
<ion-item text-wrap onclick="didClick(item0)">
|
||||
<ion-label>
|
||||
<h2>RIGHT side - no icons</h2>
|
||||
<p>Hey do you want to go to the game tonight?</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item-options *ngIf="slidingEnabled">
|
||||
<ion-button color="primary" onclick="archive('item0')">Archive</ion-button>
|
||||
<ion-button color="danger" onclick="del('item0')">Delete</ion-button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
|
||||
<ion-item-sliding id="item1">
|
||||
<ion-item text-wrap detail-push href="#" class="activated">
|
||||
<ion-label>
|
||||
<h2>LEFT side - no icons</h2>
|
||||
<p>I think I figured out how to get more Mountain Dew</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item-options side="left" *ngIf="slidingEnabled">
|
||||
<ion-button color="primary" onclick="archive('item1')">Archive</ion-button>
|
||||
<ion-button color="danger" onclick="del('item1')">Delete</ion-button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
|
||||
|
||||
<ion-item-sliding id="item2">
|
||||
<ion-item text-wrap detail-push>
|
||||
<ion-label>
|
||||
<h2>RIGHT/LEFT side - icons</h2>
|
||||
<p>I think I figured out how to get more Mountain Dew</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item-options side="left" (ionSwipe)="unread($event)" *ngIf="slidingEnabled">
|
||||
<ion-button color="secondary" expandable onclick="unread('item2')">
|
||||
<ion-icon name="ios-checkmark"></ion-icon>Unread
|
||||
</ion-button>
|
||||
</ion-item-options>
|
||||
|
||||
<ion-item-options side="right" (ionSwipe)="del('item2')" *ngIf="slidingEnabled">
|
||||
<ion-button color="primary" onclick="archive('item2')">
|
||||
<ion-icon name="mail"></ion-icon>Archive
|
||||
</ion-button>
|
||||
<ion-button color="danger" onclick="del('item2')" expandable>
|
||||
<ion-icon name="trash"></ion-icon>Delete
|
||||
</ion-button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
|
||||
<ion-item-sliding id="item3">
|
||||
<ion-item text-wrap detail-push>
|
||||
<ion-label>
|
||||
<h2>RIGHT/LEFT side - icons (slot="start")</h2>
|
||||
<p>I think I figured out how to get more Mountain Dew</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item-options side="left" icon-start (ionSwipe)="unread($event)" *ngIf="slidingEnabled">
|
||||
<ion-button color="secondary" expandable onclick="unread('item3')">
|
||||
<ion-icon name="ios-checkmark"></ion-icon>Unread
|
||||
</ion-button>
|
||||
</ion-item-options>
|
||||
|
||||
<ion-item-options icon-start (ionSwipe)="del('item3')">
|
||||
<ion-button color="primary" onclick="archive('item3')">
|
||||
<ion-icon name="mail"></ion-icon>Archive
|
||||
</ion-button>
|
||||
<ion-button color="danger" onclick="del('item3')" expandable *ngIf="slidingEnabled">
|
||||
<ion-icon name="trash"></ion-icon>Delete
|
||||
</ion-button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
|
||||
|
||||
<ion-item-sliding id="item4">
|
||||
<ion-item>
|
||||
<ion-icon name="mail" slot="start"></ion-icon>
|
||||
<ion-label>
|
||||
One Line w/ Icon, div only text
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item-options icon-start (ionSwipe)="archive($event)">
|
||||
<ion-button color="primary" onclick="archive('item4')" expandable *ngIf="slidingEnabled">
|
||||
<ion-icon name="archive"></ion-icon>Archive
|
||||
</ion-button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
|
||||
|
||||
<ion-item-sliding id="item5" *ngIf="slidingEnabled">
|
||||
<ion-item>
|
||||
<ion-avatar slot="start">
|
||||
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==">
|
||||
</ion-avatar>
|
||||
<ion-label>
|
||||
One Line w/ Avatar, div only text
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item-options>
|
||||
<ion-button color="primary" expandable>
|
||||
<ion-icon name="more"></ion-icon>More
|
||||
</ion-button>
|
||||
<ion-button color="secondary" onclick="archive('item5')">
|
||||
<ion-icon name="archive"></ion-icon>Archive
|
||||
</ion-button>
|
||||
<ion-button color="danger" onclick="del('item5')">
|
||||
<ion-icon name="trash"></ion-icon>Delete
|
||||
</ion-button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
|
||||
|
||||
<ion-item-sliding id="item7">
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
One Line, dynamic icon-start option
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item-options icon-start>
|
||||
<ion-button color="primary">
|
||||
<ion-icon name="more"></ion-icon>
|
||||
{{ moreText }}
|
||||
</ion-button>
|
||||
<ion-button color="secondary" onclick="archive('item7')">
|
||||
<ion-icon name="archive"></ion-icon>
|
||||
{{ archiveText }}
|
||||
</ion-button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
|
||||
<ion-item-sliding id="item8">
|
||||
<ion-item>
|
||||
<ion-thumbnail slot="start">
|
||||
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==">
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
<h2>DOWNLOAD</h2>
|
||||
<p>Paragraph text.</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item-options (ionSwipe)="download($event)">
|
||||
<ion-button color="primary" onclick="archive('item8')">
|
||||
<ion-icon name="archive"></ion-icon>Archive
|
||||
</ion-button>
|
||||
<ion-button color="secondary" expandable onclick="download(item8)">
|
||||
<ion-icon name="download" class="download-hide"></ion-icon>
|
||||
<div class="download-hide">Download</div>
|
||||
<ion-spinner id="download-spinner"></ion-spinner>
|
||||
</ion-button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
|
||||
<ion-item-sliding id="item9">
|
||||
<ion-item>
|
||||
<ion-thumbnail slot="start">
|
||||
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==">
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
<h2>ion-item-sliding without options (no sliding)</h2>
|
||||
<p>Paragraph text.</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-sliding>
|
||||
|
||||
<ion-item text-wrap>
|
||||
<ion-label>
|
||||
<h2>Normal ion-item (no sliding)</h2>
|
||||
<p>Paragraph text.</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item text-wrap onclick="didClick(item9)">
|
||||
<ion-label>
|
||||
<h2>Normal button (no sliding)</h2>
|
||||
<p>Hey do you want to go to the game tonight?</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
</ion-list>
|
||||
|
||||
<script>
|
||||
var dynamicColor = document.getElementById('dynamicColor');
|
||||
|
||||
function unread(item) {
|
||||
console.log('unread');
|
||||
}
|
||||
|
||||
function archive(item) {
|
||||
console.log('archive');
|
||||
}
|
||||
|
||||
function del(item) {
|
||||
console.log('del');
|
||||
}
|
||||
|
||||
function toggleSliding() {
|
||||
|
||||
}
|
||||
|
||||
function changeDynamic() {
|
||||
|
||||
}
|
||||
|
||||
function closeOpened() {
|
||||
|
||||
}
|
||||
</script>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
@ -17,6 +17,7 @@ export class Item {
|
||||
|
||||
@Prop() mode: string;
|
||||
@Prop() color: string;
|
||||
@Prop() href: string;
|
||||
|
||||
@Listen('ionStyle')
|
||||
itemStyle(ev: UIEvent) {
|
||||
@ -51,8 +52,11 @@ export class Item {
|
||||
'item-block': true
|
||||
};
|
||||
|
||||
// TODO add support for button items
|
||||
const TagType = this.href ? 'a' : 'div';
|
||||
|
||||
return (
|
||||
<div class={themedClasses}>
|
||||
<TagType class={themedClasses}>
|
||||
<slot name='start'></slot>
|
||||
<div class='item-inner'>
|
||||
<div class='input-wrapper'>
|
||||
@ -60,7 +64,7 @@ export class Item {
|
||||
</div>
|
||||
<slot name='end'></slot>
|
||||
</div>
|
||||
</div>
|
||||
</TagType>
|
||||
);
|
||||
|
||||
// template:
|
||||
|
@ -10,7 +10,7 @@ exports.config = {
|
||||
{ components: ['ion-card', 'ion-card-content', 'ion-card-header', 'ion-card-title'] },
|
||||
{ components: ['ion-fab', 'ion-fab-button', 'ion-fab-list'] },
|
||||
{ components: ['ion-gesture', 'ion-scroll'], priority: 'low' },
|
||||
{ components: ['ion-item', 'ion-item-divider', 'ion-label', 'ion-list', 'ion-list-header', 'ion-skeleton-text'] },
|
||||
{ components: ['ion-item', 'ion-item-divider', 'ion-item-sliding', 'ion-item-options', 'ion-label', 'ion-list', 'ion-list-header', 'ion-skeleton-text'] },
|
||||
{ components: ['ion-loading', 'ion-loading-controller'] },
|
||||
{ components: ['ion-menu'], priority: 'low' },
|
||||
{ components: ['ion-modal', 'ion-modal-controller'] },
|
||||
|
Reference in New Issue
Block a user