@@ -310,66 +310,94 @@ export class Select implements ComponentInterface {
310310 }
311311 this . isExpanded = true ;
312312 const overlay = ( this . overlay = await this . createOverlay ( event ) ) ;
313- overlay . onDidDismiss ( ) . then ( ( ) => {
314- this . overlay = undefined ;
315- this . isExpanded = false ;
316- this . ionDismiss . emit ( ) ;
317- this . setFocus ( ) ;
318- } ) ;
319-
320- await overlay . present ( ) ;
321313
322- const indexOfSelected = this . childOpts . findIndex ( ( o ) => o . value === this . value ) ;
323- if ( indexOfSelected > - 1 ) {
324- const selectedItem = overlay . querySelector < HTMLElement > (
325- `.select-interface-option:nth-child(${ indexOfSelected + 1 } )`
326- ) ;
314+ // Add logic to scroll selected item into view before presenting
315+ const scrollSelectedIntoView = ( ) => {
316+ const indexOfSelected = this . childOpts . findIndex ( ( o ) => o . value === this . value ) ;
317+ if ( indexOfSelected > - 1 ) {
318+ const selectedItem = overlay . querySelector < HTMLElement > (
319+ `.select-interface-option:nth-child(${ indexOfSelected + 1 } )`
320+ ) ;
321+
322+ if ( selectedItem ) {
323+ /**
324+ * Browsers such as Firefox do not
325+ * correctly delegate focus when manually
326+ * focusing an element with delegatesFocus.
327+ * We work around this by manually focusing
328+ * the interactive element.
329+ * ion-radio and ion-checkbox are the only
330+ * elements that ion-select-popover uses, so
331+ * we only need to worry about those two components
332+ * when focusing.
333+ */
334+ const interactiveEl = selectedItem . querySelector < HTMLElement > ( 'ion-radio, ion-checkbox' ) as
335+ | HTMLIonRadioElement
336+ | HTMLIonCheckboxElement
337+ | null ;
338+ if ( interactiveEl ) {
339+ selectedItem . scrollIntoView ( { block : 'nearest' } ) ;
340+ // Needs to be called before `focusVisibleElement` to prevent issue with focus event bubbling
341+ // and removing `ion-focused` style
342+ interactiveEl . setFocus ( ) ;
343+ }
327344
328- if ( selectedItem ) {
345+ focusVisibleElement ( selectedItem ) ;
346+ }
347+ } else {
329348 /**
330- * Browsers such as Firefox do not
331- * correctly delegate focus when manually
332- * focusing an element with delegatesFocus.
333- * We work around this by manually focusing
334- * the interactive element.
335- * ion-radio and ion-checkbox are the only
336- * elements that ion-select-popover uses, so
337- * we only need to worry about those two components
338- * when focusing.
349+ * If no value is set then focus the first enabled option.
339350 */
340- const interactiveEl = selectedItem . querySelector < HTMLElement > ( 'ion-radio, ion-checkbox' ) as
341- | HTMLIonRadioElement
342- | HTMLIonCheckboxElement
343- | null ;
344- if ( interactiveEl ) {
345- // Needs to be called before `focusVisibleElement` to prevent issue with focus event bubbling
346- // and removing `ion-focused` style
347- interactiveEl . setFocus ( ) ;
351+ const firstEnabledOption = overlay . querySelector < HTMLElement > (
352+ 'ion-radio:not(.radio-disabled), ion-checkbox:not(.checkbox-disabled)'
353+ ) as HTMLIonRadioElement | HTMLIonCheckboxElement | null ;
354+
355+ if ( firstEnabledOption ) {
356+ /**
357+ * Focus the option for the same reason as we do above.
358+ *
359+ * Needs to be called before `focusVisibleElement` to prevent issue with focus event bubbling
360+ * and removing `ion-focused` style
361+ */
362+ firstEnabledOption . setFocus ( ) ;
363+
364+ focusVisibleElement ( firstEnabledOption . closest ( 'ion-item' ) ! ) ;
348365 }
349-
350- focusVisibleElement ( selectedItem ) ;
351366 }
367+ } ;
368+
369+ // For modals and popovers, we can scroll before they're visible
370+ if ( this . interface === 'modal' ) {
371+ overlay . addEventListener ( 'ionModalWillPresent' , scrollSelectedIntoView , { once : true } ) ;
372+ } else if ( this . interface === 'popover' ) {
373+ overlay . addEventListener ( 'ionPopoverWillPresent' , scrollSelectedIntoView , { once : true } ) ;
352374 } else {
353375 /**
354- * If no value is set then focus the first enabled option.
376+ * For alerts and action sheets, we need to wait a frame after willPresent
377+ * because these overlays don't have their content in the DOM immediately
378+ * when willPresent fires. By waiting a frame, we ensure the content is
379+ * rendered and can be properly scrolled into view.
355380 */
356- const firstEnabledOption = overlay . querySelector < HTMLElement > (
357- 'ion-radio:not(.radio-disabled), ion-checkbox:not(.checkbox-disabled)'
358- ) as HTMLIonRadioElement | HTMLIonCheckboxElement | null ;
359-
360- if ( firstEnabledOption ) {
361- /**
362- * Focus the option for the same reason as we do above.
363- *
364- * Needs to be called before `focusVisibleElement` to prevent issue with focus event bubbling
365- * and removing `ion-focused` style
366- */
367- firstEnabledOption . setFocus ( ) ;
368-
369- focusVisibleElement ( firstEnabledOption . closest ( 'ion-item' ) ! ) ;
381+ const scrollAfterRender = ( ) => {
382+ requestAnimationFrame ( ( ) => {
383+ scrollSelectedIntoView ( ) ;
384+ } ) ;
385+ } ;
386+ if ( this . interface === 'alert' ) {
387+ overlay . addEventListener ( 'ionAlertWillPresent' , scrollAfterRender , { once : true } ) ;
388+ } else if ( this . interface === 'action-sheet' ) {
389+ overlay . addEventListener ( 'ionActionSheetWillPresent' , scrollAfterRender , { once : true } ) ;
370390 }
371391 }
372392
393+ overlay . onDidDismiss ( ) . then ( ( ) => {
394+ this . overlay = undefined ;
395+ this . isExpanded = false ;
396+ this . ionDismiss . emit ( ) ;
397+ this . setFocus ( ) ;
398+ } ) ;
399+
400+ await overlay . present ( ) ;
373401 return overlay ;
374402 }
375403
0 commit comments