text: ﻿/**
   * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
   * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
   */
  
  CKEDITOR.plugins.add( 'richcombo', {
  	requires: 'floatpanel,listblock,button',
  
  	beforeInit: function( editor ) {
  		editor.ui.addHandler( CKEDITOR.UI_RICHCOMBO, CKEDITOR.ui.richCombo.handler );
  	}
  } );
  
  ( function() {
  	var template = '<span id="{id}"' +
  		' class="cke_combo cke_combo__{name} {cls}"' +
  		' role="presentation">' +
  			'<span id="{id}_label" class="cke_combo_label">{label}</span>' +
  			'<a class="cke_combo_button" title="{title}" tabindex="-1"' +
  			( CKEDITOR.env.gecko && !CKEDITOR.env.hc ? '' : ' href="javascript:void(\'{titleJs}\')"' ) +
  			' hidefocus="true"' +
  			' role="button"' +
  			' aria-labelledby="{id}_label"' +
  			' aria-haspopup="listbox"',
  		specialClickHandler = '';
  
  	// Some browsers don't cancel key events in the keydown but in the
  	// keypress.
  	// TODO: Check if really needed.
  	if ( CKEDITOR.env.gecko && CKEDITOR.env.mac )
  		template += ' onkeypress="return false;"';
  
  	// With Firefox, we need to force the button to redraw, otherwise it
  	// will remain in the focus state.
  	if ( CKEDITOR.env.gecko )
  		template += ' onblur="this.style.cssText = this.style.cssText;"';
  
  	// In IE/Edge right click opens rich combo (#2845).
  	if ( CKEDITOR.env.ie ) {
  		specialClickHandler = 'return false;" onmouseup="CKEDITOR.tools.getMouseButton(event)==CKEDITOR.MOUSE_BUTTON_LEFT&&';
  	}
  
  	template +=
  		' onkeydown="return CKEDITOR.tools.callFunction({keydownFn},event,this);"' +
  		' onfocus="return CKEDITOR.tools.callFunction({focusFn},event);"' +
  		' onclick="' + specialClickHandler + 'CKEDITOR.tools.callFunction({clickFn},this);return false;">' +
  			'<span id="{id}_text" class="cke_combo_text cke_combo_inlinelabel">{label}</span>' +
  			'<span class="cke_combo_open">' +
  				'<span class="cke_combo_arrow">' +
  				// BLACK DOWN-POINTING TRIANGLE
  	( CKEDITOR.env.hc ? '&#9660;' : CKEDITOR.env.air ? '&nbsp;' : '' ) +
  				'</span>' +
  			'</span>' +
  		'</a>' +
  		'</span>';
  
  	var rcomboTpl = CKEDITOR.addTemplate( 'combo', template );
  
  	/**
  	 * Button UI element.
  	 *
  	 * @readonly
  	 * @property {String} [='richcombo']
  	 * @member CKEDITOR
  	 */
  	CKEDITOR.UI_RICHCOMBO = 'richcombo';
  
  	/**
  	 * @class
  	 * @todo
  	 */
  	CKEDITOR.ui.richCombo = CKEDITOR.tools.createClass( {
  		$: function( definition ) {
  			// Copy all definition properties to this object.
  			CKEDITOR.tools.extend( this, definition,
  			// Set defaults.
  			{
  				// The combo won't participate in toolbar grouping.
  				canGroup: false,
  				title: definition.label,
  				modes: { wysiwyg: 1 },
  				editorFocus: 1
  			} );
  
  			// We don't want the panel definition in this object.
  			var panelDefinition = this.panel || {};
  			delete this.panel;
  
  			this.id = CKEDITOR.tools.getNextNumber();
  
  			this.document = ( panelDefinition.parent && panelDefinition.parent.getDocument() ) || CKEDITOR.document;
  
  			panelDefinition.className = 'cke_combopanel';
  			panelDefinition.block = {
  				multiSelect: panelDefinition.multiSelect,
  				attributes: panelDefinition.attributes
  			};
  			panelDefinition.toolbarRelated = true;
  
  			this._ = {
  				panelDefinition: panelDefinition,
  				items: {},
  				listeners: []
  			};
  		},
  
  		proto: {
  			renderHtml: function( editor ) {
  				var output = [];
  				this.render( editor, output );
  				return output.join( '' );
  			},
  
  			/**
  			 * Renders the rich combo.
  			 *
  			 * @param {CKEDITOR.editor} editor The editor instance which this button is
  			 * to be used by.
  			 * @param {Array} output The output array that the HTML relative
  			 * to this button will be appended to.
  			 */
  			render: function( editor, output ) {
  				var env = CKEDITOR.env,
  					instance,
  					selLocked;
  
  				var id = 'cke_' + this.id;
  				var clickFn = CKEDITOR.tools.addFunction( function( el ) {
  					// Restore locked selection in Opera.
  					if ( selLocked ) {
  						editor.unlockSelection( 1 );
  						selLocked = 0;
  					}
  					instance.execute( el );
  				}, this );
  
  				var combo = this;
  
  				instance = {
  					id: id,
  					combo: this,
  					focus: function() {
  						var element = CKEDITOR.document.getById( id ).getChild( 1 );
  						element.focus();
  					},
  					execute: function( el ) {
  						var _ = combo._;
  
  						if ( _.state == CKEDITOR.TRISTATE_DISABLED )
  							return;
  
  						combo.createPanel( editor );
  
  						if ( _.on ) {
  							_.panel.hide();
  							return;
  						}
  
  						combo.commit();
  						var value = combo.getValue();
  						if ( value )
  							_.list.mark( value );
  						else
  							_.list.unmarkAll();
  
  						_.panel.showBlock( combo.id, new CKEDITOR.dom.element( el ), 4 );
  					},
  					clickFn: clickFn
  				};
  
  				function updateState() {
  					// Don't change state while richcombo is active (https://dev.ckeditor.com/ticket/11793).
  					if ( this.getState() == CKEDITOR.TRISTATE_ON )
  						return;
  
  					var state = this.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED;
  
  					if ( editor.readOnly && !this.readOnly )
  						state = CKEDITOR.TRISTATE_DISABLED;
  
  					this.setState( state );
  					this.setValue( '' );
  
  					// Let plugin to disable button.
  					if ( state != CKEDITOR.TRISTATE_DISABLED && this.refresh )
  						this.refresh();
  				}
  
  				// Update status when activeFilter, mode, selection or readOnly changes.
  				this._.listeners.push( editor.on( 'activeFilterChange', updateState, this ) );
  				this._.listeners.push( editor.on( 'mode', updateState, this ) );
  				this._.listeners.push( editor.on( 'selectionChange', updateState, this ) );
  				// If this combo is sensitive to readOnly state, update it accordingly.
  				!this.readOnly && this._.listeners.push( editor.on( 'readOnly', updateState, this ) );
  
  				var keyDownFn = CKEDITOR.tools.addFunction( function( ev, element ) {
  					ev = new CKEDITOR.dom.event( ev );
  
  					var keystroke = ev.getKeystroke();
  
  					switch ( keystroke ) {
  						case 13: // ENTER
  						case 32: // SPACE
  						case 40: // ARROW-DOWN
  							// Show panel
  							CKEDITOR.tools.callFunction( clickFn, element );
  							break;
  						default:
  							// Delegate the default behavior to toolbar button key handling.
  							instance.onkey( instance, keystroke );
  					}
  
  					// Avoid subsequent focus grab on editor document.
  					ev.preventDefault();
  				} );
  
  				var focusFn = CKEDITOR.tools.addFunction( function() {
  					instance.onfocus && instance.onfocus();
  				} );
  
  				selLocked = 0;
  
  				// For clean up
  				instance.keyDownFn = keyDownFn;
  
  				var params = {
  					id: id,
  					name: this.name || this.command,
  					label: this.label,
  					title: this.title,
  					cls: this.className || '',
  					titleJs: env.gecko && !env.hc ? '' : ( this.title || '' ).replace( "'", '' ),
  					keydownFn: keyDownFn,
  					focusFn: focusFn,
  					clickFn: clickFn
  				};
  
  				rcomboTpl.output( params, output );
  
  				if ( this.onRender )
  					this.onRender();
  
  				return instance;
  			},
  
  			createPanel: function( editor ) {
  				if ( this._.panel )
  					return;
  
  				var panelDefinition = this._.panelDefinition,
  					panelBlockDefinition = this._.panelDefinition.block,
  					panelParentElement = panelDefinition.parent || CKEDITOR.document.getBody(),
  					namedPanelCls = 'cke_combopanel__' + this.name,
  					panel = new CKEDITOR.ui.floatPanel( editor, panelParentElement, panelDefinition ),
  					list = panel.addListBlock( this.id, panelBlockDefinition ),
  					me = this;
  
  				panel.onShow = function() {
  					this.element.addClass( namedPanelCls );
  
  					me.setState( CKEDITOR.TRISTATE_ON );
  
  					me._.on = 1;
  
  					me.editorFocus && !editor.focusManager.hasFocus && editor.focus();
  
  					if ( me.onOpen )
  						me.onOpen();
  				};
  
  				panel.onHide = function( preventOnClose ) {
  					this.element.removeClass( namedPanelCls );
  
  					me.setState( me.modes && me.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
  
  					me._.on = 0;
  
  					if ( !preventOnClose && me.onClose )
  						me.onClose();
  				};
  
  				panel.onEscape = function() {
  					// Hide drop-down with focus returned.
  					panel.hide( 1 );
  				};
  
  				list.onClick = function( value, marked ) {
  
  					if ( me.onClick )
  						me.onClick.call( me, value, marked );
  
  					panel.hide();
  				};
  
  				this._.panel = panel;
  				this._.list = list;
  
  				panel.getBlock( this.id ).onHide = function() {
  					me._.on = 0;
  					me.setState( CKEDITOR.TRISTATE_OFF );
  				};
  
  				if ( this.init )
  					this.init();
  			},
  
  			setValue: function( value, text ) {
  				this._.value = value;
  
  				var textElement = this.document.getById( 'cke_' + this.id + '_text' );
  				if ( textElement ) {
  					if ( !( value || text ) ) {
  						text = this.label;
  						textElement.addClass( 'cke_combo_inlinelabel' );
  					} else {
  						textElement.removeClass( 'cke_combo_inlinelabel' );
  					}
  
  					textElement.setText( typeof text != 'undefined' ? text : value );
  				}
  			},
  
  			getValue: function() {
  				return this._.value || '';
  			},
  
  			unmarkAll: function() {
  				this._.list.unmarkAll();
  			},
  
  			mark: function( value ) {
  				this._.list.mark( value );
  			},
  
  			hideItem: function( value ) {
  				this._.list.hideItem( value );
  			},
  
  			hideGroup: function( groupTitle ) {
  				this._.list.hideGroup( groupTitle );
  			},
  
  			showAll: function() {
  				this._.list.showAll();
  			},
  
  			/**
  			 * Adds an entry displayed inside the rich combo panel.
  			 *
  			 * @param {String} value
  			 * @param {String} html
  			 * @param {String} text
  			 */
  			add: function( value, html, text ) {
  				this._.items[ value ] = text || value;
  				this._.list.add( value, html, text );
  			},
  
  			startGroup: function( title ) {
  				this._.list.startGroup( title );
  			},
  
  			commit: function() {
  				if ( !this._.committed ) {
  					this._.list.commit();
  					this._.committed = 1;
  					CKEDITOR.ui.fire( 'ready', this );
  				}
  				this._.committed = 1;
  			},
  
  			setState: function( state ) {
  				if ( this._.state == state )
  					return;
  
  				var el = this.document.getById( 'cke_' + this.id ),
  					linkEl = el.getElementsByTag( 'a' ).getItem( 0 );
  
  				el.setState( state, 'cke_combo' );
  
  				state == CKEDITOR.TRISTATE_DISABLED ?
  					el.setAttribute( 'aria-disabled', true ) :
  					el.removeAttribute( 'aria-disabled' );
  
  				if ( linkEl ) {
  					linkEl.setAttribute( 'aria-expanded', state == CKEDITOR.TRISTATE_ON );
  				}
  
  				this._.state = state;
  			},
  
  			getState: function() {
  				return this._.state;
  			},
  
  			enable: function() {
  				if ( this._.state == CKEDITOR.TRISTATE_DISABLED )
  					this.setState( this._.lastState );
  			},
  
  			disable: function() {
  				if ( this._.state != CKEDITOR.TRISTATE_DISABLED ) {
  					this._.lastState = this._.state;
  					this.setState( CKEDITOR.TRISTATE_DISABLED );
  				}
  			},
  
  			/**
  			 * Removes all listeners from a rich combo element.
  			 *
  			 * @since 4.11.0
  			 */
  			destroy: function() {
  				CKEDITOR.tools.array.forEach( this._.listeners, function( listener ) {
  					listener.removeListener();
  				} );
  				this._.listeners = [];
  			},
  
  			/**
  			 * Selects a rich combo item based on the first matching result from the given filter function.
  			 * The filter function takes an object as an argument with `value` and `text` fields. The values of these
  			 * fields match to arguments passed in the {@link #add} method.
  			 * In order to obtain a correct result with this method, it is required to open or initialize the rich combo panel.
  			 *
  			 * ```js
  			 * 	var richCombo = editor.ui.get( 'Font' );
  			 *
  			 * 	// Required when 'richcombo' was never open in a given editor instance.
  			 * 	richCombo.createPanel( editor );
  			 *
  			 * 	richCombo.select( function( item ) {
  			 * 		return item.value === 'Tahoma' || item.text === 'Tahoma';
  			 * 	} );
  			 * ```
  			 *
  			 * @since 4.14.0
  			 * @param {Function} callback The function should return `true` if a matching element is found.
  			 * @param {Object} callback.item An object containing the `value` and `text` fields which are compared by this callback.
  			 */
  			select: function( callback ) {
  				if ( CKEDITOR.tools.isEmpty( this._.items ) ) {
  					return;
  				}
  
  				for ( var value in this._.items ) {
  					if ( callback( {
  						value: value,
  						text: this._.items[ value ]
  					} ) ) {
  						this.setValue( value );
  						return;
  					}
  				}
  			}
  		},
  
  		/**
  		 * Represents a rich combo handler object.
  		 *
  		 * @class CKEDITOR.ui.richCombo.handler
  		 * @singleton
  		 * @extends CKEDITOR.ui.handlerDefinition
  		 */
  		statics: {
  			handler: {
  				/**
  				 * Transforms a rich combo definition into a {@link CKEDITOR.ui.richCombo} instance.
  				 *
  				 * @param {Object} definition
  				 * @returns {CKEDITOR.ui.richCombo}
  				 */
  				create: function( definition ) {
  					return new CKEDITOR.ui.richCombo( definition );
  				}
  			}
  		}
  	} );
  
  	/**
  	 * @param {String} name
  	 * @param {Object} definition
  	 * @member CKEDITOR.ui
  	 * @todo
  	 */
  	CKEDITOR.ui.prototype.addRichCombo = function( name, definition ) {
  		this.add( name, CKEDITOR.UI_RICHCOMBO, definition );
  	};
  
  } )();
  