@@ -49,14 +49,18 @@ export function useDialogAnchor<T extends HTMLElement>({
49
49
50
50
type DialogAnchorProps = PropsWithChildren < Partial < DialogAnchorOptions > > & {
51
51
id : string ;
52
+ focus ?: boolean ;
53
+ trapFocus ?: boolean ;
52
54
} & ComponentProps < 'div' > ;
53
55
54
56
export const DialogAnchor = ( {
55
57
children,
56
58
className,
59
+ focus = true ,
57
60
id,
58
61
placement = 'auto' ,
59
62
referenceElement = null ,
63
+ trapFocus,
60
64
...restDivProps
61
65
} : DialogAnchorProps ) => {
62
66
const open = useDialogIsOpen ( id ) ;
@@ -66,6 +70,43 @@ export const DialogAnchor = ({
66
70
referenceElement,
67
71
} ) ;
68
72
73
+ // handle focus and focus trap inside the dialog
74
+ useEffect ( ( ) => {
75
+ if ( ! popperElementRef . current || ! focus || ! open ) return ;
76
+ const container = popperElementRef . current ;
77
+ container . focus ( ) ;
78
+
79
+ if ( ! trapFocus ) return ;
80
+ const handleKeyDownWithTabRoundRobin = ( event : KeyboardEvent ) => {
81
+ if ( event . key !== 'Tab' ) return ;
82
+
83
+ const focusableElements = getFocusableElements ( container ) ;
84
+ if ( focusableElements . length === 0 ) return ;
85
+
86
+ const firstElement = focusableElements [ 0 ] as HTMLElement ;
87
+ const lastElement = focusableElements [ focusableElements . length - 1 ] as HTMLElement ;
88
+ if ( firstElement === lastElement ) {
89
+ event . preventDefault ( ) ;
90
+ firstElement . focus ( ) ;
91
+ }
92
+
93
+ // Trap focus within the group
94
+ if ( event . shiftKey && document . activeElement === firstElement ) {
95
+ // If Shift + Tab on the first element, move focus to the last element
96
+ event . preventDefault ( ) ;
97
+ lastElement . focus ( ) ;
98
+ } else if ( ! event . shiftKey && document . activeElement === lastElement ) {
99
+ // If Tab on the last element, move focus to the first element
100
+ event . preventDefault ( ) ;
101
+ firstElement . focus ( ) ;
102
+ }
103
+ } ;
104
+
105
+ container . addEventListener ( 'keydown' , handleKeyDownWithTabRoundRobin ) ;
106
+
107
+ return ( ) => container . removeEventListener ( 'keydown' , handleKeyDownWithTabRoundRobin ) ;
108
+ } , [ focus , popperElementRef , open , trapFocus ] ) ;
109
+
69
110
return (
70
111
< DialogPortalEntry dialogId = { id } >
71
112
< div
@@ -75,9 +116,16 @@ export const DialogAnchor = ({
75
116
data-testid = 'str-chat__dialog-contents'
76
117
ref = { popperElementRef }
77
118
style = { styles . popper }
119
+ tabIndex = { 0 }
78
120
>
79
121
{ children }
80
122
</ div >
81
123
</ DialogPortalEntry >
82
124
) ;
83
125
} ;
126
+
127
+ function getFocusableElements ( container : HTMLElement ) {
128
+ return container . querySelectorAll (
129
+ 'a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])' ,
130
+ ) ;
131
+ }
0 commit comments