1
- import React , { FC , ReactElement , cloneElement , useEffect , useRef } from 'react'
2
- import { mergeRefs , focusableChildren } from './utils'
1
+ import React , { FC , ReactElement , cloneElement , use , useEffect , useRef } from 'react'
2
+ import { mergeRefs , focusableChildren , getChildRef } from './utils'
3
3
4
4
export interface CFocusTrapProps {
5
5
/**
@@ -146,7 +146,10 @@ export const CFocusTrap: FC<CFocusTrapProps> = ({
146
146
147
147
if ( elements . length === 0 ) {
148
148
container . focus ( { preventScroll :true } )
149
- } else if ( lastTabNavDirectionRef . current === 'backward' ) {
149
+ return
150
+ }
151
+
152
+ if ( lastTabNavDirectionRef . current === 'backward' ) {
150
153
elements . at ( - 1 ) ?. focus ( { preventScroll :true } )
151
154
} else {
152
155
elements [ 0 ] . focus ( { preventScroll :true } )
@@ -161,20 +164,37 @@ export const CFocusTrap: FC<CFocusTrapProps> = ({
161
164
tabEventSourceRef . current = container
162
165
lastTabNavDirectionRef . current = event . shiftKey ?'backward' :'forward'
163
166
164
- if ( ! _additionalContainer ) {
165
- return
166
- }
167
-
168
167
const containerElements = focusableChildren ( container )
169
- const additionalElements = focusableChildren ( _additionalContainer )
168
+ const additionalElements = _additionalContainer ? focusableChildren ( _additionalContainer ) : [ ]
170
169
171
170
if ( containerElements . length === 0 && additionalElements . length === 0 ) {
172
171
// No focusable elements, prevent tab
173
172
event . preventDefault ( )
174
173
return
175
174
}
176
175
176
+ const focusableElements = [ ...containerElements , ...additionalElements ]
177
+
178
+ const firstFocusableElement = focusableElements [ 0 ] as HTMLElement
179
+ const lastFocusableElement = focusableElements . at ( - 1 ) as HTMLElement
177
180
const activeElement = document . activeElement as HTMLElement
181
+
182
+ if ( event . shiftKey && activeElement === firstFocusableElement ) {
183
+ event . preventDefault ( )
184
+ lastFocusableElement . focus ( )
185
+ return
186
+ }
187
+
188
+ if ( ! event . shiftKey && activeElement === lastFocusableElement ) {
189
+ event . preventDefault ( )
190
+ firstFocusableElement . focus ( )
191
+ return
192
+ }
193
+
194
+ if ( ! _additionalContainer ) {
195
+ return
196
+ }
197
+
178
198
const isInContainer = containerElements . includes ( activeElement )
179
199
const isInAdditional = additionalElements . includes ( activeElement )
180
200
@@ -245,7 +265,12 @@ export const CFocusTrap: FC<CFocusTrapProps> = ({
245
265
246
266
// Attach our ref to the ONLY child — no extra wrappers
247
267
const onlyChild = React . Children . only ( children )
248
- const childRef = ( onlyChild as React . ReactElement & { ref ?:React . Ref < HTMLElement > } ) . ref
268
+
269
+ // Handle different ref access patterns between React versions
270
+ // React 19+: ref is accessed via element.props.ref
271
+ // React 18 and earlier: ref is accessed via element.ref
272
+ const childRef :React . Ref < HTMLElement > | undefined = getChildRef ( onlyChild )
273
+
249
274
const mergedRef = mergeRefs ( childRef , ( node :HTMLElement | null ) => {
250
275
containerRef . current = node
251
276
} )