/**
* @ignore
* submenu control for kissy, transfer item's keyCode to menu
* @author yiminghe@gmail.com
*/
KISSY.add("menu/submenu", function (S, Node, MenuItem, SubMenuRender) {
var KeyCode = Node.KeyCode,
MENU_DELAY = 0.15;
function afterHighlightedChange(e) {
var target = e.target,
self = this;
// hover 子菜单,保持该菜单项高亮
if (target !== self && target.isMenuItem && e.newVal) {
self.clearHidePopupMenuTimers();
if (!self.get('highlighted')) {
self.set('highlighted', true);
// refresh highlightedItem of parent menu
target.set('highlighted', false);
target.set('highlighted', true);
}
}
}
* Class representing a submenu that can be added as an item to other menus.
* xclass: 'submenu'.
* @extends KISSY.Menu.Item
* @class KISSY.Menu.SubMenu
*/
return MenuItem.extend({
isSubMenu: 1,
clearShowPopupMenuTimers: function () {
var showTimer;
if (showTimer = this._showTimer) {
showTimer.cancel();
this._showTimer = null;
}
},
clearHidePopupMenuTimers: function () {
var dismissTimer;
if (dismissTimer = this._dismissTimer) {
dismissTimer.cancel();
this._dismissTimer = null;
}
},
clearSubMenuTimers: function () {
this.clearHidePopupMenuTimers();
this.clearShowPopupMenuTimers();
},
bindUI: function () {
var self = this;
self.on('afterHighlightedChange', afterHighlightedChange, self);
},
handleMouseLeaveInternal: function () {
var self = this;
self.set('highlighted', false, {
data: {
fromMouse: 1
}
});
self.clearSubMenuTimers();
var menu = self.get('menu');
if (menu.get('visible')) {
// 延迟 highlighted
self._dismissTimer = S.later(hideMenu,
self.get("menuDelay") * 1000, false, self);
}
},
handleMouseEnterInternal: function () {
var self = this;
self.set('highlighted', true, {
data: {
fromMouse: 1
}
});
self.clearSubMenuTimers();
var menu = self.get('menu');
if (!menu.get('visible')) {
self._showTimer = S.later(showMenu, self.get("menuDelay") * 1000, false, self);
}
},
* Dismisses the submenu on a delay, with the result that the user needs less
* accuracy when moving to sub menus.
* @protected
*/
_onSetHighlighted: function (v, e) {
var self = this;
// sync
if (!e) {
return;
}
self.callSuper(e);
if (e.fromMouse) {
return;
}
if (v && !e.fromKeyboard) {
showMenu.call(self);
} else if (!v) {
hideMenu.call(self);
}
},
// click ,立即显示
handleClickInternal: function () {
var self = this;
showMenu.call(self);
// trigger click event from menuitem
self.callSuper(e);
},
/**
* Handles a key event that is passed to the menu item from its parent because
* it is highlighted. If the right key is pressed the sub menu takes control
* and delegates further key events to its menu until it is dismissed OR the
* left key is pressed.
* Protected for subclass overridden.
* @param {KISSY.Event.DomEvent.Object} e key event.
* @protected
* @return {Boolean|undefined} Whether the event was handled.
*/
handleKeyDownInternal: function (e) {
var self = this,
menu = self.get('menu'),
menuChildren,
menuChild,
hasKeyboardControl_ = menu.get("visible"),
keyCode = e.keyCode;
if (!hasKeyboardControl_) {
// right
if (keyCode == KeyCode.RIGHT) {
showMenu.call(self);
menuChildren = menu.get("children");
if (menuChild = menuChildren[0]) {
menuChild.set('highlighted', true, {
data: {
fromKeyboard: 1
}
});
}
}
// enter as click
else if (keyCode == KeyCode.ENTER) {
return self.handleClickInternal(e);
}
else {
return undefined;
}
} else if (menu.handleKeyDownInternal(e)) {
}
// The menu has control and the key hasn't yet been handled, on left arrow
// we turn off key control.
// left
else if (keyCode == KeyCode.LEFT) {
// refresh highlightedItem of parent menu
self.set('highlighted', false);
self.set('highlighted', true, {
data: {
fromKeyboard: 1
}
});
} else {
return undefined;
}
return true;
},
containsElement: function (element) {
return this.get('menu').containsElement(element);
},
destructor: function () {
var self = this,
menu = self.get('menu');
self.clearSubMenuTimers();
menu.destroy();
}
},
{
ATTRS: {
* The delay before opening the sub menu in seconds. (This number is
* arbitrary, it would be good to get some user studies or a designer to play
* with some numbers).
* Defaults to: 0.15
* @cfg {Number} menuDelay
*/
* @ignore
*/
menuDelay: {
value: MENU_DELAY
},
* Menu config or instance.
* @cfg {KISSY.Menu|Object} menu
*/
* Menu config or instance.
* @property menu
* @type {KISSY.Menu|Object}
*/
* @ignore
*/
menu: {
value: {},
getter: function (v) {
if (!v.isControl) {
v.xclass = v.xclass || 'popupmenu';
v = this.createComponent(v);
this.setInternal('menu', v);
}
return v;
},
setter: function (m) {
if (m.isControl) {
m.setInternal('parent', this);
}
}
},
xrender: {
value: SubMenuRender
}
},
xclass: 'submenu'
});
// # -------------------------------- private start
function showMenu() {
var self = this,
menu = self.get('menu');
// does not put this into setter
// in case set menu before submenu item is rendered
var align = {
node: this.$el,
points: ['tr', 'tl'],
overflow: {
adjustX: 1,
adjustY: 1
}
};
S.mix(menu.get('align'), align, false);
menu.show();
self.el.setAttribute("aria-haspopup", menu.get('el').attr("id"));
}
function hideMenu() {
this.get('menu').hide();
}
// # ------------------------------------ private end
}, {
requires: ['node', './menuitem', './submenu-render']
});