@@ -18,8 +18,8 @@ export type AccordionItemProps = {
18
18
* @default false
19
19
*/
20
20
defaultOpen ?: boolean ;
21
- /** Callback function when AccordionItem toggles */
22
- onToggle ?: ( ) => void ;
21
+ /** Callback function when AccordionItem toggles due to a find in page */
22
+ onFound ?: ( ) => void ;
23
23
/** Content should be one `<Accordion.Header>` and `<Accordion.Content>` */
24
24
children : ReactNode ;
25
25
} & HTMLAttributes < HTMLDetailsElement > ;
@@ -33,7 +33,7 @@ export type AccordionItemProps = {
33
33
* </AccordionItem>
34
34
*/
35
35
export const AccordionItem = forwardRef < HTMLDetailsElement , AccordionItemProps > (
36
- ( { className, open, defaultOpen = false , onToggle , ...rest } , ref ) => {
36
+ ( { className, open, defaultOpen = false , onFound , ...rest } , ref ) => {
37
37
const isControlled = open !== undefined ;
38
38
const internalOpen = useRef ( open ?? defaultOpen ) ; // Only render open state on server, let <details> handle state in browser
39
39
const detailsRef = useRef < HTMLDetailsElement > ( null ) ;
@@ -42,18 +42,17 @@ export const AccordionItem = forwardRef<HTMLDetailsElement, AccordionItemProps>(
42
42
// Control state with a useEffect to animate on prop change and prevent native <details> toggle
43
43
useEffect ( ( ) => {
44
44
const details = detailsRef . current ;
45
- const summary = details ?. querySelector ( ':scope > u-summary' ) ;
45
+ const summary = details ?. querySelector ( ':scope > :is(summary, u-summary) ' ) ;
46
46
const handleSummaryClick = ( event : Event ) => {
47
47
event ?. preventDefault ( ) ; // Prevent native <details> toggle so we can animate
48
48
if ( ! isControlled && details ) animateToggle ( details ) ;
49
49
} ;
50
50
const handleToggle = ( ) => {
51
- if ( isControlled && details && details ?. open !== open ) {
52
- setTimeout ( ( ) => {
53
- details . open = open ;
54
- onToggle ?.( ) ;
55
- } ) ;
56
- } else onToggle ?.( ) ;
51
+ setTimeout ( ( ) => {
52
+ if ( details ?. open === details ?. hasAttribute ( 'data-open' ) ) return ;
53
+ if ( isControlled ) details ?. toggleAttribute ( 'open' , open ) ;
54
+ onFound ?.( ) ;
55
+ } ) ;
57
56
} ;
58
57
59
58
details ?. addEventListener ( 'toggle' , handleToggle , true ) ;
@@ -69,6 +68,7 @@ export const AccordionItem = forwardRef<HTMLDetailsElement, AccordionItemProps>(
69
68
< u-details
70
69
class = { cl ( 'ds-accordion__item' , className ) } // Using class since React does not translate className on custom elements
71
70
open = { internalOpen . current || undefined } // Fallback to undefined to prevent rendering open="false"
71
+ data-controlled = { isControlled || undefined } // Used to prevent glitchy toggle on beforematch when controlled
72
72
ref = { mergedRefs }
73
73
{ ...rest }
74
74
/>
@@ -78,18 +78,20 @@ export const AccordionItem = forwardRef<HTMLDetailsElement, AccordionItemProps>(
78
78
79
79
AccordionItem . displayName = 'AccordionItem' ;
80
80
81
- function animateToggle ( details : HTMLDetailsElement , open = ! details . open ) {
82
- const content = details . querySelector < HTMLElement > (
83
- ':scope > :not(summary, u-summary)' ,
84
- ) ;
81
+ const animateToggle = ( details : HTMLDetailsElement , open = ! details . open ) => {
85
82
const isAnimateSupported = 'animate' in details ;
86
83
const isReducedMotion = window . matchMedia ?.(
87
84
'(prefers-reduced-motion: reduce)' ,
88
85
) . matches ;
86
+ const content = details . querySelector < HTMLElement > (
87
+ ':scope > :not(summary, u-summary)' ,
88
+ ) ;
89
89
90
90
if ( isReducedMotion || ! isAnimateSupported || ! content ) {
91
+ details . toggleAttribute ( 'data-open' , open ) ; // Used to prevent glitchy toggle on beforematch when controlled
91
92
details . open = open ;
92
93
} else if ( details . open !== open ) {
94
+ details . toggleAttribute ( 'data-open' , true ) ; // Used to prevent glitchy toggle on beforematch when controlled
93
95
details . open = true ;
94
96
const opened = `${ content . scrollHeight } px` ;
95
97
@@ -102,7 +104,8 @@ function animateToggle(details: HTMLDetailsElement, open = !details.open) {
102
104
{ duration : 400 , easing : 'ease-in-out' } ,
103
105
) . onfinish = ( ) => {
104
106
content . style . removeProperty ( 'overflow' ) ; // Restore overlow
107
+ details . toggleAttribute ( 'data-open' , open ) ; // Used to prevent glitchy toggle on beforematch when controlled
105
108
details . open = open ;
106
109
} ;
107
110
}
108
- }
111
+ } ;
0 commit comments