Combined keyboard and mouse navigation works.

This commit is contained in:
zorkow 2015-08-21 04:34:18 +01:00
parent 5e88e80975
commit 1be417af3a
2 changed files with 246 additions and 139 deletions

View File

@ -109,6 +109,20 @@
RIGHTBUTTON: 2, // the event.button value for right button
MENUKEY: "altKey", // the event value for alternate context menu
/*************************************************************/
/*
* Enum element for key codes.
*/
KEY: {
RETURN: 13,
ESCAPE: 27,
SPACE: 32,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40
},
Mousedown: function (event) {return EVENT.Handler(event,"Mousedown",this)},
Mouseup: function (event) {return EVENT.Handler(event,"Mouseup",this)},
Mousemove: function (event) {return EVENT.Handler(event,"Mousemove",this)},
@ -150,7 +164,7 @@
//
Keydown: function (event, math) {
var jax = OUTPUT[this.jaxID];
if (event.keyCode === 32) {
if (event.keyCode === EVENT.KEY.SPACE) {
// TODO: Put the focus on the first element.
EVENT.ContextMenu(event, this);
};

View File

@ -198,10 +198,11 @@
}
});
var FALSE, HOVER;
var FALSE, HOVER, KEY;
HUB.Register.StartupHook("MathEvents Ready",function () {
FALSE = MathJax.Extension.MathEvents.Event.False;
HOVER = MathJax.Extension.MathEvents.Hover;
KEY = MathJax.Extension.MathEvents.Event.KEY;
});
/*************************************************************/
@ -250,6 +251,12 @@
this.posted = true;
menu.style.width = (menu.offsetWidth+2) + "px";
var x = event.pageX, y = event.pageY;
var node = MENU.node || event.target;
if (!x && !y && node) {
var rect = node.getBoundingClientRect();
x = rect.right;
y = rect.bottom;
}
if (!x && !y) {
x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
@ -283,7 +290,7 @@
menu.style.left = x+"px"; menu.style.top = y+"px";
if (document.selection && document.selection.empty) {document.selection.empty()}
MENU.Focus(event, menu);
MENU.Focus(menu);
return FALSE(event);
},
@ -301,7 +308,7 @@
delete MENU.jax.hover.nofade;
HOVER.UnHover(MENU.jax);
}
MENU.Unfocus();
MENU.Unfocus(menu);
return FALSE(event);
},
@ -338,55 +345,56 @@
/*
* Moving in the list of items.
*/
Up: function(item) {
var index = this.items.indexOf(item);
if (index === -1) {
return;
Keydown: function(event, menu) {
if (!this.posted) {
return FALSE(event);
}
do {
index--;
if (index < 0) {
index = this.items.length - 1;
}
} while (!this.items[index].GetNode().role);
MENU.UnfocusItem(item);
MENU.FocusItem(this.items[index]);
switch (event.keyCode) {
case KEY.ESCAPE:
this.Remove(event, menu);
break;
case KEY.RIGHT:
this.Right(event, menu);
break;
case KEY.LEFT:
this.Left(event, menu);
break;
case KEY.UP:
this.Up(event, menu);
break;
case KEY.DOWN:
this.Down(event, menu);
break;
case KEY.RETURN:
case KEY.SPACE:
this.Space(event, menu);
break;
default:
break;
}
return FALSE(event);
},
Down: function(item) {
var index = this.items.indexOf(item);
if (index === -1) {
return;
}
do {
index++;
if (index >= this.items.length) {
index = 0;
}
} while (!this.items[index].GetNode().role);
MENU.UnfocusItem(item);
MENU.FocusItem(this.items[index]);
}
Right: function(event, menu) {
MENU.Right(event, menu);
},
Left: function(event, menu) {
MENU.Left(event, menu);
},
Up: function(event, menu) {
var item = this.items[this.items.length - 1];
item.Activate(item.GetNode());
},
Down: function(event, menu) {
var item = this.items[0];
item.Activate(item.GetNode());
},
Space: function(event, menu) { }
},{
config: CONFIG,
div: null, // the DOM elements for the menu and submenus
/*************************************************************/
/*
* Enum element for key codes.
*/
KEY: {
RETURN: 13,
ESCAPE: 27,
SPACE: 32,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40
},
Remove: function (event) {return MENU.Event(event,this,"Remove")},
Mouseover: function (event) {return MENU.Event(event,this,"Mouseover")},
Mouseout: function (event) {return MENU.Event(event,this,"Mouseout")},
@ -457,51 +465,84 @@
/*
* Keyboard navigation of menu.
*/
jaxs: [],
hasJaxs: false,
oldJax: null,
jaxs: [], // List of all MathJax nodes.
hasJaxs: false, // Flag to indicate if the MathJax node list has already
// been computed.
node: null, // The node the menu was activated on.
active: null, // The currently focused item. There can only be one!
posted: false, // Is a menu open?
GetJaxs: function() {
// input.id + adding frame to get the elements.
var nodes = document.getElementsByClassName('MathJax');
for (var i = 0, node; node = nodes[i]; i++) {
MENU.jaxs.push(node);
}
},
Focus: function(event, menu) {
console.log('focusing...');
//
// Focus is a global affair, since we only ever want a single focused item.
//
Focus: function(menu) {
if (!MENU.posted) {
MENU.Activate(menu);
}
if (MENU.active) {
MENU.active.tabIndex = -1;
}
MENU.active = menu;
MENU.active.tabIndex = 0;
MENU.active.focus();
},
Activate: function(menu) {
if (!MENU.hasJaxs) {
MENU.GetJaxs();
}
MENU.oldJax = event.srcElement;
if (!MENU.node) {
MENU.node = document.getElementById(MENU.jax.inputID + '-Frame');
}
for (var j = 0, jax; jax = MENU.jaxs[j]; j++) {
jax.tabIndex = -1;
}
MENU.FocusItem(MENU.menu.items[0]);
console.log('end focusing...');
},
FocusItem: function(item) {
console.log('Focusing on item');
console.log(item);
var node = item.GetNode();
node.tabIndex = 0;
item.Activate(node);
node.focus();
MENU.posted = true;
},
Unfocus: function() {
MENU.active.tabIndex = -1;
MENU.active = null;
for (var j = 0, jax; jax = MENU.jaxs[j]; j++) {
jax.tabIndex = 0;
}
MENU.oldJax.focus();
MENU.oldJax = null;
MENU.node.focus();
MENU.node = null;
MENU.posted = false;
},
UnfocusItem: function(item) {
var node = item.GetNode();
node.tabIndex = -1;
item.Deactivate(node);
//TODO: A toggle focus method on the top level would avoid having to
//tabIndex all the Jaxs.
Move: function(event, menu, move) {
var len = MENU.jaxs.length;
if (len === 0) {
return;
}
var next = MENU.jaxs[MENU.Mod(move(MENU.jaxs.indexOf(MENU.node)), len)];
if (next === MENU.node) {
return;
}
MENU.menu.Remove(event, menu);
MENU.jax = MathJax.Hub.getJaxFor(next);
MENU.node = next;
MENU.menu.Post(null);
},
Right: function(event, menu) {
MENU.Move(event, menu, function(x) {return x + 1;});
},
Left: function(event, menu) {
MENU.Move(event, menu, function(x) {return x - 1;});
},
//TODO: Helper. To move
// Computes a mod n.
Mod: function(a, n) {
return ((a % n) + n) % n;
},
saveCookie: function () {HTML.Cookie.Set("menu",this.cookie)},
getCookie: function () {this.cookie = HTML.Cookie.Get("menu")}
@ -515,7 +556,7 @@
name: "", // The menu item's label as [id,label] pair.
node: null, // The HTML node of the item.
menu: null, // The parent menu containing that item.
menu: null, // The parent menu containing that item. HTML node.
/*
* Accessor method for node.
@ -551,33 +592,36 @@
Name: function () {return _(this.name[0],this.name[1])},
Mouseover: function (event,menu) {
if (!this.disabled) {this.Activate(menu)}
if (!this.submenu || !this.submenu.posted) {
console.log('This is not a submenu method!');
var menus = document.getElementById("MathJax_MenuFrame").childNodes,
items = this.menu.childNodes;
for (var i = 0, m = items.length; i < m; i++) {
var item = items[i].menuItem;
// Deactivates submenu items.
if (item && item.submenu && item.submenu.posted) {
item.Deactivate(items[i]);
}
}
// Removes all submenus.
m = menus.length-1;
while (m >= 0 && this.menu.menuItem !== menus[m].menuItem) {
menus[m].menuItem.posted = false;
menus[m].parentNode.removeChild(menus[m]);
m--;
}
if (this.Timer && !MENU.isMobile) {this.Timer(event,menu)}
}
this.Activate(menu);
},
Mouseout: function (event,menu) {
if (!this.submenu || !this.submenu.posted) {this.Deactivate(menu)}
if (this.timer) {clearTimeout(this.timer); delete this.timer}
},
Mouseup: function (event,menu) {return this.Remove(event,menu)},
DeactivateSubmenus: function(menu) {
var menus = document.getElementById("MathJax_MenuFrame").childNodes,
items = this.menu.childNodes;
for (var i = 0, m = items.length; i < m; i++) {
var item = items[i].menuItem;
// Deactivates submenu items.
if (item && item.submenu && item.submenu.posted) {
item.Deactivate(items[i]);
}
}
this.RemoveSubmenus(menu, menus);
},
RemoveSubmenus: function(menu, menus) {
menus = menus || document.getElementById("MathJax_MenuFrame").childNodes;
var m = menus.length-1;
while (m >= 0 && this.menu.menuItem !== menus[m].menuItem) {
menus[m].menuItem.posted = false;
menus[m].parentNode.removeChild(menus[m]);
m--;
}
},
Touchstart: function (event,menu) {return this.TouchEvent(event,menu,"Mousedown")},
Touchend: function (event,menu) {return this.TouchEvent(event,menu,"Mouseup")},
@ -599,7 +643,11 @@
Activate: function (menu) {
this.Deactivate(menu);
menu.className += " MathJax_MenuActive";
if (!this.disabled) {
menu.className += " MathJax_MenuActive";
}
this.DeactivateSubmenus(menu);
MENU.Focus(menu);
},
Deactivate: function (menu) {menu.className = menu.className.replace(/ MathJax_MenuActive/,"")},
@ -622,32 +670,79 @@
this,
{onmouseover: MENU.Mouseover, onmouseout: MENU.Mouseout,
onmousedown: MENU.Mousedown, role: this.role,
onkeydown: MENU.Keydown,
'aria-disabled': !!this.disabled});
if (this.disabled) {
def.className += " MathJax_MenuDisabled";
}
return def;
}
Keydown: function(event, menu) {
console.log('MENUEntry');
},
Keydown: function(event, item) {
switch (event.keyCode) {
case MENU.KEY.ESCAPE:
this.Remove(event, menu);
case KEY.ESCAPE:
this.Remove(event, item);
break;
case MENU.KEY.UP:
menu.parentNode.menuItem.Up(menu.menuItem);
case KEY.UP:
this.Up(event, item);
break;
case MENU.KEY.DOWN:
menu.parentNode.menuItem.Down(menu.menuItem);
case KEY.DOWN:
this.Down(event, item);
break;
case KEY.RIGHT:
this.Right(event, item);
break;
case KEY.LEFT:
this.Left(event, item);
break;
case KEY.SPACE:
case KEY.RETURN:
this.Space(event, item);
break;
default:
break;
}
return FALSE(event);
},
Remove: function(event, menu) {
MENU.UnfocusItem(this);
this.SUPER(arguments).Remove.apply(this, arguments);
Move: function(event, item, move) {
var items = this.menu.menuItem.items;
var len = items.length;
var index = items.indexOf(this);
if (index === -1) {
return;
}
do {
index = MENU.Mod(move(index), len);
} while (items[index].hidden || !items[index].GetNode().role);
this.Deactivate(item);
item = items[index];
item.Activate(item.GetNode());
},
Up: function(event, item) {
this.Move(event, item, function(x) { return x - 1; });
},
Down: function(event, item) {
this.Move(event, item, function(x) { return x + 1; });
},
Right: function(event, item) {
if (this.menu.menuItem === MENU.menu) {
MENU.Right(event, item);
}
},
Left: function(event, item) {
if (this.menu.menuItem === MENU.menu) {
MENU.Left(event, item);
} else {
this.Deactivate(item);
var sibling = item.parentNode.previousSibling;
var actives = sibling.getElementsByClassName('MathJax_MenuActive');
if (actives.length > 0) {
MENU.Focus(actives[0]);
}
this.RemoveSubmenus(item);
}
},
Space: function (event, menu) {
this.Mouseup(event, menu);
}
});
@ -665,6 +760,7 @@
},
Label: function (def,menu) {return [this.Name()]},
//TODO: Focus the popup.
Mouseup: function (event,menu) {
if (!this.disabled) {
this.Remove(event,menu);
@ -673,13 +769,6 @@
}
return FALSE(event);
}
// Keydown: function(event, menu) {
// console.log('here');
// if (event.keyCode === MENU.KEY.ESCAPE) {
// this.Remove(event, menu);
// }
// return FALSE(event);
// }
});
/*************************************************************/
@ -714,43 +803,40 @@
if (forceout) {this.Deactivate(menu); delete ITEM.lastItem; delete ITEM.lastMenu}
return result;
},
Mouseover: function(event, menu) {
this.Activate(menu);
},
Mouseup: function (event,menu) {
if (!this.disabled) {
if (!this.submenu.posted) {
if (this.timer) {clearTimeout(this.timer); delete this.timer}
this.submenu.Post(event,menu,this.ltr);
MENU.Focus(menu);
} else {
var menus = document.getElementById("MathJax_MenuFrame").childNodes,
m = menus.length-1;
while (m >= 0) {
var child = menus[m];
child.menuItem.posted = false;
child.parentNode.removeChild(child);
if (child.menuItem === this.submenu) {break};
m--;
}
this.RemoveSubmenus(menu);
}
}
return FALSE(event);
},
Keydown: function(event, menu) {
console.log('MENUSubmenu');
switch (event.keyCode) {
case MENU.KEY.RIGHT:
case MENU.KEY.SPACE:
if (!this.submenu.posted) {
this.submenu.Post(event,menu,this.ltr);
}
break;
case MENU.KEY.LEFT:
menu.parentNode.menuItem.Down(menu.menuItem);
break;
default:
break;
Activate: function (menu) {
if (!this.disabled) {
this.Deactivate(menu);
menu.className += " MathJax_MenuActive";
}
if (!this.submenu.posted) {
this.DeactivateSubmenus(menu);
}
MENU.Focus(menu);
if (!MENU.isMobile) {
this.Timer(event,menu);
}
},
Right: function(event, menu) {
if (this.submenu.items.length > 0) {
var item = this.submenu.items[0];
item.Activate(item.GetNode());
}
return this.SUPER(arguments).Keydown.apply(this, arguments);
}
});
/*************************************************************/
@ -825,8 +911,11 @@
/*************************************************************/
/*
* A menu item that is a label
*/
MENU.ITEM.LABEL = MENU.ITEM.Subclass({
* //TODO: Turn this into a focusable! No mouse interaction!
*/
MENU.ITEM.LABEL = MENU.ENTRY.Subclass({
role: "menuitem", // Aria role.
Init: function (name,def) {
if (!(name instanceof Array)) {name = [name,name]} // make [id,label] pair
this.name = name; this.With(def);
@ -834,6 +923,10 @@
Label: function (def,menu) {
def.className += " MathJax_MenuLabel";
return [this.Name()];
},
Activate: function(menu) {
this.Deactivate(menu);
MENU.Focus(menu);
}
});