From 742d0d9241fcb1a685a924e94ffdc24b3b546466 Mon Sep 17 00:00:00 2001 From: Abijeet Date: Tue, 17 Jan 2023 10:29:05 +0530 Subject: [PATCH] Update jQuery.uls to add support for up/down navigation of languages Some localisation changes are also included. Bug: T187964 Change-Id: I7d9554c9a4e2b18c805c9ef9b916c3961ae9c32c --- lib/jquery.uls/css/jquery.uls.lcd.css | 4 + .../src/jquery.uls.languagefilter.js | 21 +++- lib/jquery.uls/src/jquery.uls.lcd.js | 118 ++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) diff --git a/lib/jquery.uls/css/jquery.uls.lcd.css b/lib/jquery.uls/css/jquery.uls.lcd.css index 4b04e139..fd2b9784 100644 --- a/lib/jquery.uls/css/jquery.uls.lcd.css +++ b/lib/jquery.uls/css/jquery.uls.lcd.css @@ -138,3 +138,7 @@ bottom: 0; left: 0; } + +.uls-language-option--highlighted { + background-color: #eaeff7; +} diff --git a/lib/jquery.uls/src/jquery.uls.languagefilter.js b/lib/jquery.uls/src/jquery.uls.languagefilter.js index c87c680b..5a838642 100644 --- a/lib/jquery.uls/src/jquery.uls.languagefilter.js +++ b/lib/jquery.uls/src/jquery.uls.languagefilter.js @@ -106,7 +106,12 @@ query = ( this.$element.val() || '' ).trim().toLowerCase(); - if ( this.selectedLanguage ) { + // Check if a language is currently highlighted, and if so select that + // if user presses enter. + var highlightedLanguage = this.options.lcd.getHighlightedLanguageCode(); + if ( highlightedLanguage ) { + this.options.onSelect( highlightedLanguage, e ); + } else if ( this.selectedLanguage ) { // this.selectLanguage will be populated from a matching search this.options.onSelect( this.selectedLanguage, e ); } else if ( this.options.languages[ query ] ) { @@ -115,6 +120,16 @@ this.options.onSelect( query, e ); } + break; + case 38: // arrow up + this.options.lcd.navigateUp(); + e.preventDefault(); + + break; + case 40: // arrow down + this.options.lcd.navigateDown(); + e.preventDefault(); + break; } }, @@ -163,6 +178,10 @@ results = [], query = ( this.$element.val() || '' ).trim().toLowerCase(); + // Reset the keyboard navigation index inside LanguageCategoryDisplay (lcd) + // before re-rendering the language options + this.options.lcd.resetNavigationIndex(); + if ( query === '' ) { this.options.lcd.setGroupByRegionOverride( null ); this.resultHandler( query, languages ); diff --git a/lib/jquery.uls/src/jquery.uls.lcd.js b/lib/jquery.uls/src/jquery.uls.lcd.js index 47f08200..c61dd304 100644 --- a/lib/jquery.uls/src/jquery.uls.lcd.js +++ b/lib/jquery.uls/src/jquery.uls.lcd.js @@ -59,13 +59,119 @@ this.$cachedQuicklist = null; this.groupByRegionOverride = null; + // The index of the language option that is currently visited using arrow key navigation + // Can take values in the [0, language options list item length - 1] range for top to bottom + // navigation, or in the [-1, -language options list item length + 1] range for bottom to + // top navigation. + this.navigationIndex = null; + this.render(); this.listen(); } + // Adapted from https://stackoverflow.com/a/41754707/903324 + function isLanguageFullyVisible( $el, $holder ) { + var elementRect = $el.get( 0 ).getBoundingClientRect(); + var holderRect = $holder.get( 0 ).getBoundingClientRect(); + + return elementRect.top <= holderRect.top ? + holderRect.top <= elementRect.top : + holderRect.bottom <= elementRect.height; + } + LanguageCategoryDisplay.prototype = { constructor: LanguageCategoryDisplay, + /** + * Returns a jQuery object containing a collection of all the visible + * language option
  • elements + * + * @return {jQuery} + */ + getLanguageOptionListItems: function () { + return this.$element.find( '.uls-lcd-region-section:not(.hide)' ).find( 'li[data-code]' ); + }, + + /** + * Increases the keyboard navigation index by one and applies a specific + * class to the n-th language option
  • element (where n = navigation index) + * Currently used as event handler for the arrow down 'keydown' event, inside + * LanguageFilter. + */ + navigateDown: function () { + var maxIndex = this.getLanguageOptionListItems().length - 1; + // We support navigation starting both from the top and the bottom of the language list. + // The navigation should stop when the last language option is already highlighted (for + // top to bottom navigation). For top to bottom navigation, that happens when navigation + // index is equal to language options list item length - 1. For bottom to top + // navigation, that happens when navigation index is equal to -1. + if ( this.navigationIndex === maxIndex || this.navigationIndex === -1 ) { + return; + } + + if ( this.navigationIndex === null ) { + this.navigationIndex = 0; + } else { + this.navigationIndex++; + } + this.highlightLanguageOption(); + }, + + /** + * Decreases the keyboard navigation index by one and applies a specific + * class to the n-th language option
  • element (where n = navigation index) + * Currently used as event handler for the arrow down 'keydown' event, inside + * LanguageFilter. + */ + navigateUp: function () { + var maxIndex = this.getLanguageOptionListItems().length - 1; + // We support navigation starting both from the top and the bottom of the language list. + // The navigation should stop when the first language option is already highlighted (for + // bottom to top navigation). For top to bottom navigation, that happens when navigation + // index is equal to 0. For bottom to top navigation, that happens when navigation index + // is equal to -languageOptionListItemsLength + 1. + if ( this.navigationIndex === 0 || this.navigationIndex === -maxIndex ) { + return; + } + + this.navigationIndex--; + this.highlightLanguageOption(); + }, + + /** + * Adds a specific class ("uls-language-option--highlighted") only to the n-th + * language option
  • element (where n = navigation index) + */ + highlightLanguageOption: function () { + var $listItems = this.getLanguageOptionListItems(); + $listItems.removeClass( 'uls-language-option--highlighted' ); + + var $selectedItem = $listItems.eq( this.navigationIndex ); + $selectedItem.addClass( 'uls-language-option--highlighted' ); + + // If the selected item is not visible, then scroll the container to display it + if ( !isLanguageFullyVisible( $selectedItem, this.$element ) ) { + $selectedItem.get( 0 ).scrollIntoView( false ); + } + }, + + getHighlightedLanguageCode: function () { + if ( this.navigationIndex ) { + var $selectedItem = this.getLanguageOptionListItems().eq( this.navigationIndex ); + return $selectedItem.data( 'code' ); + } + + return null; + }, + + /** + * Resets the navigation index to null. + * Currently used inside LanguageFilter search method, to reset the keyboard navigation + */ + resetNavigationIndex: function () { + this.navigationIndex = null; + }, + /** * Adds language to the language list. * @@ -419,6 +525,18 @@ listen: function () { var lcd = this; + this.$element.on( 'mouseenter', 'li[data-code]', function () { + var $listItems = lcd.getLanguageOptionListItems(); + // Remove the previous option, and then highlight the current one. + $listItems.removeClass( 'uls-language-option--highlighted' ); + var $self = $( this ); + $self.addClass( 'uls-language-option--highlighted' ); + lcd.navigationIndex = $listItems.index( $self ); + } ).on( 'mouseleave', 'li[data-code]', function () { + $( this ).removeClass( 'uls-language-option--highlighted' ); + lcd.navigationIndex = null; + } ); + if ( this.options.clickhandler ) { this.$element.on( 'click', '.row li', function ( event ) { lcd.options.clickhandler.call( this, $( this ).data( 'code' ), event );