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