Commit a1c794c5 authored by Jacob Schatz's avatar Jacob Schatz

Merge branch 'dropdown-arrow-support' into 'master'

Dropdown arrow support

When the dropdown is open, you can scroll through the list of items with the up & down arrow keys. When an item is focused, the enter triggers the click event for that row.

Closes #14455

See merge request !3385
parents 4258589d 8e3f5eba
Pipeline #1462044 passed with stage
class GitLabDropdownFilter class GitLabDropdownFilter
BLUR_KEYCODES = [27, 40] BLUR_KEYCODES = [27, 40]
ARROW_KEY_CODES = [38, 40]
HAS_VALUE_CLASS = "has-value" HAS_VALUE_CLASS = "has-value"
constructor: (@input, @options) -> constructor: (@input, @options) ->
...@@ -22,19 +23,23 @@ class GitLabDropdownFilter ...@@ -22,19 +23,23 @@ class GitLabDropdownFilter
# Key events # Key events
timeout = "" timeout = ""
@input.on "keyup", (e) => @input.on "keyup", (e) =>
keyCode = e.which
return if ARROW_KEY_CODES.indexOf(keyCode) >= 0
if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.addClass HAS_VALUE_CLASS $inputContainer.addClass HAS_VALUE_CLASS
else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.removeClass HAS_VALUE_CLASS $inputContainer.removeClass HAS_VALUE_CLASS
if e.keyCode is 13 and @input.val() isnt "" if keyCode is 13 and @input.val() isnt ""
if @options.enterCallback if @options.enterCallback
@options.enterCallback() @options.enterCallback()
return return
clearTimeout timeout clearTimeout timeout
timeout = setTimeout => timeout = setTimeout =>
blur_field = @shouldBlur e.keyCode blur_field = @shouldBlur keyCode
search_text = @input.val() search_text = @input.val()
if blur_field and @filterInputBlur if blur_field and @filterInputBlur
...@@ -96,6 +101,7 @@ class GitLabDropdown ...@@ -96,6 +101,7 @@ class GitLabDropdown
LOADING_CLASS = "is-loading" LOADING_CLASS = "is-loading"
PAGE_TWO_CLASS = "is-page-two" PAGE_TWO_CLASS = "is-page-two"
ACTIVE_CLASS = "is-active" ACTIVE_CLASS = "is-active"
currentIndex = -1
FILTER_INPUT = '.dropdown-input .dropdown-input-field' FILTER_INPUT = '.dropdown-input .dropdown-input-field'
...@@ -145,11 +151,11 @@ class GitLabDropdown ...@@ -145,11 +151,11 @@ class GitLabDropdown
data: => data: =>
return @fullData return @fullData
callback: (data) => callback: (data) =>
currentIndex = -1
@parseData data @parseData data
@highlightRow 1
enterCallback: => enterCallback: =>
if @enterCallback if @enterCallback
@selectFirstRow() @selectRowAtIndex 0
# Event listeners # Event listeners
...@@ -218,6 +224,8 @@ class GitLabDropdown ...@@ -218,6 +224,8 @@ class GitLabDropdown
return true return true
opened: => opened: =>
@addArrowKeyEvent()
contentHtml = $('.dropdown-content', @dropdown).html() contentHtml = $('.dropdown-content', @dropdown).html()
if @remote && contentHtml is "" if @remote && contentHtml is ""
@remote.execute() @remote.execute()
...@@ -228,6 +236,7 @@ class GitLabDropdown ...@@ -228,6 +236,7 @@ class GitLabDropdown
@dropdown.trigger('shown.gl.dropdown') @dropdown.trigger('shown.gl.dropdown')
hidden: (e) => hidden: (e) =>
@removeArrayKeyEvent()
if @options.filterable if @options.filterable
@dropdown @dropdown
.find(".dropdown-input-field") .find(".dropdown-input-field")
...@@ -307,11 +316,11 @@ class GitLabDropdown ...@@ -307,11 +316,11 @@ class GitLabDropdown
if @highlight if @highlight
text = @highlightTextMatches(text, @filterInput.val()) text = @highlightTextMatches(text, @filterInput.val())
html = "<li>" html = "<li>
html += "<a href='#{url}' class='#{cssClass}'>" <a href='#{url}' class='#{cssClass}'>
html += text #{text}
html += "</a>" </a>
html += "</li>" </li>"
return html return html
...@@ -322,11 +331,11 @@ class GitLabDropdown ...@@ -322,11 +331,11 @@ class GitLabDropdown
).join('') ).join('')
noResults: -> noResults: ->
html = "<li>" html = "<li class='dropdown-menu-empty-link'>
html += "<a class='dropdown-menu-empty-link is-focused'>" <a href='#' class='is-focused'>
html += "No matching results." No matching results.
html += "</a>" </a>
html += "</li>" </li>"
highlightRow: (index) -> highlightRow: (index) ->
if @filterInput.val() isnt "" if @filterInput.val() isnt ""
...@@ -378,16 +387,81 @@ class GitLabDropdown ...@@ -378,16 +387,81 @@ class GitLabDropdown
return selectedObject return selectedObject
selectFirstRow: -> selectRowAtIndex: (index) ->
selector = '.dropdown-content li:first-child a' selector = ".dropdown-content li:not(.divider):eq(#{index}) a"
if @dropdown.find(".dropdown-toggle-page").length if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content li:first-child a" selector = ".dropdown-page-one #{selector}"
# simulate a click on the first link # simulate a click on the first link
$(selector).trigger "click" $(selector).trigger "click"
addArrowKeyEvent: ->
ARROW_KEY_CODES = [38, 40]
$input = @dropdown.find(".dropdown-input-field")
selector = '.dropdown-content li:not(.divider)'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one #{selector}"
$('body').on 'keydown', (e) =>
currentKeyCode = e.which
if ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0
e.preventDefault()
e.stopImmediatePropagation()
PREV_INDEX = currentIndex
$listItems = $(selector, @dropdown)
# if @options.filterable
# $input.blur()
if currentKeyCode is 40
# Move down
currentIndex += 1 if currentIndex < ($listItems.length - 1)
else if currentKeyCode is 38
# Move up
currentIndex -= 1 if currentIndex > 0
@highlightRowAtIndex($listItems, currentIndex) if currentIndex isnt PREV_INDEX
return false
if currentKeyCode is 13
@selectRowAtIndex currentIndex
removeArrayKeyEvent: ->
$('body').off 'keydown'
highlightRowAtIndex: ($listItems, index) ->
# Remove the class for the previously focused row
$('.is-focused', @dropdown).removeClass 'is-focused'
# Update the class for the row at the specific index
$listItem = $listItems.eq(index)
$listItem.find('a:first-child').addClass "is-focused"
# Dropdown content scroll area
$dropdownContent = $listItem.closest('.dropdown-content')
dropdownScrollTop = $dropdownContent.scrollTop()
dropdownContentHeight = $dropdownContent.outerHeight()
dropdownContentTop = $dropdownContent.prop('offsetTop')
dropdownContentBottom = dropdownContentTop + dropdownContentHeight
# Get the offset bottom of the list item
listItemHeight = $listItem.outerHeight()
listItemTop = $listItem.prop('offsetTop')
listItemBottom = listItemTop + listItemHeight
if listItemBottom > dropdownContentBottom + dropdownScrollTop
# Scroll the dropdown content down
$dropdownContent.scrollTop(listItemBottom - dropdownContentBottom)
else if listItemTop < dropdownContentTop + dropdownScrollTop
# Scroll the dropdown content up
$dropdownContent.scrollTop(listItemTop - dropdownContentTop)
$.fn.glDropdown = (opts) -> $.fn.glDropdown = (opts) ->
return @.each -> return @.each ->
if (!$.data @, 'glDropdown') if (!$.data @, 'glDropdown')
$.data(@, 'glDropdown', new GitLabDropdown @, opts) $.data(@, 'glDropdown', new GitLabDropdown @, opts)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment