fix(reorder-group): add children fallback for framework compatibility (#30593)

Issue number: resolves #30592

---------

## What is the current behavior?
Reorder group is failing for Angular, React & Vue due to the change from
`children` to `__children`.

## What is the new behavior?
- Fallback to `children` if `__children` is undefined.
- Adds an e2e test for Angular (depends on
https://github.com/ionic-team/ionic-framework/pull/30594)
- Tasks have been created to migrate and add e2e tests for the other
frameworks

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

## Other information

Dev build: `8.7.2-dev.11754087334.1815cf22`

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
This commit is contained in:
Brandy Smith
2025-08-05 10:27:18 -04:00
committed by GitHub
parent 05026c5a48
commit 1cd81b9230
11 changed files with 218 additions and 3 deletions

View File

@ -153,7 +153,7 @@ export class ReorderGroup implements ComponentInterface {
const heights = this.cachedHeights; const heights = this.cachedHeights;
heights.length = 0; heights.length = 0;
const el = this.el; const el = this.el;
const children: any = el.__children; const children: any = el.__children || el.children;
if (!children || children.length === 0) { if (!children || children.length === 0) {
return; return;
} }
@ -259,7 +259,7 @@ export class ReorderGroup implements ComponentInterface {
private completeReorder(listOrReorder?: boolean | any[]): any { private completeReorder(listOrReorder?: boolean | any[]): any {
const selectedItemEl = this.selectedItemEl; const selectedItemEl = this.selectedItemEl;
if (selectedItemEl && this.state === ReorderGroupState.Complete) { if (selectedItemEl && this.state === ReorderGroupState.Complete) {
const children: any = this.el.__children; const children: any = this.el.__children || this.el.children;
const len = children.length; const len = children.length;
const toIndex = this.lastToIndex; const toIndex = this.lastToIndex;
const fromIndex = indexForItem(selectedItemEl); const fromIndex = indexForItem(selectedItemEl);
@ -309,7 +309,7 @@ export class ReorderGroup implements ComponentInterface {
/********* DOM WRITE ********* */ /********* DOM WRITE ********* */
private reorderMove(fromIndex: number, toIndex: number) { private reorderMove(fromIndex: number, toIndex: number) {
const itemHeight = this.selectedItemHeight; const itemHeight = this.selectedItemHeight;
const children: any = this.el.__children; const children: any = this.el.__children || this.el.children;
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
const style = (children[i] as any).style; const style = (children[i] as any).style;
let value = ''; let value = '';

View File

@ -0,0 +1,25 @@
import { expect } from '@playwright/test';
import { test } from '@playwright/test';
import { dragElementBy } from '../../utils/drag-utils';
test.describe('reorder-group: angular standalone', () => {
test('should reorder the items', async ({ page }) => {
await page.goto('/standalone/reorder-group');
// Get initial order
const initialItems = await page.locator('ion-item').allTextContents();
expect(initialItems).toEqual(['Item 1', 'Item 2', 'Item 3']);
const reorderGroup = page.locator('ion-reorder-group');
// Drag the first item down to move it to the end (below Item 3)
await dragElementBy(reorderGroup.locator('ion-reorder').first(), page, 0, 300);
// Wait for the reorder to complete
await page.waitForTimeout(500);
// Verify the new order - Item 1 should now be at the end
const finalItems = await page.locator('ion-item').allTextContents();
expect(finalItems).toEqual(['Item 2', 'Item 3', 'Item 1']);
});
});

View File

@ -19,6 +19,7 @@ export const routes: Routes = [
{ path: 'providers', loadComponent: () => import('../providers/providers.component').then(c => c.ProvidersComponent) }, { path: 'providers', loadComponent: () => import('../providers/providers.component').then(c => c.ProvidersComponent) },
{ path: 'overlay-controllers', loadComponent: () => import('../overlay-controllers/overlay-controllers.component').then(c => c.OverlayControllersComponent) }, { path: 'overlay-controllers', loadComponent: () => import('../overlay-controllers/overlay-controllers.component').then(c => c.OverlayControllersComponent) },
{ path: 'button', loadComponent: () => import('../button/button.component').then(c => c.ButtonComponent) }, { path: 'button', loadComponent: () => import('../button/button.component').then(c => c.ButtonComponent) },
{ path: 'reorder-group', loadComponent: () => import('../reorder-group/reorder-group.component').then(c => c.ReorderGroupComponent) },
{ path: 'icon', loadComponent: () => import('../icon/icon.component').then(c => c.IconComponent) }, { path: 'icon', loadComponent: () => import('../icon/icon.component').then(c => c.IconComponent) },
{ path: 'split-pane', redirectTo: '/standalone/split-pane/inbox', pathMatch: 'full' }, { path: 'split-pane', redirectTo: '/standalone/split-pane/inbox', pathMatch: 'full' },
{ {

View File

@ -28,6 +28,11 @@
Icon Test Icon Test
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item routerLink="/standalone/reorder-group">
<ion-label>
Reorder Group Test
</ion-label>
</ion-item>
</ion-list> </ion-list>
<ion-list> <ion-list>

View File

@ -0,0 +1,36 @@
import { Component } from "@angular/core";
import { IonItem, IonLabel, IonReorder, IonReorderGroup } from '@ionic/angular/standalone';
import { ReorderEndCustomEvent } from "@ionic/angular";
@Component({
selector: 'app-reorder-group',
template: `
<ion-reorder-group disabled="false" (ionReorderEnd)="onReorderEnd($event)">
<ion-item>
<ion-reorder slot="end"></ion-reorder>
<ion-label>Item 1</ion-label>
</ion-item>
<ion-item>
<ion-reorder slot="end"></ion-reorder>
<ion-label>Item 2</ion-label>
</ion-item>
<ion-item>
<ion-reorder slot="end"></ion-reorder>
<ion-label>Item 3</ion-label>
</ion-item>
</ion-reorder-group>
`,
standalone: true,
imports: [IonItem, IonLabel, IonReorder, IonReorderGroup]
})
export class ReorderGroupComponent {
onReorderEnd(event: ReorderEndCustomEvent) {
if (event.detail.from !== event.detail.to) {
console.log('ionReorderEnd: Dragged from index', event.detail.from, 'to', event.detail.to);
} else {
console.log('ionReorderEnd: No position change occurred');
}
event.detail.complete();
}
}

View File

@ -36,6 +36,7 @@ import IonPopoverNested from './pages/overlay-components/IonPopoverNested';
import KeepContentsMounted from './pages/overlay-components/KeepContentsMounted'; import KeepContentsMounted from './pages/overlay-components/KeepContentsMounted';
import OverlayComponents from './pages/overlay-components/OverlayComponents'; import OverlayComponents from './pages/overlay-components/OverlayComponents';
import OverlayHooks from './pages/overlay-hooks/OverlayHooks'; import OverlayHooks from './pages/overlay-hooks/OverlayHooks';
import ReorderGroup from './pages/ReorderGroup';
setupIonicReact(); setupIonicReact();
@ -67,6 +68,7 @@ const App: React.FC = () => (
<Route path="/tabs-direct-navigation" component={TabsDirectNavigation} /> <Route path="/tabs-direct-navigation" component={TabsDirectNavigation} />
<Route path="/icons" component={Icons} /> <Route path="/icons" component={Icons} />
<Route path="/inputs" component={Inputs} /> <Route path="/inputs" component={Inputs} />
<Route path="/reorder-group" component={ReorderGroup} />
</IonRouterOutlet> </IonRouterOutlet>
</IonReactRouter> </IonReactRouter>
</IonApp> </IonApp>

View File

@ -46,6 +46,9 @@ const Main: React.FC<MainProps> = () => {
<IonItem routerLink="/inputs"> <IonItem routerLink="/inputs">
<IonLabel>Inputs</IonLabel> <IonLabel>Inputs</IonLabel>
</IonItem> </IonItem>
<IonItem routerLink="/reorder-group">
<IonLabel>Reorder Group</IonLabel>
</IonItem>
</IonList> </IonList>
</IonContent> </IonContent>
</IonPage> </IonPage>

View File

@ -0,0 +1,58 @@
import React from 'react';
import {
IonBackButton,
IonButtons,
IonContent,
IonHeader,
IonItem,
IonLabel,
IonPage,
IonReorder,
IonReorderGroup,
IonTitle,
IonToolbar,
} from '@ionic/react';
import type { ReorderEndCustomEvent } from '@ionic/react';
const ReorderGroup: React.FC = () => {
const onReorderEnd = (event: ReorderEndCustomEvent) => {
if (event.detail.from !== event.detail.to) {
console.log('ionReorderEnd: Dragged from index', event.detail.from, 'to', event.detail.to);
} else {
console.log('ionReorderEnd: No position change occurred');
}
event.detail.complete();
};
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonBackButton></IonBackButton>
</IonButtons>
<IonTitle>Reorder Group</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonReorderGroup disabled={false} onIonReorderEnd={onReorderEnd}>
<IonItem>
<IonReorder slot="end"></IonReorder>
<IonLabel>Item 1</IonLabel>
</IonItem>
<IonItem>
<IonReorder slot="end"></IonReorder>
<IonLabel>Item 2</IonLabel>
</IonItem>
<IonItem>
<IonReorder slot="end"></IonReorder>
<IonLabel>Item 3</IonLabel>
</IonItem>
</IonReorderGroup>
</IonContent>
</IonPage>
);
};
export default ReorderGroup;

View File

@ -86,6 +86,10 @@ const routes: Array<RouteRecordRaw> = [
path: '/components/range', path: '/components/range',
component: () => import('@/views/Range.vue') component: () => import('@/views/Range.vue')
}, },
{
path: '/reorder-group',
component: () => import('@/views/ReorderGroup.vue')
},
{ {
path: '/nested', path: '/nested',
component: () => import('@/views/RouterOutlet.vue'), component: () => import('@/views/RouterOutlet.vue'),

View File

@ -29,6 +29,9 @@
<ion-item router-link="/navigation" id="navigation"> <ion-item router-link="/navigation" id="navigation">
<ion-label>Navigation</ion-label> <ion-label>Navigation</ion-label>
</ion-item> </ion-item>
<ion-item router-link="/reorder-group">
<ion-label>Reorder Group</ion-label>
</ion-item>
<ion-item router-link="/routing" id="routing"> <ion-item router-link="/routing" id="routing">
<ion-label>Routing</ion-label> <ion-label>Routing</ion-label>
</ion-item> </ion-item>

View File

@ -0,0 +1,78 @@
<template>
<ion-page>
<ion-header :translucent="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Reorder Group</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true">
<ion-reorder-group :disabled="false" @ion-reorder-end="onReorderEnd">
<ion-item>
<ion-reorder slot="end"></ion-reorder>
<ion-label>Item 1</ion-label>
</ion-item>
<ion-item>
<ion-reorder slot="end"></ion-reorder>
<ion-label>Item 2</ion-label>
</ion-item>
<ion-item>
<ion-reorder slot="end"></ion-reorder>
<ion-label>Item 3</ion-label>
</ion-item>
</ion-reorder-group>
</ion-content>
</ion-page>
</template>
<script lang="ts">
import {
IonButtons,
IonBackButton,
IonContent,
IonHeader,
IonItem,
IonLabel,
IonPage,
IonReorder,
IonReorderGroup,
IonTitle,
IonToolbar,
} from "@ionic/vue";
import { defineComponent } from "vue";
import type { ReorderEndCustomEvent } from "@ionic/vue";
export default defineComponent({
components: {
IonButtons,
IonBackButton,
IonContent,
IonHeader,
IonItem,
IonLabel,
IonPage,
IonReorder,
IonReorderGroup,
IonTitle,
IonToolbar,
},
setup() {
const onReorderEnd = (event: ReorderEndCustomEvent) => {
if (event.detail.from !== event.detail.to) {
console.log('ionReorderEnd: Dragged from index', event.detail.from, 'to', event.detail.to);
} else {
console.log('ionReorderEnd: No position change occurred');
}
event.detail.complete();
};
return {
onReorderEnd,
};
},
});
</script>