Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// ==UserScript==
// @name spacnav-highlight.js
// @description Highlight the targets of spacial navigation
// @namespace http://d.hatena.ne.jp/edvakf/
// @include http://*
// ==/UserScript==
(function(){
var keyHash = {
16 : 'shift',
37 : 'left',
38 : 'up',
39 : 'right',
40 : 'down'
}
var moveFocusFunc = {
'up' : document.moveFocusUp,
'down' : document.moveFocusDown,
'left' : document.moveFocusLeft,
'right' : document.moveFocusRight,
}
function moveFocusTo(target){
/* if target is a string call moveFocus(Up|Down|Left|Right)
* THE FUNCTIONS RETURN FALSE IF THERE IS NO TARGET TO MOVE
*/
if(typeof(target)== 'string') return moveFocusFunc[target].call(document);
/* otherwise assume target is a DOM element */
if(!target.id) target.id = 'move-focus-to';
var origin = document.activeElement;
var style = origin.style;
var origNavRight = style.navRight;
style.navRight = '#'+target.id;
document.moveFocusRight();
if(document.activeElement==document.body)document.body.blur();
style.navRight = origNavRight;
if(target.id == 'move-focus-to') target.removeAttribute('id');
}
function highlightNext(theElm){
var elmSet = {}; /* cache of 4 surrounding elements */
var origBGSet = {}; /* cache of original background color of 4 elements */
function addStyle(dir){
style = elmSet[dir].style;
if(style.backgroundColor)
origBGSet[dir]=style.backgroundColor;
style.backgroundColor = 'pink';
}
function removeStyle(dir){
if(!elmSet[dir])return;
style = elmSet[dir].style;
if(origBGSet[dir])
style.backgroundColor=origBGSet[dir];
else
style.backgroundColor = '';
}
function highlight(dir){
if(!moveFocusTo(dir)) return;
elmSet[dir] = document.activeElement;
moveFocusTo(theElm);
addStyle(dir);
}
/* highlight surrounding elements */
['left','up','right','down'].forEach(highlight);
/* when arrows are pressed, move focus accordingly and highlight next */
function keyPressHandler(e){
var dir = keyHash[e.keyCode];
if(!dir || dir=='shift' || !e.shiftKey)return;
if(!moveFocusTo(dir))return; /* move focus, or abort if failed */
['down','right','up','left'].forEach(removeStyle);
/* remove styles in a backward order to adding them
* otherwise cache handling fails
*/
var target = document.activeElement;
if(!target)return;
delete elmSet;
delete origBGSet;
highlightNext(target);
e.preventDefault(); /* this cancels the default spacial navigation */
document.removeEventListener('keypress',arguments.callee,false);
}
document.addEventListener('keypress',keyPressHandler,false);
document.addEventListener('keyup',function(e){
if( keyHash[e.keyCode] !='shift' ) return;
['down','right','up','left'].forEach(removeStyle);
if(origId)theElm.id = origId;
else theElm.removeAttribute('id');
document.removeEventListener('keypress',keyPressHandler,false);
document.removeEventListener('keyup',arguments.callee,false);
delete elmSet;
delete origBGSet;
},false);
}
document.addEventListener('keydown',function(e){
if(keyHash[e.keyCode]!='shift')return;
var currentElm = document.activeElement;
var inputReady = false;
if(/^(textarea|input)$/i.test(currentElm.tagName)){
/* if element is textarea/input, differentiate "pre-focus" and "focus" */
inputReady = true;
currentElm.addEventListener('focus',focusHandler,false);
currentElm.focus();
if(!inputReady){
/* if focus event is caught, make sure element is unfocused and
* it's back to "pre-focus" state
*/
moveFocusTo(currentElm);
}
}
function focusHandler(e){
e.preventDefault(); /* if focus event is caught, cancel any */
e.stopPropagation(); /* actions associated with it */
inputReady = false;
}
/* if focus event is not caught in 0.2 sec, assume textarea/input was input-ready */
setTimeout(function(){
if(inputReady)return;
if(document.activeElement != currentElm)return;
currentElm.removeEventListener('focus',focusHandler,false);
highlightNext(currentElm);
},200);
delete currentElm;
},false);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.