diff --git a/spec/app/window-spec.coffee b/spec/app/window-spec.coffee index 631a36db5..091b1ad83 100644 --- a/spec/app/window-spec.coffee +++ b/spec/app/window-spec.coffee @@ -1,4 +1,5 @@ $ = require 'jquery' +{$$} = require 'space-pen' fsUtils = require 'fs-utils' {less} = require 'less' WindowEventHandler = require 'window-event-handler' @@ -235,3 +236,81 @@ describe "Window", -> ChildProcess.spawn.reset() $("link").appendTo(document.body).click().remove() expect(ChildProcess.spawn).not.toHaveBeenCalled() + + describe "core:focus-next and core:focus-previous", -> + describe "when there is no currently focused element", -> + it "focuses the element with the lowest/highest tabindex", -> + elements = $$ -> + @div => + @button tabindex: 2 + @input tabindex: 1 + + elements.attachToDom() + + elements.trigger "core:focus-next" + expect(elements.find("[tabindex=1]:focus")).toExist() + + $(":focus").blur() + + elements.trigger "core:focus-previous" + expect(elements.find("[tabindex=2]:focus")).toExist() + + describe "when a tabindex is set on the currently focused element", -> + it "focuses the element with the next highest tabindex", -> + elements = $$ -> + @div => + @input tabindex: 1 + @button tabindex: 2 + @button tabindex: 5 + @input tabindex: -1 + @input tabindex: 3 + @button tabindex: 7 + + elements.attachToDom() + elements.find("[tabindex=1]").focus() + + elements.trigger "core:focus-next" + expect(elements.find("[tabindex=2]:focus")).toExist() + + elements.trigger "core:focus-next" + expect(elements.find("[tabindex=3]:focus")).toExist() + + elements.focus().trigger "core:focus-next" + expect(elements.find("[tabindex=5]:focus")).toExist() + + elements.focus().trigger "core:focus-next" + expect(elements.find("[tabindex=7]:focus")).toExist() + + elements.focus().trigger "core:focus-next" + expect(elements.find("[tabindex=1]:focus")).toExist() + + elements.trigger "core:focus-previous" + expect(elements.find("[tabindex=7]:focus")).toExist() + + elements.trigger "core:focus-previous" + expect(elements.find("[tabindex=5]:focus")).toExist() + + elements.focus().trigger "core:focus-previous" + expect(elements.find("[tabindex=3]:focus")).toExist() + + elements.focus().trigger "core:focus-previous" + expect(elements.find("[tabindex=2]:focus")).toExist() + + elements.focus().trigger "core:focus-previous" + expect(elements.find("[tabindex=1]:focus")).toExist() + + it "skips disabled elements", -> + elements = $$ -> + @div => + @input tabindex: 1 + @button tabindex: 2, disabled: 'disabled' + @input tabindex: 3 + + elements.attachToDom() + elements.find("[tabindex=1]").focus() + + elements.trigger "core:focus-next" + expect(elements.find("[tabindex=3]:focus")).toExist() + + elements.trigger "core:focus-previous" + expect(elements.find("[tabindex=1]:focus")).toExist() diff --git a/src/app/keymaps/atom.cson b/src/app/keymaps/atom.cson index 94d2a2173..b475534d0 100644 --- a/src/app/keymaps/atom.cson +++ b/src/app/keymaps/atom.cson @@ -67,10 +67,10 @@ # allow standard input fields to work correctly 'input:not(.hidden-input)': + 'tab': 'core:focus-next' + 'shift-tab': 'core:focus-previous' 'left': 'native!' 'right': 'native!' - 'tab': 'native!' - 'shift-tab': 'native!' 'shift-left': 'native!' 'shift-right': 'native!' 'backspace': 'native!' @@ -81,3 +81,7 @@ 'meta-x': 'native!' 'meta-c': 'native!' 'meta-v': 'native!' + +'button': + 'tab': 'core:focus-next' + 'shift-tab': 'core:focus-previous' diff --git a/src/app/window-event-handler.coffee b/src/app/window-event-handler.coffee index c34a7235a..61d3db3bc 100644 --- a/src/app/window-event-handler.coffee +++ b/src/app/window-event-handler.coffee @@ -15,6 +15,9 @@ class WindowEventHandler window.close() @subscribeToCommand $(window), 'window:reload', => reload() + @subscribeToCommand $(document), 'core:focus-next', @focusNext + @subscribeToCommand $(document), 'core:focus-previous', @focusPrevious + @subscribe $(document), 'keydown', keymap.handleKeyEvent @subscribe $(document), 'drop', onDrop @@ -31,4 +34,50 @@ class WindowEventHandler require('child_process').spawn('open', [location]) false + eachTabIndexedElement: (callback) -> + for element in $('[tabindex]') + element = $(element) + continue if element.attr('disabled') + + tabIndex = parseInt(element.attr('tabindex')) + continue unless tabIndex >= 0 + + callback(element, tabIndex) + + focusNext: => + focusedTabIndex = parseInt($(':focus').attr('tabindex')) or -Infinity + + nextElement = null + nextTabIndex = Infinity + lowestElement = null + lowestTabIndex = Infinity + @eachTabIndexedElement (element, tabIndex) -> + if tabIndex < lowestTabIndex + lowestTabIndex = tabIndex + lowestElement = element + + if focusedTabIndex < tabIndex < nextTabIndex + nextTabIndex = tabIndex + nextElement = element + + (nextElement ? lowestElement).focus() + + focusPrevious: => + focusedTabIndex = parseInt($(':focus').attr('tabindex')) or Infinity + + previousElement = null + previousTabIndex = -Infinity + highestElement = null + highestTabIndex = -Infinity + @eachTabIndexedElement (element, tabIndex) -> + if tabIndex > highestTabIndex + highestTabIndex = tabIndex + highestElement = element + + if focusedTabIndex > tabIndex > previousTabIndex + previousTabIndex = tabIndex + previousElement = element + + (previousElement ? highestElement).focus() + _.extend WindowEventHandler.prototype, Subscriber