From fc7059fd5f9ef058338a3291817ae03ea471da03 Mon Sep 17 00:00:00 2001 From: Dmytro Poperechnyy Date: Thu, 27 Jun 2019 17:25:40 -0500 Subject: [PATCH 01/63] MC-17868: Break jQuery UI into widgets and make a prototype --- .../Theme/view/frontend/requirejs-config.js | 3 +- lib/web/jquery/composite.js | 48 + lib/web/jquery/ui-modules/accordion.js | 570 +++++ lib/web/jquery/ui-modules/autocomplete.js | 590 +++++ lib/web/jquery/ui-modules/button.js | 381 +++ lib/web/jquery/ui-modules/core.js | 822 +++++++ lib/web/jquery/ui-modules/datepicker.js | 2025 ++++++++++++++++ lib/web/jquery/ui-modules/dialog.js | 804 ++++++ lib/web/jquery/ui-modules/draggable.js | 943 +++++++ lib/web/jquery/ui-modules/droppable.js | 373 +++ lib/web/jquery/ui-modules/effect-blind.js | 69 + lib/web/jquery/ui-modules/effect-bounce.js | 100 + lib/web/jquery/ui-modules/effect-clip.js | 54 + lib/web/jquery/ui-modules/effect-drop.js | 52 + lib/web/jquery/ui-modules/effect-explode.js | 84 + lib/web/jquery/ui-modules/effect-fade.js | 17 + lib/web/jquery/ui-modules/effect-fold.js | 63 + lib/web/jquery/ui-modules/effect-highlight.js | 37 + lib/web/jquery/ui-modules/effect-puff.js | 305 +++ lib/web/jquery/ui-modules/effect-pulsate.js | 50 + lib/web/jquery/ui-modules/effect-shake.js | 61 + lib/web/jquery/ui-modules/effect-slide.js | 51 + lib/web/jquery/ui-modules/effect-transfer.js | 34 + lib/web/jquery/ui-modules/effect.js | 1279 ++++++++++ lib/web/jquery/ui-modules/menu.js | 612 +++++ lib/web/jquery/ui-modules/mouse.js | 156 ++ lib/web/jquery/ui-modules/position.js | 491 ++++ lib/web/jquery/ui-modules/progressbar.js | 131 + lib/web/jquery/ui-modules/resizable.js | 963 ++++++++ lib/web/jquery/ui-modules/selectable.js | 262 ++ lib/web/jquery/ui-modules/slider.js | 661 +++++ lib/web/jquery/ui-modules/sortable.js | 1274 ++++++++++ lib/web/jquery/ui-modules/spinner.js | 482 ++++ lib/web/jquery/ui-modules/tabs.js | 835 +++++++ lib/web/jquery/ui-modules/timepicker.js | 2158 +++++++++++++++++ lib/web/jquery/ui-modules/tooltip.js | 387 +++ 36 files changed, 17226 insertions(+), 1 deletion(-) create mode 100644 lib/web/jquery/composite.js create mode 100644 lib/web/jquery/ui-modules/accordion.js create mode 100644 lib/web/jquery/ui-modules/autocomplete.js create mode 100644 lib/web/jquery/ui-modules/button.js create mode 100644 lib/web/jquery/ui-modules/core.js create mode 100644 lib/web/jquery/ui-modules/datepicker.js create mode 100644 lib/web/jquery/ui-modules/dialog.js create mode 100644 lib/web/jquery/ui-modules/draggable.js create mode 100644 lib/web/jquery/ui-modules/droppable.js create mode 100644 lib/web/jquery/ui-modules/effect-blind.js create mode 100644 lib/web/jquery/ui-modules/effect-bounce.js create mode 100644 lib/web/jquery/ui-modules/effect-clip.js create mode 100644 lib/web/jquery/ui-modules/effect-drop.js create mode 100644 lib/web/jquery/ui-modules/effect-explode.js create mode 100644 lib/web/jquery/ui-modules/effect-fade.js create mode 100644 lib/web/jquery/ui-modules/effect-fold.js create mode 100644 lib/web/jquery/ui-modules/effect-highlight.js create mode 100644 lib/web/jquery/ui-modules/effect-puff.js create mode 100644 lib/web/jquery/ui-modules/effect-pulsate.js create mode 100644 lib/web/jquery/ui-modules/effect-shake.js create mode 100644 lib/web/jquery/ui-modules/effect-slide.js create mode 100644 lib/web/jquery/ui-modules/effect-transfer.js create mode 100644 lib/web/jquery/ui-modules/effect.js create mode 100644 lib/web/jquery/ui-modules/menu.js create mode 100644 lib/web/jquery/ui-modules/mouse.js create mode 100644 lib/web/jquery/ui-modules/position.js create mode 100644 lib/web/jquery/ui-modules/progressbar.js create mode 100644 lib/web/jquery/ui-modules/resizable.js create mode 100644 lib/web/jquery/ui-modules/selectable.js create mode 100644 lib/web/jquery/ui-modules/slider.js create mode 100644 lib/web/jquery/ui-modules/sortable.js create mode 100644 lib/web/jquery/ui-modules/spinner.js create mode 100644 lib/web/jquery/ui-modules/tabs.js create mode 100644 lib/web/jquery/ui-modules/timepicker.js create mode 100644 lib/web/jquery/ui-modules/tooltip.js diff --git a/app/code/Magento/Theme/view/frontend/requirejs-config.js b/app/code/Magento/Theme/view/frontend/requirejs-config.js index aacec30edf18..0e63158ae191 100644 --- a/app/code/Magento/Theme/view/frontend/requirejs-config.js +++ b/app/code/Magento/Theme/view/frontend/requirejs-config.js @@ -33,7 +33,8 @@ var config = { } }, paths: { - 'jquery/ui': 'jquery/jquery-ui' + 'jquery/ui': 'jquery/jquery-ui', + 'jquery-ui-modules': 'jquery/ui-modules' }, deps: [ 'jquery/jquery.mobile.custom', diff --git a/lib/web/jquery/composite.js b/lib/web/jquery/composite.js new file mode 100644 index 000000000000..dba06a485ae4 --- /dev/null +++ b/lib/web/jquery/composite.js @@ -0,0 +1,48 @@ +// Import every plugin under the sun. Bad for performance, +// but prevents the store from breaking in situations +// where a dependency was missed during the migration from +// a monolith build of jQueryUI to a modular one + +define([ + 'jquery-ui-modules/core', + 'jquery-ui-modules/accordion', + 'jquery-ui-modules/autocomplete', + 'jquery-ui-modules/button', + 'jquery-ui-modules/datepicker', + 'jquery-ui-modules/dialog', + 'jquery-ui-modules/draggable', + 'jquery-ui-modules/droppable', + 'jquery-ui-modules/effect-blind', + 'jquery-ui-modules/effect-bounce', + 'jquery-ui-modules/effect-clip', + 'jquery-ui-modules/effect-drop', + 'jquery-ui-modules/effect-explode', + 'jquery-ui-modules/effect-fade', + 'jquery-ui-modules/effect-fold', + 'jquery-ui-modules/effect-highlight', + 'jquery-ui-modules/effect-puff', + 'jquery-ui-modules/effect-pulsate', + 'jquery-ui-modules/effect-shake', + 'jquery-ui-modules/effect-slide', + 'jquery-ui-modules/effect-transfer', + 'jquery-ui-modules/effect', + 'jquery-ui-modules/menu', + 'jquery-ui-modules/mouse', + 'jquery-ui-modules/position', + 'jquery-ui-modules/progressbar', + 'jquery-ui-modules/resizable', + 'jquery-ui-modules/selectable', + 'jquery-ui-modules/slider', + 'jquery-ui-modules/sortable', + 'jquery-ui-modules/spinner', + 'jquery-ui-modules/tabs', + 'jquery-ui-modules/timepicker', + 'jquery-ui-modules/tooltip' +], function() { + console.warn( + 'Fallback to JQueryUI Compat activated. ' + + 'Your store is missing a dependency for a ' + + 'jQueryUI widget. Identifying and addressing the dependency ' + + 'will drastically improve the performance of your site' + ) +}); \ No newline at end of file diff --git a/lib/web/jquery/ui-modules/accordion.js b/lib/web/jquery/ui-modules/accordion.js new file mode 100644 index 000000000000..c9d6b4b46aa5 --- /dev/null +++ b/lib/web/jquery/ui-modules/accordion.js @@ -0,0 +1,570 @@ +/*! + * jQuery UI Accordion + * http://jqueryui.com + * + * Copyright 2014 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/accordion/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + + var uid = 0, + hideProps = {}, + showProps = {}; + + hideProps.height = hideProps.paddingTop = hideProps.paddingBottom = + hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide"; + showProps.height = showProps.paddingTop = showProps.paddingBottom = + showProps.borderTopWidth = showProps.borderBottomWidth = "show"; + + $.widget( "ui.accordion", { + version: "1.10.4", + options: { + active: 0, + animate: {}, + collapsible: false, + event: "click", + header: "> li > :first-child,> :not(li):even", + heightStyle: "auto", + icons: { + activeHeader: "ui-icon-triangle-1-s", + header: "ui-icon-triangle-1-e" + }, + + // callbacks + activate: null, + beforeActivate: null + }, + + _create: function() { + var options = this.options; + this.prevShow = this.prevHide = $(); + this.element.addClass( "ui-accordion ui-widget ui-helper-reset" ) + // ARIA + .attr( "role", "tablist" ); + + // don't allow collapsible: false and active: false / null + if ( !options.collapsible && (options.active === false || options.active == null) ) { + options.active = 0; + } + + this._processPanels(); + // handle negative values + if ( options.active < 0 ) { + options.active += this.headers.length; + } + this._refresh(); + }, + + _getCreateEventData: function() { + return { + header: this.active, + panel: !this.active.length ? $() : this.active.next(), + content: !this.active.length ? $() : this.active.next() + }; + }, + + _createIcons: function() { + var icons = this.options.icons; + if ( icons ) { + $( "" ) + .addClass( "ui-accordion-header-icon ui-icon " + icons.header ) + .prependTo( this.headers ); + this.active.children( ".ui-accordion-header-icon" ) + .removeClass( icons.header ) + .addClass( icons.activeHeader ); + this.headers.addClass( "ui-accordion-icons" ); + } + }, + + _destroyIcons: function() { + this.headers + .removeClass( "ui-accordion-icons" ) + .children( ".ui-accordion-header-icon" ) + .remove(); + }, + + _destroy: function() { + var contents; + + // clean up main element + this.element + .removeClass( "ui-accordion ui-widget ui-helper-reset" ) + .removeAttr( "role" ); + + // clean up headers + this.headers + .removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" ) + .removeAttr( "role" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "aria-selected" ) + .removeAttr( "aria-controls" ) + .removeAttr( "tabIndex" ) + .each(function() { + if ( /^ui-accordion/.test( this.id ) ) { + this.removeAttribute( "id" ); + } + }); + this._destroyIcons(); + + // clean up content panels + contents = this.headers.next() + .css( "display", "" ) + .removeAttr( "role" ) + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-labelledby" ) + .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" ) + .each(function() { + if ( /^ui-accordion/.test( this.id ) ) { + this.removeAttribute( "id" ); + } + }); + if ( this.options.heightStyle !== "content" ) { + contents.css( "height", "" ); + } + }, + + _setOption: function( key, value ) { + if ( key === "active" ) { + // _activate() will handle invalid values and update this.options + this._activate( value ); + return; + } + + if ( key === "event" ) { + if ( this.options.event ) { + this._off( this.headers, this.options.event ); + } + this._setupEvents( value ); + } + + this._super( key, value ); + + // setting collapsible: false while collapsed; open first panel + if ( key === "collapsible" && !value && this.options.active === false ) { + this._activate( 0 ); + } + + if ( key === "icons" ) { + this._destroyIcons(); + if ( value ) { + this._createIcons(); + } + } + + // #5332 - opacity doesn't cascade to positioned elements in IE + // so we need to add the disabled class to the headers and panels + if ( key === "disabled" ) { + this.headers.add( this.headers.next() ) + .toggleClass( "ui-state-disabled", !!value ); + } + }, + + _keydown: function( event ) { + if ( event.altKey || event.ctrlKey ) { + return; + } + + var keyCode = $.ui.keyCode, + length = this.headers.length, + currentIndex = this.headers.index( event.target ), + toFocus = false; + + switch ( event.keyCode ) { + case keyCode.RIGHT: + case keyCode.DOWN: + toFocus = this.headers[ ( currentIndex + 1 ) % length ]; + break; + case keyCode.LEFT: + case keyCode.UP: + toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; + break; + case keyCode.SPACE: + case keyCode.ENTER: + this._eventHandler( event ); + break; + case keyCode.HOME: + toFocus = this.headers[ 0 ]; + break; + case keyCode.END: + toFocus = this.headers[ length - 1 ]; + break; + } + + if ( toFocus ) { + $( event.target ).attr( "tabIndex", -1 ); + $( toFocus ).attr( "tabIndex", 0 ); + toFocus.focus(); + event.preventDefault(); + } + }, + + _panelKeyDown : function( event ) { + if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { + $( event.currentTarget ).prev().focus(); + } + }, + + refresh: function() { + var options = this.options; + this._processPanels(); + + // was collapsed or no panel + if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) { + options.active = false; + this.active = $(); + // active false only when collapsible is true + } else if ( options.active === false ) { + this._activate( 0 ); + // was active, but active panel is gone + } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { + // all remaining panel are disabled + if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) { + options.active = false; + this.active = $(); + // activate previous panel + } else { + this._activate( Math.max( 0, options.active - 1 ) ); + } + // was active, active panel still exists + } else { + // make sure active index is correct + options.active = this.headers.index( this.active ); + } + + this._destroyIcons(); + + this._refresh(); + }, + + _processPanels: function() { + this.headers = this.element.find( this.options.header ) + .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" ); + + this.headers.next() + .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" ) + .filter(":not(.ui-accordion-content-active)") + .hide(); + }, + + _refresh: function() { + var maxHeight, + options = this.options, + heightStyle = options.heightStyle, + parent = this.element.parent(), + accordionId = this.accordionId = "ui-accordion-" + + (this.element.attr( "id" ) || ++uid); + + this.active = this._findActive( options.active ) + .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ) + .removeClass( "ui-corner-all" ); + this.active.next() + .addClass( "ui-accordion-content-active" ) + .show(); + + this.headers + .attr( "role", "tab" ) + .each(function( i ) { + var header = $( this ), + headerId = header.attr( "id" ), + panel = header.next(), + panelId = panel.attr( "id" ); + if ( !headerId ) { + headerId = accordionId + "-header-" + i; + header.attr( "id", headerId ); + } + if ( !panelId ) { + panelId = accordionId + "-panel-" + i; + panel.attr( "id", panelId ); + } + header.attr( "aria-controls", panelId ); + panel.attr( "aria-labelledby", headerId ); + }) + .next() + .attr( "role", "tabpanel" ); + + this.headers + .not( this.active ) + .attr({ + "aria-selected": "false", + "aria-expanded": "false", + tabIndex: -1 + }) + .next() + .attr({ + "aria-hidden": "true" + }) + .hide(); + + // make sure at least one header is in the tab order + if ( !this.active.length ) { + this.headers.eq( 0 ).attr( "tabIndex", 0 ); + } else { + this.active.attr({ + "aria-selected": "true", + "aria-expanded": "true", + tabIndex: 0 + }) + .next() + .attr({ + "aria-hidden": "false" + }); + } + + this._createIcons(); + + this._setupEvents( options.event ); + + if ( heightStyle === "fill" ) { + maxHeight = parent.height(); + this.element.siblings( ":visible" ).each(function() { + var elem = $( this ), + position = elem.css( "position" ); + + if ( position === "absolute" || position === "fixed" ) { + return; + } + maxHeight -= elem.outerHeight( true ); + }); + + this.headers.each(function() { + maxHeight -= $( this ).outerHeight( true ); + }); + + this.headers.next() + .each(function() { + $( this ).height( Math.max( 0, maxHeight - + $( this ).innerHeight() + $( this ).height() ) ); + }) + .css( "overflow", "auto" ); + } else if ( heightStyle === "auto" ) { + maxHeight = 0; + this.headers.next() + .each(function() { + maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); + }) + .height( maxHeight ); + } + }, + + _activate: function( index ) { + var active = this._findActive( index )[ 0 ]; + + // trying to activate the already active panel + if ( active === this.active[ 0 ] ) { + return; + } + + // trying to collapse, simulate a click on the currently active header + active = active || this.active[ 0 ]; + + this._eventHandler({ + target: active, + currentTarget: active, + preventDefault: $.noop + }); + }, + + _findActive: function( selector ) { + return typeof selector === "number" ? this.headers.eq( selector ) : $(); + }, + + _setupEvents: function( event ) { + var events = { + keydown: "_keydown" + }; + if ( event ) { + $.each( event.split(" "), function( index, eventName ) { + events[ eventName ] = "_eventHandler"; + }); + } + + this._off( this.headers.add( this.headers.next() ) ); + this._on( this.headers, events ); + this._on( this.headers.next(), { keydown: "_panelKeyDown" }); + this._hoverable( this.headers ); + this._focusable( this.headers ); + }, + + _eventHandler: function( event ) { + var options = this.options, + active = this.active, + clicked = $( event.currentTarget ), + clickedIsActive = clicked[ 0 ] === active[ 0 ], + collapsing = clickedIsActive && options.collapsible, + toShow = collapsing ? $() : clicked.next(), + toHide = active.next(), + eventData = { + oldHeader: active, + oldPanel: toHide, + newHeader: collapsing ? $() : clicked, + newPanel: toShow + }; + + event.preventDefault(); + + if ( + // click on active header, but not collapsible + ( clickedIsActive && !options.collapsible ) || + // allow canceling activation + ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { + return; + } + + options.active = collapsing ? false : this.headers.index( clicked ); + + // when the call to ._toggle() comes after the class changes + // it causes a very odd bug in IE 8 (see #6720) + this.active = clickedIsActive ? $() : clicked; + this._toggle( eventData ); + + // switch classes + // corner classes on the previously active header stay after the animation + active.removeClass( "ui-accordion-header-active ui-state-active" ); + if ( options.icons ) { + active.children( ".ui-accordion-header-icon" ) + .removeClass( options.icons.activeHeader ) + .addClass( options.icons.header ); + } + + if ( !clickedIsActive ) { + clicked + .removeClass( "ui-corner-all" ) + .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ); + if ( options.icons ) { + clicked.children( ".ui-accordion-header-icon" ) + .removeClass( options.icons.header ) + .addClass( options.icons.activeHeader ); + } + + clicked + .next() + .addClass( "ui-accordion-content-active" ); + } + }, + + _toggle: function( data ) { + var toShow = data.newPanel, + toHide = this.prevShow.length ? this.prevShow : data.oldPanel; + + // handle activating a panel during the animation for another activation + this.prevShow.add( this.prevHide ).stop( true, true ); + this.prevShow = toShow; + this.prevHide = toHide; + + if ( this.options.animate ) { + this._animate( toShow, toHide, data ); + } else { + toHide.hide(); + toShow.show(); + this._toggleComplete( data ); + } + + toHide.attr({ + "aria-hidden": "true" + }); + toHide.prev().attr( "aria-selected", "false" ); + // if we're switching panels, remove the old header from the tab order + // if we're opening from collapsed state, remove the previous header from the tab order + // if we're collapsing, then keep the collapsing header in the tab order + if ( toShow.length && toHide.length ) { + toHide.prev().attr({ + "tabIndex": -1, + "aria-expanded": "false" + }); + } else if ( toShow.length ) { + this.headers.filter(function() { + return $( this ).attr( "tabIndex" ) === 0; + }) + .attr( "tabIndex", -1 ); + } + + toShow + .attr( "aria-hidden", "false" ) + .prev() + .attr({ + "aria-selected": "true", + tabIndex: 0, + "aria-expanded": "true" + }); + }, + + _animate: function( toShow, toHide, data ) { + var total, easing, duration, + that = this, + adjust = 0, + down = toShow.length && + ( !toHide.length || ( toShow.index() < toHide.index() ) ), + animate = this.options.animate || {}, + options = down && animate.down || animate, + complete = function() { + that._toggleComplete( data ); + }; + + if ( typeof options === "number" ) { + duration = options; + } + if ( typeof options === "string" ) { + easing = options; + } + // fall back from options to animation in case of partial down settings + easing = easing || options.easing || animate.easing; + duration = duration || options.duration || animate.duration; + + if ( !toHide.length ) { + return toShow.animate( showProps, duration, easing, complete ); + } + if ( !toShow.length ) { + return toHide.animate( hideProps, duration, easing, complete ); + } + + total = toShow.show().outerHeight(); + toHide.animate( hideProps, { + duration: duration, + easing: easing, + step: function( now, fx ) { + fx.now = Math.round( now ); + } + }); + toShow + .hide() + .animate( showProps, { + duration: duration, + easing: easing, + complete: complete, + step: function( now, fx ) { + fx.now = Math.round( now ); + if ( fx.prop !== "height" ) { + adjust += fx.now; + } else if ( that.options.heightStyle !== "content" ) { + fx.now = Math.round( total - toHide.outerHeight() - adjust ); + adjust = 0; + } + } + }); + }, + + _toggleComplete: function( data ) { + var toHide = data.oldPanel; + + toHide + .removeClass( "ui-accordion-content-active" ) + .prev() + .removeClass( "ui-corner-top" ) + .addClass( "ui-corner-all" ); + + // Work around for rendering bug in IE (#5421) + if ( toHide.length ) { + toHide.parent()[0].className = toHide.parent()[0].className; + } + this._trigger( "activate", null, data ); + } + }); + +})( jQuery ); diff --git a/lib/web/jquery/ui-modules/autocomplete.js b/lib/web/jquery/ui-modules/autocomplete.js new file mode 100644 index 000000000000..af2e43503498 --- /dev/null +++ b/lib/web/jquery/ui-modules/autocomplete.js @@ -0,0 +1,590 @@ +(function( $, undefined ) { + + $.widget( "ui.autocomplete", { + version: "1.10.4", + defaultElement: "", + options: { + appendTo: null, + autoFocus: false, + delay: 300, + minLength: 1, + position: { + my: "left top", + at: "left bottom", + collision: "none" + }, + source: null, + + // callbacks + change: null, + close: null, + focus: null, + open: null, + response: null, + search: null, + select: null + }, + + requestIndex: 0, + pending: 0, + + _create: function() { + // Some browsers only repeat keydown events, not keypress events, + // so we use the suppressKeyPress flag to determine if we've already + // handled the keydown event. #7269 + // Unfortunately the code for & in keypress is the same as the up arrow, + // so we use the suppressKeyPressRepeat flag to avoid handling keypress + // events when we know the keydown event was used to modify the + // search term. #7799 + var suppressKeyPress, suppressKeyPressRepeat, suppressInput, + nodeName = this.element[0].nodeName.toLowerCase(), + isTextarea = nodeName === "textarea", + isInput = nodeName === "input"; + + this.isMultiLine = + // Textareas are always multi-line + isTextarea ? true : + // Inputs are always single-line, even if inside a contentEditable element + // IE also treats inputs as contentEditable + isInput ? false : + // All other element types are determined by whether or not they're contentEditable + this.element.prop( "isContentEditable" ); + + this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; + this.isNewMenu = true; + + this.element + .addClass( "ui-autocomplete-input" ) + .attr( "autocomplete", "off" ); + + this._on( this.element, { + keydown: function( event ) { + if ( this.element.prop( "readOnly" ) ) { + suppressKeyPress = true; + suppressInput = true; + suppressKeyPressRepeat = true; + return; + } + + suppressKeyPress = false; + suppressInput = false; + suppressKeyPressRepeat = false; + var keyCode = $.ui.keyCode; + switch( event.keyCode ) { + case keyCode.PAGE_UP: + suppressKeyPress = true; + this._move( "previousPage", event ); + break; + case keyCode.PAGE_DOWN: + suppressKeyPress = true; + this._move( "nextPage", event ); + break; + case keyCode.UP: + suppressKeyPress = true; + this._keyEvent( "previous", event ); + break; + case keyCode.DOWN: + suppressKeyPress = true; + this._keyEvent( "next", event ); + break; + case keyCode.ENTER: + case keyCode.NUMPAD_ENTER: + // when menu is open and has focus + if ( this.menu.active ) { + // #6055 - Opera still allows the keypress to occur + // which causes forms to submit + suppressKeyPress = true; + event.preventDefault(); + this.menu.select( event ); + } + break; + case keyCode.TAB: + if ( this.menu.active ) { + this.menu.select( event ); + } + break; + case keyCode.ESCAPE: + if ( this.menu.element.is( ":visible" ) ) { + this._value( this.term ); + this.close( event ); + // Different browsers have different default behavior for escape + // Single press can mean undo or clear + // Double press in IE means clear the whole form + event.preventDefault(); + } + break; + default: + suppressKeyPressRepeat = true; + // search timeout should be triggered before the input value is changed + this._searchTimeout( event ); + break; + } + }, + keypress: function( event ) { + if ( suppressKeyPress ) { + suppressKeyPress = false; + if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { + event.preventDefault(); + } + return; + } + if ( suppressKeyPressRepeat ) { + return; + } + + // replicate some key handlers to allow them to repeat in Firefox and Opera + var keyCode = $.ui.keyCode; + switch( event.keyCode ) { + case keyCode.PAGE_UP: + this._move( "previousPage", event ); + break; + case keyCode.PAGE_DOWN: + this._move( "nextPage", event ); + break; + case keyCode.UP: + this._keyEvent( "previous", event ); + break; + case keyCode.DOWN: + this._keyEvent( "next", event ); + break; + } + }, + input: function( event ) { + if ( suppressInput ) { + suppressInput = false; + event.preventDefault(); + return; + } + this._searchTimeout( event ); + }, + focus: function() { + this.selectedItem = null; + this.previous = this._value(); + }, + blur: function( event ) { + if ( this.cancelBlur ) { + delete this.cancelBlur; + return; + } + + clearTimeout( this.searching ); + this.close( event ); + this._change( event ); + } + }); + + this._initSource(); + this.menu = $( "