mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-22 13:32:54 +08:00
fix(sliding-item): finish it
This commit is contained in:
6
packages/core/src/components.d.ts
vendored
6
packages/core/src/components.d.ts
vendored
@ -1222,6 +1222,7 @@ declare global {
|
|||||||
|
|
||||||
isRightSide?: any,
|
isRightSide?: any,
|
||||||
width?: any,
|
width?: any,
|
||||||
|
fireSwipeEvent?: any,
|
||||||
side?: string
|
side?: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1252,7 +1253,10 @@ declare global {
|
|||||||
mode?: string,
|
mode?: string,
|
||||||
color?: string,
|
color?: string,
|
||||||
|
|
||||||
close?: any
|
getOpenAmount?: any,
|
||||||
|
getSlidingPercent?: any,
|
||||||
|
close?: any,
|
||||||
|
closeOpened?: any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import { Component, Prop } from '@stencil/core';
|
import { Component, Prop } from '@stencil/core';
|
||||||
import { createThemedClasses } from '../../utils/theme';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name ItemOption
|
* @name ItemOption
|
||||||
@ -10,7 +8,10 @@ import { createThemedClasses } from '../../utils/theme';
|
|||||||
* action for the item.
|
* action for the item.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
tag: 'ion-item-option'
|
tag: 'ion-item-option',
|
||||||
|
host: {
|
||||||
|
theme: 'item-option'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
export class ItemOption {
|
export class ItemOption {
|
||||||
mode: string;
|
mode: string;
|
||||||
@ -35,18 +36,14 @@ export class ItemOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const themedClasses = createThemedClasses(this.mode, this.color, 'item-option-button');
|
|
||||||
|
|
||||||
const TagType = this.href ? 'a' : 'button';
|
const TagType = this.href ? 'a' : 'button';
|
||||||
|
return [
|
||||||
return (
|
<TagType class='item-option-button' onClick={this.clickedOptionButton.bind(this)} disabled={this.disabled}></TagType>,
|
||||||
<TagType class={themedClasses} onClick={this.clickedOptionButton.bind(this)} disabled={this.disabled}>
|
<span class='button-inner'>
|
||||||
<span class='button-inner'>
|
<slot></slot>
|
||||||
<slot></slot>
|
</span>
|
||||||
</span>
|
];
|
||||||
<div class='button-effect'></div>
|
|
||||||
</TagType>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,15 +40,21 @@ export class ItemOptions {
|
|||||||
*/
|
*/
|
||||||
@Event() ionSwipe: EventEmitter;
|
@Event() ionSwipe: EventEmitter;
|
||||||
|
|
||||||
@Method() isRightSide() {
|
@Method()
|
||||||
const isRTL = document.dir === 'rtl';
|
isRightSide() {
|
||||||
return isRightSide(this.side, isRTL, true);
|
return isRightSide(this.side, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Method() width(): number {
|
@Method()
|
||||||
|
width(): number {
|
||||||
return this.el.offsetWidth;
|
return this.el.offsetWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
fireSwipeEvent(value: any) {
|
||||||
|
this.ionSwipe.emit(value);
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return <slot></slot>;
|
return <slot></slot>;
|
||||||
}
|
}
|
||||||
|
@ -29,13 +29,13 @@ $item-ios-sliding-button-icon-color: color-contrast($colors-ios, $item-i
|
|||||||
border-bottom: $hairlines-width solid $list-ios-border-color;
|
border-bottom: $hairlines-width solid $list-ios-border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-option-button-ios {
|
.item-option-ios {
|
||||||
font-size: $item-ios-sliding-button-font-size;
|
font-size: $item-ios-sliding-button-font-size;
|
||||||
color: $item-ios-sliding-button-text-color;
|
color: $item-ios-sliding-button-text-color;
|
||||||
background-color: $item-ios-sliding-button-background-color;
|
background-color: $item-ios-sliding-button-background-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-option-button-ios .icon {
|
.item-option-ios .icon {
|
||||||
fill: $item-ios-sliding-button-icon-color;
|
fill: $item-ios-sliding-button-icon-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,12 +53,12 @@ $item-ios-sliding-button-icon-color: color-contrast($colors-ios, $item-i
|
|||||||
|
|
||||||
@each $color-name, $color-base, $color-contrast in get-colors($colors-ios) {
|
@each $color-name, $color-base, $color-contrast in get-colors($colors-ios) {
|
||||||
|
|
||||||
.item-option-button-ios-#{$color-name} {
|
.item-option-ios-#{$color-name} {
|
||||||
color: $color-contrast;
|
color: $color-contrast;
|
||||||
background-color: $color-base;
|
background-color: $color-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-option-button-ios-#{$color-name} .icon {
|
.item-option-ios-#{$color-name} .icon {
|
||||||
fill: $color-contrast;
|
fill: $color-contrast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,13 +29,13 @@ $item-md-sliding-button-icon-color: color-contrast($colors-md, $item-md
|
|||||||
border-bottom: 1px solid $list-md-border-color;
|
border-bottom: 1px solid $list-md-border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-option-button-md {
|
.item-option-md {
|
||||||
font-size: $item-md-sliding-button-font-size;
|
font-size: $item-md-sliding-button-font-size;
|
||||||
color: $item-md-sliding-button-text-color;
|
color: $item-md-sliding-button-text-color;
|
||||||
background-color: $item-md-sliding-button-background-color;
|
background-color: $item-md-sliding-button-background-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-option-button-md .icon {
|
.item-option-md .icon {
|
||||||
fill: $item-md-sliding-button-icon-color;
|
fill: $item-md-sliding-button-icon-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,12 +52,12 @@ $item-md-sliding-button-icon-color: color-contrast($colors-md, $item-md
|
|||||||
|
|
||||||
@each $color-name, $color-base, $color-contrast in get-colors($colors-md) {
|
@each $color-name, $color-base, $color-contrast in get-colors($colors-md) {
|
||||||
|
|
||||||
.item-option-button-md-#{$color-name} {
|
.item-option-md-#{$color-name} {
|
||||||
color: $color-contrast;
|
color: $color-contrast;
|
||||||
background-color: $color-base;
|
background-color: $color-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-option-button-md-#{$color-name} .icon {
|
.item-option-md-#{$color-name} .icon {
|
||||||
fill: $color-contrast;
|
fill: $color-contrast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
// Item Sliding
|
// Item Sliding
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// The hidden buttons that can be exposed under a list item by dragging
|
// The hidden right-side buttons that can be exposed under a list item with dragging.
|
||||||
|
|
||||||
ion-item-sliding {
|
ion-item-sliding {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -12,10 +12,6 @@ ion-item-sliding {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-item-sliding ion-item {
|
|
||||||
position: static;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-item-options {
|
ion-item-options {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: $z-index-item-options;
|
z-index: $z-index-item-options;
|
||||||
@ -66,29 +62,31 @@ ion-item-options[side=left] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-option-button {
|
|
||||||
@include margin(0);
|
|
||||||
@include border-radius(0);
|
|
||||||
|
|
||||||
|
ion-item-option {
|
||||||
@include padding(0, .7em);
|
@include padding(0, .7em);
|
||||||
|
|
||||||
display: inline-flex;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
height: 100%;
|
min-width: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-option-button {
|
||||||
|
@include position(0, 0, 0, 0);
|
||||||
|
@include margin(0);
|
||||||
|
@include padding(0);
|
||||||
|
@include border-radius(0);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
border: 0;
|
border: 0;
|
||||||
box-shadow: none;
|
background: none;
|
||||||
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-option-button::before {
|
ion-item-options:not([icon-start]) ion-item-option:not([icon-only]) {
|
||||||
@include margin(0, auto);
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-item-options:not([icon-start]) .item-option-button:not([icon-only]) {
|
|
||||||
.button-inner {
|
.button-inner {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@ -131,7 +129,7 @@ ion-item-sliding.active-slide {
|
|||||||
// Item Expandable Animation
|
// Item Expandable Animation
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
ion-item-option[expandable] .item-option-button {
|
ion-item-option[expandable] {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
transition-duration: 0;
|
transition-duration: 0;
|
||||||
@ -139,7 +137,7 @@ ion-item-option[expandable] .item-option-button {
|
|||||||
transition-timing-function: cubic-bezier(.65, .05, .36, 1);
|
transition-timing-function: cubic-bezier(.65, .05, .36, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-item-sliding.active-swipe-right ion-item-option[expandable] .item-option-button {
|
ion-item-sliding.active-swipe-right ion-item-option[expandable] {
|
||||||
transition-duration: .6s;
|
transition-duration: .6s;
|
||||||
transition-property: padding-left;
|
transition-property: padding-left;
|
||||||
|
|
||||||
@ -157,7 +155,7 @@ ion-item-sliding.active-swipe-right ion-item-option[expandable] .item-option-but
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-item-sliding.active-swipe-left ion-item-option[expandable] .item-option-button {
|
ion-item-sliding.active-swipe-left ion-item-option[expandable] {
|
||||||
transition-duration: .6s;
|
transition-duration: .6s;
|
||||||
transition-property: padding-right;
|
transition-property: padding-right;
|
||||||
|
|
||||||
|
@ -8,11 +8,12 @@ import { ItemOptions } from './item-options';
|
|||||||
const SWIPE_MARGIN = 30;
|
const SWIPE_MARGIN = 30;
|
||||||
const ELASTIC_FACTOR = 0.55;
|
const ELASTIC_FACTOR = 0.55;
|
||||||
|
|
||||||
const ITEM_SIDE_FLAG_NONE = 0;
|
const enum ItemSide {
|
||||||
const ITEM_SIDE_FLAG_LEFT = 1 << 0;
|
None = 0,
|
||||||
const ITEM_SIDE_FLAG_RIGHT = 1 << 1;
|
Left = 1 << 0,
|
||||||
const ITEM_SIDE_FLAG_BOTH = ITEM_SIDE_FLAG_LEFT | ITEM_SIDE_FLAG_RIGHT;
|
Right = 1 << 1,
|
||||||
|
Both = Left | Right
|
||||||
|
}
|
||||||
|
|
||||||
const enum SlidingState {
|
const enum SlidingState {
|
||||||
Disabled = 1 << 1,
|
Disabled = 1 << 1,
|
||||||
@ -135,30 +136,23 @@ const enum SlidingState {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
export class ItemSliding {
|
export class ItemSliding {
|
||||||
@Element() private el: HTMLElement;
|
|
||||||
|
|
||||||
private item: HTMLIonItemElement;
|
private item: HTMLIonItemElement;
|
||||||
private list: HTMLIonListElement;
|
private list: HTMLIonListElement;
|
||||||
|
private openAmount = 0;
|
||||||
private openAmount: number = 0;
|
private initialOpenAmount = 0;
|
||||||
private startX: number = 0;
|
private optsWidthRightSide = 0;
|
||||||
private optsWidthRightSide: number = 0;
|
private optsWidthLeftSide = 0;
|
||||||
private optsWidthLeftSide: number = 0;
|
private sides: ItemSide;
|
||||||
private sides: number;
|
|
||||||
private tmr: any = null;
|
private tmr: any = null;
|
||||||
|
|
||||||
private leftOptions: ItemOptions;
|
private leftOptions: ItemOptions;
|
||||||
private rightOptions: ItemOptions;
|
private rightOptions: ItemOptions;
|
||||||
|
|
||||||
private optsDirty: boolean = true;
|
private optsDirty: boolean = true;
|
||||||
|
private gestureOptions: any;
|
||||||
|
|
||||||
|
@Element() private el: HTMLElement;
|
||||||
@State() state: SlidingState = SlidingState.Disabled;
|
@State() state: SlidingState = SlidingState.Disabled;
|
||||||
|
|
||||||
private preSelectedContainer: ItemSliding = null;
|
|
||||||
private selectedContainer: ItemSliding = null;
|
|
||||||
openContainer: ItemSliding = null;
|
|
||||||
private firstCoordX: number;
|
|
||||||
private firstTimestamp: number;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @output {event} Emitted when the sliding position changes.
|
* @output {event} Emitted when the sliding position changes.
|
||||||
@ -183,92 +177,39 @@ export class ItemSliding {
|
|||||||
*/
|
*/
|
||||||
@Event() ionDrag: EventEmitter;
|
@Event() ionDrag: EventEmitter;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.gestureOptions = {
|
||||||
|
'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'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected ionViewDidLoad() {
|
protected ionViewDidLoad() {
|
||||||
const options = this.el.querySelectorAll('ion-item-options');
|
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
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');
|
this.item = this.el.querySelector('ion-item');
|
||||||
|
|
||||||
// Get the parent list to close open containers
|
|
||||||
this.list = this.el.closest('ion-list') as HTMLIonListElement;
|
this.list = this.el.closest('ion-list') as HTMLIonListElement;
|
||||||
|
|
||||||
|
this.updateOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
canStart(gesture: GestureDetail): boolean {
|
protected ionViewDidUnLoad() {
|
||||||
if (this.selectedContainer) {
|
this.item = this.list = this.leftOptions = this.rightOptions = null;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Get swiped sliding container
|
|
||||||
let container = this;
|
|
||||||
|
|
||||||
// Close open container if it is not the selected one.
|
|
||||||
if (this.list && container !== this.list.getOpenedItem()) {
|
|
||||||
this.closeOpened();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.preSelectedContainer = container;
|
|
||||||
this.firstCoordX = gesture.currentX;
|
|
||||||
this.firstTimestamp = Date.now();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDragStart(gesture: GestureDetail) {
|
@Method()
|
||||||
this.selectedContainer = this.preSelectedContainer;
|
|
||||||
this.list.setOpenedItem(this.selectedContainer);
|
|
||||||
this.selectedContainer.startSliding(gesture.currentX);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDragMove(gesture: GestureDetail) {
|
|
||||||
this.selectedContainer && this.selectedContainer.moveSliding(gesture.currentX);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDragEnd(gesture: GestureDetail) {
|
|
||||||
let coordX = gesture.currentX;
|
|
||||||
let deltaX = (coordX - this.firstCoordX);
|
|
||||||
let deltaT = (Date.now() - this.firstTimestamp);
|
|
||||||
this.selectedContainer.endSliding(deltaX / deltaT);
|
|
||||||
this.selectedContainer = null;
|
|
||||||
this.preSelectedContainer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
closeOpened(): boolean {
|
|
||||||
this.selectedContainer = null;
|
|
||||||
|
|
||||||
if (this.list.getOpenedItem()) {
|
|
||||||
this.list.closeSlidingItems();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
getOpenAmount(): number {
|
getOpenAmount(): number {
|
||||||
return this.openAmount;
|
return this.openAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Method()
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
getSlidingPercent(): number {
|
getSlidingPercent(): number {
|
||||||
const openAmount = this.openAmount;
|
const openAmount = this.openAmount;
|
||||||
if (openAmount > 0) {
|
if (openAmount > 0) {
|
||||||
@ -280,156 +221,6 @@ export class ItemSliding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
|
|
||||||
let optsWidth;
|
|
||||||
if (openAmount > this.optsWidthRightSide) {
|
|
||||||
optsWidth = this.optsWidthRightSide;
|
|
||||||
openAmount = optsWidth + (openAmount - optsWidth) * ELASTIC_FACTOR;
|
|
||||||
|
|
||||||
} else if (openAmount < -this.optsWidthLeftSide) {
|
|
||||||
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.emit(this);
|
|
||||||
} else if (this.state & SlidingState.SwipeLeft) {
|
|
||||||
this.leftOptions.ionSwipe.emit(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 {
|
|
||||||
var state;
|
|
||||||
if (openAmount > 0) {
|
|
||||||
state = (openAmount >= (this.optsWidthRightSide + SWIPE_MARGIN))
|
|
||||||
? SlidingState.Right | SlidingState.SwipeRight
|
|
||||||
: SlidingState.Right;
|
|
||||||
|
|
||||||
this.setState(state);
|
|
||||||
|
|
||||||
} else if (openAmount < 0) {
|
|
||||||
state = (openAmount <= (-this.optsWidthLeftSide - SWIPE_MARGIN))
|
|
||||||
? SlidingState.Left | SlidingState.SwipeLeft
|
|
||||||
: SlidingState.Left;
|
|
||||||
|
|
||||||
this.setState(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (openAmount === 0) {
|
|
||||||
this.tmr = setTimeout(() => {
|
|
||||||
this.setState(SlidingState.Disabled);
|
|
||||||
this.tmr = null;
|
|
||||||
}, 600);
|
|
||||||
this.item.style.transform = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.item.style.transform = `translate3d(${-openAmount}px,0,0)`;
|
|
||||||
this.ionDrag.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private setState(state: SlidingState) {
|
|
||||||
if (state === this.state) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the sliding item. Items can also be closed from the [List](../../list/List).
|
* Close the sliding item. Items can also be closed from the [List](../../list/List).
|
||||||
@ -470,7 +261,160 @@ export class ItemSliding {
|
|||||||
this.setOpenAmount(0, true);
|
this.setOpenAmount(0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
hostData() {
|
@Method()
|
||||||
|
closeOpened(): boolean {
|
||||||
|
return this.list && this.list.closeSlidingItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateOptions() {
|
||||||
|
const options = this.el.querySelectorAll('ion-item-options');
|
||||||
|
|
||||||
|
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.item(i);
|
||||||
|
|
||||||
|
if (option.isRightSide()) {
|
||||||
|
this.rightOptions = option;
|
||||||
|
sides |= ItemSide.Right;
|
||||||
|
} else {
|
||||||
|
this.leftOptions = option;
|
||||||
|
sides |= ItemSide.Left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.optsDirty = true;
|
||||||
|
this.sides = sides;
|
||||||
|
}
|
||||||
|
|
||||||
|
private canStart(): boolean {
|
||||||
|
const selected = this.list && this.list.getOpenedItem();
|
||||||
|
if (selected && selected !== this) {
|
||||||
|
this.closeOpened();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDragStart() {
|
||||||
|
this.list && this.list.setOpenedItem(this);
|
||||||
|
|
||||||
|
if (this.tmr) {
|
||||||
|
clearTimeout(this.tmr);
|
||||||
|
this.tmr = null;
|
||||||
|
}
|
||||||
|
if (this.openAmount === 0) {
|
||||||
|
this.optsDirty = true;
|
||||||
|
this.state = SlidingState.Enabled;
|
||||||
|
}
|
||||||
|
this.initialOpenAmount = this.openAmount;
|
||||||
|
this.item.style.transition = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDragMove(gesture: GestureDetail) {
|
||||||
|
if (this.optsDirty) {
|
||||||
|
this.calculateOptsWidth();
|
||||||
|
}
|
||||||
|
let openAmount = this.initialOpenAmount - gesture.deltaX;
|
||||||
|
|
||||||
|
switch (this.sides) {
|
||||||
|
case ItemSide.Right: openAmount = Math.max(0, openAmount); break;
|
||||||
|
case ItemSide.Left: openAmount = Math.min(0, openAmount); break;
|
||||||
|
case ItemSide.Both: break;
|
||||||
|
case ItemSide.None: return;
|
||||||
|
default: console.warn('invalid ItemSideFlags value', this.sides); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let optsWidth;
|
||||||
|
if (openAmount > this.optsWidthRightSide) {
|
||||||
|
optsWidth = this.optsWidthRightSide;
|
||||||
|
openAmount = optsWidth + (openAmount - optsWidth) * ELASTIC_FACTOR;
|
||||||
|
|
||||||
|
} else if (openAmount < -this.optsWidthLeftSide) {
|
||||||
|
optsWidth = -this.optsWidthLeftSide;
|
||||||
|
openAmount = optsWidth + (openAmount - optsWidth) * ELASTIC_FACTOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setOpenAmount(openAmount, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDragEnd(gesture: GestureDetail) {
|
||||||
|
const velocity = gesture.velocityX;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (this.state & SlidingState.SwipeRight) {
|
||||||
|
this.rightOptions.fireSwipeEvent(this);
|
||||||
|
} else if (this.state & SlidingState.SwipeLeft) {
|
||||||
|
this.leftOptions.fireSwipeEvent(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateOptsWidth() {
|
||||||
|
this.optsWidthRightSide = 0;
|
||||||
|
if (this.rightOptions) {
|
||||||
|
this.optsWidthRightSide = this.rightOptions.width();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.optsWidthLeftSide = 0;
|
||||||
|
if (this.leftOptions) {
|
||||||
|
this.optsWidthLeftSide = this.leftOptions.width();
|
||||||
|
}
|
||||||
|
this.optsDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setOpenAmount(openAmount: number, isFinal: boolean) {
|
||||||
|
if (this.tmr) {
|
||||||
|
clearTimeout(this.tmr);
|
||||||
|
this.tmr = null;
|
||||||
|
}
|
||||||
|
const style = this.item.style;
|
||||||
|
this.openAmount = openAmount;
|
||||||
|
|
||||||
|
if (isFinal) {
|
||||||
|
style.transition = '';
|
||||||
|
|
||||||
|
} else if (openAmount > 0) {
|
||||||
|
this.state = (openAmount >= (this.optsWidthRightSide + SWIPE_MARGIN))
|
||||||
|
? SlidingState.Right | SlidingState.SwipeRight
|
||||||
|
: SlidingState.Right;
|
||||||
|
|
||||||
|
} else if (openAmount < 0) {
|
||||||
|
this.state = (openAmount <= (-this.optsWidthLeftSide - SWIPE_MARGIN))
|
||||||
|
? SlidingState.Left | SlidingState.SwipeLeft
|
||||||
|
: SlidingState.Left;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openAmount === 0) {
|
||||||
|
this.tmr = setTimeout(() => {
|
||||||
|
this.state = SlidingState.Disabled;
|
||||||
|
this.tmr = null;
|
||||||
|
}, 600);
|
||||||
|
this.list && this.list.setOpenedItem(null);
|
||||||
|
style.transform = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
style.transform = `translate3d(${-openAmount}px,0,0)`;
|
||||||
|
this.ionDrag.emit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected hostData() {
|
||||||
return {
|
return {
|
||||||
class: {
|
class: {
|
||||||
'item-wrapper': true,
|
'item-wrapper': true,
|
||||||
@ -485,19 +429,7 @@ export class ItemSliding {
|
|||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return (
|
return (
|
||||||
<ion-gesture {...{
|
<ion-gesture {...this.gestureOptions}>
|
||||||
'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>
|
<slot></slot>
|
||||||
</ion-gesture>
|
</ion-gesture>
|
||||||
);
|
);
|
||||||
|
@ -29,13 +29,13 @@ $item-wp-sliding-button-icon-color: color-contrast($colors-wp, $item-wp
|
|||||||
border-bottom: 1px solid $list-wp-border-color;
|
border-bottom: 1px solid $list-wp-border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-option-button-wp {
|
.item-option-wp {
|
||||||
font-size: $item-wp-sliding-button-font-size;
|
font-size: $item-wp-sliding-button-font-size;
|
||||||
color: $item-wp-sliding-button-text-color;
|
color: $item-wp-sliding-button-text-color;
|
||||||
background-color: $item-wp-sliding-button-background-color;
|
background-color: $item-wp-sliding-button-background-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-option-button-wp .icon {
|
.item-option-wp .icon {
|
||||||
fill: $item-wp-sliding-button-icon-color;
|
fill: $item-wp-sliding-button-icon-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,12 +52,12 @@ $item-wp-sliding-button-icon-color: color-contrast($colors-wp, $item-wp
|
|||||||
|
|
||||||
@each $color-name, $color-base, $color-contrast in get-colors($colors-wp) {
|
@each $color-name, $color-base, $color-contrast in get-colors($colors-wp) {
|
||||||
|
|
||||||
.item-option-button-wp-#{$color-name} {
|
item-option-wp-#{$color-name} {
|
||||||
color: $color-contrast;
|
color: $color-contrast;
|
||||||
background-color: $color-base;
|
background-color: $color-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-option-button-wp-#{$color-name} .icon {
|
item-option-wp-#{$color-name} .icon {
|
||||||
fill: $color-contrast;
|
fill: $color-contrast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,13 +143,13 @@
|
|||||||
<p>I think I figured out how to get more Mountain Dew</p>
|
<p>I think I figured out how to get more Mountain Dew</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item-options side="left" (ionSwipe)="unread($event)" class="sliding-enabled">
|
<ion-item-options side="left" class="sliding-enabled">
|
||||||
<ion-item-option color="secondary" expandable onclick="unread('item2')">
|
<ion-item-option color="secondary" expandable onclick="unread('item2')">
|
||||||
<ion-icon name="ios-checkmark"></ion-icon>Unread
|
<ion-icon name="ios-checkmark"></ion-icon>Unread
|
||||||
</ion-item-option>
|
</ion-item-option>
|
||||||
</ion-item-options>
|
</ion-item-options>
|
||||||
|
|
||||||
<ion-item-options side="right" (ionSwipe)="del('item2')" class="sliding-enabled">
|
<ion-item-options side="right" class="sliding-enabled">
|
||||||
<ion-item-option color="primary" onclick="archive('item2')">
|
<ion-item-option color="primary" onclick="archive('item2')">
|
||||||
<ion-icon name="mail"></ion-icon>Archive
|
<ion-icon name="mail"></ion-icon>Archive
|
||||||
</ion-item-option>
|
</ion-item-option>
|
||||||
@ -166,13 +166,13 @@
|
|||||||
<p>I think I figured out how to get more Mountain Dew</p>
|
<p>I think I figured out how to get more Mountain Dew</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item-options side="left" icon-start (ionSwipe)="unread($event)" class="sliding-enabled">
|
<ion-item-options side="left" icon-start class="sliding-enabled">
|
||||||
<ion-item-option color="secondary" expandable onclick="unread('item3')">
|
<ion-item-option color="secondary" expandable onclick="unread('item3')">
|
||||||
<ion-icon name="ios-checkmark"></ion-icon>Unread
|
<ion-icon name="ios-checkmark"></ion-icon>Unread
|
||||||
</ion-item-option>
|
</ion-item-option>
|
||||||
</ion-item-options>
|
</ion-item-options>
|
||||||
|
|
||||||
<ion-item-options icon-start (ionSwipe)="del('item3')">
|
<ion-item-options icon-start>
|
||||||
<ion-item-option color="primary" onclick="archive('item3')">
|
<ion-item-option color="primary" onclick="archive('item3')">
|
||||||
<ion-icon name="mail"></ion-icon>Archive
|
<ion-icon name="mail"></ion-icon>Archive
|
||||||
</ion-item-option>
|
</ion-item-option>
|
||||||
@ -190,7 +190,7 @@
|
|||||||
One Line w/ Icon, div only text
|
One Line w/ Icon, div only text
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item-options icon-start (ionSwipe)="archive($event)">
|
<ion-item-options icon-start>
|
||||||
<ion-item-option color="primary" onclick="archive('item4')" expandable class="sliding-enabled">
|
<ion-item-option color="primary" onclick="archive('item4')" expandable class="sliding-enabled">
|
||||||
<ion-icon name="archive"></ion-icon>Archive
|
<ion-icon name="archive"></ion-icon>Archive
|
||||||
</ion-item-option>
|
</ion-item-option>
|
||||||
@ -249,7 +249,7 @@
|
|||||||
<p>Paragraph text.</p>
|
<p>Paragraph text.</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item-options (ionSwipe)="download('item8')">
|
<ion-item-options>
|
||||||
<ion-item-option color="primary" onclick="archive('item8')">
|
<ion-item-option color="primary" onclick="archive('item8')">
|
||||||
<ion-icon name="archive"></ion-icon>Archive
|
<ion-icon name="archive"></ion-icon>Archive
|
||||||
</ion-item-option>
|
</ion-item-option>
|
||||||
@ -379,6 +379,9 @@
|
|||||||
function reload() {
|
function reload() {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
document.addEventListener('ionSwipe', (ev)=> console.log('SWIPE!!', ev.detail));
|
||||||
|
document.addEventListener('ionDrag', (ev)=> console.log('DRAG!!', ev.detail.getOpenAmount()));
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
img {
|
img {
|
||||||
|
@ -28,9 +28,13 @@ export class List {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
closeSlidingItems() {
|
closeSlidingItems(): boolean {
|
||||||
this.openedItem && this.openedItem.close();
|
if (this.openedItem) {
|
||||||
this.openedItem = null;
|
this.openedItem.close();
|
||||||
|
this.openedItem = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
|
@ -85,7 +85,7 @@ export class MenuController {
|
|||||||
* time. If there are multiple menus on the same side, then enabling one menu
|
* time. If there are multiple menus on the same side, then enabling one menu
|
||||||
* will also automatically disable all the others that are on the same side.
|
* will also automatically disable all the others that are on the same side.
|
||||||
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
||||||
* @return {Menu} Returns the instance of the menu, which is useful for chaining.
|
* @return {HTMLIonMenuElement} Returns the instance of the menu, which is useful for chaining.
|
||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
enable(shouldEnable: boolean, menuId?: string): HTMLIonMenuElement {
|
enable(shouldEnable: boolean, menuId?: string): HTMLIonMenuElement {
|
||||||
@ -100,7 +100,7 @@ export class MenuController {
|
|||||||
* Used to enable or disable the ability to swipe open the menu.
|
* Used to enable or disable the ability to swipe open the menu.
|
||||||
* @param {boolean} shouldEnable True if it should be swipe-able, false if not.
|
* @param {boolean} shouldEnable True if it should be swipe-able, false if not.
|
||||||
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
||||||
* @return {Menu} Returns the instance of the menu, which is useful for chaining.
|
* @return {HTMLIonMenuElement} Returns the instance of the menu, which is useful for chaining.
|
||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
swipeEnable(shouldEnable: boolean, menuId?: string): HTMLIonMenuElement {
|
swipeEnable(shouldEnable: boolean, menuId?: string): HTMLIonMenuElement {
|
||||||
@ -145,7 +145,7 @@ export class MenuController {
|
|||||||
* provided, then it'll try to find the menu using the menu's `id`
|
* provided, then it'll try to find the menu using the menu's `id`
|
||||||
* property. If a menu is not found then it'll return `null`.
|
* property. If a menu is not found then it'll return `null`.
|
||||||
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
||||||
* @return {Menu} Returns the instance of the menu if found, otherwise `null`.
|
* @return {HTMLIonMenuElement} Returns the instance of the menu if found, otherwise `null`.
|
||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
get(menuId?: string): HTMLIonMenuElement {
|
get(menuId?: string): HTMLIonMenuElement {
|
||||||
@ -188,7 +188,7 @@ export class MenuController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {Array<Menu>} Returns an array of all menu instances.
|
* @return {Array<HTMLIonMenuElement>} Returns an array of all menu instances.
|
||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
getMenus(): HTMLIonMenuElement[] {
|
getMenus(): HTMLIonMenuElement[] {
|
||||||
|
@ -82,8 +82,7 @@ export class Menu {
|
|||||||
@Prop() side: Side = 'start';
|
@Prop() side: Side = 'start';
|
||||||
@PropDidChange('side')
|
@PropDidChange('side')
|
||||||
sideChanged() {
|
sideChanged() {
|
||||||
const isRTL = false;
|
this.isRightSide = isRightSide(this.side);
|
||||||
this.isRightSide = isRightSide(this.side, isRTL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -175,7 +175,8 @@ export function checkEdgeSide(posX: number, isRightSide: boolean, maxEdgeStart:
|
|||||||
* @param isRTL whether the application dir is rtl
|
* @param isRTL whether the application dir is rtl
|
||||||
* @param defaultRight whether the default side is right
|
* @param defaultRight whether the default side is right
|
||||||
*/
|
*/
|
||||||
export function isRightSide(side: Side, isRTL: boolean, defaultRight: boolean = false): boolean {
|
export function isRightSide(side: Side, defaultRight: boolean = false): boolean {
|
||||||
|
const isRTL = document.dir === 'rtl';
|
||||||
switch (side) {
|
switch (side) {
|
||||||
case 'right': return true;
|
case 'right': return true;
|
||||||
case 'left': return false;
|
case 'left': return false;
|
||||||
|
Reference in New Issue
Block a user