@@ -41,6 +41,53 @@ export class Accordion implements ComponentInterface {
4141 private accordionGroupEl ?: HTMLIonAccordionGroupElement | null ;
4242 private updateListener = ( ev : CustomEvent < AccordionGroupChangeEventDetail > ) => {
4343 const initialUpdate = ev . detail ?. initial ?? false ;
44+ console . log (
45+ '[Accordion]' ,
46+ this . value ,
47+ 'updateListener - initial:' ,
48+ initialUpdate ,
49+ 'hasInteracted:' ,
50+ this . hasInteracted ,
51+ 'hasEverBeenExpanded:' ,
52+ this . hasEverBeenExpanded ,
53+ 'currentState:' ,
54+ this . state
55+ ) ;
56+
57+ /**
58+ * Determine if this update will cause an actual state change.
59+ * We only want to mark as "interacted" if the state is changing.
60+ */
61+ const accordionGroup = this . accordionGroupEl ;
62+ if ( accordionGroup ) {
63+ const value = accordionGroup . value ;
64+ const accordionValue = this . value ;
65+ const shouldExpand = Array . isArray ( value ) ? value . includes ( accordionValue ) : value === accordionValue ;
66+ const isExpanded = this . state === AccordionState . Expanded || this . state === AccordionState . Expanding ;
67+ const stateWillChange = shouldExpand !== isExpanded ;
68+
69+ console . log (
70+ '[Accordion]' ,
71+ this . value ,
72+ 'shouldExpand:' ,
73+ shouldExpand ,
74+ 'isExpanded:' ,
75+ isExpanded ,
76+ 'stateWillChange:' ,
77+ stateWillChange
78+ ) ;
79+
80+ /**
81+ * If this is not an initial update AND the state is actually changing,
82+ * mark that we've had an interaction. This prevents redundant updates
83+ * (like React StrictMode re-renders) from incorrectly enabling animations.
84+ */
85+ if ( ! initialUpdate && stateWillChange ) {
86+ console . log ( '[Accordion]' , this . value , 'Setting hasInteracted to true' ) ;
87+ this . hasInteracted = true ;
88+ }
89+ }
90+
4491 this . updateState ( initialUpdate ) ;
4592 } ;
4693 private contentEl : HTMLDivElement | undefined ;
@@ -55,12 +102,18 @@ export class Accordion implements ComponentInterface {
55102 @State ( ) isNext = false ;
56103 @State ( ) isPrevious = false ;
57104 /**
58- * Tracks whether the component has completed its initial render.
59- * Animations are disabled until after the first render completes.
60- * This prevents the accordion from animating when it starts
61- * expanded or collapsed on initial load.
105+ * Tracks whether a user-initiated interaction has occurred.
106+ * Animations are disabled until the first interaction happens.
107+ * This prevents the accordion from animating when it's programmatically
108+ * set to an expanded or collapsed state on initial load.
109+ */
110+ @State ( ) hasInteracted = false ;
111+
112+ /**
113+ * Tracks if this accordion has ever been expanded.
114+ * Used to prevent the first expansion from animating.
62115 */
63- @ State ( ) hasRendered = false ;
116+ private hasEverBeenExpanded = false ;
64117
65118 /**
66119 * The value of the accordion. Defaults to an autogenerated
@@ -130,18 +183,6 @@ export class Accordion implements ComponentInterface {
130183 } ) ;
131184 }
132185
133- componentDidRender ( ) {
134- /**
135- * After the first render completes, mark that we've rendered.
136- * Setting this state property triggers a re-render, at which point
137- * animations will be enabled. This ensures animations are disabled
138- * only for the initial render, avoiding unwanted animations on load.
139- */
140- if ( ! this . hasRendered ) {
141- this . hasRendered = true ;
142- }
143- }
144-
145186 private setItemDefaults = ( ) => {
146187 const ionItem = this . getSlottedHeaderIonItem ( ) ;
147188 if ( ! ionItem ) {
@@ -236,21 +277,37 @@ export class Accordion implements ComponentInterface {
236277 } ;
237278
238279 private expandAccordion = ( initialUpdate = false ) => {
280+ console . log ( '[Accordion]' , this . value , 'expandAccordion - initialUpdate:' , initialUpdate , 'state:' , this . state ) ;
239281 const { contentEl, contentElWrapper } = this ;
240282 if ( initialUpdate || contentEl === undefined || contentElWrapper === undefined ) {
241283 this . state = AccordionState . Expanded ;
284+ /**
285+ * Mark that this accordion has been expanded at least once.
286+ * Do this even on initial expansion so future interactions animate.
287+ */
288+ this . hasEverBeenExpanded = true ;
289+ console . log ( '[Accordion]' , this . value , 'expandAccordion early return - hasEverBeenExpanded set to true' ) ;
242290 return ;
243291 }
244292
245293 if ( this . state === AccordionState . Expanded ) {
294+ console . log ( '[Accordion]' , this . value , 'expandAccordion - already expanded, returning' ) ;
246295 return ;
247296 }
248297
249298 if ( this . currentRaf !== undefined ) {
250299 cancelAnimationFrame ( this . currentRaf ) ;
251300 }
252301
302+ /**
303+ * Mark that this accordion has been expanded at least once.
304+ * This allows subsequent expansions to animate.
305+ */
306+ this . hasEverBeenExpanded = true ;
307+ console . log ( '[Accordion]' , this . value , 'expandAccordion - hasEverBeenExpanded set to true' ) ;
308+
253309 if ( this . shouldAnimate ( ) ) {
310+ console . log ( '[Accordion]' , this . value , 'expandAccordion - will animate' ) ;
254311 raf ( ( ) => {
255312 this . state = AccordionState . Expanding ;
256313
@@ -315,11 +372,23 @@ export class Accordion implements ComponentInterface {
315372 */
316373 private shouldAnimate = ( ) => {
317374 /**
318- * Don't animate until after the first render cycle completes .
375+ * Don't animate until after the first user interaction .
319376 * This prevents animations on initial load when accordions
320- * start in an expanded or collapsed state.
377+ * start in an expanded or collapsed state programmatically.
378+ *
379+ * Additionally, don't animate the very first expansion even if
380+ * hasInteracted is true. This handles edge cases like React StrictMode
381+ * where effects run twice and might incorrectly mark as interacted.
321382 */
322- if ( ! this . hasRendered ) {
383+ if ( ! this . hasInteracted || ! this . hasEverBeenExpanded ) {
384+ console . log (
385+ '[Accordion]' ,
386+ this . value ,
387+ 'shouldAnimate: false - hasInteracted:' ,
388+ this . hasInteracted ,
389+ 'hasEverBeenExpanded:' ,
390+ this . hasEverBeenExpanded
391+ ) ;
323392 return false ;
324393 }
325394
@@ -418,6 +487,12 @@ export class Accordion implements ComponentInterface {
418487
419488 if ( disabled || readonly ) return ;
420489
490+ /**
491+ * Mark that the user has interacted with the accordion.
492+ * This enables animations for all future state changes.
493+ */
494+ this . hasInteracted = true ;
495+
421496 if ( accordionGroupEl ) {
422497 /**
423498 * Because the accordion group may or may
@@ -438,6 +513,20 @@ export class Accordion implements ComponentInterface {
438513 const expanded = this . state === AccordionState . Expanded || this . state === AccordionState . Expanding ;
439514 const headerPart = expanded ? 'header expanded' : 'header' ;
440515 const contentPart = expanded ? 'content expanded' : 'content' ;
516+ const shouldAnimate = this . shouldAnimate ( ) ;
517+
518+ console . log (
519+ '[Accordion]' ,
520+ this . value ,
521+ 'render - state:' ,
522+ this . state ,
523+ 'shouldAnimate:' ,
524+ shouldAnimate ,
525+ 'hasInteracted:' ,
526+ this . hasInteracted ,
527+ 'hasEverBeenExpanded:' ,
528+ this . hasEverBeenExpanded
529+ ) ;
441530
442531 this . setAria ( expanded ) ;
443532
@@ -456,7 +545,7 @@ export class Accordion implements ComponentInterface {
456545 'accordion-disabled' : disabled ,
457546 'accordion-readonly' : readonly ,
458547
459- 'accordion-animated' : this . shouldAnimate ( ) ,
548+ 'accordion-animated' : shouldAnimate ,
460549 } }
461550 >
462551 < div
0 commit comments