fix(reorder-group): delegate dom reordering

fixes #15836
This commit is contained in:
Manu Mtz.-Almeida
2018-10-08 09:46:26 -05:00
parent 0983f95d9f
commit 5f659420fd
18 changed files with 279 additions and 219 deletions

View File

@ -0,0 +1,4 @@
export interface RefresherEventDetail {
complete(): void;
}

View File

@ -1,6 +1,6 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, QueueApi, State, Watch } from '@stencil/core';
import { Gesture, GestureDetail, Mode } from '../../interface';
import { Gesture, GestureDetail, Mode, RefresherEventDetail } from '../../interface';
import { createThemedClasses } from '../../utils/theme';
@Component({
@ -20,6 +20,8 @@ export class Refresher implements ComponentInterface {
mode!: Mode;
@Element() el!: HTMLElement;
@Prop({ context: 'queue' }) queue!: QueueApi;
/**
@ -34,8 +36,6 @@ export class Refresher implements ComponentInterface {
*/
@State() private state: RefresherState = RefresherState.Inactive;
@Element() el!: HTMLElement;
/**
* The minimum distance the user must pull down until the
* refresher will go into the `refreshing` state. Defaults to `60`.
@ -77,7 +77,7 @@ export class Refresher implements ComponentInterface {
* Updates the refresher state to `refreshing`. The `complete()` method should be
* called when the async operation has completed.
*/
@Event() ionRefresh!: EventEmitter<void>;
@Event() ionRefresh!: EventEmitter<RefresherEventDetail>;
/**
* Emitted while the user is pulling down the content and exposing the refresher.
@ -307,7 +307,9 @@ export class Refresher implements ComponentInterface {
// emit "refresh" because it was pulled down far enough
// and they let go to begin refreshing
this.ionRefresh.emit();
this.ionRefresh.emit({
complete: this.complete.bind(this)
});
}
private close(state: RefresherState, delay: string) {

View File

@ -1,6 +1,50 @@
# ion-reorder-group
The reorder group is a wrapper component for items with the `ion-reorder` component. For more information, see the [Reorder documentation](../reorder/).
The reorder group is a wrapper component for items with the `ion-reorder` component, Check [Reorder documentation](../reorder/) for further information about the anchor component that is used to drag items within the `ion-reorder-group` list.
Once the user drags an item and drops it in a new position, the `ionItemReorder` event is dispatched. A handler for it must be implemented by the developer to commit changes.
```js
reorderGroup.addEventListener('ionItemReorder', (ev) => {
console.log(`Moving item from ${ev.detail.from} to ${ev.detail.to}`);
this.dataList = reorderArray(this.dataList, ev.detail.from, ev.detail.to);
ev.detail.complete();
});
```
The event's detail includes all the relevant information about the reorder operation, including the `from` and `to` indexes. The meaning of this indexes are pretty self-explanatory, the item **from** index X, moved **to** the index Y.
For example, in this list we move the item at index `0` to the index `3`:
```
BEFORE | AFTER
0 | 1
1 | 2
2 | 3
3 | 0
4 | 4
```
```
detail: {
from: 0
to: 3
}
```
Once the data structure has been updated to reflect the reorder change, the `complete()` method must be called.
This method finishes the reorder operation and resets all the CSS transforms applied by the `ion-reorder-group` component.
Fortunately this `complete()` method can optionally accept an array as input and it will return a reordered copy, based in `detail.from` and `detail.to`.
```ts
this.dataList = reorderGroup.complete(this.dataList);
```
This utility is really handy when
<!-- Auto Generated Below -->
@ -14,9 +58,16 @@ The reorder group is a wrapper component for items with the `ion-reorder` compon
## Events
| Event | Description |
| ---------------- | ----------- |
| `ionItemReorder` | |
| Event | Description |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ionItemReorder` | Event that needs to be listen to in order to respond to reorder action. `ion-reorder-group` uses this event to delegate to the user the reordering of data array. The complete() method exposed as |
## Methods
| Method | Description |
| ---------- | ----------- |
| `complete` | |
----------------------------------------------

View File

@ -0,0 +1,6 @@
export interface ItemReorderDetail {
from: number;
to: number;
complete: (data?: boolean | any[]) => any;
}

View File

@ -1,8 +1,14 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, QueueApi, State, Watch } from '@stencil/core';
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, QueueApi, State, Watch } from '@stencil/core';
import { Gesture, GestureDetail } from '../../interface';
import { Gesture, GestureDetail, ItemReorderDetail } from '../../interface';
import { hapticSelectionChanged, hapticSelectionEnd, hapticSelectionStart } from '../../utils/haptic';
const enum ReordeGroupState {
Idle = 0,
Active = 1,
Complete = 2
}
@Component({
tag: 'ion-reorder-group',
styleUrl: 'reorder-group.scss'
@ -23,7 +29,7 @@ export class ReorderGroup implements ComponentInterface {
private containerTop = 0;
private containerBottom = 0;
@State() activated = false;
@State() state = ReordeGroupState.Idle;
@Element() el!: HTMLElement;
@ -43,7 +49,14 @@ export class ReorderGroup implements ComponentInterface {
delete a.a;
}
@Event() ionItemReorder!: EventEmitter;
/**
* Event that needs to be listen to in order to respond to reorder action.
* `ion-reorder-group` uses this event to delegate to the user the reordering of data array.
*
*
* The complete() method exposed as
*/
@Event({ bubbles: false }) ionItemReorder!: EventEmitter<ItemReorderDetail>;
async componentDidLoad() {
const contentEl = this.el.closest('ion-content');
@ -73,8 +86,13 @@ export class ReorderGroup implements ComponentInterface {
this.onEnd();
}
@Method()
complete(listOrReorder?: boolean | any[]): Promise<any> {
return Promise.resolve(this.completeSync(listOrReorder));
}
private canStart(ev: GestureDetail): boolean {
if (this.selectedItemEl) {
if (this.selectedItemEl || this.state !== ReordeGroupState.Idle) {
return false;
}
const target = ev.event.target as HTMLElement;
@ -111,7 +129,7 @@ export class ReorderGroup implements ComponentInterface {
child.$ionIndex = i;
}
const box = this.el.getBoundingClientRect();
const box = el.getBoundingClientRect();
this.containerTop = box.top;
this.containerBottom = box.bottom;
@ -128,7 +146,7 @@ export class ReorderGroup implements ComponentInterface {
this.lastToIndex = indexForItem(item);
this.selectedItemHeight = item.offsetHeight;
this.activated = true;
this.state = ReordeGroupState.Active;
item.classList.add(ITEM_REORDER_SELECTED);
@ -163,48 +181,62 @@ export class ReorderGroup implements ComponentInterface {
}
private onEnd() {
this.activated = false;
const selectedItem = this.selectedItemEl;
this.state = ReordeGroupState.Complete;
if (!selectedItem) {
this.state = ReordeGroupState.Idle;
return;
}
const children = this.el.children as any;
const toIndex = this.lastToIndex;
const fromIndex = indexForItem(selectedItem);
const ref = (fromIndex < toIndex)
? children[toIndex + 1]
: children[toIndex];
this.el.insertBefore(selectedItem, ref);
const len = children.length;
for (let i = 0; i < len; i++) {
children[i].style['transform'] = '';
}
const reorderInactive = () => {
if (this.selectedItemEl) {
this.selectedItemEl.style.transition = '';
this.selectedItemEl.classList.remove(ITEM_REORDER_SELECTED);
this.selectedItemEl = undefined;
}
};
if (toIndex === fromIndex) {
selectedItem.style.transition = 'transform 200ms ease-in-out';
setTimeout(reorderInactive, 200);
setTimeout(() => this.completeSync(), 200);
} else {
reorderInactive();
this.ionItemReorder.emit({
from: fromIndex,
to: toIndex
to: toIndex,
complete: this.completeSync.bind(this)
});
}
hapticSelectionEnd();
}
private completeSync(listOrReorder?: boolean | any[]): any {
const selectedItemEl = this.selectedItemEl;
if (selectedItemEl && this.state === ReordeGroupState.Complete) {
const children = this.el.children as any;
const len = children.length;
const toIndex = this.lastToIndex;
const fromIndex = indexForItem(selectedItemEl);
if (listOrReorder === true) {
const ref = (fromIndex < toIndex)
? children[toIndex + 1]
: children[toIndex];
this.el.insertBefore(selectedItemEl, ref);
}
if (Array.isArray(listOrReorder)) {
listOrReorder = reorderArray(listOrReorder, fromIndex, toIndex);
}
for (let i = 0; i < len; i++) {
children[i].style['transform'] = '';
}
selectedItemEl.style.transition = '';
selectedItemEl.classList.remove(ITEM_REORDER_SELECTED);
this.selectedItemEl = undefined;
this.state = ReordeGroupState.Idle;
}
return listOrReorder;
}
private itemIndexForTop(deltaY: number): number {
const heights = this.cachedHeights;
let i = 0;
@ -257,7 +289,7 @@ export class ReorderGroup implements ComponentInterface {
return {
class: {
'reorder-enabled': !this.disabled,
'reorder-list-active': this.activated,
'reorder-list-active': this.state !== ReordeGroupState.Idle,
}
};
}
@ -284,3 +316,10 @@ function findReorderItem(node: HTMLElement, container: HTMLElement): HTMLElement
const AUTO_SCROLL_MARGIN = 60;
const SCROLL_JUMP = 10;
const ITEM_REORDER_SELECTED = 'reorder-selected';
function reorderArray(array: any[], from: number, to: number): any[] {
const element = array[from];
array.splice(from, 1);
array.splice(to, 0, element);
return array.slice();
}

View File

@ -114,6 +114,9 @@
function toggleEdit() {
const reorderGroup = document.getElementById('reorder');
reorderGroup.disabled = !reorderGroup.disabled;
reorderGroup.addEventListener('ionItemReorder', ({detail}) => {
detail.complete(true);
});
}
</script>
</body>

View File

@ -0,0 +1,49 @@
```html
<ion-content>
<ion-list>
<ion-reorder-group>
<ion-item>
<ion-label>
Item 1
</ion-label>
<ion-reorder slot="end"></ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 2 (default ion-reorder slot="start")
</ion-label>
<ion-reorder slot="start"></ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 3 (custom ion-reorder)
</ion-label>
<ion-reorder slot="end">
<ion-icon name="pizza"></ion-icon>
</ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 4 (custom ion-reorder slot="start")
</ion-label>
<ion-reorder slot="start">
<ion-icon name="pizza"></ion-icon>
</ion-reorder>
</ion-item>
<ion-reorder>
<ion-item>
<ion-label>
Item 5 (the whole item can be dragged)
</ion-label>
</ion-item>
</ion-reorder>
</ion-reorder-group>
</ion-list>
</ion-content>
```

View File

@ -0,0 +1,60 @@
```html
<ion-content>
<ion-list>
<ion-reorder-group disabled="false">
<ion-item>
<ion-label>
Item 1
</ion-label>
<ion-reorder slot="end"></ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 2 (default ion-reorder slot="start")
</ion-label>
<ion-reorder slot="start"></ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 3 (custom ion-reorder)
</ion-label>
<ion-reorder slot="end">
<ion-icon name="pizza"></ion-icon>
</ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 4 (custom ion-reorder slot="start")
</ion-label>
<ion-reorder slot="start">
<ion-icon name="pizza"></ion-icon>
</ion-reorder>
</ion-item>
<ion-reorder>
<ion-item>
<ion-label>
Item 5 (the whole item can be dragged)
</ion-label>
</ion-item>
</ion-reorder>
</ion-reorder-group>
</ion-list>
</ion-content>
```
```javascript
const reorderGroup = document.querySelector('ion-reorder-group');
reorderGroup.addEventListener('ionItemReorder', ({detail}) => {
// finishing the reorder, true means ion-reorder-group with reorder the DOM
detail.complete(true);
// or:
// reorderGroup.complete(true)
});
```

View File

@ -1,6 +1,19 @@
# ion-reorder
Reorder is a component that allows an item to be dragged to change its order. It can be used within an `ion-reorder-group` to provide a visual drag and drop interface.
Reorder is a component that allows an item to be dragged to change its order. It must be used within an `ion-reorder-group` to provide a visual drag and drop interface.
`ion-reorder` is the anchor users will use to drag and drop items inside the `ion-reorder-group`. It must be added to `ion-item` in order for them to be draggable.
```html
<ion-item>
<ion-label>
Item
</ion-label>
<ion-reorder slot="end"></ion-reorder>
</ion-item>
```
The position of the
<!-- Auto Generated Below -->

View File

@ -1,88 +0,0 @@
```html
<ion-content>
<ion-list>
<ion-reorder-group>
<ion-item>
<ion-label>
Item 1
</ion-label>
<ion-reorder slot="end"></ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 2
</ion-label>
<ion-reorder slot="end"></ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 3 (default ion-reorder slot="start")
</ion-label>
<ion-reorder slot="start"></ion-reorder>
</ion-item>
<ion-item color="secondary">
<ion-label>
Item 4 (default ion-reorder slot="start")
</ion-label>
<ion-reorder slot="start"></ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 5 (custom ion-reorder)
</ion-label>
<ion-reorder slot="end">
<ion-icon name="pizza"></ion-icon>
</ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 6 (custom ion-reorder)
</ion-label>
<ion-reorder slot="end">
<ion-icon name="pizza"></ion-icon>
</ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 7 (custom ion-reorder slot="start")
</ion-label>
<ion-reorder slot="start">
<ion-icon name="pizza"></ion-icon>
</ion-reorder>
</ion-item>
<ion-reorder>
<ion-item>
<ion-label>
Item 8 (the whole item can be dragged)
</ion-label>
</ion-item>
</ion-reorder>
<ion-reorder>
<ion-item>
<ion-label>
Item 9 (the whole item can be dragged)
</ion-label>
</ion-item>
</ion-reorder>
<ion-reorder>
<ion-item>
<ion-label>
Item 10 (the whole item can be dragged)
</ion-label>
</ion-item>
</ion-reorder>
</ion-reorder-group>
</ion-list>
</ion-content>
```

View File

@ -1,88 +0,0 @@
```html
<ion-content>
<ion-list>
<ion-reorder-group>
<ion-item>
<ion-label>
Item 1
</ion-label>
<ion-reorder slot="end"></ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 2
</ion-label>
<ion-reorder slot="end"></ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 3 (default ion-reorder slot="start")
</ion-label>
<ion-reorder slot="start"></ion-reorder>
</ion-item>
<ion-item color="secondary">
<ion-label>
Item 4 (default ion-reorder slot="start")
</ion-label>
<ion-reorder slot="start"></ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 5 (custom ion-reorder)
</ion-label>
<ion-reorder slot="end">
<ion-icon name="pizza"></ion-icon>
</ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 6 (custom ion-reorder)
</ion-label>
<ion-reorder slot="end">
<ion-icon name="pizza"></ion-icon>
</ion-reorder>
</ion-item>
<ion-item>
<ion-label>
Item 7 (custom ion-reorder slot="start")
</ion-label>
<ion-reorder slot="start">
<ion-icon name="pizza"></ion-icon>
</ion-reorder>
</ion-item>
<ion-reorder>
<ion-item>
<ion-label>
Item 8 (the whole item can be dragged)
</ion-label>
</ion-item>
</ion-reorder>
<ion-reorder>
<ion-item>
<ion-label>
Item 9 (the whole item can be dragged)
</ion-label>
</ion-item>
</ion-reorder>
<ion-reorder>
<ion-item>
<ion-label>
Item 10 (the whole item can be dragged)
</ion-label>
</ion-item>
</ion-reorder>
</ion-reorder-group>
</ion-list>
</ion-content>
```