RootView can no longer be focused.

Allowing root view to be focused was stealing focus away from the
editor whenever a click event made it to the root view. This unnecessary
switching of focus was interfering with the ability to drag tabs.

But if RootView can't be focused, focus ends up being returned to the
document body when there are no focusable elements. This would be fine,
except for the fact that we frequently bind global events on root view,
and so they aren't triggered when events are triggered on the body. We
could just bind all global events on the body, but this would require
us to always attach elements to the DOM during specs, which is a serious
performance killer in specs.

The workaround is in the keymap. When the keymap handles a key event
that was triggered on the body, it triggers the corresponding semantic
event on the root view anyway, so from the event perspective, it's as
if the root view actually had focus. The only place this might fall
down is if someone wants to capture raw key events. But that's the
keymap's job anyway, and we maybe add a hook on the keymap if such a
need ever arises.
This commit is contained in:
Nathan Sobo
2013-02-08 15:56:55 -07:00
parent 2d968c11a1
commit 1cab51cefa
7 changed files with 25 additions and 11 deletions

View File

@@ -1,5 +1,6 @@
Keymap = require 'keymap'
$ = require 'jquery'
RootView = require 'root-view'
describe "Keymap", ->
fragment = null
@@ -23,8 +24,8 @@ describe "Keymap", ->
keymap.bindKeys '.command-mode', 'x': 'deleteChar'
keymap.bindKeys '.insert-mode', 'x': 'insertChar'
deleteCharHandler = jasmine.createSpy 'deleteCharHandler'
insertCharHandler = jasmine.createSpy 'insertCharHandler'
deleteCharHandler = jasmine.createSpy('deleteCharHandler')
insertCharHandler = jasmine.createSpy('insertCharHandler')
fragment.on 'deleteChar', deleteCharHandler
fragment.on 'insertChar', insertCharHandler
@@ -149,6 +150,19 @@ describe "Keymap", ->
keymap.handleKeyEvent(keydownEvent('y', target: target))
expect(bazHandler).toHaveBeenCalled()
describe "when the event's target is the document body", ->
it "triggers the mapped event on the rootView", ->
rootView = new RootView
keymap.bindKeys 'body', 'x': 'foo'
fooHandler = jasmine.createSpy("fooHandler")
rootView.on 'foo', fooHandler
result = keymap.handleKeyEvent(keydownEvent('x', target: document.body))
expect(result).toBe(false)
expect(fooHandler).toHaveBeenCalled()
expect(deleteCharHandler).not.toHaveBeenCalled()
expect(insertCharHandler).not.toHaveBeenCalled()
describe "when at least one binding partially matches the event's keystroke", ->
[quitHandler, closeOtherWindowsHandler] = []

View File

@@ -201,11 +201,11 @@ describe "RootView", ->
expect(rootView.find('#two')).not.toMatchSelector(':focus')
describe "when there are no visible focusable elements", ->
it "retains focus itself", ->
it "surrenders focus to the body", ->
rootView.remove()
rootView = new RootView(require.resolve 'fixtures')
rootView.attachToDom()
expect(rootView).toMatchSelector(':focus')
expect(document.activeElement).toBe $('body')[0]
describe "panes", ->
[pane1, newPaneContent] = []

View File

@@ -93,9 +93,8 @@ window.keyIdentifierForKey = (key) ->
"U+00" + charCode.toString(16)
window.keydownEvent = (key, properties={}) ->
event = $.Event "keydown", _.extend({originalEvent: { keyIdentifier: keyIdentifierForKey(key) }}, properties)
# event.keystroke = (new Keymap).keystrokeStringForEvent(event)
event
properties = $.extend({originalEvent: { keyIdentifier: keyIdentifierForKey(key) }}, properties)
$.Event("keydown", properties)
window.mouseEvent = (type, properties) ->
if properties.point

View File

@@ -99,6 +99,7 @@ class Keymap
b.specificity - a.specificity
triggerCommandEvent: (keyEvent, commandName) ->
keyEvent.target = rootView[0] if keyEvent.target == document.body and window.rootView
commandEvent = $.Event(commandName)
commandEvent.keyEvent = keyEvent
aborted = false

View File

@@ -18,7 +18,7 @@ class RootView extends View
disabledPackages: []
@content: ->
@div id: 'root-view', tabindex: 0, =>
@div id: 'root-view', =>
@div id: 'horizontal', outlet: 'horizontal', =>
@div id: 'vertical', outlet: 'vertical', =>
@div id: 'panes', outlet: 'panes'

View File

@@ -205,7 +205,7 @@ describe 'FuzzyFinder', ->
expect(finder.miniEditor.isFocused).toBeFalsy()
describe "when no editors are open", ->
it "detaches the finder and focuses the previously focused element", ->
it "detaches the finder and surrenders focus to the body", ->
rootView.attachToDom()
rootView.getActiveEditor().destroyActiveEditSession()
@@ -217,7 +217,7 @@ describe 'FuzzyFinder', ->
finder.cancel()
expect(finder.hasParent()).toBeFalsy()
expect($(document.activeElement).view()).toBe rootView
expect(document.activeElement).toBe $('body')[0]
expect(finder.miniEditor.isFocused).toBeFalsy()
describe "cached file paths", ->

View File

@@ -1,4 +1,4 @@
'#root-view':
'body':
'meta-\\': 'tree-view:toggle'
'meta-|': 'tree-view:reveal-active-file'