JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.4.2
[ckeditor.git] / _source / plugins / menu / plugin.js
index 790f16f..1ea12b2 100644 (file)
@@ -1,5 +1,5 @@
 /*\r
-Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
 For licensing, see LICENSE.html or http://ckeditor.com/license\r
 */\r
 \r
@@ -8,57 +8,65 @@ CKEDITOR.plugins.add( 'menu',
        beforeInit : function( editor )\r
        {\r
                var groups = editor.config.menu_groups.split( ',' ),\r
-                       groupsOrder = {};\r
+                       groupsOrder = editor._.menuGroups = {},\r
+                       menuItems = editor._.menuItems = {};\r
 \r
                for ( var i = 0 ; i < groups.length ; i++ )\r
                        groupsOrder[ groups[ i ] ] = i + 1;\r
 \r
-               editor._.menuGroups = groupsOrder;\r
-               editor._.menuItems = {};\r
-       },\r
-\r
-       requires : [ 'floatpanel' ]\r
-});\r
+               editor.addMenuGroup = function( name, order )\r
+                       {\r
+                               groupsOrder[ name ] = order || 100;\r
+                       };\r
 \r
-CKEDITOR.tools.extend( CKEDITOR.editor.prototype,\r
-{\r
-       addMenuGroup : function( name, order )\r
-       {\r
-               this._.menuGroups[ name ] = order || 100;\r
-       },\r
+               editor.addMenuItem = function( name, definition )\r
+                       {\r
+                               if ( groupsOrder[ definition.group ] )\r
+                                       menuItems[ name ] = new CKEDITOR.menuItem( this, name, definition );\r
+                       };\r
 \r
-       addMenuItem : function( name, definition )\r
-       {\r
-               if ( this._.menuGroups[ definition.group ] )\r
-                       this._.menuItems[ name ] = new CKEDITOR.menuItem( this, name, definition );\r
-       },\r
+               editor.addMenuItems = function( definitions )\r
+                       {\r
+                               for ( var itemName in definitions )\r
+                               {\r
+                                       this.addMenuItem( itemName, definitions[ itemName ] );\r
+                               }\r
+                       };\r
 \r
-       addMenuItems : function( definitions )\r
-       {\r
-               for ( var itemName in definitions )\r
-               {\r
-                       this.addMenuItem( itemName, definitions[ itemName ] );\r
-               }\r
+               editor.getMenuItem = function( name )\r
+                       {\r
+                               return menuItems[ name ];\r
+                       };\r
        },\r
 \r
-       getMenuItem : function( name )\r
-       {\r
-               return this._.menuItems[ name ];\r
-       }\r
+       requires : [ 'floatpanel' ]\r
 });\r
 \r
 (function()\r
 {\r
        CKEDITOR.menu = CKEDITOR.tools.createClass(\r
        {\r
-               $ : function( editor, level )\r
+               $ : function( editor, definition )\r
                {\r
+                       definition = this._.definition = definition || {};\r
                        this.id = 'cke_' + CKEDITOR.tools.getNextNumber();\r
 \r
                        this.editor = editor;\r
                        this.items = [];\r
 \r
-                       this._.level = level || 1;\r
+                       this._.level = definition.level || 1;\r
+\r
+                       var panelDefinition = CKEDITOR.tools.extend( {}, definition.panel,\r
+                       {\r
+                               css : editor.skin.editor.css,\r
+                               level : this._.level - 1,\r
+                               block : {}\r
+                       } );\r
+\r
+                       var attrs = panelDefinition.block.attributes = ( panelDefinition.attributes || {} );\r
+                       // Provide default role of 'menu'.\r
+                       !attrs.role && ( attrs.role = 'menu' );\r
+                       this._.panelDefinition = panelDefinition;\r
                },\r
 \r
                _ :\r
@@ -67,31 +75,43 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
                        {\r
                                var menu = this._.subMenu,\r
                                        item = this.items[ index ],\r
-                                       subItems = item.getItems && item.getItems();\r
+                                       subItemDefs = item.getItems && item.getItems();\r
 \r
                                // If this item has no subitems, we just hide the submenu, if\r
                                // available, and return back.\r
-                               if ( !subItems )\r
+                               if ( !subItemDefs )\r
                                {\r
                                        this._.panel.hideChild();\r
                                        return;\r
                                }\r
 \r
+                               // Record parent menu focused item first (#3389).\r
+                               var block = this._.panel.getBlock( this.id );\r
+                               block._.focusIndex = index;\r
+\r
                                // Create the submenu, if not available, or clean the existing\r
                                // one.\r
                                if ( menu )\r
                                        menu.removeAll();\r
                                else\r
                                {\r
-                                       menu = this._.subMenu = new CKEDITOR.menu( this.editor, this._.level + 1 );\r
+                                       menu = this._.subMenu = new CKEDITOR.menu( this.editor,\r
+                                                                  CKEDITOR.tools.extend( {}, this._.definition, { level : this._.level + 1 }, true ) );\r
                                        menu.parent = this;\r
                                        menu.onClick = CKEDITOR.tools.bind( this.onClick, this );\r
+                                       // Sub menu use their own scope for binding onEscape.\r
+                                       menu.onEscape = this.onEscape;\r
                                }\r
 \r
                                // Add all submenu items to the menu.\r
-                               for ( var itemName in subItems )\r
+                               for ( var subItemName in subItemDefs )\r
                                {\r
-                                       menu.add( this.editor.getMenuItem( itemName ) );\r
+                                       var subItem = this.editor.getMenuItem( subItemName );\r
+                                       if ( subItem )\r
+                                       {\r
+                                               subItem.state = subItemDefs[ subItemName ];\r
+                                               menu.add( subItem );\r
+                                       }\r
                                }\r
 \r
                                // Get the element representing the current item.\r
@@ -130,18 +150,15 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
                                // Create the floating panel for this menu.\r
                                if ( !panel )\r
                                {\r
-                                       panel = this._.panel = new CKEDITOR.ui.floatPanel( this.editor, CKEDITOR.document.getBody(),\r
-                                               {\r
-                                                       css : [ CKEDITOR.getUrl( editor.skinPath + 'editor.css' ) ],\r
-                                                       level : this._.level - 1,\r
-                                                       className : editor.skinClass + ' cke_contextmenu'\r
-                                               },\r
-                                               this._.level);\r
+                                       panel = this._.panel = new CKEDITOR.ui.floatPanel( this.editor,\r
+                                               CKEDITOR.document.getBody(),\r
+                                               this._.panelDefinition,\r
+                                               this._.level );\r
 \r
-                                       panel.onEscape = CKEDITOR.tools.bind( function()\r
+                                       panel.onEscape = CKEDITOR.tools.bind( function( keystroke )\r
                                        {\r
-                                               this.onEscape && this.onEscape();\r
-                                               this.hide();\r
+                                               if ( this.onEscape && this.onEscape( keystroke ) === false )\r
+                                                       return false;\r
                                        },\r
                                        this );\r
 \r
@@ -152,7 +169,7 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
                                        this );\r
 \r
                                        // Create an autosize block inside the panel.\r
-                                       var block = panel.addBlock( this.id );\r
+                                       var block = panel.addBlock( this.id, this._.panelDefinition.block );\r
                                        block.autoSize = true;\r
 \r
                                        var keys = block.keys;\r
@@ -161,7 +178,7 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
                                        keys[ 38 ]      = 'prev';                                       // ARROW-UP\r
                                        keys[ CKEDITOR.SHIFT + 9 ]      = 'prev';       // SHIFT + TAB\r
                                        keys[ 32 ]      = 'click';                                      // SPACE\r
-                                       keys[ 39 ]      = 'click';                                      // ARROW-RIGHT\r
+                                       keys[ ( editor.lang.dir == 'rtl' ? 37 : 39 ) ]  = 'click';  // ARROW-RIGHT/ARROW-LEFT(rtl)\r
 \r
                                        element = this._.element = block.element;\r
                                        element.addClass( editor.skinClass );\r
@@ -173,15 +190,15 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
                                        this._.itemOverFn = CKEDITOR.tools.addFunction( function( index )\r
                                                {\r
                                                        clearTimeout( this._.showSubTimeout );\r
-                                                       this._.showSubTimeout = CKEDITOR.tools.setTimeout( this._.showSubMenu, editor.config.menu_subMenuDelay, this, [ index ] );\r
+                                                       this._.showSubTimeout = CKEDITOR.tools.setTimeout( this._.showSubMenu, editor.config.menu_subMenuDelay || 400, this, [ index ] );\r
                                                },\r
-                                               this);\r
+                                               this );\r
 \r
                                        this._.itemOutFn = CKEDITOR.tools.addFunction( function( index )\r
                                                {\r
                                                        clearTimeout( this._.showSubTimeout );\r
                                                },\r
-                                               this);\r
+                                               this );\r
 \r
                                        this._.itemClickFn = CKEDITOR.tools.addFunction( function( index )\r
                                                {\r
@@ -198,14 +215,17 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
                                                        else\r
                                                                this.onClick && this.onClick( item );\r
                                                },\r
-                                               this);\r
+                                               this );\r
                                }\r
 \r
                                // Put the items in the right order.\r
                                sortItems( items );\r
 \r
+                               var chromeRoot = editor.container.getChild( 1 ),\r
+                                       mixedContentClass = chromeRoot.hasClass( 'cke_mixed_dir_content' ) ? ' cke_mixed_dir_content' : '';\r
+\r
                                // Build the HTML that composes the menu and its items.\r
-                               var output = [ '<div class="cke_menu">' ];\r
+                               var output = [ '<div class="cke_menu' + mixedContentClass + '" role="presentation">' ];\r
 \r
                                var length = items.length,\r
                                        lastGroup = length && items[ 0 ].group;\r
@@ -215,7 +235,7 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
                                        var item = items[ i ];\r
                                        if ( lastGroup != item.group )\r
                                        {\r
-                                               output.push( '<div class="cke_menuseparator"></div>' );\r
+                                               output.push( '<div class="cke_menuseparator" role="separator"></div>' );\r
                                                lastGroup = item.group;\r
                                        }\r
 \r
@@ -232,6 +252,8 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
                                        this.parent._.panel.showAsChild( panel, this.id, offsetParent, corner, offsetX, offsetY );\r
                                else\r
                                        panel.showBlock( this.id, offsetParent, corner, offsetX, offsetY );\r
+\r
+                               editor.fire( 'menuShow', [ panel ] );\r
                        },\r
 \r
                        hide : function()\r
@@ -288,12 +310,12 @@ CKEDITOR.menuItem = CKEDITOR.tools.createClass(
                                'off' );\r
 \r
                        var htmlLabel = this.label;\r
-                       if ( state == CKEDITOR.TRISTATE_DISABLED )\r
-                               htmlLabel = this.editor.lang.common.unavailable.replace( '%1', htmlLabel );\r
 \r
                        if ( this.className )\r
                                classes += ' ' + this.className;\r
 \r
+                       var hasSubMenu = this.getItems;\r
+\r
                        output.push(\r
                                '<span class="cke_menuitem">' +\r
                                '<a id="', id, '"' +\r
@@ -301,7 +323,11 @@ CKEDITOR.menuItem = CKEDITOR.tools.createClass(
                                        ' title="', this.label, '"' +\r
                                        ' tabindex="-1"' +\r
                                        '_cke_focus=1' +\r
-                                       ' hidefocus="true"' );\r
+                                       ' hidefocus="true"' +\r
+                                       ' role="menuitem"' +\r
+                                       ( hasSubMenu ? 'aria-haspopup="true"' : '' ) +\r
+                                       ( state == CKEDITOR.TRISTATE_DISABLED ? 'aria-disabled="true"' : '' ) +\r
+                                       ( state == CKEDITOR.TRISTATE_ON ? 'aria-pressed="true"' : '' ) );\r
 \r
                        // Some browsers don't cancel key events in the keydown but in the\r
                        // keypress.\r
@@ -328,15 +354,21 @@ CKEDITOR.menuItem = CKEDITOR.tools.createClass(
                                        ' onclick="CKEDITOR.tools.callFunction(', menu._.itemClickFn, ',', index, '); return false;"' +\r
                                        '>' +\r
                                                '<span class="cke_icon_wrapper"><span class="cke_icon"' +\r
-                                                       ( this.icon ? ' style="background-image:url(' + CKEDITOR.getUrl( this.icon ) + ');background-position:0 ' + offset + 'px;"></span>'\r
+                                                       ( this.icon ? ' style="background-image:url(' + CKEDITOR.getUrl( this.icon ) + ');background-position:0 ' + offset + 'px;"'\r
                                                        : '' ) +\r
                                                        '></span></span>' +\r
                                                '<span class="cke_label">' );\r
 \r
-                       if ( this.getItems )\r
+                       if ( hasSubMenu )\r
                        {\r
                                output.push(\r
-                                                       '<span class="cke_menuarrow"></span>' );\r
+                                                       '<span class="cke_menuarrow">',\r
+                                                               '<span>&#',\r
+                                                                       ( this.editor.lang.dir == 'rtl' ?\r
+                                                                               '9668' :        // BLACK LEFT-POINTING POINTER\r
+                                                                               '9658' ),       // BLACK RIGHT-POINTING POINTER\r
+                                                               ';</span>',\r
+                                                       '</span>' );\r
                        }\r
 \r
                        output.push(\r
@@ -358,7 +390,6 @@ CKEDITOR.menuItem = CKEDITOR.tools.createClass(
  * // Remove the submenu delay.\r
  * config.menu_subMenuDelay = 0;\r
  */\r
-CKEDITOR.config.menu_subMenuDelay = 400;\r
 \r
 /**\r
  * A comma separated list of items group names to be displayed in the context\r
@@ -374,4 +405,4 @@ CKEDITOR.config.menu_groups =
        'form,' +\r
        'tablecell,tablecellproperties,tablerow,tablecolumn,table,'+\r
        'anchor,link,image,flash,' +\r
-       'checkbox,radio,textfield,hiddenfield,imagebutton,button,select,textarea';\r
+       'checkbox,radio,textfield,hiddenfield,imagebutton,button,select,textarea,div';\r