@@ -187,15 +187,11 @@ const LookupSelectedState: FC<LookupSelectedStateProps> = ({
187187type LookupScopeSelectorProps = {
188188 scopes : LookupScope [ ] ;
189189 targetScope ?: string ;
190- scopeOpened : boolean ;
191- scopeFocusedIndex : number ;
192190 disabled ?: boolean ;
193191 scopeListboxId : string ;
194192 getScopeOptionId : ( index : number ) => string ;
195- onScopeMenuClick : ( ) => void ;
196- onScopeBlur : ( e : FocusEvent ) => void ;
197- onScopeKeyDown : ( e : KeyboardEvent ) => void ;
198- onScopeSelect : ( scope : string ) => void ;
193+ onScopeMenuClick ?: ( ) => void ;
194+ onScopeSelect ?: ( scope : string ) => void ;
199195} ;
200196
201197/**
@@ -204,18 +200,146 @@ type LookupScopeSelectorProps = {
204200const LookupScopeSelector : FC < LookupScopeSelectorProps > = ( {
205201 scopes,
206202 targetScope,
207- scopeOpened,
208- scopeFocusedIndex,
209203 disabled,
210204 scopeListboxId,
211205 getScopeOptionId,
212- onScopeMenuClick,
213- onScopeBlur,
214- onScopeKeyDown,
215- onScopeSelect,
206+ onScopeMenuClick : onScopeMenuClick_ ,
207+ onScopeSelect : onScopeSelect_ ,
216208} ) => {
209+ const [ scopeOpened , setScopeOpened ] = useState ( false ) ;
210+ const [ scopeFocusedIndex , setScopeFocusedIndex ] = useState < number > ( - 1 ) ;
211+
217212 const currentScope = scopes . find ( ( scope ) => scope . label === targetScope ) ;
218213
214+ // Scroll focused scope element into view
215+ const scrollFocusedScopeIntoView = useEventCallback (
216+ ( nextFocusedIndex : number ) => {
217+ if ( nextFocusedIndex < 0 || ! scopes ) {
218+ return ;
219+ }
220+
221+ const scopeDropdown = document . getElementById ( scopeListboxId ) ;
222+ if ( ! scopeDropdown ) {
223+ return ;
224+ }
225+
226+ const targetElement = scopeDropdown . querySelector (
227+ `#${ CSS . escape ( getScopeOptionId ( nextFocusedIndex ) ) } `
228+ ) ;
229+
230+ if ( ! ( targetElement instanceof HTMLElement ) ) {
231+ return ;
232+ }
233+
234+ targetElement . focus ( ) ;
235+ }
236+ ) ;
237+
238+ const onScopeSelect = useEventCallback ( ( scope : string ) => {
239+ setScopeOpened ( false ) ;
240+ onScopeSelect_ ?.( scope ) ;
241+ } ) ;
242+
243+ const onScopeMenuClick = useEventCallback ( ( ) => {
244+ setScopeOpened ( ! scopeOpened ) ;
245+ onScopeMenuClick_ ?.( ) ;
246+ } ) ;
247+
248+ const onScopeKeyDown = useKeyHandler ( {
249+ type : 'scope' ,
250+ opened : scopeOpened ,
251+ onOpen : ( ) => {
252+ if ( ! scopes ) return ;
253+ setScopeOpened ( true ) ;
254+ setScopeFocusedIndex ( 0 ) ;
255+ } ,
256+ onClose : ( ) => {
257+ setScopeOpened ( false ) ;
258+ setScopeFocusedIndex ( - 1 ) ;
259+ } ,
260+ onNavigateDown : ( ) => {
261+ if ( ! scopes ) return ;
262+ const nextIndex = Math . min ( scopeFocusedIndex + 1 , scopes . length - 1 ) ;
263+ setScopeFocusedIndex ( nextIndex ) ;
264+ scrollFocusedScopeIntoView ( nextIndex ) ;
265+ } ,
266+ onNavigateUp : ( ) => {
267+ if ( ! scopes ) return ;
268+ const prevIndex = Math . max ( scopeFocusedIndex - 1 , 0 ) ;
269+ setScopeFocusedIndex ( prevIndex ) ;
270+ scrollFocusedScopeIntoView ( prevIndex ) ;
271+ } ,
272+ onSelect : ( ) => {
273+ if ( ! scopes ) return ;
274+ if ( scopeOpened && scopeFocusedIndex >= 0 ) {
275+ const selectedScope = scopes [ scopeFocusedIndex ] ;
276+ if ( selectedScope ) {
277+ onScopeSelect ( selectedScope . label ) ;
278+ setScopeOpened ( false ) ;
279+ setScopeFocusedIndex ( - 1 ) ;
280+ }
281+ } else {
282+ setScopeOpened ( ! scopeOpened ) ;
283+ }
284+ } ,
285+ isTabNavigationIgnored : ( direction ) => {
286+ if ( ! scopes ) {
287+ return false ;
288+ }
289+
290+ return (
291+ scopeFocusedIndex === - 1 ||
292+ ( direction === 'backward' && scopeFocusedIndex <= 0 ) ||
293+ ( direction === 'forward' && scopeFocusedIndex >= scopes . length - 1 )
294+ ) ;
295+ } ,
296+ onTabNavigation : ( direction ) => {
297+ if ( ! scopes ) return ;
298+ if ( direction === 'backward' ) {
299+ if ( scopeFocusedIndex <= 0 ) {
300+ setScopeOpened ( false ) ;
301+ setScopeFocusedIndex ( - 1 ) ;
302+ } else {
303+ const prevIndex = Math . max ( scopeFocusedIndex - 1 , 0 ) ;
304+ setScopeFocusedIndex ( prevIndex ) ;
305+ scrollFocusedScopeIntoView ( prevIndex ) ;
306+ }
307+ } else {
308+ if ( scopeFocusedIndex >= scopes . length - 1 ) {
309+ setScopeOpened ( false ) ;
310+ setScopeFocusedIndex ( - 1 ) ;
311+ } else {
312+ const nextIndex = Math . min ( scopeFocusedIndex + 1 , scopes . length - 1 ) ;
313+ setScopeFocusedIndex ( nextIndex ) ;
314+ scrollFocusedScopeIntoView ( nextIndex ) ;
315+ }
316+ }
317+ } ,
318+ } ) ;
319+
320+ const onScopeBlur = useEventCallback ( ( e : FocusEvent ) => {
321+ if ( e . relatedTarget !== null ) {
322+ if ( ! scopes ) {
323+ return ;
324+ }
325+
326+ const prevIndex = Math . max ( scopeFocusedIndex - 1 , 0 ) ;
327+ const nextIndex = Math . min ( scopeFocusedIndex + 1 , scopes . length - 1 ) ;
328+
329+ if (
330+ e . relatedTarget . id === getScopeOptionId ( prevIndex ) ||
331+ e . relatedTarget . id === getScopeOptionId ( nextIndex )
332+ ) {
333+ // catch keyborad event
334+ return ;
335+ }
336+ }
337+
338+ setTimeout ( ( ) => {
339+ setScopeOpened ( false ) ;
340+ } , 10 ) ;
341+ } ) ;
342+
219343 return (
220344 < div className = 'slds-combobox_object-switcher slds-combobox-addon_start' >
221345 < div className = 'slds-form-element' >
@@ -781,8 +905,6 @@ export const Lookup = createFC<LookupProps, { isFormElement: boolean }>(
781905 ( scopes && scopes . length > 0 ? scopes [ 0 ] . label : undefined )
782906 ) ;
783907 const [ focusedValue , setFocusedValue ] = useState < string | undefined > ( ) ;
784- const [ scopeOpened , setScopeOpened ] = useState ( false ) ;
785- const [ scopeFocusedIndex , setScopeFocusedIndex ] = useState < number > ( - 1 ) ;
786908
787909 const listHeaderIdSeed = useMemo (
788910 ( ) => [ ...data . map ( ( entry ) => entry . value ) , 'header' ] . join ( '-' ) ,
@@ -864,30 +986,6 @@ export const Lookup = createFC<LookupProps, { isFormElement: boolean }>(
864986 }
865987 ) ;
866988
867- // Scroll focused scope element into view
868- const scrollFocusedScopeIntoView = useEventCallback (
869- ( nextFocusedIndex : number ) => {
870- if ( nextFocusedIndex < 0 || ! scopes ) {
871- return ;
872- }
873-
874- const scopeDropdown = document . getElementById ( scopeListboxId ) ;
875- if ( ! scopeDropdown ) {
876- return ;
877- }
878-
879- const targetElement = scopeDropdown . querySelector (
880- `#${ CSS . escape ( getScopeOptionId ( nextFocusedIndex ) ) } `
881- ) ;
882-
883- if ( ! ( targetElement instanceof HTMLElement ) ) {
884- return ;
885- }
886-
887- targetElement . focus ( ) ;
888- }
889- ) ;
890-
891989 // Set initial focus when dropdown opens
892990 useEffect ( ( ) => {
893991 if ( ! opened ) {
@@ -1055,115 +1153,6 @@ export const Lookup = createFC<LookupProps, { isFormElement: boolean }>(
10551153 } , 10 ) ;
10561154 } ) ;
10571155
1058- const onScopeSelect = useEventCallback ( ( scope : string ) => {
1059- setTargetScope ( scope ) ;
1060- setScopeOpened ( false ) ;
1061- onScopeSelect_ ?.( scope ) ;
1062- } ) ;
1063-
1064- const onScopeMenuClick = useEventCallback ( ( ) => {
1065- setScopeOpened ( ! scopeOpened ) ;
1066- onScopeMenuClick_ ?.( ) ;
1067- } ) ;
1068-
1069- const onScopeKeyDown = useKeyHandler ( {
1070- type : 'scope' ,
1071- opened : scopeOpened ,
1072- onOpen : ( ) => {
1073- if ( ! scopes ) return ;
1074- setScopeOpened ( true ) ;
1075- setScopeFocusedIndex ( 0 ) ;
1076- } ,
1077- onClose : ( ) => {
1078- setScopeOpened ( false ) ;
1079- setScopeFocusedIndex ( - 1 ) ;
1080- } ,
1081- onNavigateDown : ( ) => {
1082- if ( ! scopes ) return ;
1083- const nextIndex = Math . min ( scopeFocusedIndex + 1 , scopes . length - 1 ) ;
1084- setScopeFocusedIndex ( nextIndex ) ;
1085- scrollFocusedScopeIntoView ( nextIndex ) ;
1086- } ,
1087- onNavigateUp : ( ) => {
1088- if ( ! scopes ) return ;
1089- const prevIndex = Math . max ( scopeFocusedIndex - 1 , 0 ) ;
1090- setScopeFocusedIndex ( prevIndex ) ;
1091- scrollFocusedScopeIntoView ( prevIndex ) ;
1092- } ,
1093- onSelect : ( ) => {
1094- if ( ! scopes ) return ;
1095- if ( scopeOpened && scopeFocusedIndex >= 0 ) {
1096- const selectedScope = scopes [ scopeFocusedIndex ] ;
1097- if ( selectedScope ) {
1098- onScopeSelect ( selectedScope . label ) ;
1099- setScopeOpened ( false ) ;
1100- setScopeFocusedIndex ( - 1 ) ;
1101- }
1102- } else {
1103- setScopeOpened ( ! scopeOpened ) ;
1104- }
1105- } ,
1106- isTabNavigationIgnored : ( direction ) => {
1107- if ( ! scopes ) {
1108- return false ;
1109- }
1110-
1111- return (
1112- scopeFocusedIndex === - 1 ||
1113- ( direction === 'backward' && scopeFocusedIndex <= 0 ) ||
1114- ( direction === 'forward' && scopeFocusedIndex >= scopes . length - 1 )
1115- ) ;
1116- } ,
1117- onTabNavigation : ( direction ) => {
1118- if ( ! scopes ) return ;
1119- if ( direction === 'backward' ) {
1120- if ( scopeFocusedIndex <= 0 ) {
1121- setScopeOpened ( false ) ;
1122- setScopeFocusedIndex ( - 1 ) ;
1123- } else {
1124- const prevIndex = Math . max ( scopeFocusedIndex - 1 , 0 ) ;
1125- setScopeFocusedIndex ( prevIndex ) ;
1126- scrollFocusedScopeIntoView ( prevIndex ) ;
1127- }
1128- } else {
1129- if ( scopeFocusedIndex >= scopes . length - 1 ) {
1130- setScopeOpened ( false ) ;
1131- setScopeFocusedIndex ( - 1 ) ;
1132- } else {
1133- const nextIndex = Math . min (
1134- scopeFocusedIndex + 1 ,
1135- scopes . length - 1
1136- ) ;
1137- setScopeFocusedIndex ( nextIndex ) ;
1138- scrollFocusedScopeIntoView ( nextIndex ) ;
1139- }
1140- }
1141- } ,
1142- } ) ;
1143-
1144- const onScopeBlur = useEventCallback ( ( e : FocusEvent ) => {
1145- if ( e . relatedTarget !== null ) {
1146- if ( ! scopes ) {
1147- return ;
1148- }
1149-
1150- const prevIndex = Math . max ( scopeFocusedIndex - 1 , 0 ) ;
1151- const nextIndex = Math . min ( scopeFocusedIndex + 1 , scopes . length - 1 ) ;
1152-
1153- if (
1154- e . relatedTarget . id === getScopeOptionId ( prevIndex ) ||
1155- e . relatedTarget . id === getScopeOptionId ( nextIndex )
1156- ) {
1157- // catch keyborad event
1158- return ;
1159- }
1160- }
1161-
1162- setTimeout ( ( ) => {
1163- setScopeOpened ( false ) ;
1164- } , 10 ) ;
1165- } ) ;
1166-
11671156 const hasSelection = selected != null ;
11681157
11691158 const containerClassNames = classnames (
@@ -1224,15 +1213,14 @@ export const Lookup = createFC<LookupProps, { isFormElement: boolean }>(
12241213 < LookupScopeSelector
12251214 scopes = { scopes }
12261215 targetScope = { targetScope }
1227- scopeOpened = { scopeOpened }
1228- scopeFocusedIndex = { scopeFocusedIndex }
12291216 disabled = { disabled }
12301217 scopeListboxId = { scopeListboxId }
12311218 getScopeOptionId = { getScopeOptionId }
1232- onScopeMenuClick = { onScopeMenuClick }
1233- onScopeBlur = { onScopeBlur }
1234- onScopeKeyDown = { onScopeKeyDown }
1235- onScopeSelect = { onScopeSelect }
1219+ onScopeMenuClick = { onScopeMenuClick_ }
1220+ onScopeSelect = { ( scope ) => {
1221+ setTargetScope ( scope ) ;
1222+ onScopeSelect_ ?.( scope ) ;
1223+ } }
12361224 />
12371225 < div className = 'slds-combobox_container slds-combobox-addon_end' >
12381226 < div className = { comboboxClassNames } ref = { elementRef } >
0 commit comments