1- import { useCallback , useEffect , useRef , useState } from 'react' ;
1+ import { useCallback , useState } from 'react' ;
2+
3+ import { useRefEffect } from '../../../hooks' ;
24
35export const useMenuContentController = ( {
46 onOpenChange,
57 side,
68 defaultOpen,
79 sideOffset,
10+ open : controlledOpen ,
811} : {
912 defaultOpen ?: boolean ;
1013 side ?: 'top' | 'bottom' | 'left' | 'right' ;
1114 onOpenChange ?: ( open : boolean ) => void ;
15+ open ?: boolean ;
1216 sideOffset ?: number ;
1317} = { } ) => {
1418 const [ open , setOpen ] = useState ( defaultOpen ?? false ) ;
19+ const actualOpen = controlledOpen ?? open ;
1520 const contentSide = side ?? 'bottom' ;
1621 const [ contentOffset , setContentOffset ] = useState < number > ( 0 ) ;
17- const contentRef = useRef < HTMLDivElement > ( null ) ;
1822
1923 const handleOpenChange = useCallback (
2024 ( open : boolean ) => {
@@ -23,65 +27,69 @@ export const useMenuContentController = ({
2327 } ,
2428 [ onOpenChange ]
2529 ) ;
26- useEffect ( ( ) => {
27- if ( ! open || ! contentRef . current ) return ;
30+ const contentRef = useRefEffect < HTMLDivElement > (
31+ contentElement => {
32+ if ( ! actualOpen ) return ;
2833
29- const wrapperElement = contentRef . current . parentNode as HTMLDivElement ;
34+ const wrapperElement = contentElement . parentNode as HTMLDivElement ;
3035
31- const updateContentOffset = ( ) => {
32- if ( ! contentRef . current ) return ;
33- const contentRect = wrapperElement . getBoundingClientRect ( ) ;
34- if ( contentSide === 'bottom' ) {
35- setContentOffset ( prev => {
36- const viewportHeight = window . innerHeight ;
37- const newOffset = Math . min (
38- viewportHeight - ( contentRect . bottom - prev ) ,
39- 0
40- ) ;
41- return newOffset ;
42- } ) ;
43- } else if ( contentSide === 'top' ) {
44- setContentOffset ( prev => {
45- const newOffset = Math . max ( contentRect . top - prev , 0 ) ;
46- return newOffset ;
47- } ) ;
48- } else if ( contentSide === 'left' ) {
49- setContentOffset ( prev => {
50- const newOffset = Math . max ( contentRect . left - prev , 0 ) ;
51- return newOffset ;
52- } ) ;
53- } else if ( contentSide === 'right' ) {
54- setContentOffset ( prev => {
55- const viewportWidth = window . innerWidth ;
56- const newOffset = Math . min (
57- viewportWidth - ( contentRect . right - prev ) ,
58- 0
59- ) ;
60- return newOffset ;
61- } ) ;
62- }
63- } ;
64- let animationFrame : number = 0 ;
65- const requestUpdateContentOffset = ( ) => {
66- cancelAnimationFrame ( animationFrame ) ;
67- animationFrame = requestAnimationFrame ( updateContentOffset ) ;
68- } ;
36+ const updateContentOffset = ( ) => {
37+ if ( ! contentElement ) return ;
38+ const contentRect = wrapperElement . getBoundingClientRect ( ) ;
39+ if ( contentSide === 'bottom' ) {
40+ setContentOffset ( prev => {
41+ const viewportHeight = window . innerHeight ;
42+ const newOffset = Math . min (
43+ viewportHeight - ( contentRect . bottom - prev ) ,
44+ 0
45+ ) ;
46+ return newOffset ;
47+ } ) ;
48+ } else if ( contentSide === 'top' ) {
49+ setContentOffset ( prev => {
50+ const newOffset = Math . min ( contentRect . top + prev , 0 ) ;
51+ return newOffset ;
52+ } ) ;
53+ } else if ( contentSide === 'left' ) {
54+ setContentOffset ( prev => {
55+ const newOffset = Math . min ( contentRect . left + prev , 0 ) ;
56+ return newOffset ;
57+ } ) ;
58+ } else if ( contentSide === 'right' ) {
59+ setContentOffset ( prev => {
60+ const viewportWidth = window . innerWidth ;
61+ const newOffset = Math . min (
62+ viewportWidth - ( contentRect . right - prev ) ,
63+ 0
64+ ) ;
65+ return newOffset ;
66+ } ) ;
67+ }
68+ } ;
69+ let animationFrame : number = 0 ;
70+ const requestUpdateContentOffset = ( ) => {
71+ cancelAnimationFrame ( animationFrame ) ;
72+ animationFrame = requestAnimationFrame ( updateContentOffset ) ;
73+ } ;
6974
70- const observer = new ResizeObserver ( requestUpdateContentOffset ) ;
71- observer . observe ( wrapperElement ) ;
72- window . addEventListener ( 'resize' , requestUpdateContentOffset ) ;
73- requestUpdateContentOffset ( ) ;
74- return ( ) => {
75- observer . disconnect ( ) ;
76- window . removeEventListener ( 'resize' , requestUpdateContentOffset ) ;
77- cancelAnimationFrame ( animationFrame ) ;
78- } ;
79- } , [ contentSide , open ] ) ;
75+ const observer = new ResizeObserver ( requestUpdateContentOffset ) ;
76+ observer . observe ( wrapperElement ) ;
77+ window . addEventListener ( 'resize' , requestUpdateContentOffset ) ;
78+ requestUpdateContentOffset ( ) ;
79+ return ( ) => {
80+ observer . disconnect ( ) ;
81+ window . removeEventListener ( 'resize' , requestUpdateContentOffset ) ;
82+ cancelAnimationFrame ( animationFrame ) ;
83+ } ;
84+ } ,
85+ [ actualOpen , contentSide ]
86+ ) ;
8087
8188 return {
8289 handleOpenChange,
8390 contentSide,
8491 contentOffset : ( sideOffset ?? 0 ) + contentOffset ,
8592 contentRef,
93+ open : actualOpen ,
8694 } ;
8795} ;
0 commit comments