mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-22 21:48:42 +08:00
feat(reorder): adds ion-reorder-group to core
This commit is contained in:
@ -38,7 +38,7 @@ export class Gesture {
|
|||||||
@Prop() gestureName: string = '';
|
@Prop() gestureName: string = '';
|
||||||
@Prop() gesturePriority: number = 0;
|
@Prop() gesturePriority: number = 0;
|
||||||
@Prop() maxAngle: number = 40;
|
@Prop() maxAngle: number = 40;
|
||||||
@Prop() threshold: number = 20;
|
@Prop() threshold: number = 10;
|
||||||
@Prop() type: string = 'pan';
|
@Prop() type: string = 'pan';
|
||||||
|
|
||||||
@Prop() canStart: GestureCallback;
|
@Prop() canStart: GestureCallback;
|
||||||
@ -209,7 +209,7 @@ export class Gesture {
|
|||||||
const detail = this.detail;
|
const detail = this.detail;
|
||||||
this.calcGestureData(ev);
|
this.calcGestureData(ev);
|
||||||
if (this.pan.detect(detail.currentX, detail.currentY)) {
|
if (this.pan.detect(detail.currentX, detail.currentY)) {
|
||||||
if (this.pan.isGesture() !== 0) {
|
if (this.pan.isGesture()) {
|
||||||
if (!this.tryToCapturePan()) {
|
if (!this.tryToCapturePan()) {
|
||||||
this.abortGesture();
|
this.abortGesture();
|
||||||
}
|
}
|
||||||
@ -463,6 +463,7 @@ export interface GestureDetail {
|
|||||||
deltaX?: number;
|
deltaX?: number;
|
||||||
deltaY?: number;
|
deltaY?: number;
|
||||||
timeStamp?: number;
|
timeStamp?: number;
|
||||||
|
data?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,13 +7,14 @@ export class PanRecognizer {
|
|||||||
private dirty: boolean = false;
|
private dirty: boolean = false;
|
||||||
private threshold: number;
|
private threshold: number;
|
||||||
private maxCosine: number;
|
private maxCosine: number;
|
||||||
|
private isDirX: boolean;
|
||||||
|
|
||||||
private angle = 0;
|
private angle = 0;
|
||||||
private isPan = 0;
|
private isPan = 0;
|
||||||
|
|
||||||
|
constructor(direction: string, threshold: number, maxAngle: number) {
|
||||||
constructor(private direction: string, threshold: number, maxAngle: number) {
|
|
||||||
const radians = maxAngle * (Math.PI / 180);
|
const radians = maxAngle * (Math.PI / 180);
|
||||||
|
this.isDirX = direction === 'x';
|
||||||
this.maxCosine = Math.cos(radians);
|
this.maxCosine = Math.cos(radians);
|
||||||
this.threshold = threshold * threshold;
|
this.threshold = threshold * threshold;
|
||||||
}
|
}
|
||||||
@ -35,32 +36,31 @@ export class PanRecognizer {
|
|||||||
const deltaY = (y - this.startY);
|
const deltaY = (y - this.startY);
|
||||||
const distance = deltaX * deltaX + deltaY * deltaY;
|
const distance = deltaX * deltaX + deltaY * deltaY;
|
||||||
|
|
||||||
if (distance >= this.threshold) {
|
if (distance < this.threshold) {
|
||||||
var angle = Math.atan2(deltaY, deltaX);
|
return false;
|
||||||
var cosine = (this.direction === 'y')
|
}
|
||||||
? Math.sin(angle)
|
const hypotenuse = Math.sqrt(distance);
|
||||||
: Math.cos(angle);
|
const cosine = ((this.isDirX) ? deltaX : deltaY) / hypotenuse;
|
||||||
|
|
||||||
this.angle = angle;
|
if (cosine > this.maxCosine) {
|
||||||
|
this.isPan = 1;
|
||||||
|
|
||||||
if (cosine > this.maxCosine) {
|
} else if (cosine < -this.maxCosine) {
|
||||||
this.isPan = 1;
|
this.isPan = -1;
|
||||||
|
|
||||||
} else if (cosine < -this.maxCosine) {
|
} else {
|
||||||
this.isPan = -1;
|
this.isPan = 0;
|
||||||
|
|
||||||
} else {
|
|
||||||
this.isPan = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dirty = false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
this.dirty = false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
isGesture(): number {
|
isGesture(): boolean {
|
||||||
|
return this.isPan !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDirection(): number {
|
||||||
return this.isPan;
|
return this.isPan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,3 +97,10 @@ ion-input.item {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[reorderAnchor] {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
pointer-events: all !important;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
@ -20,9 +20,6 @@ export class Item {
|
|||||||
private itemStyles: { [key: string]: CssClassMap } = Object.create(null);
|
private itemStyles: { [key: string]: CssClassMap } = Object.create(null);
|
||||||
private label: any;
|
private label: any;
|
||||||
|
|
||||||
// TODO get reorder from a parent list/group
|
|
||||||
@State() reorder: boolean = false;
|
|
||||||
|
|
||||||
@Element() private el: HTMLElement;
|
@Element() private el: HTMLElement;
|
||||||
|
|
||||||
@Prop() mode: string;
|
@Prop() mode: string;
|
||||||
@ -131,10 +128,6 @@ export class Item {
|
|||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
<slot name='end'></slot>
|
<slot name='end'></slot>
|
||||||
{ this.reorder
|
|
||||||
? <ion-reorder></ion-reorder>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class='button-effect'></div>
|
<div class='button-effect'></div>
|
||||||
</TagType>
|
</TagType>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, Method, State } from '@stencil/core';
|
import { Component, Method, Prop, State } from '@stencil/core';
|
||||||
|
|
||||||
import { ItemSliding } from '../item-sliding/item-sliding';
|
import { ItemSliding } from '../item-sliding/item-sliding';
|
||||||
|
|
||||||
@ -15,9 +15,18 @@ import { ItemSliding } from '../item-sliding/item-sliding';
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
export class List {
|
export class List {
|
||||||
|
|
||||||
@State() openContainer: ItemSliding;
|
@State() openContainer: ItemSliding;
|
||||||
|
@Prop() radioGroup: boolean;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (this.radioGroup) {
|
||||||
|
return (
|
||||||
|
<ion-radio-group>
|
||||||
|
<slot></slot>
|
||||||
|
</ion-radio-group>
|
||||||
|
);
|
||||||
|
}
|
||||||
return <slot></slot>;
|
return <slot></slot>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
383
packages/core/src/components/reorder/reorder-group.tsx
Normal file
383
packages/core/src/components/reorder/reorder-group.tsx
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
import { Component, Element, Prop, PropDidChange, State } from '@stencil/core';
|
||||||
|
import { GestureDetail } from '../../index';
|
||||||
|
import { reorderArray } from '../../utils/helpers';
|
||||||
|
import { CSS_PROP } from '../animation-controller/constants';
|
||||||
|
|
||||||
|
// const AUTO_SCROLL_MARGIN = 60;
|
||||||
|
// const SCROLL_JUMP = 10;
|
||||||
|
const ITEM_REORDER_ACTIVE = 'reorder-active';
|
||||||
|
|
||||||
|
|
||||||
|
export class ReorderIndexes {
|
||||||
|
constructor(public from: number, public to: number) {}
|
||||||
|
|
||||||
|
applyTo(array: any) {
|
||||||
|
reorderArray(array, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name ReorderGroup
|
||||||
|
* @description
|
||||||
|
* Item reorder adds the ability to change an item's order in a group.
|
||||||
|
* It can be used within an `ion-list` or `ion-item-group` to provide a
|
||||||
|
* visual drag and drop interface.
|
||||||
|
*
|
||||||
|
* ## Grouping Items
|
||||||
|
*
|
||||||
|
* All reorderable items must be grouped in the same element. If an item
|
||||||
|
* should not be reordered, it shouldn't be included in this group. For
|
||||||
|
* example, the following code works because the items are grouped in the
|
||||||
|
* `<ion-list>`:
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <ion-list reorder="true">
|
||||||
|
* <ion-item *ngFor="let item of items">{% raw %}{{ item }}{% endraw %}</ion-item>
|
||||||
|
* </ion-list>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* However, the below list includes a header that shouldn't be reordered:
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <ion-list reorder="true">
|
||||||
|
* <ion-list-header>Header</ion-list-header>
|
||||||
|
* <ion-item *ngFor="let item of items">{% raw %}{{ item }}{% endraw %}</ion-item>
|
||||||
|
* </ion-list>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* In order to mix different sets of items, `ion-item-group` should be used to
|
||||||
|
* group the reorderable items:
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <ion-list>
|
||||||
|
* <ion-list-header>Header</ion-list-header>
|
||||||
|
* <ion-item-group reorder="true">
|
||||||
|
* <ion-item *ngFor="let item of items">{% raw %}{{ item }}{% endraw %}</ion-item>
|
||||||
|
* </ion-item-group>
|
||||||
|
* </ion-list>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* It's important to note that in this example, the `[reorder]` directive is applied to
|
||||||
|
* the `<ion-item-group>` instead of the `<ion-list>`. This way makes it possible to
|
||||||
|
* mix items that should and shouldn't be reordered.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* ## Implementing the Reorder Function
|
||||||
|
*
|
||||||
|
* When the item is dragged and dropped into the new position, the `(ionItemReorder)` event is
|
||||||
|
* emitted. This event provides the initial index (from) and the new index (to) of the reordered
|
||||||
|
* item. For example, if the first item is dragged to the fifth position, the event will emit
|
||||||
|
* `{from: 0, to: 4}`. Note that the index starts at zero.
|
||||||
|
*
|
||||||
|
* A function should be called when the event is emitted that handles the reordering of the items.
|
||||||
|
* See [usage](#usage) below for some examples.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @usage
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <ion-list>
|
||||||
|
* <ion-list-header>Header</ion-list-header>
|
||||||
|
* <ion-item-group reorder="true" (ionItemReorder)="reorderItems($event)">
|
||||||
|
* <ion-item *ngFor="let item of items">{% raw %}{{ item }}{% endraw %}</ion-item>
|
||||||
|
* </ion-item-group>
|
||||||
|
* </ion-list>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* class MyComponent {
|
||||||
|
* items = [];
|
||||||
|
*
|
||||||
|
* constructor() {
|
||||||
|
* for (let x = 0; x < 5; x++) {
|
||||||
|
* this.items.push(x);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* reorderItems(indexes) {
|
||||||
|
* let element = this.items[indexes.from];
|
||||||
|
* this.items.splice(indexes.from, 1);
|
||||||
|
* this.items.splice(indexes.to, 0, element);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Ionic also provides a helper function called `reorderArray` to
|
||||||
|
* reorder the array of items. This can be used instead:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* import { reorderArray } from 'ionic-angular';
|
||||||
|
*
|
||||||
|
* class MyComponent {
|
||||||
|
* items = [];
|
||||||
|
*
|
||||||
|
* constructor() {
|
||||||
|
* for (let x = 0; x < 5; x++) {
|
||||||
|
* this.items.push(x);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* reorderItems(indexes) {
|
||||||
|
* this.items = reorderArray(this.items, indexes);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* Alternatevely you can execute helper function inside template:
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <ion-list>
|
||||||
|
* <ion-list-header>Header</ion-list-header>
|
||||||
|
* <ion-item-group reorder="true" (ionItemReorder)="$event.applyTo(items)">
|
||||||
|
* <ion-item *ngFor="let item of items">{% raw %}{{ item }}{% endraw %}</ion-item>
|
||||||
|
* </ion-item-group>
|
||||||
|
* </ion-list>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @demo /docs/demos/src/item-reorder/
|
||||||
|
* @see {@link /docs/components#lists List Component Docs}
|
||||||
|
* @see {@link ../../list/List List API Docs}
|
||||||
|
* @see {@link ../Item Item API Docs}
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
tag: 'ion-reorder-group',
|
||||||
|
styleUrl: 'reorder.scss'
|
||||||
|
})
|
||||||
|
export class ReorderGroup {
|
||||||
|
|
||||||
|
private selectedItemEle: HTMLElement = null;
|
||||||
|
private selectedItemHeight: number;
|
||||||
|
private lastToIndex: number;
|
||||||
|
private lastYcoord: number;
|
||||||
|
private topOfList: number;
|
||||||
|
private cachedHeights: number[] = [];
|
||||||
|
private containerEle: HTMLElement;
|
||||||
|
|
||||||
|
@State() _enabled: boolean = false;
|
||||||
|
@State() _iconVisible: boolean = false;
|
||||||
|
@State() _actived: boolean = false;
|
||||||
|
|
||||||
|
@Element() ele: HTMLElement;
|
||||||
|
|
||||||
|
@Prop() enabled: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @input {string} Which side of the view the ion-reorder should be placed. Default `"end"`.
|
||||||
|
*/
|
||||||
|
@Prop() side: string;
|
||||||
|
|
||||||
|
@PropDidChange('enabled')
|
||||||
|
enabledChanged(enabled: boolean) {
|
||||||
|
if (enabled) {
|
||||||
|
this._enabled = true;
|
||||||
|
Context.dom.raf(() => {
|
||||||
|
this._iconVisible = true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._iconVisible = false;
|
||||||
|
setTimeout(() => this._enabled = false, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewDidLoad() {
|
||||||
|
this.containerEle = this.ele.querySelector('ion-gesture') as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewDidUnload() {
|
||||||
|
this.onDragEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private canStart(ev: GestureDetail): boolean {
|
||||||
|
if (this.selectedItemEle) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const target = ev.event.target as HTMLElement;
|
||||||
|
const reorderEle = target.closest('[reorderAnchor]') as HTMLElement;
|
||||||
|
if (!reorderEle) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const item = findReorderItem(reorderEle, this.containerEle);
|
||||||
|
if (!item) {
|
||||||
|
console.error('reorder node not found');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ev.data = item;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDragStart(ev: GestureDetail) {
|
||||||
|
const item = this.selectedItemEle = ev.data;
|
||||||
|
const heights = this.cachedHeights;
|
||||||
|
heights.length = 0;
|
||||||
|
const ele = this.containerEle;
|
||||||
|
const children: any = ele.children;
|
||||||
|
if (!children || children.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sum = 0;
|
||||||
|
for (let i = 0, ilen = children.length; i < ilen; i++) {
|
||||||
|
var child = children[i];
|
||||||
|
sum += child.offsetHeight;
|
||||||
|
heights.push(sum);
|
||||||
|
child.$ionIndex = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.topOfList = item.getBoundingClientRect().top;
|
||||||
|
this._actived = true;
|
||||||
|
this.lastYcoord = -100;
|
||||||
|
this.lastToIndex = indexForItem(item);
|
||||||
|
this.selectedItemHeight = item.offsetHeight;
|
||||||
|
|
||||||
|
item.classList.add(ITEM_REORDER_ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDragMove(ev: GestureDetail) {
|
||||||
|
const selectedItem = this.selectedItemEle;
|
||||||
|
if (!selectedItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ev.event.preventDefault();
|
||||||
|
|
||||||
|
// // Get coordinate
|
||||||
|
const posY = ev.deltaY;
|
||||||
|
|
||||||
|
// Scroll if we reach the scroll margins
|
||||||
|
// const scrollPosition = this.scroll(posY);
|
||||||
|
// Only perform hit test if we moved at least 30px from previous position
|
||||||
|
if (Math.abs(posY - this.lastYcoord) > 30) {
|
||||||
|
let toIndex = this.itemIndexForDelta(posY);
|
||||||
|
if (toIndex !== undefined && (toIndex !== this.lastToIndex)) {
|
||||||
|
let fromIndex = indexForItem(selectedItem);
|
||||||
|
this.lastToIndex = toIndex;
|
||||||
|
this.lastYcoord = posY;
|
||||||
|
this._reorderMove(fromIndex, toIndex, this.selectedItemHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update selected item position
|
||||||
|
(selectedItem.style as any)[CSS_PROP.transformProp] = `translateY(${posY}px)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDragEnd() {
|
||||||
|
this._actived = false;
|
||||||
|
const selectedItem = this.selectedItemEle;
|
||||||
|
if (!selectedItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if (ev.event) {
|
||||||
|
// ev.event.preventDefault();
|
||||||
|
// ev.event.stopPropagation();
|
||||||
|
// }
|
||||||
|
|
||||||
|
const toIndex = this.lastToIndex;
|
||||||
|
const fromIndex = indexForItem(selectedItem);
|
||||||
|
|
||||||
|
const ref = (fromIndex < toIndex)
|
||||||
|
? this.containerEle.children[toIndex + 1]
|
||||||
|
: this.containerEle.children[toIndex];
|
||||||
|
|
||||||
|
this.containerEle.insertBefore(this.selectedItemEle, ref);
|
||||||
|
|
||||||
|
const children = this.containerEle.children;
|
||||||
|
const len = children.length;
|
||||||
|
const transform = CSS_PROP.transformProp;
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
(children[i] as any).style[transform] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const reorderInactive = () => {
|
||||||
|
this.selectedItemEle.style.transition = '';
|
||||||
|
this.selectedItemEle.classList.remove(ITEM_REORDER_ACTIVE);
|
||||||
|
this.selectedItemEle = null;
|
||||||
|
};
|
||||||
|
if (toIndex === fromIndex) {
|
||||||
|
selectedItem.style.transition = 'transform 200ms ease-in-out';
|
||||||
|
setTimeout(reorderInactive, 200);
|
||||||
|
} else {
|
||||||
|
reorderInactive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private itemIndexForDelta(deltaY: number): number {
|
||||||
|
const heights = this.cachedHeights;
|
||||||
|
let sum = deltaY + this.topOfList - (this.selectedItemHeight / 2);
|
||||||
|
for (var i = 0; i < heights.length; i++) {
|
||||||
|
if (heights[i] > sum) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _reorderMove(fromIndex: number, toIndex: number, itemHeight: number) {
|
||||||
|
/********* DOM WRITE ********* */
|
||||||
|
const children = this.containerEle.children;
|
||||||
|
const transform = CSS_PROP.transformProp;
|
||||||
|
for (var i = 0; i < children.length; i++) {
|
||||||
|
const style = (children[i] as any).style;
|
||||||
|
let value = '';
|
||||||
|
if (i > fromIndex && i <= toIndex) {
|
||||||
|
value = `translateY(${-itemHeight}px)`;
|
||||||
|
} else if (i < fromIndex && i >= toIndex) {
|
||||||
|
value = `translateY(${itemHeight}px)`;
|
||||||
|
}
|
||||||
|
style[transform] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hostData() {
|
||||||
|
return {
|
||||||
|
class: {
|
||||||
|
'reorder-enabled': this._enabled,
|
||||||
|
'reorder-list-active': this._actived,
|
||||||
|
'reorder-visible': this._iconVisible,
|
||||||
|
'reorder-side-start': this.side === 'start'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<ion-gesture props={{
|
||||||
|
disableScroll: true,
|
||||||
|
canStart: this.canStart.bind(this),
|
||||||
|
onStart: this.onDragStart.bind(this),
|
||||||
|
onMove: this.onDragMove.bind(this),
|
||||||
|
onEnd: this.onDragEnd.bind(this),
|
||||||
|
enabled: this.enabled,
|
||||||
|
gestureName: 'reorder',
|
||||||
|
gesturePriority: 30,
|
||||||
|
type: 'pan',
|
||||||
|
direction: 'y',
|
||||||
|
threshold: 0,
|
||||||
|
attachTo: 'parent'
|
||||||
|
}}>
|
||||||
|
<slot></slot>
|
||||||
|
</ion-gesture>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
function indexForItem(element: any): number {
|
||||||
|
return element['$ionIndex'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
function findReorderItem(node: HTMLElement, container: HTMLElement): HTMLElement {
|
||||||
|
let nested = 0;
|
||||||
|
let parent;
|
||||||
|
while (node && nested < 6) {
|
||||||
|
parent = node.parentNode as HTMLElement;
|
||||||
|
if (parent === container) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
node = parent;
|
||||||
|
nested++;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
64
packages/core/src/components/reorder/reorder.scss
Normal file
64
packages/core/src/components/reorder/reorder.scss
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
@import "../../themes/ionic.globals";
|
||||||
|
|
||||||
|
$reorder-initial-transform: 160% !default;
|
||||||
|
|
||||||
|
// Reorder group general
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
.reorder-enabled [reorderAnchor] {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reorder-list-active ion-gesture > * {
|
||||||
|
transition: transform 300ms;
|
||||||
|
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reorder-list-active ion-gesture *:not([reorderAnchor]) {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reorder-active {
|
||||||
|
position: relative;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, .4);
|
||||||
|
opacity: .8;
|
||||||
|
transition: none !important;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Reorder icon
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
ion-reorder {
|
||||||
|
@include transform(translate3d($reorder-initial-transform, 0, 0));
|
||||||
|
|
||||||
|
margin-top: auto !important;
|
||||||
|
margin-bottom: auto !important;
|
||||||
|
|
||||||
|
font-size: 1.7em;
|
||||||
|
opacity: .25;
|
||||||
|
|
||||||
|
line-height: 0;
|
||||||
|
|
||||||
|
transition: transform 140ms ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-reorder ion-icon {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reorder-side-start ion-reorder {
|
||||||
|
@include transform(translate3d(-$reorder-initial-transform, 0, 0));
|
||||||
|
|
||||||
|
order: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reorder-visible ion-reorder {
|
||||||
|
@include transform(translate3d(0, 0, 0));
|
||||||
|
}
|
||||||
|
|
20
packages/core/src/components/reorder/reorder.tsx
Normal file
20
packages/core/src/components/reorder/reorder.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Component } from '@stencil/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
tag: 'ion-reorder',
|
||||||
|
})
|
||||||
|
export class ItemReorder {
|
||||||
|
|
||||||
|
hostData() {
|
||||||
|
return {
|
||||||
|
attrs: {
|
||||||
|
'reorderAnchor': '',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <ion-icon name='reorder'></ion-icon>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
87
packages/core/src/components/reorder/test/basic.html
Normal file
87
packages/core/src/components/reorder/test/basic.html
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html dir="ltr">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Ionic Reorder</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-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Item Reorder</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button onclick="toggleEdit()">Toggle</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<ion-list>
|
||||||
|
<ion-reorder-group id="reorder">
|
||||||
|
<ion-item>
|
||||||
|
Item 1
|
||||||
|
<ion-reorder slot="end"></ion-reorder>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>Item 2
|
||||||
|
<ion-reorder slot="end"></ion-reorder>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>Item 3
|
||||||
|
<ion-icon reorderAnchor name="pizza" slot="end"></ion-icon>
|
||||||
|
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>Item 4
|
||||||
|
<ion-icon reorderAnchor name="pizza" slot="end"></ion-icon>
|
||||||
|
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>Item 5
|
||||||
|
<ion-icon reorderAnchor name="pizza" slot="end"></ion-icon>
|
||||||
|
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>Item 6
|
||||||
|
<ion-icon reorderAnchor name="pizza" slot="end"></ion-icon>
|
||||||
|
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>Item 7
|
||||||
|
<ion-icon reorderAnchor name="pizza" slot="end"></ion-icon>
|
||||||
|
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>Item 8
|
||||||
|
<ion-icon reorderAnchor name="pizza" slot="end"></ion-icon>
|
||||||
|
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>Item 9
|
||||||
|
<ion-icon reorderAnchor name="pizza" slot="end"></ion-icon>
|
||||||
|
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>Item 10
|
||||||
|
<ion-icon reorderAnchor name="pizza" slot="end"></ion-icon>
|
||||||
|
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>Item 11
|
||||||
|
<ion-icon reorderAnchor name="pizza" slot="end"></ion-icon>
|
||||||
|
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>Item 12
|
||||||
|
<ion-icon reorderAnchor name="pizza" slot="end"></ion-icon>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>Item 13
|
||||||
|
<ion-icon reorderAnchor name="pizza" slot="end"></ion-icon>
|
||||||
|
</ion-item>
|
||||||
|
</ion-reorder-group>
|
||||||
|
</ion-list>
|
||||||
|
</ion-content>
|
||||||
|
</ion-app>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleEdit() {
|
||||||
|
const list = document.getElementById('reorder');
|
||||||
|
list.enabled = !list.enabled;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -268,3 +268,13 @@ export function hasFocusedTextInput() {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export function reorderArray(array: any[], indexes: {from: number, to: number}): any[] {
|
||||||
|
const element = array[indexes.from];
|
||||||
|
array.splice(indexes.from, 1);
|
||||||
|
array.splice(indexes.to, 0, element);
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
@ -23,6 +23,7 @@ exports.config = {
|
|||||||
{ components: ['ion-modal', 'ion-modal-controller'] },
|
{ components: ['ion-modal', 'ion-modal-controller'] },
|
||||||
{ components: ['ion-popover', 'ion-popover-controller'] },
|
{ components: ['ion-popover', 'ion-popover-controller'] },
|
||||||
{ components: ['ion-radio', 'ion-radio-group'] },
|
{ components: ['ion-radio', 'ion-radio-group'] },
|
||||||
|
{ components: ['ion-reorder', 'ion-reorder-group'] },
|
||||||
{ components: ['ion-searchbar'] },
|
{ components: ['ion-searchbar'] },
|
||||||
{ components: ['ion-segment', 'ion-segment-button'] },
|
{ components: ['ion-segment', 'ion-segment-button'] },
|
||||||
{ components: ['ion-select', 'ion-select-option', 'ion-select-popover'] },
|
{ components: ['ion-select', 'ion-select-option', 'ion-select-popover'] },
|
||||||
|
Reference in New Issue
Block a user