PageSourceSearch

https://www.cimmyt.org/wp-includes/js/jquery/ui/autocomplete.js?ver=1.13.3

js cimmyt.org collected 2026-09-24 08:36:45 UTC 17,530 bytes, 678 lines download raw bytes

1/*!
2 * jQuery UI Autocomplete 1.13.3
3 * https://jqueryui.com
4 *
5 * Copyright OpenJS Foundation and other contributors
6 * Released under the MIT license.
7 * https://jquery.org/license
8 */
9
10//>>label: Autocomplete
11//>>group: Widgets
12//>>description: Lists suggested words as the user is typing.
13//>>docs: https://api.jqueryui.com/autocomplete/
14//>>demos: https://jqueryui.com/autocomplete/
15//>>css.structure: ../../themes/base/core.css
16//>>css.structure: ../../themes/base/autocomplete.css
17//>>css.theme: ../../themes/base/theme.css
18
19( function( factory ) {
20	"use strict";
21
22	if ( typeof define === "function" && define.amd ) {
23
24		// AMD. Register as an anonymous module.
25		define( [
26			"jquery",
27			"./menu",
28			"../keycode",
29			"../position",
30			"../safe-active-element",
31			"../version",
32			"../widget"
33		], factory );
34	} else {
35
36		// Browser globals
37		factory( jQuery );
38	}
39} )( function( $ ) {
40"use strict";
vendor: 16,589 bytes, lines 40-675
40
41
42$.widget( "ui.autocomplete", {
43	version: "1.13.3",
44	defaultElement: "<input>",
45	options: {
46		appendTo: null,
47		autoFocus: false,
48		delay: 300,
49		minLength: 1,
50		position: {
51			my: "left top",
52			at: "left bottom",
53			collision: "none"
54		},
55		source: null,
56
57		// Callbacks
58		change: null,
59		close: null,
60		focus: null,
61		open: null,
62		response: null,
63		search: null,
64		select: null
65	},
66
67	requestIndex: 0,
68	pending: 0,
69	liveRegionTimer: null,
70
71	_create: function() {
72
73		// Some browsers only repeat keydown events, not keypress events,
74		// so we use the suppressKeyPress flag to determine if we've already
75		// handled the keydown event. #7269
76		// Unfortunately the code for & in keypress is the same as the up arrow,
77		// so we use the suppressKeyPressRepeat flag to avoid handling keypress
78		// events when we know the keydown event was used to modify the
79		// search term. #7799
80		var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
81			nodeName = this.element[ 0 ].nodeName.toLowerCase(),
82			isTextarea = nodeName === "textarea",
83			isInput = nodeName === "input";
84
85		// Textareas are always multi-line
86		// Inputs are always single-line, even if inside a contentEditable element
87		// IE also treats inputs as contentEditable
88		// All other element types are determined by whether or not they're contentEditable
89		this.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element );
90
91		this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
92		this.isNewMenu = true;
93
94		this._addClass( "ui-autocomplete-input" );
95		this.element.attr( "autocomplete", "off" );
96
97		this._on( this.element, {
98			keydown: function( event ) {
99				if ( this.element.prop( "readOnly" ) ) {
100					suppressKeyPress = true;
101					suppressInput = true;
102					suppressKeyPressRepeat = true;
103					return;
104				}
105
106				suppressKeyPress = false;
107				suppressInput = false;
108				suppressKeyPressRepeat = false;
109				var keyCode = $.ui.keyCode;
110				switch ( event.keyCode ) {
111				case keyCode.PAGE_UP:
112					suppressKeyPress = true;
113					this._move( "previousPage", event );
114					break;
115				case keyCode.PAGE_DOWN:
116					suppressKeyPress = true;
117					this._move( "nextPage", event );
118					break;
119				case keyCode.UP:
120					suppressKeyPress = true;
121					this._keyEvent( "previous", event );
122					break;
123				case keyCode.DOWN:
124					suppressKeyPress = true;
125					this._keyEvent( "next", event );
126					break;
127				case keyCode.ENTER:
128
129					// when menu is open and has focus
130					if ( this.menu.active ) {
131
132						// #6055 - Opera still allows the keypress to occur
133						// which causes forms to submit
134						suppressKeyPress = true;
135						event.preventDefault();
136						this.menu.select( event );
137					}
138					break;
139				case keyCode.TAB:
140					if ( this.menu.active ) {
141						this.menu.select( event );
142					}
143					break;
144				case keyCode.ESCAPE:
145					if ( this.menu.element.is( ":visible" ) ) {
146						if ( !this.isMultiLine ) {
147							this._value( this.term );
148						}
149						this.close( event );
150
151						// Different browsers have different default behavior for escape
152						// Single press can mean undo or clear
153						// Double press in IE means clear the whole form
154						event.preventDefault();
155					}
156					break;
157				default:
158					suppressKeyPressRepeat = true;
159
160					// search timeout should be triggered before the input value is changed
161					this._searchTimeout( event );
162					break;
163				}
164			},
165			keypress: function( event ) {
166				if ( suppressKeyPress ) {
167					suppressKeyPress = false;
168					if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
169						event.preventDefault();
170					}
171					return;
172				}
173				if ( suppressKeyPressRepeat ) {
174					return;
175				}
176
177				// Replicate some key handlers to allow them to repeat in Firefox and Opera
178				var keyCode = $.ui.keyCode;
179				switch ( event.keyCode ) {
180				case keyCode.PAGE_UP:
181					this._move( "previousPage", event );
182					break;
183				case keyCode.PAGE_DOWN:
184					this._move( "nextPage", event );
185					break;
186				case keyCode.UP:
187					this._keyEvent( "previous", event );
188					break;
189				case keyCode.DOWN:
190					this._keyEvent( "next", event );
191					break;
192				}
193			},
194			input: function( event ) {
195				if ( suppressInput ) {
196					suppressInput = false;
197					event.preventDefault();
198					return;
199				}
200				this._searchTimeout( event );
201			},
202			focus: function() {
203				this.selectedItem = null;
204				this.previous = this._value();
205			},
206			blur: function( event ) {
207				clearTimeout( this.searching );
208				this.close( event );
209				this._change( event );
210			}
211		} );
212
213		this._initSource();
214		this.menu = $( "<ul>" )
215			.appendTo( this._appendTo() )
216			.menu( {
217
218				// disable ARIA support, the live region takes care of that
219				role: null
220			} )
221			.hide()
222
223			// Support: IE 11 only, Edge <= 14
224			// For other browsers, we preventDefault() on the mousedown event
225			// to keep the dropdown from taking focus from the input. This doesn't
226			// work for IE/Edge, causing problems with selection and scrolling (#9638)
227			// Happily, IE and Edge support an "unselectable" attribute that
228			// prevents an element from receiving focus, exactly what we want here.
229			.attr( {
230				"unselectable": "on"
231			} )
232			.menu( "instance" );
233
234		this._addClass( this.menu.element, "ui-autocomplete", "ui-front" );
235		this._on( this.menu.element, {
236			mousedown: function( event ) {
237
238				// Prevent moving focus out of the text field
239				event.preventDefault();
240			},
241			menufocus: function( event, ui ) {
242				var label, item;
243
244				// support: Firefox
245				// Prevent accidental activation of menu items in Firefox (#7024 #9118)
246				if ( this.isNewMenu ) {
247					this.isNewMenu = false;
248					if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
249						this.menu.blur();
250
251						this.document.one( "mousemove", function() {
252							$( event.target ).trigger( event.originalEvent );
253						} );
254
255						return;
256					}
257				}
258
259				item = ui.item.data( "ui-autocomplete-item" );
260				if ( false !== this._trigger( "focus", event, { item: item } ) ) {
261
262					// use value to match what will end up in the input, if it was a key event
263					if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
264						this._value( item.value );
265					}
266				}
267
268				// Announce the value in the liveRegion
269				label = ui.item.attr( "aria-label" ) || item.value;
270				if ( label && String.prototype.trim.call( label ).length ) {
271					clearTimeout( this.liveRegionTimer );
272					this.liveRegionTimer = this._delay( function() {
273						this.liveRegion.html( $( "<div>" ).text( label ) );
274					}, 100 );
275				}
276			},
277			menuselect: function( event, ui ) {
278				var item = ui.item.data( "ui-autocomplete-item" ),
279					previous = this.previous;
280
281				// Only trigger when focus was lost (click on menu)
282				if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {
283					this.element.trigger( "focus" );
284					this.previous = previous;
285
286					// #6109 - IE triggers two focus events and the second
287					// is asynchronous, so we need to reset the previous
288					// term synchronously and asynchronously :-(
289					this._delay( function() {
290						this.previous = previous;
291						this.selectedItem = item;
292					} );
293				}
294
295				if ( false !== this._trigger( "select", event, { item: item } ) ) {
296					this._value( item.value );
297				}
298
299				// reset the term after the select event
300				// this allows custom select handling to work properly
301				this.term = this._value();
302
303				this.close( event );
304				this.selectedItem = item;
305			}
306		} );
307
308		this.liveRegion = $( "<div>", {
309			role: "status",
310			"aria-live": "assertive",
311			"aria-relevant": "additions"
312		} )
313			.appendTo( this.document[ 0 ].body );
314
315		this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" );
316
317		// Turning off autocomplete prevents the browser from remembering the
318		// value when navigating through history, so we re-enable autocomplete
319		// if the page is unloaded before the widget is destroyed. #7790
320		this._on( this.window, {
321			beforeunload: function() {
322				this.element.removeAttr( "autocomplete" );
323			}
324		} );
325	},
326
327	_destroy: function() {
328		clearTimeout( this.searching );
329		this.element.removeAttr( "autocomplete" );
330		this.menu.element.remove();
331		this.liveRegion.remove();
332	},
333
334	_setOption: function( key, value ) {
335		this._super( key, value );
336		if ( key === "source" ) {
337			this._initSource();
338		}
339		if ( key === "appendTo" ) {
340			this.menu.element.appendTo( this._appendTo() );
341		}
342		if ( key === "disabled" && value && this.xhr ) {
343			this.xhr.abort();
344		}
345	},
346
347	_isEventTargetInWidget: function( event ) {
348		var menuElement = this.menu.element[ 0 ];
349
350		return event.target === this.element[ 0 ] ||
351			event.target === menuElement ||
352			$.contains( menuElement, event.target );
353	},
354
355	_closeOnClickOutside: function( event ) {
356		if ( !this._isEventTargetInWidget( event ) ) {
357			this.close();
358		}
359	},
360
361	_appendTo: function() {
362		var element = this.options.appendTo;
363
364		if ( element ) {
365			element = element.jquery || element.nodeType ?
366				$( element ) :
367				this.document.find( element ).eq( 0 );
368		}
369
370		if ( !element || !element[ 0 ] ) {
371			element = this.element.closest( ".ui-front, dialog" );
372		}
373
374		if ( !element.length ) {
375			element = this.document[ 0 ].body;
376		}
377
378		return element;
379	},
380
381	_initSource: function() {
382		var array, url,
383			that = this;
384		if ( Array.isArray( this.options.source ) ) {
385			array = this.options.source;
386			this.source = function( request, response ) {
387				response( $.ui.autocomplete.filter( array, request.term ) );
388			};
389		} else if ( typeof this.options.source === "string" ) {
390			url = this.options.source;
391			this.source = function( request, response ) {
392				if ( that.xhr ) {
393					that.xhr.abort();
394				}
395				that.xhr = $.ajax( {
396					url: url,
397					data: request,
398					dataType: "json",
399					success: function( data ) {
400						response( data );
401					},
402					error: function() {
403						response( [] );
404					}
405				} );
406			};
407		} else {
408			this.source = this.options.source;
409		}
410	},
411
412	_searchTimeout: function( event ) {
413		clearTimeout( this.searching );
414		this.searching = this._delay( function() {
415
416			// Search if the value has changed, or if the user retypes the same value (see #7434)
417			var equalValues = this.term === this._value(),
418				menuVisible = this.menu.element.is( ":visible" ),
419				modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
420
421			if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) {
422				this.selectedItem = null;
423				this.search( null, event );
424			}
425		}, this.options.delay );
426	},
427
428	search: function( value, event ) {
429		value = value != null ? value : this._value();
430
431		// Always save the actual value, not the one passed as an argument
432		this.term = this._value();
433
434		if ( value.length < this.options.minLength ) {
435			return this.close( event );
436		}
437
438		if ( this._trigger( "search", event ) === false ) {
439			return;
440		}
441
442		return this._search( value );
443	},
444
445	_search: function( value ) {
446		this.pending++;
447		this._addClass( "ui-autocomplete-loading" );
448		this.cancelSearch = false;
449
450		this.source( { term: value }, this._response() );
451	},
452
453	_response: function() {
454		var index = ++this.requestIndex;
455
456		return function( content ) {
457			if ( index === this.requestIndex ) {
458				this.__response( content );
459			}
460
461			this.pending--;
462			if ( !this.pending ) {
463				this._removeClass( "ui-autocomplete-loading" );
464			}
465		}.bind( this );
466	},
467
468	__response: function( content ) {
469		if ( content ) {
470			content = this._normalize( content );
471		}
472		this._trigger( "response", null, { content: content } );
473		if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
474			this._suggest( content );
475			this._trigger( "open" );
476		} else {
477
478			// use ._close() instead of .close() so we don't cancel future searches
479			this._close();
480		}
481	},
482
483	close: function( event ) {
484		this.cancelSearch = true;
485		this._close( event );
486	},
487
488	_close: function( event ) {
489
490		// Remove the handler that closes the menu on outside clicks
491		this._off( this.document, "mousedown" );
492
493		if ( this.menu.element.is( ":visible" ) ) {
494			this.menu.element.hide();
495			this.menu.blur();
496			this.isNewMenu = true;
497			this._trigger( "close", event );
498		}
499	},
500
501	_change: function( event ) {
502		if ( this.previous !== this._value() ) {
503			this._trigger( "change", event, { item: this.selectedItem } );
504		}
505	},
506
507	_normalize: function( items ) {
508
509		// assume all items have the right format when the first item is complete
510		if ( items.length && items[ 0 ].label && items[ 0 ].value ) {
511			return items;
512		}
513		return $.map( items, function( item ) {
514			if ( typeof item === "string" ) {
515				return {
516					label: item,
517					value: item
518				};
519			}
520			return $.extend( {}, item, {
521				label: item.label || item.value,
522				value: item.value || item.label
523			} );
524		} );
525	},
526
527	_suggest: function( items ) {
528		var ul = this.menu.element.empty();
529		this._renderMenu( ul, items );
530		this.isNewMenu = true;
531		this.menu.refresh();
532
533		// Size and position menu
534		ul.show();
535		this._resizeMenu();
536		ul.position( $.extend( {
537			of: this.element
538		}, this.options.position ) );
539
540		if ( this.options.autoFocus ) {
541			this.menu.next();
542		}
543
544		// Listen for interactions outside of the widget (#6642)
545		this._on( this.document, {
546			mousedown: "_closeOnClickOutside"
547		} );
548	},
549
550	_resizeMenu: function() {
551		var ul = this.menu.element;
552		ul.outerWidth( Math.max(
553
554			// Firefox wraps long text (possibly a rounding bug)
555			// so we add 1px to avoid the wrapping (#7513)
556			ul.width( "" ).outerWidth() + 1,
557			this.element.outerWidth()
558		) );
559	},
560
561	_renderMenu: function( ul, items ) {
562		var that = this;
563		$.each( items, function( index, item ) {
564			that._renderItemData( ul, item );
565		} );
566	},
567
568	_renderItemData: function( ul, item ) {
569		return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
570	},
571
572	_renderItem: function( ul, item ) {
573		return $( "<li>" )
574			.append( $( "<div>" ).text( item.label ) )
575			.appendTo( ul );
576	},
577
578	_move: function( direction, event ) {
579		if ( !this.menu.element.is( ":visible" ) ) {
580			this.search( null, event );
581			return;
582		}
583		if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
584				this.menu.isLastItem() && /^next/.test( direction ) ) {
585
586			if ( !this.isMultiLine ) {
587				this._value( this.term );
588			}
589
590			this.menu.blur();
591			return;
592		}
593		this.menu[ direction ]( event );
594	},
595
596	widget: function() {
597		return this.menu.element;
598	},
599
600	_value: function() {
601		return this.valueMethod.apply( this.element, arguments );
602	},
603
604	_keyEvent: function( keyEvent, event ) {
605		if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
606			this._move( keyEvent, event );
607
608			// Prevents moving cursor to beginning/end of the text field in some browsers
609			event.preventDefault();
610		}
611	},
612
613	// Support: Chrome <=50
614	// We should be able to just use this.element.prop( "isContentEditable" )
615	// but hidden elements always report false in Chrome.
616	// https://code.google.com/p/chromium/issues/detail?id=313082
617	_isContentEditable: function( element ) {
618		if ( !element.length ) {
619			return false;
620		}
621
622		var editable = element.prop( "contentEditable" );
623
624		if ( editable === "inherit" ) {
625			return this._isContentEditable( element.parent() );
626		}
627
628		return editable === "true";
629	}
630} );
631
632$.extend( $.ui.autocomplete, {
633	escapeRegex: function( value ) {
634		return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
635	},
636	filter: function( array, term ) {
637		var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" );
638		return $.grep( array, function( value ) {
639			return matcher.test( value.label || value.value || value );
640		} );
641	}
642} );
643
644// Live region extension, adding a `messages` option
645// NOTE: This is an experimental API. We are still investigating
646// a full solution for string manipulation and internationalization.
647$.widget( "ui.autocomplete", $.ui.autocomplete, {
648	options: {
649		messages: {
650			noResults: "No search results.",
651			results: function( amount ) {
652				return amount + ( amount > 1 ? " results are" : " result is" ) +
653					" available, use up and down arrow keys to navigate.";
654			}
655		}
656	},
657
658	__response: function( content ) {
659		var message;
660		this._superApply( arguments );
661		if ( this.options.disabled || this.cancelSearch ) {
662			return;
663		}
664		if ( content && content.length ) {
665			message = this.options.messages.results( content.length );
666		} else {
667			message = this.options.messages.noResults;
668		}
669		clearTimeout( this.liveRegionTimer );
670		this.liveRegionTimer = this._delay( function() {
671			this.liveRegion.html( $( "<div>" ).text( message ) );
672		}, 100 );
673	}
674} );
675
676return $.ui.autocomplete;
677
678} );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.