Compare commits

...

8 Commits

Author SHA1 Message Date
Brandy Smith
0d084fd4bc fix(radio-group): remove wrapper causing Node.removeChild errors 2025-08-18 17:08:33 -04:00
Brandy Smith
9e1cf88acd revert unrelated test app changes 2025-08-18 17:04:22 -04:00
Brandy Smith
4054c879d5 chore: build 2025-08-18 17:02:44 -04:00
Brandy Smith
374bbe1f07 test(radio): remove initial value 2025-08-18 17:01:08 -04:00
Brandy Smith
78208e2052 test(core): add radio group test to reproduce blur issue 2025-08-18 17:01:07 -04:00
Brandy Smith
770d9b2511 test(angular): add radio group test to reproduce blur issue 2025-08-18 17:01:07 -04:00
Brandy Smith
d43c13c8a0 test(react): add radio group page 2025-08-18 17:00:51 -04:00
Brandy Smith
9bbb73ecde test(react): fix test app navigation and routing 2025-08-18 17:00:09 -04:00
8 changed files with 195 additions and 9 deletions

View File

@@ -293,14 +293,7 @@ export class RadioGroup implements ComponentInterface {
class={mode}
>
{this.renderHintText()}
{/*
TODO(FW-6279): Wrapping the slot in a div is a workaround due to a
Stencil issue. Without the wrapper, the children radio will fire the
blur event on focus, instead of waiting for them to be blurred.
*/}
<div class="radio-group-wrapper">
<slot></slot>
</div>
<slot></slot>
</Host>
);
}

View File

@@ -24,7 +24,7 @@
<ion-content>
<ion-list>
<ion-radio-group name="items" id="group" value="1">
<ion-radio-group name="items" id="group">
<ion-list-header>
<ion-label>Radio Group Header</ion-label>
</ion-list-header>
@@ -45,8 +45,38 @@
<ion-radio value="4">Item 4</ion-radio>
</ion-item>
</ion-radio-group>
<ion-radio-group id="dynamic">
<ion-list-header>
<ion-label>Dynamic Radio Group</ion-label>
</ion-list-header>
</ion-radio-group>
</ion-list>
</ion-content>
</ion-app>
<script>
const dynamicGroup = document.getElementById('dynamic');
// Keep the existing radio-level listeners for comparison
document.addEventListener('ionBlur', (ev) => {
console.log('ionBlur', ev);
});
document.addEventListener('ionFocus', (ev) => {
console.log('ionFocus', ev);
});
setTimeout(() => {
for (let i = 1; i < 11; i++) {
const item = document.createElement('ion-item');
const radio = document.createElement('ion-radio');
radio.value = `item-${i}`;
radio.innerText = `Item ${i}`;
item.appendChild(radio);
dynamicGroup.appendChild(item);
}
}, 1000);
</script>
</body>
</html>

View File

