@@ -2,7 +2,7 @@ import { withHeadless } from '@primereact/core/headless';
22import { useFocusTrap } from '@primereact/headless/focustrap' ;
33import { useControlledState } from '@primereact/hooks/use-controlled-state' ;
44import { useEventListener } from '@primereact/hooks/use-event-listener' ;
5- import { focus , getFirstFocusableElement } from '@primeuix/utils/dom' ;
5+ import { focus , getFirstFocusableElement , toElement } from '@primeuix/utils/dom' ;
66import * as React from 'react' ;
77import { defaultProps } from './usePopover.props' ;
88
@@ -18,58 +18,80 @@ export const usePopover = withHeadless({
1818 const [ rendered , setRendered ] = React . useState < boolean > ( ! ! openState ) ;
1919
2020 // elements
21- const [ anchorEl , setAnchorEl ] = React . useState < HTMLElement | null > ( null ) ;
22- const [ positionerEl , setPositionerEl ] = React . useState < HTMLDivElement | null > ( null ) ;
23- const [ popupEl , setPopupEl ] = React . useState < HTMLElement | null > ( null ) ;
24- const [ arrowEl , setArrowEl ] = React . useState < HTMLDivElement | null > ( null ) ;
21+ const [ anchorElement , setAnchorElement ] = React . useState < HTMLElement | null > ( null ) ;
22+ const [ positionerElement , setPositionerElement ] = React . useState < HTMLDivElement | null > ( null ) ;
23+ const [ popupElement , setPopupElement ] = React . useState < HTMLElement | null > ( null ) ;
24+ const [ arrowElement , setArrowElement ] = React . useState < HTMLDivElement | null > ( null ) ;
2525
2626 const state = {
2727 open : openState ,
2828 rendered,
2929 trapped : ! ! props . trapped ,
30- anchorEl ,
31- positionerEl ,
32- popupEl ,
33- arrowEl
30+ anchorElement ,
31+ positionerElement ,
32+ popupElement ,
33+ arrowElement
3434 } ;
3535
3636 const anchorRef = React . useRef < HTMLElement | null > ( null ) ;
37+ const anchorFallbackRef = React . useRef < HTMLElement | null > ( null ) ;
3738 const positionerRef = React . useRef < HTMLDivElement | null > ( null ) ;
3839 const popupRef = React . useRef < HTMLElement | null > ( null ) ;
3940 const arrowRef = React . useRef < HTMLDivElement | null > ( null ) ;
4041
4142 const focusTrap = useFocusTrap ( {
42- trapped : ! ! props . trapped && ! ! popupEl ,
43+ trapped : ! ! props . trapped && ! ! popupElement ,
4344 autoFocus : false
4445 } ) ;
4546
4647 const setAnchorRef = React . useCallback ( ( node : HTMLElement | null ) => {
47- if ( node === anchorRef . current ) return ;
48+ const element = toElement ( node ) ?? null ;
4849
49- anchorRef . current = node ;
50- setAnchorEl ( node ) ;
50+ if ( ! element || element === anchorRef . current ) return ;
51+
52+ anchorRef . current = element ;
53+ setAnchorElement ( element || anchorFallbackRef . current ) ;
54+ } , [ ] ) ;
55+
56+ const setAnchorFallbackRef = React . useCallback ( ( node : HTMLElement | null ) => {
57+ const element = toElement ( node ) ?? null ;
58+
59+ if ( ! element || element === anchorFallbackRef . current ) return ;
60+
61+ anchorFallbackRef . current = element ;
62+
63+ if ( ! anchorRef . current ) {
64+ anchorRef . current = element ;
65+ setAnchorElement ( element ) ;
66+ }
5167 } , [ ] ) ;
5268
5369 const setPositionerRef = React . useCallback ( ( node : HTMLDivElement | null ) => {
54- if ( node === positionerRef . current ) return ;
70+ const element = ( toElement ( node ) ?? null ) as HTMLDivElement | null ;
71+
72+ if ( ! element || element === positionerRef . current ) return ;
5573
56- positionerRef . current = node ;
57- setPositionerEl ( node ) ;
74+ positionerRef . current = element ;
75+ setPositionerElement ( element ) ;
5876 } , [ ] ) ;
5977
6078 const setPopupRef = React . useCallback ( ( node : HTMLElement | null ) => {
61- if ( node === popupRef . current ) return ;
79+ const element = toElement ( node ) ?? null ;
6280
63- popupRef . current = node ;
64- focusTrap . containerRef . current = node ;
65- setPopupEl ( node ) ;
81+ if ( ! element || element === popupRef . current ) return ;
82+
83+ popupRef . current = element ;
84+ focusTrap . containerRef . current = element ;
85+ setPopupElement ( element ) ;
6686 } , [ ] ) ;
6787
6888 const setArrowRef = React . useCallback ( ( node : HTMLDivElement | null ) => {
69- if ( node === arrowRef . current ) return ;
89+ const element = ( toElement ( node ) ?? null ) as HTMLDivElement | null ;
90+
91+ if ( ! element || element === arrowRef . current ) return ;
7092
71- arrowRef . current = node ;
72- setArrowEl ( node ) ;
93+ arrowRef . current = element ;
94+ setArrowElement ( element ) ;
7395 } , [ ] ) ;
7496
7597 function setOpen ( open : boolean , originalEvent ?: Event ) {
@@ -85,40 +107,42 @@ export const usePopover = withHeadless({
85107 const handleFocusOut = ( event : FocusEvent ) => {
86108 const relatedTarget = event . relatedTarget as Node | null ;
87109
88- if ( ! relatedTarget || positionerRef . current ?. contains ( relatedTarget ) || anchorRef . current ?. contains ( relatedTarget ) ) return ;
110+ if ( ! relatedTarget || positionerRef . current ?. contains ( relatedTarget ) || anchorRef . current ?. contains ( relatedTarget ) || anchorFallbackRef . current ?. contains ( relatedTarget ) ) return ;
89111
90112 setOpen ( false , event ) ;
91113 } ;
92114
93115 const onOpenComplete = React . useCallback ( ( ) => {
94116 if ( props . autoFocus === false ) return ;
95117
96- const popupElement = popupRef . current ;
118+ const popupNode = popupRef . current ;
97119
98- if ( ! popupElement ) return ;
120+ if ( ! popupNode ) return ;
99121
100122 const activeElement = document . activeElement as HTMLElement | null ;
123+ const anchorNode = anchorRef . current || anchorFallbackRef . current ;
101124
102- if ( activeElement && anchorRef . current ?. contains ( activeElement ) && anchorRef . current !== activeElement ) {
125+ if ( activeElement && anchorNode ?. contains ( activeElement ) && anchorNode !== activeElement ) {
103126 return ;
104127 }
105128
106- const firstFocusable = getFirstFocusableElement ( popupElement ) ;
129+ const firstFocusable = getFirstFocusableElement ( popupNode ) ;
107130
108131 if ( firstFocusable ) {
109132 focus ( firstFocusable as HTMLElement , { preventScroll : true } ) ;
110133 } else {
111- focus ( popupElement , { preventScroll : true } ) ;
134+ focus ( popupNode , { preventScroll : true } ) ;
112135 }
113136 } , [ props . autoFocus ] ) ;
114137
115138 const [ bindOutsideClickListener , unbindOutsideClickListener ] = useEventListener ( {
116139 type : 'click' ,
117140 listener : ( event : Event ) => {
118- const positionerElement = positionerRef . current ;
119- const anchorElement = anchorRef . current ;
141+ const positionerNode = positionerRef . current ;
142+ const anchorNode = anchorRef . current ;
143+ const anchorFallbackNode = anchorFallbackRef . current ;
120144
121- if ( openState && positionerElement && ! positionerElement . contains ( event . target as Node ) && ( ! anchorElement || ! anchorElement . contains ( event . target as Node ) ) ) {
145+ if ( openState && positionerNode && ! positionerNode . contains ( event . target as Node ) && ( ! anchorNode || ! anchorNode . contains ( event . target as Node ) ) && ( ! anchorFallbackNode || ! anchorFallbackNode . contains ( event . target as Node ) ) ) {
122146 setOpen ( false , event ) ;
123147 }
124148 }
@@ -129,7 +153,7 @@ export const usePopover = withHeadless({
129153 listener : ( event : Event ) => {
130154 if ( ( event as KeyboardEvent ) . key === 'Escape' ) {
131155 setOpen ( false , event ) ;
132- focus ( anchorRef . current as HTMLElement , { preventScroll : true } ) ;
156+ focus ( ( anchorRef . current || anchorFallbackRef . current ) as HTMLElement , { preventScroll : true } ) ;
133157 }
134158 }
135159 } ) ;
@@ -155,14 +179,14 @@ export const usePopover = withHeadless({
155179 } , [ openState , props . closeOnEscape , bindOutsideClickListener , unbindOutsideClickListener , bindEscapeListener , unbindEscapeListener ] ) ;
156180
157181 React . useEffect ( ( ) => {
158- const positionerElement = positionerRef . current ;
182+ const positionerNode = positionerRef . current ;
159183
160- if ( ! openState || ! positionerElement || props . trapped ) return ;
184+ if ( ! openState || ! positionerNode || props . trapped ) return ;
161185
162- positionerElement . addEventListener ( 'focusout' , handleFocusOut ) ;
186+ positionerNode . addEventListener ( 'focusout' , handleFocusOut ) ;
163187
164188 return ( ) => {
165- positionerElement . removeEventListener ( 'focusout' , handleFocusOut ) ;
189+ positionerNode . removeEventListener ( 'focusout' , handleFocusOut ) ;
166190 } ;
167191 } , [ openState , props . trapped , handleFocusOut ] ) ;
168192
@@ -198,6 +222,7 @@ export const usePopover = withHeadless({
198222 // refs
199223 setArrowRef,
200224 setAnchorRef,
225+ setAnchorFallbackRef,
201226 setPopupRef,
202227 setPositionerRef,
203228 // prop getters
0 commit comments