1 /*! jQuery UI - v1.13.1 - 2022-02-02
3 * Includes: widget.js, position.js, keycode.js, unique-id.js, widgets/autocomplete.js, widgets/menu.js
4 * Copyright jQuery Foundation and other contributors; Licensed MIT */
6 ( function( factory ) {
9 if ( typeof define === "function" && define.amd ) {
11 // AMD. Register as an anonymous module.
12 define( [ "jquery" ], factory );
23 var version = $.ui.version = "1.13.1";
27 * jQuery UI Widget 1.13.1
30 * Copyright jQuery Foundation and other contributors
31 * Released under the MIT license.
32 * http://jquery.org/license
37 //>>description: Provides a factory for creating stateful widgets with a common API.
38 //>>docs: http://api.jqueryui.com/jQuery.widget/
39 //>>demos: http://jqueryui.com/widget/
43 var widgetHasOwnProperty = Array.prototype.hasOwnProperty;
44 var widgetSlice = Array.prototype.slice;
46 $.cleanData = ( function( orig ) {
47 return function( elems ) {
49 for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) {
51 // Only trigger remove when necessary to save time
52 events = $._data( elem, "events" );
53 if ( events && events.remove ) {
54 $( elem ).triggerHandler( "remove" );
61 $.widget = function( name, base, prototype ) {
62 var existingConstructor, constructor, basePrototype;
64 // ProxiedPrototype allows the provided prototype to remain unmodified
65 // so that it can be used as a mixin for multiple widgets (#8876)
66 var proxiedPrototype = {};
68 var namespace = name.split( "." )[ 0 ];
69 name = name.split( "." )[ 1 ];
70 var fullName = namespace + "-" + name;
77 if ( Array.isArray( prototype ) ) {
78 prototype = $.extend.apply( null, [ {} ].concat( prototype ) );
81 // Create selector for plugin
82 $.expr.pseudos[ fullName.toLowerCase() ] = function( elem ) {
83 return !!$.data( elem, fullName );
86 $[ namespace ] = $[ namespace ] || {};
87 existingConstructor = $[ namespace ][ name ];
88 constructor = $[ namespace ][ name ] = function( options, element ) {
90 // Allow instantiation without "new" keyword
91 if ( !this || !this._createWidget ) {
92 return new constructor( options, element );
95 // Allow instantiation without initializing for simple inheritance
96 // must use "new" keyword (the code above always passes args)
97 if ( arguments.length ) {
98 this._createWidget( options, element );
102 // Extend with the existing constructor to carry over any static properties
103 $.extend( constructor, existingConstructor, {
104 version: prototype.version,
106 // Copy the object used to create the prototype in case we need to
107 // redefine the widget later
108 _proto: $.extend( {}, prototype ),
110 // Track widgets that inherit from this widget in case this widget is
111 // redefined after a widget inherits from it
112 _childConstructors: []
115 basePrototype = new base();
117 // We need to make the options hash a property directly on the new instance
118 // otherwise we'll modify the options hash on the prototype that we're
120 basePrototype.options = $.widget.extend( {}, basePrototype.options );
121 $.each( prototype, function( prop, value ) {
122 if ( typeof value !== "function" ) {
123 proxiedPrototype[ prop ] = value;
126 proxiedPrototype[ prop ] = ( function() {
128 return base.prototype[ prop ].apply( this, arguments );
131 function _superApply( args ) {
132 return base.prototype[ prop ].apply( this, args );
136 var __super = this._super;
137 var __superApply = this._superApply;
140 this._super = _super;
141 this._superApply = _superApply;
143 returnValue = value.apply( this, arguments );
145 this._super = __super;
146 this._superApply = __superApply;
152 constructor.prototype = $.widget.extend( basePrototype, {
154 // TODO: remove support for widgetEventPrefix
155 // always use the name + a colon as the prefix, e.g., draggable:start
156 // don't prefix for widgets that aren't DOM-based
157 widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name
158 }, proxiedPrototype, {
159 constructor: constructor,
160 namespace: namespace,
162 widgetFullName: fullName
165 // If this widget is being redefined then we need to find all widgets that
166 // are inheriting from it and redefine all of them so that they inherit from
167 // the new version of this widget. We're essentially trying to replace one
168 // level in the prototype chain.
169 if ( existingConstructor ) {
170 $.each( existingConstructor._childConstructors, function( i, child ) {
171 var childPrototype = child.prototype;
173 // Redefine the child widget using the same prototype that was
174 // originally used, but inherit from the new version of the base
175 $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor,
179 // Remove the list of existing child constructors from the old constructor
180 // so the old child constructors can be garbage collected
181 delete existingConstructor._childConstructors;
183 base._childConstructors.push( constructor );
186 $.widget.bridge( name, constructor );
191 $.widget.extend = function( target ) {
192 var input = widgetSlice.call( arguments, 1 );
194 var inputLength = input.length;
198 for ( ; inputIndex < inputLength; inputIndex++ ) {
199 for ( key in input[ inputIndex ] ) {
200 value = input[ inputIndex ][ key ];
201 if ( widgetHasOwnProperty.call( input[ inputIndex ], key ) && value !== undefined ) {
204 if ( $.isPlainObject( value ) ) {
205 target[ key ] = $.isPlainObject( target[ key ] ) ?
206 $.widget.extend( {}, target[ key ], value ) :
208 // Don't extend strings, arrays, etc. with objects
209 $.widget.extend( {}, value );
211 // Copy everything else by reference
213 target[ key ] = value;
221 $.widget.bridge = function( name, object ) {
222 var fullName = object.prototype.widgetFullName || name;
223 $.fn[ name ] = function( options ) {
224 var isMethodCall = typeof options === "string";
225 var args = widgetSlice.call( arguments, 1 );
226 var returnValue = this;
228 if ( isMethodCall ) {
230 // If this is an empty collection, we need to have the instance method
231 // return undefined instead of the jQuery instance
232 if ( !this.length && options === "instance" ) {
233 returnValue = undefined;
235 this.each( function() {
237 var instance = $.data( this, fullName );
239 if ( options === "instance" ) {
240 returnValue = instance;
245 return $.error( "cannot call methods on " + name +
246 " prior to initialization; " +
247 "attempted to call method '" + options + "'" );
250 if ( typeof instance[ options ] !== "function" ||
251 options.charAt( 0 ) === "_" ) {
252 return $.error( "no such method '" + options + "' for " + name +
253 " widget instance" );
256 methodValue = instance[ options ].apply( instance, args );
258 if ( methodValue !== instance && methodValue !== undefined ) {
259 returnValue = methodValue && methodValue.jquery ?
260 returnValue.pushStack( methodValue.get() ) :
268 // Allow multiple hashes to be passed on init
270 options = $.widget.extend.apply( null, [ options ].concat( args ) );
273 this.each( function() {
274 var instance = $.data( this, fullName );
276 instance.option( options || {} );
277 if ( instance._init ) {
281 $.data( this, fullName, new object( options, this ) );
290 $.Widget = function( /* options, element */ ) {};
291 $.Widget._childConstructors = [];
293 $.Widget.prototype = {
294 widgetName: "widget",
295 widgetEventPrefix: "",
296 defaultElement: "<div>",
306 _createWidget: function( options, element ) {
307 element = $( element || this.defaultElement || this )[ 0 ];
308 this.element = $( element );
309 this.uuid = widgetUuid++;
310 this.eventNamespace = "." + this.widgetName + this.uuid;
313 this.hoverable = $();
314 this.focusable = $();
315 this.classesElementLookup = {};
317 if ( element !== this ) {
318 $.data( element, this.widgetFullName, this );
319 this._on( true, this.element, {
320 remove: function( event ) {
321 if ( event.target === element ) {
326 this.document = $( element.style ?
328 // Element within the document
329 element.ownerDocument :
331 // Element is window or document
332 element.document || element );
333 this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow );
336 this.options = $.widget.extend( {},
338 this._getCreateOptions(),
343 if ( this.options.disabled ) {
344 this._setOptionDisabled( this.options.disabled );
347 this._trigger( "create", null, this._getCreateEventData() );
351 _getCreateOptions: function() {
355 _getCreateEventData: $.noop,
361 destroy: function() {
365 $.each( this.classesElementLookup, function( key, value ) {
366 that._removeClass( value, key );
369 // We can probably remove the unbind calls in 2.0
370 // all event bindings should go through this._on()
372 .off( this.eventNamespace )
373 .removeData( this.widgetFullName );
375 .off( this.eventNamespace )
376 .removeAttr( "aria-disabled" );
378 // Clean up events and states
379 this.bindings.off( this.eventNamespace );
388 option: function( key, value ) {
394 if ( arguments.length === 0 ) {
396 // Don't return a reference to the internal hash
397 return $.widget.extend( {}, this.options );
400 if ( typeof key === "string" ) {
402 // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
404 parts = key.split( "." );
406 if ( parts.length ) {
407 curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
408 for ( i = 0; i < parts.length - 1; i++ ) {
409 curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
410 curOption = curOption[ parts[ i ] ];
413 if ( arguments.length === 1 ) {
414 return curOption[ key ] === undefined ? null : curOption[ key ];
416 curOption[ key ] = value;
418 if ( arguments.length === 1 ) {
419 return this.options[ key ] === undefined ? null : this.options[ key ];
421 options[ key ] = value;
425 this._setOptions( options );
430 _setOptions: function( options ) {
433 for ( key in options ) {
434 this._setOption( key, options[ key ] );
440 _setOption: function( key, value ) {
441 if ( key === "classes" ) {
442 this._setOptionClasses( value );
445 this.options[ key ] = value;
447 if ( key === "disabled" ) {
448 this._setOptionDisabled( value );
454 _setOptionClasses: function( value ) {
455 var classKey, elements, currentElements;
457 for ( classKey in value ) {
458 currentElements = this.classesElementLookup[ classKey ];
459 if ( value[ classKey ] === this.options.classes[ classKey ] ||
461 !currentElements.length ) {
465 // We are doing this to create a new jQuery object because the _removeClass() call
466 // on the next line is going to destroy the reference to the current elements being
467 // tracked. We need to save a copy of this collection so that we can add the new classes
469 elements = $( currentElements.get() );
470 this._removeClass( currentElements, classKey );
472 // We don't use _addClass() here, because that uses this.options.classes
473 // for generating the string of classes. We want to use the value passed in from
474 // _setOption(), this is the new value of the classes option which was passed to
475 // _setOption(). We pass this value directly to _classes().
476 elements.addClass( this._classes( {
485 _setOptionDisabled: function( value ) {
486 this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value );
488 // If the widget is becoming disabled, then nothing is interactive
490 this._removeClass( this.hoverable, null, "ui-state-hover" );
491 this._removeClass( this.focusable, null, "ui-state-focus" );
496 return this._setOptions( { disabled: false } );
499 disable: function() {
500 return this._setOptions( { disabled: true } );
503 _classes: function( options ) {
507 options = $.extend( {
508 element: this.element,
509 classes: this.options.classes || {}
512 function bindRemoveEvent() {
513 var nodesToBind = [];
515 options.element.each( function( _, element ) {
516 var isTracked = $.map( that.classesElementLookup, function( elements ) {
519 .some( function( elements ) {
520 return elements.is( element );
524 nodesToBind.push( element );
528 that._on( $( nodesToBind ), {
529 remove: "_untrackClassesElement"
533 function processClassString( classes, checkOption ) {
535 for ( i = 0; i < classes.length; i++ ) {
536 current = that.classesElementLookup[ classes[ i ] ] || $();
539 current = $( $.uniqueSort( current.get().concat( options.element.get() ) ) );
541 current = $( current.not( options.element ).get() );
543 that.classesElementLookup[ classes[ i ] ] = current;
544 full.push( classes[ i ] );
545 if ( checkOption && options.classes[ classes[ i ] ] ) {
546 full.push( options.classes[ classes[ i ] ] );
551 if ( options.keys ) {
552 processClassString( options.keys.match( /\S+/g ) || [], true );
554 if ( options.extra ) {
555 processClassString( options.extra.match( /\S+/g ) || [] );
558 return full.join( " " );
561 _untrackClassesElement: function( event ) {
563 $.each( that.classesElementLookup, function( key, value ) {
564 if ( $.inArray( event.target, value ) !== -1 ) {
565 that.classesElementLookup[ key ] = $( value.not( event.target ).get() );
569 this._off( $( event.target ) );
572 _removeClass: function( element, keys, extra ) {
573 return this._toggleClass( element, keys, extra, false );
576 _addClass: function( element, keys, extra ) {
577 return this._toggleClass( element, keys, extra, true );
580 _toggleClass: function( element, keys, extra, add ) {
581 add = ( typeof add === "boolean" ) ? add : extra;
582 var shift = ( typeof element === "string" || element === null ),
584 extra: shift ? keys : extra,
585 keys: shift ? element : keys,
586 element: shift ? this.element : element,
589 options.element.toggleClass( this._classes( options ), add );
593 _on: function( suppressDisabledCheck, element, handlers ) {
597 // No suppressDisabledCheck flag, shuffle arguments
598 if ( typeof suppressDisabledCheck !== "boolean" ) {
600 element = suppressDisabledCheck;
601 suppressDisabledCheck = false;
604 // No element argument, shuffle and use this.element
607 element = this.element;
608 delegateElement = this.widget();
610 element = delegateElement = $( element );
611 this.bindings = this.bindings.add( element );
614 $.each( handlers, function( event, handler ) {
615 function handlerProxy() {
617 // Allow widgets to customize the disabled handling
618 // - disabled as an array instead of boolean
619 // - disabled class as method for disabling individual parts
620 if ( !suppressDisabledCheck &&
621 ( instance.options.disabled === true ||
622 $( this ).hasClass( "ui-state-disabled" ) ) ) {
625 return ( typeof handler === "string" ? instance[ handler ] : handler )
626 .apply( instance, arguments );
629 // Copy the guid so direct unbinding works
630 if ( typeof handler !== "string" ) {
631 handlerProxy.guid = handler.guid =
632 handler.guid || handlerProxy.guid || $.guid++;
635 var match = event.match( /^([\w:-]*)\s*(.*)$/ );
636 var eventName = match[ 1 ] + instance.eventNamespace;
637 var selector = match[ 2 ];
640 delegateElement.on( eventName, selector, handlerProxy );
642 element.on( eventName, handlerProxy );
647 _off: function( element, eventName ) {
648 eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) +
650 element.off( eventName );
652 // Clear the stack to avoid memory leaks (#10056)
653 this.bindings = $( this.bindings.not( element ).get() );
654 this.focusable = $( this.focusable.not( element ).get() );
655 this.hoverable = $( this.hoverable.not( element ).get() );
658 _delay: function( handler, delay ) {
659 function handlerProxy() {
660 return ( typeof handler === "string" ? instance[ handler ] : handler )
661 .apply( instance, arguments );
664 return setTimeout( handlerProxy, delay || 0 );
667 _hoverable: function( element ) {
668 this.hoverable = this.hoverable.add( element );
670 mouseenter: function( event ) {
671 this._addClass( $( event.currentTarget ), null, "ui-state-hover" );
673 mouseleave: function( event ) {
674 this._removeClass( $( event.currentTarget ), null, "ui-state-hover" );
679 _focusable: function( element ) {
680 this.focusable = this.focusable.add( element );
682 focusin: function( event ) {
683 this._addClass( $( event.currentTarget ), null, "ui-state-focus" );
685 focusout: function( event ) {
686 this._removeClass( $( event.currentTarget ), null, "ui-state-focus" );
691 _trigger: function( type, event, data ) {
693 var callback = this.options[ type ];
696 event = $.Event( event );
697 event.type = ( type === this.widgetEventPrefix ?
699 this.widgetEventPrefix + type ).toLowerCase();
701 // The original event may come from any element
702 // so we need to reset the target on the new event
703 event.target = this.element[ 0 ];
705 // Copy original event properties over to the new event
706 orig = event.originalEvent;
708 for ( prop in orig ) {
709 if ( !( prop in event ) ) {
710 event[ prop ] = orig[ prop ];
715 this.element.trigger( event, data );
716 return !( typeof callback === "function" &&
717 callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false ||
718 event.isDefaultPrevented() );
722 $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
723 $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
724 if ( typeof options === "string" ) {
725 options = { effect: options };
729 var effectName = !options ?
731 options === true || typeof options === "number" ?
733 options.effect || defaultEffect;
735 options = options || {};
736 if ( typeof options === "number" ) {
737 options = { duration: options };
738 } else if ( options === true ) {
742 hasOptions = !$.isEmptyObject( options );
743 options.complete = callback;
745 if ( options.delay ) {
746 element.delay( options.delay );
749 if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
750 element[ method ]( options );
751 } else if ( effectName !== method && element[ effectName ] ) {
752 element[ effectName ]( options.duration, options.easing, callback );
754 element.queue( function( next ) {
755 $( this )[ method ]();
757 callback.call( element[ 0 ] );
765 var widget = $.widget;
769 * jQuery UI Position 1.13.1
770 * http://jqueryui.com
772 * Copyright jQuery Foundation and other contributors
773 * Released under the MIT license.
774 * http://jquery.org/license
776 * http://api.jqueryui.com/position/
781 //>>description: Positions elements relative to other elements.
782 //>>docs: http://api.jqueryui.com/position/
783 //>>demos: http://jqueryui.com/position/
787 var cachedScrollbarWidth,
790 rhorizontal = /left|center|right/,
791 rvertical = /top|center|bottom/,
792 roffset = /[\+\-]\d+(\.[\d]+)?%?/,
795 _position = $.fn.position;
797 function getOffsets( offsets, width, height ) {
799 parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
800 parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
804 function parseCss( element, property ) {
805 return parseInt( $.css( element, property ), 10 ) || 0;
808 function isWindow( obj ) {
809 return obj != null && obj === obj.window;
812 function getDimensions( elem ) {
814 if ( raw.nodeType === 9 ) {
817 height: elem.height(),
818 offset: { top: 0, left: 0 }
821 if ( isWindow( raw ) ) {
824 height: elem.height(),
825 offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
828 if ( raw.preventDefault ) {
832 offset: { top: raw.pageY, left: raw.pageX }
836 width: elem.outerWidth(),
837 height: elem.outerHeight(),
838 offset: elem.offset()
843 scrollbarWidth: function() {
844 if ( cachedScrollbarWidth !== undefined ) {
845 return cachedScrollbarWidth;
848 div = $( "<div style=" +
849 "'display:block;position:absolute;width:200px;height:200px;overflow:hidden;'>" +
850 "<div style='height:300px;width:auto;'></div></div>" ),
851 innerDiv = div.children()[ 0 ];
853 $( "body" ).append( div );
854 w1 = innerDiv.offsetWidth;
855 div.css( "overflow", "scroll" );
857 w2 = innerDiv.offsetWidth;
860 w2 = div[ 0 ].clientWidth;
865 return ( cachedScrollbarWidth = w1 - w2 );
867 getScrollInfo: function( within ) {
868 var overflowX = within.isWindow || within.isDocument ? "" :
869 within.element.css( "overflow-x" ),
870 overflowY = within.isWindow || within.isDocument ? "" :
871 within.element.css( "overflow-y" ),
872 hasOverflowX = overflowX === "scroll" ||
873 ( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ),
874 hasOverflowY = overflowY === "scroll" ||
875 ( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight );
877 width: hasOverflowY ? $.position.scrollbarWidth() : 0,
878 height: hasOverflowX ? $.position.scrollbarWidth() : 0
881 getWithinInfo: function( element ) {
882 var withinElement = $( element || window ),
883 isElemWindow = isWindow( withinElement[ 0 ] ),
884 isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9,
885 hasOffset = !isElemWindow && !isDocument;
887 element: withinElement,
888 isWindow: isElemWindow,
889 isDocument: isDocument,
890 offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 },
891 scrollLeft: withinElement.scrollLeft(),
892 scrollTop: withinElement.scrollTop(),
893 width: withinElement.outerWidth(),
894 height: withinElement.outerHeight()
899 $.fn.position = function( options ) {
900 if ( !options || !options.of ) {
901 return _position.apply( this, arguments );
904 // Make a copy, we don't want to modify arguments
905 options = $.extend( {}, options );
907 var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
909 // Make sure string options are treated as CSS selectors
910 target = typeof options.of === "string" ?
911 $( document ).find( options.of ) :
914 within = $.position.getWithinInfo( options.within ),
915 scrollInfo = $.position.getScrollInfo( within ),
916 collision = ( options.collision || "flip" ).split( " " ),
919 dimensions = getDimensions( target );
920 if ( target[ 0 ].preventDefault ) {
922 // Force left top to allow flipping
923 options.at = "left top";
925 targetWidth = dimensions.width;
926 targetHeight = dimensions.height;
927 targetOffset = dimensions.offset;
929 // Clone to reuse original targetOffset later
930 basePosition = $.extend( {}, targetOffset );
932 // Force my and at to have valid horizontal and vertical positions
933 // if a value is missing or invalid, it will be converted to center
934 $.each( [ "my", "at" ], function() {
935 var pos = ( options[ this ] || "" ).split( " " ),
939 if ( pos.length === 1 ) {
940 pos = rhorizontal.test( pos[ 0 ] ) ?
941 pos.concat( [ "center" ] ) :
942 rvertical.test( pos[ 0 ] ) ?
943 [ "center" ].concat( pos ) :
944 [ "center", "center" ];
946 pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
947 pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
950 horizontalOffset = roffset.exec( pos[ 0 ] );
951 verticalOffset = roffset.exec( pos[ 1 ] );
953 horizontalOffset ? horizontalOffset[ 0 ] : 0,
954 verticalOffset ? verticalOffset[ 0 ] : 0
957 // Reduce to just the positions without the offsets
959 rposition.exec( pos[ 0 ] )[ 0 ],
960 rposition.exec( pos[ 1 ] )[ 0 ]
964 // Normalize collision option
965 if ( collision.length === 1 ) {
966 collision[ 1 ] = collision[ 0 ];
969 if ( options.at[ 0 ] === "right" ) {
970 basePosition.left += targetWidth;
971 } else if ( options.at[ 0 ] === "center" ) {
972 basePosition.left += targetWidth / 2;
975 if ( options.at[ 1 ] === "bottom" ) {
976 basePosition.top += targetHeight;
977 } else if ( options.at[ 1 ] === "center" ) {
978 basePosition.top += targetHeight / 2;
981 atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
982 basePosition.left += atOffset[ 0 ];
983 basePosition.top += atOffset[ 1 ];
985 return this.each( function() {
986 var collisionPosition, using,
988 elemWidth = elem.outerWidth(),
989 elemHeight = elem.outerHeight(),
990 marginLeft = parseCss( this, "marginLeft" ),
991 marginTop = parseCss( this, "marginTop" ),
992 collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) +
994 collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) +
996 position = $.extend( {}, basePosition ),
997 myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
999 if ( options.my[ 0 ] === "right" ) {
1000 position.left -= elemWidth;
1001 } else if ( options.my[ 0 ] === "center" ) {
1002 position.left -= elemWidth / 2;
1005 if ( options.my[ 1 ] === "bottom" ) {
1006 position.top -= elemHeight;
1007 } else if ( options.my[ 1 ] === "center" ) {
1008 position.top -= elemHeight / 2;
1011 position.left += myOffset[ 0 ];
1012 position.top += myOffset[ 1 ];
1014 collisionPosition = {
1015 marginLeft: marginLeft,
1016 marginTop: marginTop
1019 $.each( [ "left", "top" ], function( i, dir ) {
1020 if ( $.ui.position[ collision[ i ] ] ) {
1021 $.ui.position[ collision[ i ] ][ dir ]( position, {
1022 targetWidth: targetWidth,
1023 targetHeight: targetHeight,
1024 elemWidth: elemWidth,
1025 elemHeight: elemHeight,
1026 collisionPosition: collisionPosition,
1027 collisionWidth: collisionWidth,
1028 collisionHeight: collisionHeight,
1029 offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
1038 if ( options.using ) {
1040 // Adds feedback as second argument to using callback, if present
1041 using = function( props ) {
1042 var left = targetOffset.left - position.left,
1043 right = left + targetWidth - elemWidth,
1044 top = targetOffset.top - position.top,
1045 bottom = top + targetHeight - elemHeight,
1049 left: targetOffset.left,
1050 top: targetOffset.top,
1052 height: targetHeight
1056 left: position.left,
1061 horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
1062 vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
1064 if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
1065 feedback.horizontal = "center";
1067 if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
1068 feedback.vertical = "middle";
1070 if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
1071 feedback.important = "horizontal";
1073 feedback.important = "vertical";
1075 options.using.call( this, props, feedback );
1079 elem.offset( $.extend( position, { using: using } ) );
1085 left: function( position, data ) {
1086 var within = data.within,
1087 withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
1088 outerWidth = within.width,
1089 collisionPosLeft = position.left - data.collisionPosition.marginLeft,
1090 overLeft = withinOffset - collisionPosLeft,
1091 overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
1094 // Element is wider than within
1095 if ( data.collisionWidth > outerWidth ) {
1097 // Element is initially over the left side of within
1098 if ( overLeft > 0 && overRight <= 0 ) {
1099 newOverRight = position.left + overLeft + data.collisionWidth - outerWidth -
1101 position.left += overLeft - newOverRight;
1103 // Element is initially over right side of within
1104 } else if ( overRight > 0 && overLeft <= 0 ) {
1105 position.left = withinOffset;
1107 // Element is initially over both left and right sides of within
1109 if ( overLeft > overRight ) {
1110 position.left = withinOffset + outerWidth - data.collisionWidth;
1112 position.left = withinOffset;
1116 // Too far left -> align with left edge
1117 } else if ( overLeft > 0 ) {
1118 position.left += overLeft;
1120 // Too far right -> align with right edge
1121 } else if ( overRight > 0 ) {
1122 position.left -= overRight;
1124 // Adjust based on position and margin
1126 position.left = max( position.left - collisionPosLeft, position.left );
1129 top: function( position, data ) {
1130 var within = data.within,
1131 withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
1132 outerHeight = data.within.height,
1133 collisionPosTop = position.top - data.collisionPosition.marginTop,
1134 overTop = withinOffset - collisionPosTop,
1135 overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
1138 // Element is taller than within
1139 if ( data.collisionHeight > outerHeight ) {
1141 // Element is initially over the top of within
1142 if ( overTop > 0 && overBottom <= 0 ) {
1143 newOverBottom = position.top + overTop + data.collisionHeight - outerHeight -
1145 position.top += overTop - newOverBottom;
1147 // Element is initially over bottom of within
1148 } else if ( overBottom > 0 && overTop <= 0 ) {
1149 position.top = withinOffset;
1151 // Element is initially over both top and bottom of within
1153 if ( overTop > overBottom ) {
1154 position.top = withinOffset + outerHeight - data.collisionHeight;
1156 position.top = withinOffset;
1160 // Too far up -> align with top
1161 } else if ( overTop > 0 ) {
1162 position.top += overTop;
1164 // Too far down -> align with bottom edge
1165 } else if ( overBottom > 0 ) {
1166 position.top -= overBottom;
1168 // Adjust based on position and margin
1170 position.top = max( position.top - collisionPosTop, position.top );
1175 left: function( position, data ) {
1176 var within = data.within,
1177 withinOffset = within.offset.left + within.scrollLeft,
1178 outerWidth = within.width,
1179 offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
1180 collisionPosLeft = position.left - data.collisionPosition.marginLeft,
1181 overLeft = collisionPosLeft - offsetLeft,
1182 overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
1183 myOffset = data.my[ 0 ] === "left" ?
1185 data.my[ 0 ] === "right" ?
1188 atOffset = data.at[ 0 ] === "left" ?
1190 data.at[ 0 ] === "right" ?
1193 offset = -2 * data.offset[ 0 ],
1197 if ( overLeft < 0 ) {
1198 newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth -
1199 outerWidth - withinOffset;
1200 if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
1201 position.left += myOffset + atOffset + offset;
1203 } else if ( overRight > 0 ) {
1204 newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset +
1205 atOffset + offset - offsetLeft;
1206 if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
1207 position.left += myOffset + atOffset + offset;
1211 top: function( position, data ) {
1212 var within = data.within,
1213 withinOffset = within.offset.top + within.scrollTop,
1214 outerHeight = within.height,
1215 offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
1216 collisionPosTop = position.top - data.collisionPosition.marginTop,
1217 overTop = collisionPosTop - offsetTop,
1218 overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
1219 top = data.my[ 1 ] === "top",
1222 data.my[ 1 ] === "bottom" ?
1225 atOffset = data.at[ 1 ] === "top" ?
1227 data.at[ 1 ] === "bottom" ?
1228 -data.targetHeight :
1230 offset = -2 * data.offset[ 1 ],
1233 if ( overTop < 0 ) {
1234 newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight -
1235 outerHeight - withinOffset;
1236 if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {
1237 position.top += myOffset + atOffset + offset;
1239 } else if ( overBottom > 0 ) {
1240 newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset +
1242 if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {
1243 position.top += myOffset + atOffset + offset;
1250 $.ui.position.flip.left.apply( this, arguments );
1251 $.ui.position.fit.left.apply( this, arguments );
1254 $.ui.position.flip.top.apply( this, arguments );
1255 $.ui.position.fit.top.apply( this, arguments );
1262 var position = $.ui.position;
1266 * jQuery UI Keycode 1.13.1
1267 * http://jqueryui.com
1269 * Copyright jQuery Foundation and other contributors
1270 * Released under the MIT license.
1271 * http://jquery.org/license
1276 //>>description: Provide keycodes as keynames
1277 //>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/
1280 var keycode = $.ui.keyCode = {
1301 * jQuery UI Unique ID 1.13.1
1302 * http://jqueryui.com
1304 * Copyright jQuery Foundation and other contributors
1305 * Released under the MIT license.
1306 * http://jquery.org/license
1311 //>>description: Functions to generate and remove uniqueId's
1312 //>>docs: http://api.jqueryui.com/uniqueId/
1315 var uniqueId = $.fn.extend( {
1316 uniqueId: ( function() {
1320 return this.each( function() {
1322 this.id = "ui-id-" + ( ++uuid );
1328 removeUniqueId: function() {
1329 return this.each( function() {
1330 if ( /^ui-id-\d+$/.test( this.id ) ) {
1331 $( this ).removeAttr( "id" );
1339 var safeActiveElement = $.ui.safeActiveElement = function( document ) {
1342 // Support: IE 9 only
1343 // IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
1345 activeElement = document.activeElement;
1347 activeElement = document.body;
1350 // Support: IE 9 - 11 only
1351 // IE may return null instead of an element
1352 // Interestingly, this only seems to occur when NOT in an iframe
1353 if ( !activeElement ) {
1354 activeElement = document.body;
1357 // Support: IE 11 only
1358 // IE11 returns a seemingly empty object in some cases when accessing
1359 // document.activeElement from an <iframe>
1360 if ( !activeElement.nodeName ) {
1361 activeElement = document.body;
1364 return activeElement;
1369 * jQuery UI Menu 1.13.1
1370 * http://jqueryui.com
1372 * Copyright jQuery Foundation and other contributors
1373 * Released under the MIT license.
1374 * http://jquery.org/license
1379 //>>description: Creates nestable menus.
1380 //>>docs: http://api.jqueryui.com/menu/
1381 //>>demos: http://jqueryui.com/menu/
1382 //>>css.structure: ../../themes/base/core.css
1383 //>>css.structure: ../../themes/base/menu.css
1384 //>>css.theme: ../../themes/base/theme.css
1387 var widgetsMenu = $.widget( "ui.menu", {
1389 defaultElement: "<ul>",
1393 submenu: "ui-icon-caret-1-e"
1409 _create: function() {
1410 this.activeMenu = this.element;
1412 // Flag used to prevent firing of the click handler
1413 // as the event bubbles up through nested menus
1414 this.mouseHandled = false;
1415 this.lastMousePosition = { x: null, y: null };
1419 role: this.options.role,
1423 this._addClass( "ui-menu", "ui-widget ui-widget-content" );
1426 // Prevent focus from sticking to links inside menu after clicking
1427 // them (focus should always stay on UL during navigation).
1428 "mousedown .ui-menu-item": function( event ) {
1429 event.preventDefault();
1431 this._activateItem( event );
1433 "click .ui-menu-item": function( event ) {
1434 var target = $( event.target );
1435 var active = $( $.ui.safeActiveElement( this.document[ 0 ] ) );
1436 if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
1437 this.select( event );
1439 // Only set the mouseHandled flag if the event will bubble, see #9469.
1440 if ( !event.isPropagationStopped() ) {
1441 this.mouseHandled = true;
1444 // Open submenu on click
1445 if ( target.has( ".ui-menu" ).length ) {
1446 this.expand( event );
1447 } else if ( !this.element.is( ":focus" ) &&
1448 active.closest( ".ui-menu" ).length ) {
1450 // Redirect focus to the menu
1451 this.element.trigger( "focus", [ true ] );
1453 // If the active item is on the top level, let it stay active.
1454 // Otherwise, blur the active item since it is no longer visible.
1455 if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
1456 clearTimeout( this.timer );
1461 "mouseenter .ui-menu-item": "_activateItem",
1462 "mousemove .ui-menu-item": "_activateItem",
1463 mouseleave: "collapseAll",
1464 "mouseleave .ui-menu": "collapseAll",
1465 focus: function( event, keepActiveItem ) {
1467 // If there's already an active item, keep it active
1468 // If not, activate the first item
1469 var item = this.active || this._menuItems().first();
1471 if ( !keepActiveItem ) {
1472 this.focus( event, item );
1475 blur: function( event ) {
1476 this._delay( function() {
1477 var notContained = !$.contains(
1479 $.ui.safeActiveElement( this.document[ 0 ] )
1481 if ( notContained ) {
1482 this.collapseAll( event );
1491 // Clicks outside of a menu collapse any open menus
1492 this._on( this.document, {
1493 click: function( event ) {
1494 if ( this._closeOnDocumentClick( event ) ) {
1495 this.collapseAll( event, true );
1498 // Reset the mouseHandled flag
1499 this.mouseHandled = false;
1504 _activateItem: function( event ) {
1506 // Ignore mouse events while typeahead is active, see #10458.
1507 // Prevents focusing the wrong item when typeahead causes a scroll while the mouse
1508 // is over an item in the menu
1509 if ( this.previousFilter ) {
1513 // If the mouse didn't actually move, but the page was scrolled, ignore the event (#9356)
1514 if ( event.clientX === this.lastMousePosition.x &&
1515 event.clientY === this.lastMousePosition.y ) {
1519 this.lastMousePosition = {
1524 var actualTarget = $( event.target ).closest( ".ui-menu-item" ),
1525 target = $( event.currentTarget );
1527 // Ignore bubbled events on parent items, see #11641
1528 if ( actualTarget[ 0 ] !== target[ 0 ] ) {
1532 // If the item is already active, there's nothing to do
1533 if ( target.is( ".ui-state-active" ) ) {
1537 // Remove ui-state-active class from siblings of the newly focused menu item
1538 // to avoid a jump caused by adjacent elements both having a class with a border
1539 this._removeClass( target.siblings().children( ".ui-state-active" ),
1540 null, "ui-state-active" );
1541 this.focus( event, target );
1544 _destroy: function() {
1545 var items = this.element.find( ".ui-menu-item" )
1546 .removeAttr( "role aria-disabled" ),
1547 submenus = items.children( ".ui-menu-item-wrapper" )
1549 .removeAttr( "tabIndex role aria-haspopup" );
1551 // Destroy (sub)menus
1553 .removeAttr( "aria-activedescendant" )
1554 .find( ".ui-menu" ).addBack()
1555 .removeAttr( "role aria-labelledby aria-expanded aria-hidden aria-disabled " +
1560 submenus.children().each( function() {
1561 var elem = $( this );
1562 if ( elem.data( "ui-menu-submenu-caret" ) ) {
1568 _keydown: function( event ) {
1569 var match, prev, character, skip,
1570 preventDefault = true;
1572 switch ( event.keyCode ) {
1573 case $.ui.keyCode.PAGE_UP:
1574 this.previousPage( event );
1576 case $.ui.keyCode.PAGE_DOWN:
1577 this.nextPage( event );
1579 case $.ui.keyCode.HOME:
1580 this._move( "first", "first", event );
1582 case $.ui.keyCode.END:
1583 this._move( "last", "last", event );
1585 case $.ui.keyCode.UP:
1586 this.previous( event );
1588 case $.ui.keyCode.DOWN:
1591 case $.ui.keyCode.LEFT:
1592 this.collapse( event );
1594 case $.ui.keyCode.RIGHT:
1595 if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
1596 this.expand( event );
1599 case $.ui.keyCode.ENTER:
1600 case $.ui.keyCode.SPACE:
1601 this._activate( event );
1603 case $.ui.keyCode.ESCAPE:
1604 this.collapse( event );
1607 preventDefault = false;
1608 prev = this.previousFilter || "";
1611 // Support number pad values
1612 character = event.keyCode >= 96 && event.keyCode <= 105 ?
1613 ( event.keyCode - 96 ).toString() : String.fromCharCode( event.keyCode );
1615 clearTimeout( this.filterTimer );
1617 if ( character === prev ) {
1620 character = prev + character;
1623 match = this._filterMenuItems( character );
1624 match = skip && match.index( this.active.next() ) !== -1 ?
1625 this.active.nextAll( ".ui-menu-item" ) :
1628 // If no matches on the current filter, reset to the last character pressed
1629 // to move down the menu to the first item that starts with that character
1630 if ( !match.length ) {
1631 character = String.fromCharCode( event.keyCode );
1632 match = this._filterMenuItems( character );
1635 if ( match.length ) {
1636 this.focus( event, match );
1637 this.previousFilter = character;
1638 this.filterTimer = this._delay( function() {
1639 delete this.previousFilter;
1642 delete this.previousFilter;
1646 if ( preventDefault ) {
1647 event.preventDefault();
1651 _activate: function( event ) {
1652 if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
1653 if ( this.active.children( "[aria-haspopup='true']" ).length ) {
1654 this.expand( event );
1656 this.select( event );
1661 refresh: function() {
1662 var menus, items, newSubmenus, newItems, newWrappers,
1664 icon = this.options.icons.submenu,
1665 submenus = this.element.find( this.options.menus );
1667 this._toggleClass( "ui-menu-icons", null, !!this.element.find( ".ui-icon" ).length );
1669 // Initialize nested menus
1670 newSubmenus = submenus.filter( ":not(.ui-menu)" )
1673 role: this.options.role,
1674 "aria-hidden": "true",
1675 "aria-expanded": "false"
1678 var menu = $( this ),
1680 submenuCaret = $( "<span>" ).data( "ui-menu-submenu-caret", true );
1682 that._addClass( submenuCaret, "ui-menu-icon", "ui-icon " + icon );
1684 .attr( "aria-haspopup", "true" )
1685 .prepend( submenuCaret );
1686 menu.attr( "aria-labelledby", item.attr( "id" ) );
1689 this._addClass( newSubmenus, "ui-menu", "ui-widget ui-widget-content ui-front" );
1691 menus = submenus.add( this.element );
1692 items = menus.find( this.options.items );
1694 // Initialize menu-items containing spaces and/or dashes only as dividers
1695 items.not( ".ui-menu-item" ).each( function() {
1696 var item = $( this );
1697 if ( that._isDivider( item ) ) {
1698 that._addClass( item, "ui-menu-divider", "ui-widget-content" );
1702 // Don't refresh list items that are already adapted
1703 newItems = items.not( ".ui-menu-item, .ui-menu-divider" );
1704 newWrappers = newItems.children()
1709 role: this._itemRole()
1711 this._addClass( newItems, "ui-menu-item" )
1712 ._addClass( newWrappers, "ui-menu-item-wrapper" );
1714 // Add aria-disabled attribute to any disabled menu item
1715 items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
1717 // If the active item has been removed, blur the menu
1718 if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
1723 _itemRole: function() {
1727 }[ this.options.role ];
1730 _setOption: function( key, value ) {
1731 if ( key === "icons" ) {
1732 var icons = this.element.find( ".ui-menu-icon" );
1733 this._removeClass( icons, null, this.options.icons.submenu )
1734 ._addClass( icons, null, value.submenu );
1736 this._super( key, value );
1739 _setOptionDisabled: function( value ) {
1740 this._super( value );
1742 this.element.attr( "aria-disabled", String( value ) );
1743 this._toggleClass( null, "ui-state-disabled", !!value );
1746 focus: function( event, item ) {
1747 var nested, focused, activeParent;
1748 this.blur( event, event && event.type === "focus" );
1750 this._scrollIntoView( item );
1752 this.active = item.first();
1754 focused = this.active.children( ".ui-menu-item-wrapper" );
1755 this._addClass( focused, null, "ui-state-active" );
1757 // Only update aria-activedescendant if there's a role
1758 // otherwise we assume focus is managed elsewhere
1759 if ( this.options.role ) {
1760 this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
1763 // Highlight active parent menu item, if any
1764 activeParent = this.active
1766 .closest( ".ui-menu-item" )
1767 .children( ".ui-menu-item-wrapper" );
1768 this._addClass( activeParent, null, "ui-state-active" );
1770 if ( event && event.type === "keydown" ) {
1773 this.timer = this._delay( function() {
1778 nested = item.children( ".ui-menu" );
1779 if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) {
1780 this._startOpening( nested );
1782 this.activeMenu = item.parent();
1784 this._trigger( "focus", event, { item: item } );
1787 _scrollIntoView: function( item ) {
1788 var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
1789 if ( this._hasScroll() ) {
1790 borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0;
1791 paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0;
1792 offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
1793 scroll = this.activeMenu.scrollTop();
1794 elementHeight = this.activeMenu.height();
1795 itemHeight = item.outerHeight();
1798 this.activeMenu.scrollTop( scroll + offset );
1799 } else if ( offset + itemHeight > elementHeight ) {
1800 this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
1805 blur: function( event, fromFocus ) {
1807 clearTimeout( this.timer );
1810 if ( !this.active ) {
1814 this._removeClass( this.active.children( ".ui-menu-item-wrapper" ),
1815 null, "ui-state-active" );
1817 this._trigger( "blur", event, { item: this.active } );
1821 _startOpening: function( submenu ) {
1822 clearTimeout( this.timer );
1824 // Don't open if already open fixes a Firefox bug that caused a .5 pixel
1825 // shift in the submenu position when mousing over the caret icon
1826 if ( submenu.attr( "aria-hidden" ) !== "true" ) {
1830 this.timer = this._delay( function() {
1832 this._open( submenu );
1836 _open: function( submenu ) {
1837 var position = $.extend( {
1839 }, this.options.position );
1841 clearTimeout( this.timer );
1842 this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
1844 .attr( "aria-hidden", "true" );
1848 .removeAttr( "aria-hidden" )
1849 .attr( "aria-expanded", "true" )
1850 .position( position );
1853 collapseAll: function( event, all ) {
1854 clearTimeout( this.timer );
1855 this.timer = this._delay( function() {
1857 // If we were passed an event, look for the submenu that contains the event
1858 var currentMenu = all ? this.element :
1859 $( event && event.target ).closest( this.element.find( ".ui-menu" ) );
1861 // If we found no valid submenu ancestor, use the main menu to close all
1863 if ( !currentMenu.length ) {
1864 currentMenu = this.element;
1867 this._close( currentMenu );
1871 // Work around active item staying active after menu is blurred
1872 this._removeClass( currentMenu.find( ".ui-state-active" ), null, "ui-state-active" );
1874 this.activeMenu = currentMenu;
1875 }, all ? 0 : this.delay );
1878 // With no arguments, closes the currently active menu - if nothing is active
1879 // it closes all menus. If passed an argument, it will search for menus BELOW
1880 _close: function( startMenu ) {
1882 startMenu = this.active ? this.active.parent() : this.element;
1885 startMenu.find( ".ui-menu" )
1887 .attr( "aria-hidden", "true" )
1888 .attr( "aria-expanded", "false" );
1891 _closeOnDocumentClick: function( event ) {
1892 return !$( event.target ).closest( ".ui-menu" ).length;
1895 _isDivider: function( item ) {
1897 // Match hyphen, em dash, en dash
1898 return !/[^\-\u2014\u2013\s]/.test( item.text() );
1901 collapse: function( event ) {
1902 var newItem = this.active &&
1903 this.active.parent().closest( ".ui-menu-item", this.element );
1904 if ( newItem && newItem.length ) {
1906 this.focus( event, newItem );
1910 expand: function( event ) {
1911 var newItem = this.active && this._menuItems( this.active.children( ".ui-menu" ) ).first();
1913 if ( newItem && newItem.length ) {
1914 this._open( newItem.parent() );
1916 // Delay so Firefox will not hide activedescendant change in expanding submenu from AT
1917 this._delay( function() {
1918 this.focus( event, newItem );
1923 next: function( event ) {
1924 this._move( "next", "first", event );
1927 previous: function( event ) {
1928 this._move( "prev", "last", event );
1931 isFirstItem: function() {
1932 return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
1935 isLastItem: function() {
1936 return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
1939 _menuItems: function( menu ) {
1940 return ( menu || this.element )
1941 .find( this.options.items )
1942 .filter( ".ui-menu-item" );
1945 _move: function( direction, filter, event ) {
1947 if ( this.active ) {
1948 if ( direction === "first" || direction === "last" ) {
1950 [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
1954 [ direction + "All" ]( ".ui-menu-item" )
1958 if ( !next || !next.length || !this.active ) {
1959 next = this._menuItems( this.activeMenu )[ filter ]();
1962 this.focus( event, next );
1965 nextPage: function( event ) {
1966 var item, base, height;
1968 if ( !this.active ) {
1972 if ( this.isLastItem() ) {
1975 if ( this._hasScroll() ) {
1976 base = this.active.offset().top;
1977 height = this.element.innerHeight();
1979 // jQuery 3.2 doesn't include scrollbars in innerHeight, add it back.
1980 if ( $.fn.jquery.indexOf( "3.2." ) === 0 ) {
1981 height += this.element[ 0 ].offsetHeight - this.element.outerHeight();
1984 this.active.nextAll( ".ui-menu-item" ).each( function() {
1986 return item.offset().top - base - height < 0;
1989 this.focus( event, item );
1991 this.focus( event, this._menuItems( this.activeMenu )
1992 [ !this.active ? "first" : "last" ]() );
1996 previousPage: function( event ) {
1997 var item, base, height;
1998 if ( !this.active ) {
2002 if ( this.isFirstItem() ) {
2005 if ( this._hasScroll() ) {
2006 base = this.active.offset().top;
2007 height = this.element.innerHeight();
2009 // jQuery 3.2 doesn't include scrollbars in innerHeight, add it back.
2010 if ( $.fn.jquery.indexOf( "3.2." ) === 0 ) {
2011 height += this.element[ 0 ].offsetHeight - this.element.outerHeight();
2014 this.active.prevAll( ".ui-menu-item" ).each( function() {
2016 return item.offset().top - base + height > 0;
2019 this.focus( event, item );
2021 this.focus( event, this._menuItems( this.activeMenu ).first() );
2025 _hasScroll: function() {
2026 return this.element.outerHeight() < this.element.prop( "scrollHeight" );
2029 select: function( event ) {
2031 // TODO: It should never be possible to not have an active item at this
2032 // point, but the tests don't trigger mouseenter before click.
2033 this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
2034 var ui = { item: this.active };
2035 if ( !this.active.has( ".ui-menu" ).length ) {
2036 this.collapseAll( event, true );
2038 this._trigger( "select", event, ui );
2041 _filterMenuItems: function( character ) {
2042 var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ),
2043 regex = new RegExp( "^" + escapedCharacter, "i" );
2045 return this.activeMenu
2046 .find( this.options.items )
2048 // Only match on items, not dividers or other content (#10571)
2049 .filter( ".ui-menu-item" )
2050 .filter( function() {
2052 String.prototype.trim.call(
2053 $( this ).children( ".ui-menu-item-wrapper" ).text() ) );
2060 * jQuery UI Autocomplete 1.13.1
2061 * http://jqueryui.com
2063 * Copyright jQuery Foundation and other contributors
2064 * Released under the MIT license.
2065 * http://jquery.org/license
2068 //>>label: Autocomplete
2070 //>>description: Lists suggested words as the user is typing.
2071 //>>docs: http://api.jqueryui.com/autocomplete/
2072 //>>demos: http://jqueryui.com/autocomplete/
2073 //>>css.structure: ../../themes/base/core.css
2074 //>>css.structure: ../../themes/base/autocomplete.css
2075 //>>css.theme: ../../themes/base/theme.css
2078 $.widget( "ui.autocomplete", {
2080 defaultElement: "<input>",
2105 liveRegionTimer: null,
2107 _create: function() {
2109 // Some browsers only repeat keydown events, not keypress events,
2110 // so we use the suppressKeyPress flag to determine if we've already
2111 // handled the keydown event. #7269
2112 // Unfortunately the code for & in keypress is the same as the up arrow,
2113 // so we use the suppressKeyPressRepeat flag to avoid handling keypress
2114 // events when we know the keydown event was used to modify the
2115 // search term. #7799
2116 var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
2117 nodeName = this.element[ 0 ].nodeName.toLowerCase(),
2118 isTextarea = nodeName === "textarea",
2119 isInput = nodeName === "input";
2121 // Textareas are always multi-line
2122 // Inputs are always single-line, even if inside a contentEditable element
2123 // IE also treats inputs as contentEditable
2124 // All other element types are determined by whether or not they're contentEditable
2125 this.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element );
2127 this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
2128 this.isNewMenu = true;
2130 this._addClass( "ui-autocomplete-input" );
2131 this.element.attr( "autocomplete", "off" );
2133 this._on( this.element, {
2134 keydown: function( event ) {
2135 if ( this.element.prop( "readOnly" ) ) {
2136 suppressKeyPress = true;
2137 suppressInput = true;
2138 suppressKeyPressRepeat = true;
2142 suppressKeyPress = false;
2143 suppressInput = false;
2144 suppressKeyPressRepeat = false;
2145 var keyCode = $.ui.keyCode;
2146 switch ( event.keyCode ) {
2147 case keyCode.PAGE_UP:
2148 suppressKeyPress = true;
2149 this._move( "previousPage", event );
2151 case keyCode.PAGE_DOWN:
2152 suppressKeyPress = true;
2153 this._move( "nextPage", event );
2156 suppressKeyPress = true;
2157 this._keyEvent( "previous", event );
2160 suppressKeyPress = true;
2161 this._keyEvent( "next", event );
2165 // when menu is open and has focus
2166 if ( this.menu.active ) {
2168 // #6055 - Opera still allows the keypress to occur
2169 // which causes forms to submit
2170 suppressKeyPress = true;
2171 event.preventDefault();
2172 this.menu.select( event );
2176 if ( this.menu.active ) {
2177 this.menu.select( event );
2180 case keyCode.ESCAPE:
2181 if ( this.menu.element.is( ":visible" ) ) {
2182 if ( !this.isMultiLine ) {
2183 this._value( this.term );
2185 this.close( event );
2187 // Different browsers have different default behavior for escape
2188 // Single press can mean undo or clear
2189 // Double press in IE means clear the whole form
2190 event.preventDefault();
2194 suppressKeyPressRepeat = true;
2196 // search timeout should be triggered before the input value is changed
2197 this._searchTimeout( event );
2201 keypress: function( event ) {
2202 if ( suppressKeyPress ) {
2203 suppressKeyPress = false;
2204 if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
2205 event.preventDefault();
2209 if ( suppressKeyPressRepeat ) {
2213 // Replicate some key handlers to allow them to repeat in Firefox and Opera
2214 var keyCode = $.ui.keyCode;
2215 switch ( event.keyCode ) {
2216 case keyCode.PAGE_UP:
2217 this._move( "previousPage", event );
2219 case keyCode.PAGE_DOWN:
2220 this._move( "nextPage", event );
2223 this._keyEvent( "previous", event );
2226 this._keyEvent( "next", event );
2230 input: function( event ) {
2231 if ( suppressInput ) {
2232 suppressInput = false;
2233 event.preventDefault();
2236 this._searchTimeout( event );
2239 this.selectedItem = null;
2240 this.previous = this._value();
2242 blur: function( event ) {
2243 clearTimeout( this.searching );
2244 this.close( event );
2245 this._change( event );
2250 this.menu = $( "<ul>" )
2251 .appendTo( this._appendTo() )
2254 // disable ARIA support, the live region takes care of that
2259 // Support: IE 11 only, Edge <= 14
2260 // For other browsers, we preventDefault() on the mousedown event
2261 // to keep the dropdown from taking focus from the input. This doesn't
2262 // work for IE/Edge, causing problems with selection and scrolling (#9638)
2263 // Happily, IE and Edge support an "unselectable" attribute that
2264 // prevents an element from receiving focus, exactly what we want here.
2266 "unselectable": "on"
2268 .menu( "instance" );
2270 this._addClass( this.menu.element, "ui-autocomplete", "ui-front" );
2271 this._on( this.menu.element, {
2272 mousedown: function( event ) {
2274 // Prevent moving focus out of the text field
2275 event.preventDefault();
2277 menufocus: function( event, ui ) {
2281 // Prevent accidental activation of menu items in Firefox (#7024 #9118)
2282 if ( this.isNewMenu ) {
2283 this.isNewMenu = false;
2284 if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
2287 this.document.one( "mousemove", function() {
2288 $( event.target ).trigger( event.originalEvent );
2295 item = ui.item.data( "ui-autocomplete-item" );
2296 if ( false !== this._trigger( "focus", event, { item: item } ) ) {
2298 // use value to match what will end up in the input, if it was a key event
2299 if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
2300 this._value( item.value );
2304 // Announce the value in the liveRegion
2305 label = ui.item.attr( "aria-label" ) || item.value;
2306 if ( label && String.prototype.trim.call( label ).length ) {
2307 clearTimeout( this.liveRegionTimer );
2308 this.liveRegionTimer = this._delay( function() {
2309 this.liveRegion.html( $( "<div>" ).text( label ) );
2313 menuselect: function( event, ui ) {
2314 var item = ui.item.data( "ui-autocomplete-item" ),
2315 previous = this.previous;
2317 // Only trigger when focus was lost (click on menu)
2318 if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {
2319 this.element.trigger( "focus" );
2320 this.previous = previous;
2322 // #6109 - IE triggers two focus events and the second
2323 // is asynchronous, so we need to reset the previous
2324 // term synchronously and asynchronously :-(
2325 this._delay( function() {
2326 this.previous = previous;
2327 this.selectedItem = item;
2331 if ( false !== this._trigger( "select", event, { item: item } ) ) {
2332 this._value( item.value );
2335 // reset the term after the select event
2336 // this allows custom select handling to work properly
2337 this.term = this._value();
2339 this.close( event );
2340 this.selectedItem = item;
2344 this.liveRegion = $( "<div>", {
2346 "aria-live": "assertive",
2347 "aria-relevant": "additions"
2349 .appendTo( this.document[ 0 ].body );
2351 this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" );
2353 // Turning off autocomplete prevents the browser from remembering the
2354 // value when navigating through history, so we re-enable autocomplete
2355 // if the page is unloaded before the widget is destroyed. #7790
2356 this._on( this.window, {
2357 beforeunload: function() {
2358 this.element.removeAttr( "autocomplete" );
2363 _destroy: function() {
2364 clearTimeout( this.searching );
2365 this.element.removeAttr( "autocomplete" );
2366 this.menu.element.remove();
2367 this.liveRegion.remove();
2370 _setOption: function( key, value ) {
2371 this._super( key, value );
2372 if ( key === "source" ) {
2375 if ( key === "appendTo" ) {
2376 this.menu.element.appendTo( this._appendTo() );
2378 if ( key === "disabled" && value && this.xhr ) {
2383 _isEventTargetInWidget: function( event ) {
2384 var menuElement = this.menu.element[ 0 ];
2386 return event.target === this.element[ 0 ] ||
2387 event.target === menuElement ||
2388 $.contains( menuElement, event.target );
2391 _closeOnClickOutside: function( event ) {
2392 if ( !this._isEventTargetInWidget( event ) ) {
2397 _appendTo: function() {
2398 var element = this.options.appendTo;
2401 element = element.jquery || element.nodeType ?
2403 this.document.find( element ).eq( 0 );
2406 if ( !element || !element[ 0 ] ) {
2407 element = this.element.closest( ".ui-front, dialog" );
2410 if ( !element.length ) {
2411 element = this.document[ 0 ].body;
2417 _initSource: function() {
2420 if ( Array.isArray( this.options.source ) ) {
2421 array = this.options.source;
2422 this.source = function( request, response ) {
2423 response( $.ui.autocomplete.filter( array, request.term ) );
2425 } else if ( typeof this.options.source === "string" ) {
2426 url = this.options.source;
2427 this.source = function( request, response ) {
2431 that.xhr = $.ajax( {
2435 success: function( data ) {
2444 this.source = this.options.source;
2448 _searchTimeout: function( event ) {
2449 clearTimeout( this.searching );
2450 this.searching = this._delay( function() {
2452 // Search if the value has changed, or if the user retypes the same value (see #7434)
2453 var equalValues = this.term === this._value(),
2454 menuVisible = this.menu.element.is( ":visible" ),
2455 modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
2457 if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) {
2458 this.selectedItem = null;
2459 this.search( null, event );
2461 }, this.options.delay );
2464 search: function( value, event ) {
2465 value = value != null ? value : this._value();
2467 // Always save the actual value, not the one passed as an argument
2468 this.term = this._value();
2470 if ( value.length < this.options.minLength ) {
2471 return this.close( event );
2474 if ( this._trigger( "search", event ) === false ) {
2478 return this._search( value );
2481 _search: function( value ) {
2483 this._addClass( "ui-autocomplete-loading" );
2484 this.cancelSearch = false;
2486 this.source( { term: value }, this._response() );
2489 _response: function() {
2490 var index = ++this.requestIndex;
2492 return function( content ) {
2493 if ( index === this.requestIndex ) {
2494 this.__response( content );
2498 if ( !this.pending ) {
2499 this._removeClass( "ui-autocomplete-loading" );
2504 __response: function( content ) {
2506 content = this._normalize( content );
2508 this._trigger( "response", null, { content: content } );
2509 if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
2510 this._suggest( content );
2511 this._trigger( "open" );
2514 // use ._close() instead of .close() so we don't cancel future searches
2519 close: function( event ) {
2520 this.cancelSearch = true;
2521 this._close( event );
2524 _close: function( event ) {
2526 // Remove the handler that closes the menu on outside clicks
2527 this._off( this.document, "mousedown" );
2529 if ( this.menu.element.is( ":visible" ) ) {
2530 this.menu.element.hide();
2532 this.isNewMenu = true;
2533 this._trigger( "close", event );
2537 _change: function( event ) {
2538 if ( this.previous !== this._value() ) {
2539 this._trigger( "change", event, { item: this.selectedItem } );
2543 _normalize: function( items ) {
2545 // assume all items have the right format when the first item is complete
2546 if ( items.length && items[ 0 ].label && items[ 0 ].value ) {
2549 return $.map( items, function( item ) {
2550 if ( typeof item === "string" ) {
2556 return $.extend( {}, item, {
2557 label: item.label || item.value,
2558 value: item.value || item.label
2563 _suggest: function( items ) {
2564 var ul = this.menu.element.empty();
2565 this._renderMenu( ul, items );
2566 this.isNewMenu = true;
2567 this.menu.refresh();
2569 // Size and position menu
2572 ul.position( $.extend( {
2574 }, this.options.position ) );
2576 if ( this.options.autoFocus ) {
2580 // Listen for interactions outside of the widget (#6642)
2581 this._on( this.document, {
2582 mousedown: "_closeOnClickOutside"
2586 _resizeMenu: function() {
2587 var ul = this.menu.element;
2588 ul.outerWidth( Math.max(
2590 // Firefox wraps long text (possibly a rounding bug)
2591 // so we add 1px to avoid the wrapping (#7513)
2592 ul.width( "" ).outerWidth() + 1,
2593 this.element.outerWidth()
2597 _renderMenu: function( ul, items ) {
2599 $.each( items, function( index, item ) {
2600 that._renderItemData( ul, item );
2604 _renderItemData: function( ul, item ) {
2605 return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
2608 _renderItem: function( ul, item ) {
2610 .append( $( "<div>" ).text( item.label ) )
2614 _move: function( direction, event ) {
2615 if ( !this.menu.element.is( ":visible" ) ) {
2616 this.search( null, event );
2619 if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
2620 this.menu.isLastItem() && /^next/.test( direction ) ) {
2622 if ( !this.isMultiLine ) {
2623 this._value( this.term );
2629 this.menu[ direction ]( event );
2632 widget: function() {
2633 return this.menu.element;
2636 _value: function() {
2637 return this.valueMethod.apply( this.element, arguments );
2640 _keyEvent: function( keyEvent, event ) {
2641 if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
2642 this._move( keyEvent, event );
2644 // Prevents moving cursor to beginning/end of the text field in some browsers
2645 event.preventDefault();
2649 // Support: Chrome <=50
2650 // We should be able to just use this.element.prop( "isContentEditable" )
2651 // but hidden elements always report false in Chrome.
2652 // https://code.google.com/p/chromium/issues/detail?id=313082
2653 _isContentEditable: function( element ) {
2654 if ( !element.length ) {
2658 var editable = element.prop( "contentEditable" );
2660 if ( editable === "inherit" ) {
2661 return this._isContentEditable( element.parent() );
2664 return editable === "true";
2668 $.extend( $.ui.autocomplete, {
2669 escapeRegex: function( value ) {
2670 return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
2672 filter: function( array, term ) {
2673 var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" );
2674 return $.grep( array, function( value ) {
2675 return matcher.test( value.label || value.value || value );
2680 // Live region extension, adding a `messages` option
2681 // NOTE: This is an experimental API. We are still investigating
2682 // a full solution for string manipulation and internationalization.
2683 $.widget( "ui.autocomplete", $.ui.autocomplete, {
2686 noResults: "No search results.",
2687 results: function( amount ) {
2688 return amount + ( amount > 1 ? " results are" : " result is" ) +
2689 " available, use up and down arrow keys to navigate.";
2694 __response: function( content ) {
2696 this._superApply( arguments );
2697 if ( this.options.disabled || this.cancelSearch ) {
2700 if ( content && content.length ) {
2701 message = this.options.messages.results( content.length );
2703 message = this.options.messages.noResults;
2705 clearTimeout( this.liveRegionTimer );
2706 this.liveRegionTimer = this._delay( function() {
2707 this.liveRegion.html( $( "<div>" ).text( message ) );
2712 var widgetsAutocomplete = $.ui.autocomplete;