@@ -21,6 +21,7 @@ export const routes: Routes = [
{ 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: 'radio-group', loadComponent: () => import('../radio-group/radio-group.component').then(c => c.RadioGroupComponent) },
{ path: 'split-pane', redirectTo: '/standalone/split-pane/inbox', pathMatch: 'full' },
{
path: 'split-pane',

View File

@@ -32,6 +32,10 @@
<ion-label>
Reorder Group Test
</ion-label>
<ion-item routerLink="/standalone/radio-group">
<ion-label>
Radio Group Test
</ion-label>
</ion-item>
</ion-list>

View File

@@ -0,0 +1,64 @@
import { Component } from "@angular/core";
import { IonItem, IonLabel, IonList, IonListHeader, IonRadio, IonRadioGroup } from '@ionic/angular/standalone';
@Component({
selector: 'app-radio-group',
template: `
<ion-list>
<ion-list-header>
<ion-label>Static Radio Group</ion-label>
</ion-list-header>
<ion-radio-group>
<ion-item>
<ion-radio (ionBlur)="onBlur($event)" (ionFocus)="onFocus($event)" value="one">Item 1</ion-radio>
</ion-item>
<ion-item>
<ion-radio (ionBlur)="onBlur($event)" (ionFocus)="onFocus($event)" value="two">Item 2</ion-radio>
</ion-item>
<ion-item>
<ion-radio (ionBlur)="onBlur($event)" (ionFocus)="onFocus($event)" value="three">Item 3</ion-radio>
</ion-item>
</ion-radio-group>
<ion-list-header>
<ion-label>Dynamic Radio Group</ion-label>
</ion-list-header>
<ion-radio-group>
@for (item of items; track item.value) {
<ion-item>
<ion-radio (ionBlur)="onBlur($event)" (ionFocus)="onFocus($event)" [value]="item.value">{{item.text}}</ion-radio>
</ion-item>
}
</ion-radio-group>
</ion-list>`,
standalone: true,
imports: [IonItem, IonLabel, IonList, IonListHeader, IonRadio, IonRadioGroup]
})
export class RadioGroupComponent {
items: { text: string; value: string }[] = [];
ionViewWillEnter() {
this.items = [
{
text: 'Item 1',
value: 'item-1'
},
{
text: 'Item 2',
value: 'item-2'
},
{
text: 'Item 3',
value: 'item-3'
}
];
}
onBlur(event: any) {
console.log('ionBlur', event);
}
onFocus(event: any) {
console.log('ionFocus', event);
}
}

View File

@@ -24,6 +24,7 @@ import './theme/variables.css';
import Icons from './pages/Icons';
import Inputs from './pages/Inputs';
import Main from './pages/Main';
import RadioGroup from './pages/RadioGroup';
import Tabs from './pages/Tabs';
import TabsBasic from './pages/TabsBasic';
import NavComponent from './pages/navigation/NavComponent';
@@ -69,6 +70,7 @@ const App: React.FC = () => (
<Route path="/icons" component={Icons} />
<Route path="/inputs" component={Inputs} />
<Route path="/reorder-group" component={ReorderGroup} />
<Route path="/radio-group" component={RadioGroup} />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>

View File

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

View File

@@ -0,0 +1,89 @@
import { IonBackButton, IonButtons, IonContent, IonHeader, IonItem, IonList, IonPage, IonRadio, IonRadioGroup, IonSearchbar, IonTitle, IonToolbar } from '@ionic/react';
import React, { useState } from 'react';
interface RadioGroupProps {
}
export interface Item {
text: string;
value: string;
}
const items: Item[] = [
{ text: 'Apple', value: 'apple' },
{ text: 'Apricot', value: 'apricot' },
{ text: 'Banana', value: 'banana' },
{ text: 'Blackberry', value: 'blackberry' },
{ text: 'Blueberry', value: 'blueberry' },
{ text: 'Cherry', value: 'cherry' },
{ text: 'Cranberry', value: 'cranberry' },
{ text: 'Grape', value: 'grape' },
{ text: 'Grapefruit', value: 'grapefruit' },
{ text: 'Guava', value: 'guava' },
];
const RadioGroup: React.FC<RadioGroupProps> = () => {
const [filteredItems, setFilteredItems] = useState<Item[]>([...items]);
const searchbarInput = (event: any) => {
filterList(event.target.value);
};
/**
* Update the rendered view with
* the provided search query. If no
* query is provided, all data
* will be rendered.
*/
const filterList = (searchQuery: string | null | undefined) => {
/**
* If no search query is defined,
* return all options.
*/
if (searchQuery === undefined || searchQuery === null) {
setFilteredItems([...items]);
} else {
/**
* Otherwise, normalize the search
* query and check to see which items
* contain the search query as a substring.
*/
const normalizedQuery = searchQuery.toLowerCase();
setFilteredItems(
items.filter((item) => {
return item.text.toLowerCase().includes(normalizedQuery);
})
);
}
};
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonBackButton></IonBackButton>
</IonButtons>
<IonTitle>Radio Group</IonTitle>
</IonToolbar>
<IonToolbar>
<IonSearchbar onIonInput={searchbarInput}></IonSearchbar>
</IonToolbar>
</IonHeader>
<IonContent>
<IonList inset={true}>
<IonRadioGroup>
{filteredItems.map((item) => (
<IonItem key={item.value}>
<IonRadio value={item.value}>{item.text}</IonRadio>
</IonItem>
))}
</IonRadioGroup>
</IonList>
</IonContent>
</IonPage>
);
};
export default RadioGroup;