commit 1be417af3a98edd42e2bc89432e08a2d733dfb08
parent 5e88e809751cc5ca9b4a733e4a16e03e240f0d47
Author: zorkow <volker.sorge@gmail.com>
Date: Fri, 21 Aug 2015 04:34:18 +0100
Combined keyboard and mouse navigation works.
Diffstat:
2 files changed, 246 insertions(+), 139 deletions(-)
diff --git a/unpacked/extensions/MathEvents.js b/unpacked/extensions/MathEvents.js
@@ -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);
};
diff --git a/unpacked/extensions/MathMenu.js b/unpacked/extensions/MathMenu.js
@@ -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]);
- },
- Down: function(item) {
- var index = this.items.indexOf(item);
- if (index === -1) {
- return;
+ 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;
}
- do {
- index++;
- if (index >= this.items.length) {
- index = 0;
- }
- } while (!this.items[index].GetNode().role);
- MENU.UnfocusItem(item);
- MENU.FocusItem(this.items[index]);
- }
-
+ return FALSE(event);
+ },
+ 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 KEY.UP:
+ this.Up(event, item);
+ break;
+ case KEY.DOWN:
+ this.Down(event, item);
+ break;
+ case KEY.RIGHT:
+ this.Right(event, item);
break;
- case MENU.KEY.UP:
- menu.parentNode.menuItem.Up(menu.menuItem);
+ case KEY.LEFT:
+ this.Left(event, item);
break;
- case MENU.KEY.DOWN:
- menu.parentNode.menuItem.Down(menu.menuItem);
+ 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);
}
});