()
| 370 | }; |
| 371 | |
| 372 | let tab = () => { |
| 373 | if (!allowsTabNavigation && ref.current) { |
| 374 | // There may be elements that are "tabbable" inside a collection (e.g. in a grid cell). |
| 375 | // However, collections should be treated as a single tab stop, with arrow key navigation internally. |
| 376 | // We don't control the rendering of these, so we can't override the tabIndex to prevent tabbing. |
| 377 | // Instead, we handle the Tab key, and move focus manually to the first/last tabbable element |
| 378 | // in the collection, so that the browser default behavior will apply starting from that element |
| 379 | // rather than the currently focused one. |
| 380 | |
| 381 | let walker = getFocusableTreeWalker(ref.current, {tabbable: true}); |
| 382 | let next: FocusableElement | undefined = undefined; |
| 383 | let last: FocusableElement; |
| 384 | do { |
| 385 | last = walker.lastChild() as FocusableElement; |
| 386 | if (last) { |
| 387 | next = last; |
| 388 | } |
| 389 | } while (last); |
| 390 | |
| 391 | // If the active element is NOT tabbable but is contained by an element that IS tabbable (aka the cell), the browser will actually move focus to |
| 392 | // the containing element. We need to special case this so that tab will move focus out of the grid instead of looping between |
| 393 | // focusing the containing cell and back to the non-tabbable child element |
| 394 | let activeElement = getActiveElement(); |
| 395 | if (next && (!isFocusWithin(next) || (activeElement && !isTabbable(activeElement)))) { |
| 396 | focusWithoutScrolling(next); |
| 397 | } |
| 398 | } |
| 399 | return {shouldContinuePropagation: true, shouldPreventDefault: false}; |
| 400 | }; |
| 401 | |
| 402 | let shiftTab = () => { |
| 403 | if (!allowsTabNavigation && ref.current) { |
nothing calls this directly
no test coverage detected