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 RIGHTBUTTON: 2, // the event.button value for right button
MENUKEY: "altKey", // the event value for alternate context menu 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)}, Mousedown: function (event) {return EVENT.Handler(event,"Mousedown",this)},
Mouseup: function (event) {return EVENT.Handler(event,"Mouseup",this)}, Mouseup: function (event) {return EVENT.Handler(event,"Mouseup",this)},
Mousemove: function (event) {return EVENT.Handler(event,"Mousemove",this)}, Mousemove: function (event) {return EVENT.Handler(event,"Mousemove",this)},
@ -150,7 +164,7 @@
// //
Keydown: function (event, math) { Keydown: function (event, math) {
var jax = OUTPUT[this.jaxID]; var jax = OUTPUT[this.jaxID];
if (event.keyCode === 32) { if (event.keyCode === EVENT.KEY.SPACE) {
// TODO: Put the focus on the first element. // TODO: Put the focus on the first element.
EVENT.ContextMenu(event, this); EVENT.ContextMenu(event, this);
}; };

View File

@ -198,10 +198,11 @@
} }
}); });
var FALSE, HOVER; var FALSE, HOVER, KEY;
HUB.Register.StartupHook("MathEvents Ready",function () { HUB.Register.StartupHook("MathEvents Ready",function () {
FALSE = MathJax.Extension.MathEvents.Event.False; FALSE = MathJax.Extension.MathEvents.Event.False;
HOVER = MathJax.Extension.MathEvents.Hover; HOVER = MathJax.Extension.MathEvents.Hover;
KEY = MathJax.Extension.MathEvents.Event.KEY;
}); });
/*************************************************************/ /*************************************************************/
@ -250,6 +251,12 @@
this.posted = true; this.posted = true;
menu.style.width = (menu.offsetWidth+2) + "px"; menu.style.width = (menu.offsetWidth+2) + "px";
var x = event.pageX, y = event.pageY; 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) { if (!x && !y) {
x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
@ -283,7 +290,7 @@
menu.style.left = x+"px"; menu.style.top = y+"px"; menu.style.left = x+"px"; menu.style.top = y+"px";
if (document.selection && document.selection.empty) {document.selection.empty()} if (document.selection && document.selection.empty) {document.selection.empty()}
MENU.Focus(event, menu); MENU.Focus(menu);
return FALSE(event); return FALSE(event);
}, },
@ -301,7 +308,7 @@
delete MENU.jax.hover.nofade; delete MENU.jax.hover.nofade;
HOVER.UnHover(MENU.jax); HOVER.UnHover(MENU.jax);
} }
MENU.Unfocus(); MENU.Unfocus(menu);
return FALSE(event); return FALSE(event);
}, },
@ -338,55 +345,56 @@
/* /*
* Moving in the list of items. * Moving in the list of items.
*/ */
Up: function(item) { Keydown: function(event, menu) {
var index = this.items.indexOf(item); if (!this.posted) {
if (index === -1) { return FALSE(event);
return;
} }
do { switch (event.keyCode) {
index--; case KEY.ESCAPE:
if (index < 0) { this.Remove(event, menu);
index = this.items.length - 1; 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;
} }
} while (!this.items[index].GetNode().role); return FALSE(event);
MENU.UnfocusItem(item);
MENU.FocusItem(this.items[index]);
}, },
Down: function(item) { Right: function(event, menu) {
var index = this.items.indexOf(item); MENU.Right(event, menu);
if (index === -1) { },
return; Left: function(event, menu) {
} MENU.Left(event, menu);
do { },
index++; Up: function(event, menu) {
if (index >= this.items.length) { var item = this.items[this.items.length - 1];
index = 0; item.Activate(item.GetNode());
} },
} while (!this.items[index].GetNode().role); Down: function(event, menu) {
MENU.UnfocusItem(item); var item = this.items[0];
MENU.FocusItem(this.items[index]); item.Activate(item.GetNode());
} },
Space: function(event, menu) { }
},{ },{
config: CONFIG, config: CONFIG,
div: null, // the DOM elements for the menu and submenus 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")}, Remove: function (event) {return MENU.Event(event,this,"Remove")},
Mouseover: function (event) {return MENU.Event(event,this,"Mouseover")}, Mouseover: function (event) {return MENU.Event(event,this,"Mouseover")},
Mouseout: function (event) {return MENU.Event(event,this,"Mouseout")}, Mouseout: function (event) {return MENU.Event(event,this,"Mouseout")},
@ -457,50 +465,83 @@
/* /*
* Keyboard navigation of menu. * Keyboard navigation of menu.
*/ */
jaxs: [], jaxs: [], // List of all MathJax nodes.
hasJaxs: false, hasJaxs: false, // Flag to indicate if the MathJax node list has already
oldJax: null, // 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() { GetJaxs: function() {
// input.id + adding frame to get the elements.
var nodes = document.getElementsByClassName('MathJax'); var nodes = document.getElementsByClassName('MathJax');
for (var i = 0, node; node = nodes[i]; i++) { for (var i = 0, node; node = nodes[i]; i++) {
MENU.jaxs.push(node); 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) { if (!MENU.hasJaxs) {
MENU.GetJaxs(); 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++) { for (var j = 0, jax; jax = MENU.jaxs[j]; j++) {
jax.tabIndex = -1; jax.tabIndex = -1;
} }
MENU.FocusItem(MENU.menu.items[0]); MENU.posted = true;
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();
}, },
Unfocus: function() { Unfocus: function() {
MENU.active.tabIndex = -1;
MENU.active = null;
for (var j = 0, jax; jax = MENU.jaxs[j]; j++) { for (var j = 0, jax; jax = MENU.jaxs[j]; j++) {
jax.tabIndex = 0; jax.tabIndex = 0;
} }
MENU.oldJax.focus(); MENU.node.focus();
MENU.oldJax = null; MENU.node = null;
MENU.posted = false;
}, },
UnfocusItem: function(item) { //TODO: A toggle focus method on the top level would avoid having to
var node = item.GetNode(); //tabIndex all the Jaxs.
node.tabIndex = -1; Move: function(event, menu, move) {
item.Deactivate(node); 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)}, saveCookie: function () {HTML.Cookie.Set("menu",this.cookie)},
getCookie: function () {this.cookie = HTML.Cookie.Get("menu")} getCookie: function () {this.cookie = HTML.Cookie.Get("menu")}
@ -515,7 +556,7 @@
name: "", // The menu item's label as [id,label] pair. name: "", // The menu item's label as [id,label] pair.
node: null, // The HTML node of the item. 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. * Accessor method for node.
@ -551,9 +592,16 @@
Name: function () {return _(this.name[0],this.name[1])}, Name: function () {return _(this.name[0],this.name[1])},
Mouseover: function (event,menu) { Mouseover: function (event,menu) {
if (!this.disabled) {this.Activate(menu)} this.Activate(menu);
if (!this.submenu || !this.submenu.posted) { },
console.log('This is not a submenu method!'); 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, var menus = document.getElementById("MathJax_MenuFrame").childNodes,
items = this.menu.childNodes; items = this.menu.childNodes;
for (var i = 0, m = items.length; i < m; i++) { for (var i = 0, m = items.length; i < m; i++) {
@ -563,21 +611,17 @@
item.Deactivate(items[i]); item.Deactivate(items[i]);
} }
} }
// Removes all submenus. this.RemoveSubmenus(menu, menus);
m = menus.length-1; },
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) { while (m >= 0 && this.menu.menuItem !== menus[m].menuItem) {
menus[m].menuItem.posted = false; menus[m].menuItem.posted = false;
menus[m].parentNode.removeChild(menus[m]); menus[m].parentNode.removeChild(menus[m]);
m--; m--;
} }
if (this.Timer && !MENU.isMobile) {this.Timer(event,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)},
Touchstart: function (event,menu) {return this.TouchEvent(event,menu,"Mousedown")}, Touchstart: function (event,menu) {return this.TouchEvent(event,menu,"Mousedown")},
Touchend: function (event,menu) {return this.TouchEvent(event,menu,"Mouseup")}, Touchend: function (event,menu) {return this.TouchEvent(event,menu,"Mouseup")},
@ -599,7 +643,11 @@
Activate: function (menu) { Activate: function (menu) {
this.Deactivate(menu); this.Deactivate(menu);
if (!this.disabled) {
menu.className += " MathJax_MenuActive"; menu.className += " MathJax_MenuActive";
}
this.DeactivateSubmenus(menu);
MENU.Focus(menu);
}, },
Deactivate: function (menu) {menu.className = menu.className.replace(/ MathJax_MenuActive/,"")}, Deactivate: function (menu) {menu.className = menu.className.replace(/ MathJax_MenuActive/,"")},
@ -622,32 +670,79 @@
this, this,
{onmouseover: MENU.Mouseover, onmouseout: MENU.Mouseout, {onmouseover: MENU.Mouseover, onmouseout: MENU.Mouseout,
onmousedown: MENU.Mousedown, role: this.role, onmousedown: MENU.Mousedown, role: this.role,
onkeydown: MENU.Keydown,
'aria-disabled': !!this.disabled}); 'aria-disabled': !!this.disabled});
if (this.disabled) { if (this.disabled) {
def.className += " MathJax_MenuDisabled"; def.className += " MathJax_MenuDisabled";
} }
return def; return def;
} },
Keydown: function(event, menu) { Keydown: function(event, item) {
console.log('MENUEntry');
switch (event.keyCode) { switch (event.keyCode) {
case MENU.KEY.ESCAPE: case KEY.ESCAPE:
this.Remove(event, menu); this.Remove(event, item);
break; break;
case MENU.KEY.UP: case KEY.UP:
menu.parentNode.menuItem.Up(menu.menuItem); this.Up(event, item);
break; break;
case MENU.KEY.DOWN: case KEY.DOWN:
menu.parentNode.menuItem.Down(menu.menuItem); 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; break;
default: default:
break; break;
} }
return FALSE(event); return FALSE(event);
}, },
Remove: function(event, menu) { Move: function(event, item, move) {
MENU.UnfocusItem(this); var items = this.menu.menuItem.items;
this.SUPER(arguments).Remove.apply(this, arguments); 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()]}, Label: function (def,menu) {return [this.Name()]},
//TODO: Focus the popup.
Mouseup: function (event,menu) { Mouseup: function (event,menu) {
if (!this.disabled) { if (!this.disabled) {
this.Remove(event,menu); this.Remove(event,menu);
@ -673,13 +769,6 @@
} }
return FALSE(event); 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} if (forceout) {this.Deactivate(menu); delete ITEM.lastItem; delete ITEM.lastMenu}
return result; return result;
}, },
Mouseover: function(event, menu) {
this.Activate(menu);
},
Mouseup: function (event,menu) { Mouseup: function (event,menu) {
if (!this.disabled) { if (!this.disabled) {
if (!this.submenu.posted) { if (!this.submenu.posted) {
if (this.timer) {clearTimeout(this.timer); delete this.timer} if (this.timer) {clearTimeout(this.timer); delete this.timer}
this.submenu.Post(event,menu,this.ltr); this.submenu.Post(event,menu,this.ltr);
MENU.Focus(menu);
} else { } else {
var menus = document.getElementById("MathJax_MenuFrame").childNodes, this.RemoveSubmenus(menu);
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--;
}
} }
} }
return FALSE(event); return FALSE(event);
}, },
Keydown: function(event, menu) { Activate: function (menu) {
console.log('MENUSubmenu'); if (!this.disabled) {
switch (event.keyCode) { this.Deactivate(menu);
case MENU.KEY.RIGHT: menu.className += " MathJax_MenuActive";
case MENU.KEY.SPACE: }
if (!this.submenu.posted) { if (!this.submenu.posted) {
this.submenu.Post(event,menu,this.ltr); this.DeactivateSubmenus(menu);
} }
break; MENU.Focus(menu);
case MENU.KEY.LEFT: if (!MENU.isMobile) {
menu.parentNode.menuItem.Down(menu.menuItem); this.Timer(event,menu);
break; }
default: },
break; 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 * A menu item that is a label
* //TODO: Turn this into a focusable! No mouse interaction!
*/ */
MENU.ITEM.LABEL = MENU.ITEM.Subclass({ MENU.ITEM.LABEL = MENU.ENTRY.Subclass({
role: "menuitem", // Aria role.
Init: function (name,def) { Init: function (name,def) {
if (!(name instanceof Array)) {name = [name,name]} // make [id,label] pair if (!(name instanceof Array)) {name = [name,name]} // make [id,label] pair
this.name = name; this.With(def); this.name = name; this.With(def);
@ -834,6 +923,10 @@
Label: function (def,menu) { Label: function (def,menu) {
def.className += " MathJax_MenuLabel"; def.className += " MathJax_MenuLabel";
return [this.Name()]; return [this.Name()];
},
Activate: function(menu) {
this.Deactivate(menu);
MENU.Focus(menu);
} }
}); });