X-Git-Url: https://jasonwoof.com/gitweb/?p=ckeditor.git;a=blobdiff_plain;f=_source%2Fplugins%2Fdialog%2Fplugin.js;h=6b5c2e437cf1505dd7f62d6427c1a42c430d11b7;hp=fa4cc99a206958defbd46f8a7393d6a721d2f984;hb=refs%2Ftags%2Fv3.2;hpb=c6e377a02b54abc07129d72b632763c727476a15 diff --git a/_source/plugins/dialog/plugin.js b/_source/plugins/dialog/plugin.js index fa4cc99..6b5c2e4 100644 --- a/_source/plugins/dialog/plugin.js +++ b/_source/plugins/dialog/plugin.js @@ -127,6 +127,12 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; this.parts = themeBuilt.parts; + CKEDITOR.tools.setTimeout( function() + { + editor.fire( 'ariaWidget', this.parts.contents ); + }, + 0, this ); + // Set the startup styles for the dialog, avoiding it enlarging the // page size on the dialog creation. this.parts.dialog.setStyles( @@ -244,6 +250,26 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; this.hide(); }, this ); + // Sort focus list according to tab order definitions. + function setupFocus() + { + var focusList = me._.focusList; + focusList.sort( function( a, b ) + { + // Mimics browser tab order logics; + if ( a.tabIndex != b.tabIndex ) + return b.tabIndex - a.tabIndex; + // Sort is not stable in some browsers, + // fall-back the comparator to 'focusIndex'; + else + return a.focusIndex - b.focusIndex; + }); + + var size = focusList.length; + for ( var i = 0; i < size; i++ ) + focusList[ i ].focusIndex = i; + } + function changeFocus( forward ) { var focusList = me._.focusList, @@ -251,7 +277,17 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; if ( focusList.length < 1 ) return; - var startIndex = ( me._.currentFocusIndex + offset + focusList.length ) % focusList.length, + var current = me._.currentFocusIndex; + + // Trigger the 'blur' event of any input element before anything, + // since certain UI updates may depend on it. + try + { + focusList[ current ].getInputElement().$.blur(); + } + catch( e ){} + + var startIndex = ( current + offset + focusList.length ) % focusList.length, currentIndex = startIndex; while ( !focusList[ currentIndex ].isFocusable() ) { @@ -266,6 +302,8 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; focusList[ currentIndex ].select(); } + this.changeFocus = changeFocus; + var processed; function focusKeydownHandler( evt ) @@ -312,6 +350,14 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; me._.tabs[ nextId ][ 0 ].focus(); processed = 1; } + else if ( ( keystroke == 13 || keystroke == 32 ) && me._.tabBarMode ) + { + this.selectPage( this._.currentTabId ); + this._.tabBarMode = false; + this._.currentFocusIndex = -1; + changeFocus( true ); + processed = 1; + } if ( processed ) { @@ -325,14 +371,15 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; processed && evt.data.preventDefault(); } + var dialogElement = this._.element; // Add the dialog keyboard handlers. this.on( 'show', function() { - CKEDITOR.document.on( 'keydown', focusKeydownHandler, this, null, 0 ); + dialogElement.on( 'keydown', focusKeydownHandler, this, null, 0 ); // Some browsers instead, don't cancel key events in the keydown, but in the // keypress. So we must do a longer trip in those cases. (#4531) if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) - CKEDITOR.document.on( 'keypress', focusKeyPressHandler, this ); + dialogElement.on( 'keypress', focusKeyPressHandler, this ); if ( CKEDITOR.env.ie6Compat ) { @@ -342,9 +389,9 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; } ); this.on( 'hide', function() { - CKEDITOR.document.removeListener( 'keydown', focusKeydownHandler ); + dialogElement.removeListener( 'keydown', focusKeydownHandler ); if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) - CKEDITOR.document.removeListener( 'keypress', focusKeyPressHandler ); + dialogElement.removeListener( 'keypress', focusKeyPressHandler ); } ); this.on( 'iframeAdded', function( evt ) { @@ -355,10 +402,30 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; // Auto-focus logic in dialog. this.on( 'show', function() { - if ( !this._.hasFocus ) + // Setup tabIndex on showing the dialog instead of on loading + // to allow dynamic tab order happen in dialog definition. + setupFocus(); + + if ( editor.config.dialog_startupFocusTab + && me._.tabIdList.length > 1 ) + { + me._.tabBarMode = true; + me._.tabs[ me._.currentTabId ][ 0 ].focus(); + } + else if ( !this._.hasFocus ) { this._.currentFocusIndex = -1; - changeFocus( true ); + + // Decide where to put the initial focus. + if ( definition.onFocus ) + { + var initialFocus = definition.onFocus.call( this ); + // Focus the field that the user specified. + initialFocus && initialFocus.focus(); + } + // Focus the first field in layout order. + else + changeFocus( true ); /* * IE BUG: If the initial focus went into a non-text element (e.g. button), @@ -407,28 +474,22 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; for ( var i = 0 ; i < definition.contents.length ; i++ ) this.addPage( definition.contents[i] ); - var tabRegex = /cke_dialog_tab(\s|$|_)/, - tabOuterRegex = /cke_dialog_tab(\s|$)/; this.parts['tabs'].on( 'click', function( evt ) { - var target = evt.data.getTarget(), firstNode = target, id, page; - + var target = evt.data.getTarget(); // If we aren't inside a tab, bail out. - if ( !( tabRegex.test( target.$.className ) || target.getName() == 'a' ) ) - return; - - // Find the outer container of the tab. - id = target.$.id.substr( 0, target.$.id.lastIndexOf( '_' ) ); - this.selectPage( id ); - - if ( this._.tabBarMode ) + if ( target.hasClass( 'cke_dialog_tab' ) ) { - this._.tabBarMode = false; - this._.currentFocusIndex = -1; - changeFocus( true ); + var id = target.$.id; + this.selectPage( id.substr( 0, id.lastIndexOf( '_' ) ) ); + if ( this._.tabBarMode ) + { + this._.tabBarMode = false; + this._.currentFocusIndex = -1; + changeFocus( true ); + } + evt.data.preventDefault(); } - - evt.data.preventDefault(); }, this ); // Insert buttons. @@ -453,6 +514,8 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; { this.element = element; this.focusIndex = index; + // TODO: support tabIndex for focusables. + this.tabIndex = 0; this.isFocusable = function() { return !element.getAttribute( 'disabled' ) && element.isVisible(); @@ -810,18 +873,26 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; // Create the HTML for the tab and the content block. var page = CKEDITOR.dom.element.createFromHtml( pageHtml.join( '' ) ); - var tab = CKEDITOR.dom.element.createFromHtml( [ + page.setAttribute( 'role', 'tabpanel' ); + + var env = CKEDITOR.env; + var tabId = contents.id + '_' + CKEDITOR.tools.getNextNumber(), + tab = CKEDITOR.dom.element.createFromHtml( [ ' 0 ? ' cke_last' : 'cke_first' ), titleHtml, ( !!contents.hidden ? ' style="display:none"' : '' ), - ' id="', contents.id + '_', CKEDITOR.tools.getNextNumber(), '"' + - ' href="javascript:void(0)"', - ' hidefocus="true">', + ' id="', tabId, '"', + env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(0)"', + ' tabIndex="-1"', + ' hidefocus="true"', + ' role="tab">', contents.label, '' ].join( '' ) ); + page.setAttribute( 'aria-labelledby', tabId ); + // If only a single page exist, a different style is used in the central pane. if ( this._.pageCount === 0 ) this.parts.dialog.addClass( 'cke_single_page' ); @@ -880,6 +951,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; tab.removeClass( 'cke_dialog_tab_selected' ); page.hide(); } + page.setAttribute( 'aria-hidden', i != id ); } var selected = this._.tabs[id]; @@ -1938,7 +2010,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; html = [ '<', nodeName, ' ' ], styles = ( stylesArg && stylesArg.call ? stylesArg( elementDefinition ) : stylesArg ) || {}, attributes = ( attributesArg && attributesArg.call ? attributesArg( elementDefinition ) : attributesArg ) || {}, - innerHTML = ( contentsArg && contentsArg.call ? contentsArg( dialog, elementDefinition ) : contentsArg ) || '', + innerHTML = ( contentsArg && contentsArg.call ? contentsArg.call( this, dialog, elementDefinition ) : contentsArg ) || '', domId = this.domId = attributes.id || CKEDITOR.tools.getNextNumber() + '_uiElement', id = this.id = elementDefinition.id, i; @@ -2023,6 +2095,8 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; // Register the object as a tab focus if it can be included. if ( this.keyboardFocusable ) { + this.tabIndex = elementDefinition.tabIndex || 0; + this.focusIndex = dialog._.focusList.push( this ) - 1; this.on( 'focus', function() { @@ -2085,7 +2159,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; className = 'cke_dialog_ui_hbox_first'; if ( i == childHtmlList.length - 1 ) className = 'cke_dialog_ui_hbox_last'; - html.push( '' ); return html.join( '' ); }; - CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'vbox' }, htmlList, 'div', null, null, innerHTML ); + CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'vbox' }, htmlList, 'div', null, { role : 'presentation' }, innerHTML ); } }; })(); @@ -2564,9 +2641,12 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; { editor.openDialog( this.dialogName ); }, + // Dialog commands just open a dialog ui, thus require no undo logic, // undo support should dedicate to specific dialog implementation. - canUndo: false + canUndo: false, + + editorFocus : CKEDITOR.env.ie }; (function() @@ -2770,6 +2850,15 @@ CKEDITOR.plugins.add( 'dialog', */ /** + * If the dialog has more than one tab, put focus into the first tab as soon as dialog is opened. + * @name CKEDITOR.config.dialog_startupFocusTab + * @type Boolean + * @default false + * @example + * config.dialog_startupFocusTab = true; + */ + +/** * The distance of magnetic borders used in moving and resizing dialogs, * measured in pixels. * @name CKEDITOR.config.dialog_magnetDistance