windowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinux
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
738 lines
22 KiB
738 lines
22 KiB
9 years ago
|
/**
|
||
|
* A menu object. This is the container to which you may add {@link Ext.menu.Item menu items}.
|
||
|
*
|
||
|
* Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Components}.
|
||
|
* Menus may also contain {@link Ext.panel.Panel#dockedItems docked items} because it extends {@link Ext.panel.Panel}.
|
||
|
*
|
||
|
* By default, non {@link Ext.menu.Item menu items} are indented so that they line up with the text of menu items. clearing
|
||
|
* the icon column. To make a contained general {@link Ext.Component Component} left aligned configure the child
|
||
|
* Component with `indent: false.
|
||
|
*
|
||
|
* By default, Menus are absolutely positioned, floating Components. By configuring a Menu with `{@link #floating}: false`,
|
||
|
* a Menu may be used as a child of a {@link Ext.container.Container Container}.
|
||
|
*
|
||
|
* @example
|
||
|
* Ext.create('Ext.menu.Menu', {
|
||
|
* width: 100,
|
||
|
* margin: '0 0 10 0',
|
||
|
* floating: false, // usually you want this set to True (default)
|
||
|
* renderTo: Ext.getBody(), // usually rendered by it's containing component
|
||
|
* items: [{
|
||
|
* text: 'regular item 1'
|
||
|
* },{
|
||
|
* text: 'regular item 2'
|
||
|
* },{
|
||
|
* text: 'regular item 3'
|
||
|
* }]
|
||
|
* });
|
||
|
*
|
||
|
* Ext.create('Ext.menu.Menu', {
|
||
|
* width: 100,
|
||
|
* plain: true,
|
||
|
* floating: false, // usually you want this set to True (default)
|
||
|
* renderTo: Ext.getBody(), // usually rendered by it's containing component
|
||
|
* items: [{
|
||
|
* text: 'plain item 1'
|
||
|
* },{
|
||
|
* text: 'plain item 2'
|
||
|
* },{
|
||
|
* text: 'plain item 3'
|
||
|
* }]
|
||
|
* });
|
||
|
*/
|
||
|
Ext.define('Ext.menu.Menu', {
|
||
|
extend: 'Ext.panel.Panel',
|
||
|
alias: 'widget.menu',
|
||
|
requires: [
|
||
|
'Ext.layout.container.VBox',
|
||
|
'Ext.menu.CheckItem',
|
||
|
'Ext.menu.Item',
|
||
|
'Ext.menu.Manager',
|
||
|
'Ext.menu.Separator'
|
||
|
],
|
||
|
|
||
|
mixins: [
|
||
|
'Ext.util.FocusableContainer'
|
||
|
],
|
||
|
|
||
|
/**
|
||
|
* @property {Ext.menu.Menu} parentMenu
|
||
|
* The parent Menu of this Menu.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @cfg {Boolean} [enableKeyNav=true]
|
||
|
* @deprecated 5.1.0 Intra-menu key navigation is always enabled.
|
||
|
*/
|
||
|
enableKeyNav: true,
|
||
|
|
||
|
/**
|
||
|
* @cfg {Boolean} [allowOtherMenus=false]
|
||
|
* True to allow multiple menus to be displayed at the same time.
|
||
|
*/
|
||
|
allowOtherMenus: false,
|
||
|
|
||
|
/**
|
||
|
* @cfg {String} ariaRole
|
||
|
* @private
|
||
|
*/
|
||
|
ariaRole: 'menu',
|
||
|
|
||
|
/**
|
||
|
* @cfg {Boolean} autoRender
|
||
|
* Floating is true, so autoRender always happens.
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @cfg {Boolean} [floating=true]
|
||
|
* A Menu configured as `floating: true` (the default) will be rendered as an absolutely positioned,
|
||
|
* {@link Ext.Component#floating floating} {@link Ext.Component Component}. If configured as `floating: false`, the Menu may be
|
||
|
* used as a child item of another {@link Ext.container.Container Container}.
|
||
|
*/
|
||
|
floating: true,
|
||
|
|
||
|
/**
|
||
|
* @cfg {Boolean} constrain
|
||
|
* Menus are constrained to the document body by default.
|
||
|
* @private
|
||
|
*/
|
||
|
constrain: true,
|
||
|
|
||
|
/**
|
||
|
* @cfg {Boolean} [hidden]
|
||
|
* True to initially render the Menu as hidden, requiring to be shown manually.
|
||
|
*
|
||
|
* Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.
|
||
|
*/
|
||
|
hidden: true,
|
||
|
|
||
|
hideMode: 'visibility',
|
||
|
|
||
|
/**
|
||
|
* @cfg {Boolean} [ignoreParentClicks=false]
|
||
|
* True to ignore clicks on any item in this menu that is a parent item (displays a submenu)
|
||
|
* so that the submenu is not dismissed when clicking the parent item.
|
||
|
*/
|
||
|
ignoreParentClicks: false,
|
||
|
|
||
|
/**
|
||
|
* @property {Boolean} isMenu
|
||
|
* `true` in this class to identify an object as an instantiated Menu, or subclass thereof.
|
||
|
*/
|
||
|
isMenu: true,
|
||
|
|
||
|
/**
|
||
|
* @cfg {Ext.enums.Layout/Object} layout
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @cfg {Boolean} [showSeparator=true]
|
||
|
* True to show the icon separator.
|
||
|
*/
|
||
|
showSeparator : true,
|
||
|
|
||
|
/**
|
||
|
* @cfg {Number} [minWidth=120]
|
||
|
* The minimum width of the Menu. The default minWidth only applies when the {@link #floating} config is true.
|
||
|
*/
|
||
|
minWidth: undefined,
|
||
|
|
||
|
defaultMinWidth: 120,
|
||
|
|
||
|
/**
|
||
|
* @cfg {String} [defaultAlign="tl-bl?"]
|
||
|
* The default {@link Ext.util.Positionable#getAlignToXY Ext.dom.Element#getAlignToXY} anchor position value for this menu
|
||
|
* relative to its owner. Used in conjunction with {@link #showBy}.
|
||
|
*/
|
||
|
defaultAlign: 'tl-bl?',
|
||
|
|
||
|
/**
|
||
|
* @cfg {Boolean} [plain=false]
|
||
|
* True to remove the incised line down the left side of the menu and to not indent general Component items.
|
||
|
*
|
||
|
* {@link Ext.menu.Item MenuItem}s will *always* have space at their start for an icon. With the `plain` setting,
|
||
|
* non {@link Ext.menu.Item MenuItem} child components will not be indented to line up.
|
||
|
*
|
||
|
* Basically, `plain:true` makes a Menu behave more like a regular {@link Ext.layout.container.HBox HBox layout}
|
||
|
* {@link Ext.panel.Panel Panel} which just has the same background as a Menu.
|
||
|
*
|
||
|
* See also the {@link #showSeparator} config.
|
||
|
*/
|
||
|
|
||
|
focusOnToFront: false,
|
||
|
bringParentToFront: false,
|
||
|
|
||
|
defaultFocus: ':focusable',
|
||
|
|
||
|
// private
|
||
|
menuClickBuffer: 0,
|
||
|
baseCls: Ext.baseCSSPrefix + 'menu',
|
||
|
_iconSeparatorCls: Ext.baseCSSPrefix + 'menu-icon-separator',
|
||
|
_itemCmpCls: Ext.baseCSSPrefix + 'menu-item-cmp',
|
||
|
|
||
|
/**
|
||
|
* @event click
|
||
|
* Fires when this menu is clicked
|
||
|
* @param {Ext.menu.Menu} menu The menu which has been clicked
|
||
|
* @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.
|
||
|
* @param {Ext.event.Event} e The underlying {@link Ext.event.Event}.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @event mouseenter
|
||
|
* Fires when the mouse enters this menu
|
||
|
* @param {Ext.menu.Menu} menu The menu
|
||
|
* @param {Ext.event.Event} e The underlying {@link Ext.event.Event}
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @event mouseleave
|
||
|
* Fires when the mouse leaves this menu
|
||
|
* @param {Ext.menu.Menu} menu The menu
|
||
|
* @param {Ext.event.Event} e The underlying {@link Ext.event.Event}
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @event mouseover
|
||
|
* Fires when the mouse is hovering over this menu
|
||
|
* @param {Ext.menu.Menu} menu The menu
|
||
|
* @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.
|
||
|
* @param {Ext.event.Event} e The underlying {@link Ext.event.Event}
|
||
|
*/
|
||
|
|
||
|
layout: {
|
||
|
type: 'vbox',
|
||
|
align: 'stretchmax',
|
||
|
overflowHandler: 'Scroller'
|
||
|
},
|
||
|
|
||
|
initComponent: function() {
|
||
|
var me = this,
|
||
|
cls = [Ext.baseCSSPrefix + 'menu'],
|
||
|
bodyCls = me.bodyCls ? [me.bodyCls] : [],
|
||
|
isFloating = me.floating !== false,
|
||
|
listeners = {
|
||
|
element: 'el',
|
||
|
click: me.onClick,
|
||
|
mouseover: me.onMouseOver,
|
||
|
scope: me
|
||
|
};
|
||
|
|
||
|
if (Ext.supports.Touch) {
|
||
|
listeners.pointerdown = me.onMouseOver;
|
||
|
}
|
||
|
me.on(listeners);
|
||
|
me.on({
|
||
|
beforeshow: me.onBeforeShow,
|
||
|
scope: me
|
||
|
});
|
||
|
|
||
|
// Menu classes
|
||
|
if (me.plain) {
|
||
|
cls.push(Ext.baseCSSPrefix + 'menu-plain');
|
||
|
}
|
||
|
me.cls = cls.join(' ');
|
||
|
|
||
|
// Menu body classes
|
||
|
bodyCls.push(Ext.baseCSSPrefix + 'menu-body', Ext.dom.Element.unselectableCls);
|
||
|
me.bodyCls = bodyCls.join(' ');
|
||
|
|
||
|
if (isFloating) {
|
||
|
// only apply the minWidth when we're floating & one hasn't already been set
|
||
|
if (me.minWidth === undefined) {
|
||
|
me.minWidth = me.defaultMinWidth;
|
||
|
}
|
||
|
} else {
|
||
|
// hidden defaults to false if floating is configured as false
|
||
|
me.hidden = !!me.initialConfig.hidden;
|
||
|
me.constrain = false;
|
||
|
}
|
||
|
|
||
|
me.callParent(arguments);
|
||
|
|
||
|
// Configure items prior to render with special classes to align
|
||
|
// non MenuItem child components with their MenuItem siblings.
|
||
|
Ext.override(me.getLayout(), {
|
||
|
configureItem: me.configureItem
|
||
|
});
|
||
|
},
|
||
|
|
||
|
// Private implementation for Menus. They are a special case, in that in the vast majority
|
||
|
// (nearly all?) of use cases they shouldn't be constrained to anything other than the viewport.
|
||
|
// See EXTJS-13596.
|
||
|
initFloatConstrain: Ext.emptyFn,
|
||
|
|
||
|
// As menus are never contained, a Menu's visibility only ever depends upon its own hidden state.
|
||
|
// Ignore hiddenness from the ancestor hierarchy, override it with local hidden state.
|
||
|
getInherited: function() {
|
||
|
var result = this.callParent();
|
||
|
result.hidden = this.hidden;
|
||
|
return result;
|
||
|
},
|
||
|
|
||
|
beforeRender: function() {
|
||
|
this.callParent(arguments);
|
||
|
|
||
|
// Menus are usually floating: true, which means they shrink wrap their items.
|
||
|
// However, when they are contained, and not auto sized, we must stretch the items.
|
||
|
if (!this.getSizeModel().width.shrinkWrap) {
|
||
|
this.layout.align = 'stretch';
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onBoxReady: function() {
|
||
|
var me = this,
|
||
|
iconSeparatorCls = me._iconSeparatorCls;
|
||
|
|
||
|
me.focusableKeyNav.map.processEvent = function(e) {
|
||
|
// ESC may be from input fields, and FocusableContainers ignore keys from
|
||
|
// input fields. We do not want to ignore ESC. ESC hide menus.
|
||
|
if (e.keyCode === e.ESC) {
|
||
|
e.target = me.el.dom;
|
||
|
}
|
||
|
return e;
|
||
|
};
|
||
|
|
||
|
// Handle ESC key
|
||
|
me.focusableKeyNav.map.addBinding([{
|
||
|
key: 27,
|
||
|
handler: me.onEscapeKey,
|
||
|
scope: me
|
||
|
},
|
||
|
// Handle character shotrcuts
|
||
|
{
|
||
|
key: /[\w]/,
|
||
|
handler: me.onShortcutKey,
|
||
|
scope: me,
|
||
|
shift: false,
|
||
|
ctrl: false,
|
||
|
alt: false
|
||
|
}]);
|
||
|
|
||
|
me.callParent(arguments);
|
||
|
|
||
|
// TODO: Move this to a subTemplate When we support them in the future
|
||
|
if (me.showSeparator) {
|
||
|
me.iconSepEl = me.body.insertFirst({
|
||
|
role: 'presentation',
|
||
|
cls: iconSeparatorCls + ' ' + iconSeparatorCls + '-' + me.ui,
|
||
|
html: ' '
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Modern IE browsers have click events translated to PointerEvents, and b/c of this the
|
||
|
// event isn't being canceled like it needs to be. So, we need to add an extra listener.
|
||
|
if (Ext.supports.MSPointerEvents || Ext.supports.PointerEvents) {
|
||
|
me.el.on({
|
||
|
scope: me,
|
||
|
click: me.preventClick,
|
||
|
translate: false
|
||
|
});
|
||
|
}
|
||
|
|
||
|
me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);
|
||
|
},
|
||
|
|
||
|
onFocusLeave: function(e) {
|
||
|
var me = this;
|
||
|
|
||
|
me.callParent([e]);
|
||
|
me.mixins.focusablecontainer.onFocusLeave.call(me, e);
|
||
|
if (me.floating) {
|
||
|
me.hide();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @param {Ext.Component} item The child item to test for focusability.
|
||
|
* Returns whether a menu item can be activated or not.
|
||
|
* @return {Boolean} `true` if the passed item is focusable.
|
||
|
*/
|
||
|
canActivateItem: function(item) {
|
||
|
return item && item.isFocusable();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Deactivates the current active item on the menu, if one exists.
|
||
|
*/
|
||
|
deactivateActiveItem: function() {
|
||
|
var me = this,
|
||
|
activeItem = me.lastFocusedChild;
|
||
|
|
||
|
if (activeItem) {
|
||
|
activeItem.blur();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// @private
|
||
|
getItemFromEvent: function(e) {
|
||
|
var me = this,
|
||
|
renderTarget = me.layout.getRenderTarget().dom,
|
||
|
toEl = e.getTarget();
|
||
|
|
||
|
// See which top level element the event is in and find its owning Component.
|
||
|
while (toEl.parentNode !== renderTarget) {
|
||
|
toEl = toEl.parentNode;
|
||
|
if (!toEl) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
return Ext.getCmp(toEl.id);
|
||
|
},
|
||
|
|
||
|
lookupComponent: function(cmp) {
|
||
|
var me = this;
|
||
|
|
||
|
if (typeof cmp === 'string') {
|
||
|
cmp = me.lookupItemFromString(cmp);
|
||
|
} else if (Ext.isObject(cmp)) {
|
||
|
cmp = me.lookupItemFromObject(cmp);
|
||
|
}
|
||
|
|
||
|
// Apply our minWidth to all of our non-docked child components (Menu extends Panel)
|
||
|
// so it's accounted for in our VBox layout
|
||
|
if (!cmp.dock) {
|
||
|
cmp.minWidth = cmp.minWidth || me.minWidth;
|
||
|
}
|
||
|
|
||
|
return cmp;
|
||
|
},
|
||
|
|
||
|
// @private
|
||
|
lookupItemFromObject: function(cmp) {
|
||
|
var me = this;
|
||
|
|
||
|
if (!cmp.isComponent) {
|
||
|
if (!cmp.xtype) {
|
||
|
cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
|
||
|
} else {
|
||
|
cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (cmp.isMenuItem) {
|
||
|
cmp.parentMenu = me;
|
||
|
}
|
||
|
|
||
|
return cmp;
|
||
|
},
|
||
|
|
||
|
// @private
|
||
|
lookupItemFromString: function(cmp) {
|
||
|
return (cmp === 'separator' || cmp === '-') ?
|
||
|
new Ext.menu.Separator()
|
||
|
: new Ext.menu.Item({
|
||
|
canActivate: false,
|
||
|
hideOnClick: false,
|
||
|
plain: true,
|
||
|
text: cmp
|
||
|
});
|
||
|
},
|
||
|
|
||
|
// Override applied to the Menu's layout. Runs in the context of the layout.
|
||
|
// Add special classes to allow non MenuItem components to coexist with MenuItems.
|
||
|
// If there is only *one* child, then this Menu is just a vehicle for floating
|
||
|
// and aligning the component, so do not do this.
|
||
|
configureItem: function(cmp) {
|
||
|
var me = this.owner,
|
||
|
prefix = Ext.baseCSSPrefix,
|
||
|
ui = me.ui,
|
||
|
cls, cmpCls;
|
||
|
|
||
|
if (cmp.isMenuItem) {
|
||
|
cmp.setUI(ui);
|
||
|
} else if (me.items.getCount() > 1 && !cmp.rendered && !cmp.dock) {
|
||
|
cmpCls = me._itemCmpCls;
|
||
|
cls = [cmpCls + ' ' + cmpCls + '-' + ui];
|
||
|
|
||
|
// The "plain" setting means that the menu does not look so much like a menu. It's more like a grey Panel.
|
||
|
// So it has no vertical separator.
|
||
|
// Plain menus also will not indent non MenuItem components; there is nothing to indent them to the right of.
|
||
|
if (!me.plain && (cmp.indent !== false || cmp.iconCls === 'no-icon')) {
|
||
|
cls.push(prefix + 'menu-item-indent-' + ui);
|
||
|
}
|
||
|
|
||
|
if (cmp.rendered) {
|
||
|
cmp.el.addCls(cls);
|
||
|
} else {
|
||
|
cmp.cls = (cmp.cls || '') + ' ' + cls.join(' ');
|
||
|
}
|
||
|
// So we can clean the item if it gets removed.
|
||
|
cmp.$extraMenuCls = cls;
|
||
|
}
|
||
|
|
||
|
// @noOptimize.callParent
|
||
|
this.callParent(arguments);
|
||
|
},
|
||
|
|
||
|
onRemove: function(cmp) {
|
||
|
this.callParent([cmp]);
|
||
|
|
||
|
// Remove any extra classes we added to non-MenuItem child items
|
||
|
if (!cmp.isDestroyed && cmp.$extraMenuCls) {
|
||
|
cmp.el.removeCls(cmp.$extraMenuCls);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onClick: function(e) {
|
||
|
var me = this,
|
||
|
type = e.type,
|
||
|
item,
|
||
|
clickResult,
|
||
|
iskeyEvent = type === 'keydown';
|
||
|
|
||
|
if (me.disabled) {
|
||
|
e.stopEvent();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
item = me.getItemFromEvent(e);
|
||
|
if (item && item.isMenuItem) {
|
||
|
if (!item.menu || !me.ignoreParentClicks) {
|
||
|
clickResult = item.onClick(e);
|
||
|
} else {
|
||
|
e.stopEvent();
|
||
|
}
|
||
|
|
||
|
// SPACE and ENTER invokes the menu
|
||
|
if (item.menu && clickResult !== false && iskeyEvent) {
|
||
|
item.expandMenu(e, 0);
|
||
|
}
|
||
|
}
|
||
|
// Click event may be fired without an item, so we need a second check
|
||
|
if (!item || item.disabled) {
|
||
|
item = undefined;
|
||
|
}
|
||
|
me.fireEvent('click', me, item, e);
|
||
|
},
|
||
|
|
||
|
onDestroy: function() {
|
||
|
var me = this;
|
||
|
|
||
|
me.parentMenu = me.ownerCmp = null;
|
||
|
if (me.rendered) {
|
||
|
me.el.un(me.mouseMonitor);
|
||
|
Ext.destroy(me.iconSepEl);
|
||
|
}
|
||
|
me.callParent(arguments);
|
||
|
},
|
||
|
|
||
|
onMouseLeave: function(e) {
|
||
|
if (this.disabled) {
|
||
|
return;
|
||
|
}
|
||
|
this.fireEvent('mouseleave', this, e);
|
||
|
},
|
||
|
|
||
|
onMouseOver: function(e) {
|
||
|
var me = this,
|
||
|
fromEl = e.getRelatedTarget(),
|
||
|
mouseEnter = !me.el.contains(fromEl),
|
||
|
item = me.getItemFromEvent(e),
|
||
|
parentMenu = me.parentMenu,
|
||
|
ownerCmp = me.ownerCmp;
|
||
|
|
||
|
if (mouseEnter && parentMenu) {
|
||
|
parentMenu.setActiveItem(ownerCmp);
|
||
|
ownerCmp.cancelDeferHide();
|
||
|
parentMenu.mouseMonitor.mouseenter();
|
||
|
}
|
||
|
|
||
|
if (me.disabled) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Do not activate the item if the mouseover was within the item, and it's already active
|
||
|
if (item) {
|
||
|
if (!item.containsFocus) {
|
||
|
item.focus();
|
||
|
}
|
||
|
if (item.expandMenu) {
|
||
|
item.expandMenu(e);
|
||
|
}
|
||
|
}
|
||
|
if (mouseEnter) {
|
||
|
me.fireEvent('mouseenter', me, e);
|
||
|
}
|
||
|
me.fireEvent('mouseover', me, item, e);
|
||
|
},
|
||
|
|
||
|
setActiveItem: function(item) {
|
||
|
var me = this;
|
||
|
|
||
|
if (item && (item !== me.lastFocusedChild)) {
|
||
|
me.focusChild(item, 1);
|
||
|
// Focusing will scroll the item into view.
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onEscapeKey: function() {
|
||
|
if (this.floating) {
|
||
|
this.hide();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onShortcutKey: function(keyCode, e) {
|
||
|
var shortcutChar = String.fromCharCode(e.getCharCode()),
|
||
|
items = this.query('>[text]'),
|
||
|
len = items.length,
|
||
|
item = this.lastFocusedChild,
|
||
|
focusIndex = Ext.Array.indexOf(items, item),
|
||
|
i = focusIndex;
|
||
|
|
||
|
// Loop through all items which have a text property starting at the one after the current focus.
|
||
|
for (;;) {
|
||
|
if (++i === len) {
|
||
|
i = 0;
|
||
|
}
|
||
|
item = items[i];
|
||
|
|
||
|
// Looped back to start - no matches
|
||
|
if (i === focusIndex) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Found a text match
|
||
|
if (item.text && item.text[0].toUpperCase() === shortcutChar) {
|
||
|
item.focus();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// Tabbing in a floating menu must hide, but not move focus.
|
||
|
// onHide takes care of moving focus back to an owner Component.
|
||
|
onFocusableContainerTabKey: function(e) {
|
||
|
if (this.floating) {
|
||
|
this.hide();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onFocusableContainerEnterKey: function(e) {
|
||
|
this.onClick(e);
|
||
|
},
|
||
|
|
||
|
onFocusableContainerSpaceKey: function(e) {
|
||
|
this.onClick(e);
|
||
|
},
|
||
|
|
||
|
onFocusableContainerLeftKey: function(e) {
|
||
|
// If we are a submenu, then left arrow focuses the owning MenuItem
|
||
|
if (this.parentMenu) {
|
||
|
this.ownerCmp.focus();
|
||
|
this.hide();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onFocusableContainerRightKey: function(e) {
|
||
|
var me = this,
|
||
|
focusItem = me.lastFocusedChild;
|
||
|
|
||
|
if (focusItem && focusItem.expandMenu) {
|
||
|
focusItem.expandMenu(e, 0);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onBeforeShow: function() {
|
||
|
// Do not allow show immediately after a hide
|
||
|
if (Ext.Date.getElapsed(this.lastHide) < this.menuClickBuffer) {
|
||
|
return false;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
beforeShow: function() {
|
||
|
var me = this,
|
||
|
activeEl,
|
||
|
viewHeight;
|
||
|
|
||
|
// Constrain the height to the containing element's viewable area
|
||
|
if (me.floating) {
|
||
|
|
||
|
if (!me.hasFloatMenuParent() && !me.allowOtherMenus) {
|
||
|
Ext.menu.Manager.hideAll();
|
||
|
}
|
||
|
// Only register a focusAnchor to return to on hide if the active element is not the document
|
||
|
// If there's no focusAnchor, we return to the ownerCmp, or first focusable ancestor.
|
||
|
activeEl = Ext.Element.getActiveElement();
|
||
|
me.focusAnchor = activeEl === document.body ? null : activeEl;
|
||
|
|
||
|
me.savedMaxHeight = me.maxHeight;
|
||
|
viewHeight = me.container.getViewSize().height;
|
||
|
me.maxHeight = Math.min(me.maxHeight || viewHeight, viewHeight);
|
||
|
}
|
||
|
|
||
|
me.callParent(arguments);
|
||
|
},
|
||
|
|
||
|
afterShow: function() {
|
||
|
var me = this;
|
||
|
|
||
|
me.callParent(arguments);
|
||
|
Ext.menu.Manager.onShow(me);
|
||
|
|
||
|
// Restore configured maxHeight
|
||
|
if (me.floating && me.autoFocus) {
|
||
|
me.maxHeight = me.savedMaxHeight;
|
||
|
me.focus();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onHide: function(animateTarget, cb, scope) {
|
||
|
var me = this,
|
||
|
focusTarget;
|
||
|
|
||
|
// If we contain focus just before element hide, move it elsewhere before hiding
|
||
|
if (me.el.contains(Ext.Element.getActiveElement())) {
|
||
|
// focusAnchor was the active element before this menu was shown.
|
||
|
focusTarget = me.focusAnchor || me.ownerCmp || me.up(':focusable');
|
||
|
|
||
|
// Component hide processing will focus the "previousFocus" element.
|
||
|
if (focusTarget) {
|
||
|
me.previousFocus = focusTarget;
|
||
|
}
|
||
|
}
|
||
|
me.callParent([animateTarget, cb, scope]);
|
||
|
me.lastHide = Ext.Date.now();
|
||
|
Ext.menu.Manager.onHide(me);
|
||
|
},
|
||
|
|
||
|
preventClick: function (e) {
|
||
|
var item = this.getItemFromEvent(e);
|
||
|
if (item && !item.href) {
|
||
|
e.preventDefault();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
privates: {
|
||
|
hasFloatMenuParent: function() {
|
||
|
return this.parentMenu || this.up('menu[floating=true]');
|
||
|
},
|
||
|
|
||
|
setOwnerCmp: function(comp, instanced) {
|
||
|
var me = this;
|
||
|
|
||
|
me.parentMenu = comp.isMenuItem ? comp : null;
|
||
|
me.ownerCmp = comp;
|
||
|
me.registerWithOwnerCt();
|
||
|
|
||
|
delete me.hierarchicallyHidden;
|
||
|
if (me.inheritedState && instanced) {
|
||
|
me.invalidateInheritedState();
|
||
|
}
|
||
|
|
||
|
if (me.reference) {
|
||
|
me.fixReference();
|
||
|
}
|
||
|
|
||
|
// We have been added to a container, we may have child references
|
||
|
// or be a reference ourself. At this point we have no way of knowing if
|
||
|
// our references are correct, so trigger a fix.
|
||
|
if (instanced) {
|
||
|
Ext.ComponentManager.markReferencesDirty();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|