1
1
import * as React from 'react' ;
2
+ import { Platform } from 'react-native' ;
2
3
import type { View , AccessibilityState , LayoutRectangle } from 'react-native' ;
3
4
4
5
import { memoize , mergeStyles } from '@fluentui-react-native/framework' ;
5
6
import type { LayoutEvent } from '@fluentui-react-native/interactive-hooks' ;
6
7
import { useSelectedKey } from '@fluentui-react-native/interactive-hooks' ;
8
+ import type { IKeyboardEvent } from '@office-iss/react-native-win32' ;
7
9
8
10
import type { TabListInfo , TabListProps } from './TabList.types' ;
9
11
import type { AnimatedIndicatorStyles } from '../TabListAnimatedIndicator/TabListAnimatedIndicator.types' ;
@@ -72,6 +74,41 @@ export const useTabList = (props: TabListProps): TabListInfo => {
72
74
[ setTabKeys ] ,
73
75
) ;
74
76
77
+ const incrementSelectedTab = React . useCallback (
78
+ ( goBackward : boolean ) => {
79
+ const currentIndex = tabKeys . indexOf ( selectedTabKey ) ;
80
+
81
+ const direction = goBackward ? - 1 : 1 ;
82
+ let increment = 1 ;
83
+ let newTabKey : string ;
84
+
85
+ // We want to only switch selection to non-disabled tabs. This loop allows us to skip over disabled ones.
86
+ while ( increment <= tabKeys . length ) {
87
+ let newIndex = ( currentIndex + direction * increment ) % tabKeys . length ;
88
+
89
+ if ( newIndex < 0 ) {
90
+ newIndex = tabKeys . length + newIndex ;
91
+ }
92
+
93
+ newTabKey = tabKeys [ newIndex ] ;
94
+
95
+ if ( disabledStateMap [ newTabKey ] ) {
96
+ increment += 1 ;
97
+ } else {
98
+ break ;
99
+ }
100
+ }
101
+
102
+ // Unable to find a non-disabled next tab, early return
103
+ if ( increment > tabKeys . length ) {
104
+ return ;
105
+ }
106
+
107
+ data . onKeySelect ( newTabKey ) ;
108
+ } ,
109
+ [ data , disabledStateMap , selectedTabKey , tabKeys ] ,
110
+ ) ;
111
+
75
112
// State variables and functions for saving layout info and other styling information to style the animated indicator.
76
113
const [ listLayoutMap , setListLayoutMap ] = React . useState < { [ key : string ] : LayoutRectangle } > ( { } ) ;
77
114
const [ tabListLayout , setTabListLayout ] = React . useState < LayoutRectangle > ( ) ;
@@ -127,6 +164,19 @@ export const useTabList = (props: TabListProps): TabListInfo => {
127
164
// eslint-disable-next-line react-hooks/exhaustive-deps
128
165
} , [ isSelectedTabDisabled ] ) ;
129
166
167
+ // win32 only prop used to implemement CTRL + TAB shortcut native to windows tab components
168
+ const onRootKeyDown = React . useCallback (
169
+ ( e : IKeyboardEvent ) => {
170
+ if ( ( Platform . OS as string ) === 'win32' && e . nativeEvent . key === 'Tab' && e . nativeEvent . ctrlKey ) {
171
+ incrementSelectedTab ( e . nativeEvent . shiftKey ) ;
172
+ setInvoked ( true ) ; // on win32, set focus on the new tab without triggering narration twice
173
+ }
174
+
175
+ props . onKeyDown ?.( e ) ;
176
+ } ,
177
+ [ incrementSelectedTab , props ] ,
178
+ ) ;
179
+
130
180
return {
131
181
props : {
132
182
...props ,
@@ -137,6 +187,7 @@ export const useTabList = (props: TabListProps): TabListInfo => {
137
187
componentRef : componentRef ,
138
188
defaultTabbableElement : focusedTabRef ,
139
189
isCircularNavigation : isCircularNavigation ?? false ,
190
+ onKeyDown : onRootKeyDown ,
140
191
onLayout : onTabListLayout ,
141
192
size : size ,
142
193
vertical : vertical ,
0 commit comments