Skip to content

Commit 70dbcc9

Browse files
feat(reorder-group): add ionReorderStart, ionReorderMove, ionReorderEnd events (#30471)
Issue number: resolves #23148 resolves #27614 --------- ## What is the current behavior? The `ion-reorder-group` only emits an `ionItemReorder` event when the reorder gesture ends AND the item position has changed. There is no way to listen for when the gesture starts, is actively moving, or ends without the item changing position. ## What is the new behavior? - Adds an `ionReorderStart` event that is fired without any details on the start of the gesture. - Adds an `ionReorderMove` event that is fired continuously during gesture move and includes the `from` and `to` detail. - Adds an `ionReorderEnd` event that is fired at the end of the gesture and always includes the `from` and `to` detail, even if they are the same. - Deprecates the `ionItemReorder` event, recommending to use the `ionReorderEnd` instead. ## Does this introduce a breaking change? - [ ] Yes - [x] No While this does not introduce a breaking change, it does deprecate the `ionItemReorder` event in favor of the `ionReorderEnd` event. This event behaves a bit differently since it is always emitted on end. If the `from` and `to` are the same, it will still emit them, so it's possible to check if they are the same to determine if `ionReorderEnd` fired without moving item positions. ---- Co-authored-by: sfinktah <[email protected]> Co-authored-by: Brandy Smith <[email protected]>
1 parent 87a1a5f commit 70dbcc9

File tree

22 files changed

+527
-54
lines changed

22 files changed

+527
-54
lines changed

core/api.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,9 @@ ion-reorder-group,none
15081508
ion-reorder-group,prop,disabled,boolean,true,false,false
15091509
ion-reorder-group,method,complete,complete(listOrReorder?: boolean | any[]) => Promise<any>
15101510
ion-reorder-group,event,ionItemReorder,ItemReorderEventDetail,true
1511+
ion-reorder-group,event,ionReorderEnd,ReorderEndEventDetail,true
1512+
ion-reorder-group,event,ionReorderMove,ReorderMoveEventDetail,true
1513+
ion-reorder-group,event,ionReorderStart,void,true
15111514

15121515
ion-ripple-effect,shadow
15131516
ion-ripple-effect,prop,type,"bounded" | "unbounded",'bounded',false,false

core/src/components.d.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { PopoverSize, PositionAlign, PositionReference, PositionSide, TriggerAct
3030
import { RadioGroupChangeEventDetail, RadioGroupCompareFn } from "./components/radio-group/radio-group-interface";
3131
import { PinFormatter, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue } from "./components/range/range-interface";
3232
import { RefresherEventDetail } from "./components/refresher/refresher-interface";
33-
import { ItemReorderEventDetail } from "./components/reorder-group/reorder-group-interface";
33+
import { ItemReorderEventDetail, ReorderEndEventDetail, ReorderMoveEventDetail } from "./components/reorder-group/reorder-group-interface";
3434
import { NavigationHookCallback } from "./components/route/route-interface";
3535
import { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface";
3636
import { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface";
@@ -68,7 +68,7 @@ export { PopoverSize, PositionAlign, PositionReference, PositionSide, TriggerAct
6868
export { RadioGroupChangeEventDetail, RadioGroupCompareFn } from "./components/radio-group/radio-group-interface";
6969
export { PinFormatter, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue } from "./components/range/range-interface";
7070
export { RefresherEventDetail } from "./components/refresher/refresher-interface";
71-
export { ItemReorderEventDetail } from "./components/reorder-group/reorder-group-interface";
71+
export { ItemReorderEventDetail, ReorderEndEventDetail, ReorderMoveEventDetail } from "./components/reorder-group/reorder-group-interface";
7272
export { NavigationHookCallback } from "./components/route/route-interface";
7373
export { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface";
7474
export { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface";
@@ -2770,7 +2770,7 @@ export namespace Components {
27702770
}
27712771
interface IonReorderGroup {
27722772
/**
2773-
* Completes the reorder operation. Must be called by the `ionItemReorder` event. If a list of items is passed, the list will be reordered and returned in the proper order. If no parameters are passed or if `true` is passed in, the reorder will complete and the item will remain in the position it was dragged to. If `false` is passed, the reorder will complete and the item will bounce back to its original position.
2773+
* Completes the reorder operation. Must be called by the `ionReorderEnd` event. If a list of items is passed, the list will be reordered and returned in the proper order. If no parameters are passed or if `true` is passed in, the reorder will complete and the item will remain in the position it was dragged to. If `false` is passed, the reorder will complete and the item will bounce back to its original position.
27742774
* @param listOrReorder A list of items to be sorted and returned in the new order or a boolean of whether or not the reorder should reposition the item.
27752775
*/
27762776
"complete": (listOrReorder?: boolean | any[]) => Promise<any>;
@@ -4755,6 +4755,9 @@ declare global {
47554755
};
47564756
interface HTMLIonReorderGroupElementEventMap {
47574757
"ionItemReorder": ItemReorderEventDetail;
4758+
"ionReorderStart": void;
4759+
"ionReorderMove": ReorderMoveEventDetail;
4760+
"ionReorderEnd": ReorderEndEventDetail;
47584761
}
47594762
interface HTMLIonReorderGroupElement extends Components.IonReorderGroup, HTMLStencilElement {
47604763
addEventListener<K extends keyof HTMLIonReorderGroupElementEventMap>(type: K, listener: (this: HTMLIonReorderGroupElement, ev: IonReorderGroupCustomEvent<HTMLIonReorderGroupElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
@@ -8039,9 +8042,22 @@ declare namespace LocalJSX {
80398042
*/
80408043
"disabled"?: boolean;
80418044
/**
8042-
* Event that needs to be listened to in order to complete the reorder action. Once the event has been emitted, the `complete()` method then needs to be called in order to finalize the reorder action.
8045+
* Event that needs to be listened to in order to complete the reorder action.
8046+
* @deprecated Use `ionReorderEnd` instead. If you are accessing `event.detail.from` or `event.detail.to` and relying on them being different you should now add checks as they are always emitted in `ionReorderEnd`, even when they are the same.
80438047
*/
80448048
"onIonItemReorder"?: (event: IonReorderGroupCustomEvent<ItemReorderEventDetail>) => void;
8049+
/**
8050+
* Event that is emitted when the reorder gesture ends. The from and to properties are always available, regardless of if the reorder gesture moved the item. If the item did not change from its start position, the from and to properties will be the same. Once the event has been emitted, the `complete()` method then needs to be called in order to finalize the reorder action.
8051+
*/
8052+
"onIonReorderEnd"?: (event: IonReorderGroupCustomEvent<ReorderEndEventDetail>) => void;
8053+
/**
8054+
* Event that is emitted as the reorder gesture moves.
8055+
*/
8056+
"onIonReorderMove"?: (event: IonReorderGroupCustomEvent<ReorderMoveEventDetail>) => void;
8057+
/**
8058+
* Event that is emitted when the reorder gesture starts.
8059+
*/
8060+
"onIonReorderStart"?: (event: IonReorderGroupCustomEvent<void>) => void;
80458061
}
80468062
interface IonRippleEffect {
80478063
/**

core/src/components/item/test/reorder/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
}
8585
function initGroup(group) {
8686
var groupEl = document.getElementById(group.id);
87-
groupEl.addEventListener('ionItemReorder', function (ev) {
87+
groupEl.addEventListener('ionReorderEnd', function (ev) {
8888
ev.detail.complete();
8989
});
9090
var groupItems = [];
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,33 @@
1+
// TODO(FW-6590): Remove this once the deprecated event is removed
12
export interface ItemReorderEventDetail {
23
from: number;
34
to: number;
45
complete: (data?: boolean | any[]) => any;
56
}
67

8+
// TODO(FW-6590): Remove this once the deprecated event is removed
79
export interface ItemReorderCustomEvent extends CustomEvent {
810
detail: ItemReorderEventDetail;
911
target: HTMLIonReorderGroupElement;
1012
}
13+
14+
export interface ReorderMoveEventDetail {
15+
from: number;
16+
to: number;
17+
}
18+
19+
export interface ReorderEndEventDetail {
20+
from: number;
21+
to: number;
22+
complete: (data?: boolean | any[]) => any;
23+
}
24+
25+
export interface ReorderMoveCustomEvent extends CustomEvent {
26+
detail: ReorderMoveEventDetail;
27+
target: HTMLIonReorderGroupElement;
28+
}
29+
30+
export interface ReorderEndCustomEvent extends CustomEvent {
31+
detail: ReorderEndEventDetail;
32+
target: HTMLIonReorderGroupElement;
33+
}

core/src/components/reorder-group/reorder-group.tsx

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { hapticSelectionChanged, hapticSelectionEnd, hapticSelectionStart } from
77
import { getIonMode } from '../../global/ionic-global';
88
import type { Gesture, GestureDetail } from '../../interface';
99

10-
import type { ItemReorderEventDetail } from './reorder-group-interface';
10+
import type { ItemReorderEventDetail, ReorderMoveEventDetail, ReorderEndEventDetail } from './reorder-group-interface';
1111

1212
// TODO(FW-2832): types
1313

@@ -51,12 +51,35 @@ export class ReorderGroup implements ComponentInterface {
5151
}
5252
}
5353

54+
// TODO(FW-6590): Remove this in a major release.
5455
/**
5556
* Event that needs to be listened to in order to complete the reorder action.
57+
* @deprecated Use `ionReorderEnd` instead. If you are accessing
58+
* `event.detail.from` or `event.detail.to` and relying on them
59+
* being different you should now add checks as they are always emitted
60+
* in `ionReorderEnd`, even when they are the same.
61+
*/
62+
@Event() ionItemReorder!: EventEmitter<ItemReorderEventDetail>;
63+
64+
/**
65+
* Event that is emitted when the reorder gesture starts.
66+
*/
67+
@Event() ionReorderStart!: EventEmitter<void>;
68+
69+
/**
70+
* Event that is emitted as the reorder gesture moves.
71+
*/
72+
@Event() ionReorderMove!: EventEmitter<ReorderMoveEventDetail>;
73+
74+
/**
75+
* Event that is emitted when the reorder gesture ends.
76+
* The from and to properties are always available, regardless of
77+
* if the reorder gesture moved the item. If the item did not change
78+
* from its start position, the from and to properties will be the same.
5679
* Once the event has been emitted, the `complete()` method then needs
5780
* to be called in order to finalize the reorder action.
5881
*/
59-
@Event() ionItemReorder!: EventEmitter<ItemReorderEventDetail>;
82+
@Event() ionReorderEnd!: EventEmitter<ReorderEndEventDetail>;
6083

6184
async connectedCallback() {
6285
const contentEl = findClosestIonContent(this.el);
@@ -88,7 +111,7 @@ export class ReorderGroup implements ComponentInterface {
88111
}
89112

90113
/**
91-
* Completes the reorder operation. Must be called by the `ionItemReorder` event.
114+
* Completes the reorder operation. Must be called by the `ionReorderEnd` event.
92115
*
93116
* If a list of items is passed, the list will be reordered and returned in the
94117
* proper order.
@@ -164,6 +187,8 @@ export class ReorderGroup implements ComponentInterface {
164187
item.classList.add(ITEM_REORDER_SELECTED);
165188

166189
hapticSelectionStart();
190+
191+
this.ionReorderStart.emit();
167192
}
168193

169194
private onMove(ev: GestureDetail) {
@@ -180,6 +205,7 @@ export class ReorderGroup implements ComponentInterface {
180205
const currentY = Math.max(top, Math.min(ev.currentY, bottom));
181206
const deltaY = scroll + currentY - ev.startY;
182207
const normalizedY = currentY - top;
208+
const fromIndex = this.lastToIndex;
183209
const toIndex = this.itemIndexForTop(normalizedY);
184210
if (toIndex !== this.lastToIndex) {
185211
const fromIndex = indexForItem(selectedItem);
@@ -191,6 +217,11 @@ export class ReorderGroup implements ComponentInterface {
191217

192218
// Update selected item position
193219
selectedItem.style.transform = `translateY(${deltaY}px)`;
220+
221+
this.ionReorderMove.emit({
222+
from: fromIndex,
223+
to: toIndex,
224+
});
194225
}
195226

196227
private onEnd() {
@@ -207,6 +238,7 @@ export class ReorderGroup implements ComponentInterface {
207238
if (toIndex === fromIndex) {
208239
this.completeReorder();
209240
} else {
241+
// TODO(FW-6590): Remove this once the deprecated event is removed
210242
this.ionItemReorder.emit({
211243
from: fromIndex,
212244
to: toIndex,
@@ -215,6 +247,12 @@ export class ReorderGroup implements ComponentInterface {
215247
}
216248

217249
hapticSelectionEnd();
250+
251+
this.ionReorderEnd.emit({
252+
from: fromIndex,
253+
to: toIndex,
254+
complete: this.completeReorder.bind(this),
255+
});
218256
}
219257

220258
private completeReorder(listOrReorder?: boolean | any[]): any {

core/src/components/reorder-group/test/basic/index.html

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,25 @@
122122
const reorderGroup = document.getElementById('reorder');
123123
reorderGroup.disabled = !reorderGroup.disabled;
124124

125+
// TODO(FW-6590): Remove this once the deprecated event is removed
125126
reorderGroup.addEventListener('ionItemReorder', ({ detail }) => {
126-
console.log('Dragged from index', detail.from, 'to', detail.to);
127+
console.log('ionItemReorder: Dragged from index', detail.from, 'to', detail.to);
128+
});
129+
130+
reorderGroup.addEventListener('ionReorderStart', () => {
131+
console.log('ionReorderStart');
132+
});
133+
134+
reorderGroup.addEventListener('ionReorderMove', ({ detail }) => {
135+
console.log('ionReorderMove: Dragged from index', detail.from, 'to', detail.to);
136+
});
137+
138+
reorderGroup.addEventListener('ionReorderEnd', ({ detail }) => {
139+
if (detail.from !== detail.to) {
140+
console.log('ionReorderEnd: Dragged from index', detail.from, 'to', detail.to);
141+
} else {
142+
console.log('ionReorderEnd: No position change occurred');
143+
}
127144

128145
detail.complete();
129146
});

core/src/components/reorder-group/test/data/index.html

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
1515
</head>
1616

17-
<body onLoad="render()">
17+
<body>
1818
<ion-app>
1919
<ion-header>
2020
<ion-toolbar>
@@ -24,7 +24,7 @@
2424

2525
<ion-content id="content">
2626
<ion-list>
27-
<ion-reorder-group id="reorderGroup" disabled="false">
27+
<ion-reorder-group disabled="false">
2828
<!-- items will be inserted here -->
2929
</ion-reorder-group>
3030
</ion-list>
@@ -36,27 +36,44 @@
3636
for (var i = 0; i < 30; i++) {
3737
items.push(i + 1);
3838
}
39-
const reorderGroup = document.getElementById('reorderGroup');
40-
41-
function render() {
42-
let html = '';
43-
for (let item of items) {
44-
html += `
45-
<ion-item>
46-
<ion-label>${item}</ion-label>
47-
<ion-reorder slot="end"></ion-reorder>
48-
</ion-item>`;
49-
}
50-
reorderGroup.innerHTML = html;
51-
}
52-
53-
reorderGroup.addEventListener('ionItemReorder', ({ detail }) => {
54-
console.log('Dragged from index', detail.from, 'to', detail.to);
39+
const reorderGroup = document.querySelector('ion-reorder-group');
40+
reorderItems(items);
5541

42+
reorderGroup.addEventListener('ionReorderEnd', ({ detail }) => {
43+
// Before complete is called with the items they will remain in the
44+
// order before the drag
5645
console.log('Before complete', items);
46+
47+
// Finish the reorder and position the item in the DOM based on
48+
// where the gesture ended. Update the items variable to the
49+
// new order of items
5750
items = detail.complete(items);
51+
52+
// Reorder the items in the DOM
53+
reorderItems(items);
54+
55+
// After complete is called the items will be in the new order
5856
console.log('After complete', items);
5957
});
58+
59+
function reorderItems(items) {
60+
reorderGroup.replaceChildren();
61+
62+
let reordered = '';
63+
64+
for (let i = 0; i < items.length; i++) {
65+
reordered += `
66+
<ion-item>
67+
<ion-label>
68+
Item ${items[i]}
69+
</ion-label>
70+
<ion-reorder slot="end"></ion-reorder>
71+
</ion-item>
72+
`;
73+
}
74+
75+
reorderGroup.innerHTML = reordered;
76+
}
6077
</script>
6178
</body>
6279
</html>

core/src/components/reorder-group/test/interactive/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@
3737
</ion-reorder-group>
3838
<script>
3939
const group = document.querySelector('ion-reorder-group');
40-
group.addEventListener('ionItemReorder', (ev) => {
40+
group.addEventListener('ionReorderEnd', (ev) => {
4141
ev.detail.complete();
42-
window.dispatchEvent(new CustomEvent('ionItemReorderComplete'));
42+
window.dispatchEvent(new CustomEvent('ionReorderComplete'));
4343
});
4444
</script>
4545
</body>

core/src/components/reorder-group/test/interactive/reorder-group.e2e.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,24 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
1111
});
1212
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
1313
const items = page.locator('ion-item');
14-
const ionItemReorderComplete = await page.spyOnEvent('ionItemReorderComplete');
14+
const ionReorderComplete = await page.spyOnEvent('ionReorderComplete');
1515

1616
await expect(items).toContainText(['Item 1', 'Item 2', 'Item 3', 'Item 4']);
1717

1818
await dragElementBy(items.nth(1), page, 0, 300);
19-
await ionItemReorderComplete.next();
19+
await ionReorderComplete.next();
2020

2121
await expect(items).toContainText(['Item 1', 'Item 3', 'Item 4', 'Item 2']);
2222
});
2323
test('should drag and drop when ion-item wraps ion-reorder', async ({ page }) => {
2424
const reorderHandle = page.locator('ion-reorder');
2525
const items = page.locator('ion-item');
26-
const ionItemReorderComplete = await page.spyOnEvent('ionItemReorderComplete');
26+
const ionReorderComplete = await page.spyOnEvent('ionReorderComplete');
2727

2828
await expect(items).toContainText(['Item 1', 'Item 2', 'Item 3', 'Item 4']);
2929

3030
await dragElementBy(reorderHandle.nth(0), page, 0, 300);
31-
await ionItemReorderComplete.next();
31+
await ionReorderComplete.next();
3232

3333
await expect(items).toContainText(['Item 2', 'Item 3', 'Item 4', 'Item 1']);
3434
});

core/src/components/reorder-group/test/nested/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@
6868
customElements.define('app-reorder', AppReorder);
6969

7070
const group = document.querySelector('ion-reorder-group');
71-
group.addEventListener('ionItemReorder', (ev) => {
71+
group.addEventListener('ionReorderEnd', (ev) => {
7272
ev.detail.complete();
73-
window.dispatchEvent(new CustomEvent('ionItemReorderComplete'));
73+
window.dispatchEvent(new CustomEvent('ionReorderComplete'));
7474
});
7575
</script>
7676
</ion-app>

0 commit comments

Comments
 (0)