diff options
Diffstat (limited to 'ui')
-rw-r--r-- | ui/i18n/jquery.ui.datepicker-hu.js | 12 | ||||
-rw-r--r-- | ui/i18n/jquery.ui.datepicker-kk.js (renamed from ui/i18n/jquery.ui.datepicker-kz.js) | 4 | ||||
-rw-r--r-- | ui/jquery.effects.core.js | 40 | ||||
-rw-r--r-- | ui/jquery.effects.scale.js | 21 | ||||
-rw-r--r-- | ui/jquery.ui.accordion.js | 26 | ||||
-rw-r--r-- | ui/jquery.ui.autocomplete.js | 58 | ||||
-rw-r--r-- | ui/jquery.ui.button.js | 10 | ||||
-rw-r--r-- | ui/jquery.ui.datepicker.js | 29 | ||||
-rw-r--r-- | ui/jquery.ui.dialog.js | 20 | ||||
-rw-r--r-- | ui/jquery.ui.menu.js | 379 | ||||
-rw-r--r-- | ui/jquery.ui.menubar.js | 30 | ||||
-rw-r--r-- | ui/jquery.ui.popup.js | 172 | ||||
-rw-r--r-- | ui/jquery.ui.position.js | 215 | ||||
-rw-r--r-- | ui/jquery.ui.selectable.js | 1 | ||||
-rw-r--r-- | ui/jquery.ui.sortable.js | 19 | ||||
-rw-r--r-- | ui/jquery.ui.spinner.js | 306 | ||||
-rw-r--r-- | ui/jquery.ui.tabs.js | 87 | ||||
-rw-r--r-- | ui/jquery.ui.tooltip.js | 6 | ||||
-rw-r--r-- | ui/jquery.ui.widget.js | 30 |
19 files changed, 882 insertions, 583 deletions
diff --git a/ui/i18n/jquery.ui.datepicker-hu.js b/ui/i18n/jquery.ui.datepicker-hu.js index 2eea8b240..b28c268c1 100644 --- a/ui/i18n/jquery.ui.datepicker-hu.js +++ b/ui/i18n/jquery.ui.datepicker-hu.js @@ -2,19 +2,19 @@ /* Written by Istvan Karaszi (jquery@spam.raszi.hu). */ jQuery(function($){ $.datepicker.regional['hu'] = { - closeText: 'bezárás', - prevText: '« vissza', - nextText: 'előre »', + closeText: 'bezár', + prevText: 'vissza', + nextText: 'előre', currentText: 'ma', monthNames: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'], monthNamesShort: ['Jan', 'Feb', 'Már', 'Ápr', 'Máj', 'Jún', 'Júl', 'Aug', 'Szep', 'Okt', 'Nov', 'Dec'], - dayNames: ['Vasárnap', 'Hétfö', 'Kedd', 'Szerda', 'Csütörtök', 'Péntek', 'Szombat'], + dayNames: ['Vasárnap', 'Hétfő', 'Kedd', 'Szerda', 'Csütörtök', 'Péntek', 'Szombat'], dayNamesShort: ['Vas', 'Hét', 'Ked', 'Sze', 'Csü', 'Pén', 'Szo'], dayNamesMin: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'], - weekHeader: 'Hé', - dateFormat: 'yy-mm-dd', + weekHeader: 'Hét', + dateFormat: 'yy.mm.dd.', firstDay: 1, isRTL: false, showMonthAfterYear: true, diff --git a/ui/i18n/jquery.ui.datepicker-kz.js b/ui/i18n/jquery.ui.datepicker-kk.js index 658c21275..dcd6a65df 100644 --- a/ui/i18n/jquery.ui.datepicker-kz.js +++ b/ui/i18n/jquery.ui.datepicker-kk.js @@ -1,7 +1,7 @@ /* Kazakh (UTF-8) initialisation for the jQuery UI date picker plugin. */ /* Written by Dmitriy Karasyov (dmitriy.karasyov@gmail.com). */ jQuery(function($){ - $.datepicker.regional['kz'] = { + $.datepicker.regional['kk'] = { closeText: 'Жабу', prevText: '<Алдыңғы', nextText: 'Келесі>', @@ -19,5 +19,5 @@ jQuery(function($){ isRTL: false, showMonthAfterYear: false, yearSuffix: ''}; - $.datepicker.setDefaults($.datepicker.regional['kz']); + $.datepicker.setDefaults($.datepicker.regional['kk']); }); diff --git a/ui/jquery.effects.core.js b/ui/jquery.effects.core.js index daef9cb30..233b4f96d 100644 --- a/ui/jquery.effects.core.js +++ b/ui/jquery.effects.core.js @@ -161,7 +161,7 @@ var classAnimationActions = [ "add", "remove", "toggle" ], // prefix used for storing data on .data() dataSpace = "ec.storage."; -$.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function(_, prop) { +$.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function( _, prop ) { $.fx.step[ prop ] = function( fx ) { if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) { jQuery.style( fx.elem, prop, fx.end ); @@ -171,8 +171,8 @@ $.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopS }); function getElementStyles() { - var style = document.defaultView - ? document.defaultView.getComputedStyle(this, null) + var style = this.ownerDocument.defaultView + ? this.ownerDocument.defaultView.getComputedStyle( this, null ) : this.currentStyle, newStyle = {}, key, @@ -223,8 +223,8 @@ $.effects.animateClass = function( value, duration, easing, callback ) { return this.queue( function() { var animated = $( this ), - baseClass = animated.attr( "class" ), - finalClass, + baseClass = animated.attr( "class" ) || "", + applyClassChange, allAnimations = o.children ? animated.find( "*" ).andSelf() : animated; // map the animated objects to store the original styles. @@ -232,18 +232,19 @@ $.effects.animateClass = function( value, duration, easing, callback ) { var el = $( this ); return { el: el, - originalStyleAttr: el.attr( "style" ) || " ", start: getElementStyles.call( this ) }; }); // apply class change - $.each( classAnimationActions, function(i, action) { - if ( value[ action ] ) { - animated[ action + "Class" ]( value[ action ] ); - } - }); - finalClass = animated.attr( "class" ); + applyClassChange = function() { + $.each( classAnimationActions, function(i, action) { + if ( value[ action ] ) { + animated[ action + "Class" ]( value[ action ] ); + } + }); + }; + applyClassChange(); // map all animated objects again - calculate new styles and diff allAnimations = allAnimations.map(function() { @@ -275,16 +276,15 @@ $.effects.animateClass = function( value, duration, easing, callback ) { $.when.apply( $, allAnimations.get() ).done(function() { // set the final class - animated.attr( "class", finalClass ); + applyClassChange(); - // for each animated element + // for each animated element, + // clear all css properties that were animated $.each( arguments, function() { - if ( typeof this.el.attr( "style" ) === "object" ) { - this.el.attr( "style" ).cssText = ""; - this.el.attr( "style" ).cssText = this.originalStyleAttr; - } else { - this.el.attr( "style", this.originalStyleAttr ); - } + var el = this.el; + $.each( this.diff, function(key) { + el.css( key, '' ); + }); }); // this is guarnteed to be there if you use jQuery.speed() diff --git a/ui/jquery.effects.scale.js b/ui/jquery.effects.scale.js index 000fdee28..96a9269ec 100644 --- a/ui/jquery.effects.scale.js +++ b/ui/jquery.effects.scale.js @@ -117,9 +117,7 @@ $.effects.effect.size = function( o, done ) { scale = o.scale || "both", origin = o.origin || [ "middle", "center" ], original, baseline, factor, - position = el.css( "position" ), - originalVerticalPositioning = el.css( "bottom" ) !== "auto" ? "bottom" : "top"; - originalHorizontalPositioning = el.css( "right" ) !== "auto" ? "right" : "left"; + position = el.css( "position" ); if ( mode === "show" ) { el.show(); @@ -260,32 +258,19 @@ $.effects.effect.size = function( o, done ) { left: el.to.left }); } else { - $.each([ originalVerticalPositioning, originalHorizontalPositioning ], function( idx, pos ) { + $.each([ "top", "left" ], function( idx, pos ) { el.css( pos, function( _, str ) { var val = parseInt( str, 10 ), toRef = idx ? el.to.left : el.to.top, delta = idx ? el.to.outerWidth - el.from.outerWidth: el.to.outerHeight - el.from.outerHeight, same = origin[ idx ] === pos, - mid = origin[ idx ] === "middle" || origin[ idx ] === "center", - direction = pos == "left" || pos == "top"; + mid = origin[ idx ] === "middle" || origin[ idx ] === "center"; // if original was "auto", recalculate the new value from wrapper if ( str === "auto" ) { return toRef + "px"; } - // if not setting left or top - if ( !direction ) { - - // if the position is relative, bottom/right are reversed meaning - if ( position === "relative" ) { - toRef *= -1; - - // otherwise, if its NOT a midpoint origin, compensate for the outerWidth difference - } else if ( !mid ) { - toRef -= delta * ( same ? -1 : 1 ); - } - } return val + toRef + "px"; }); }); diff --git a/ui/jquery.ui.accordion.js b/ui/jquery.ui.accordion.js index c976e3e69..8289ee81b 100644 --- a/ui/jquery.ui.accordion.js +++ b/ui/jquery.ui.accordion.js @@ -13,8 +13,6 @@ */ (function( $, undefined ) { -var lastToggle = {}; - // TODO: use ui-accordion-header-active class and fix styling $.widget( "ui.accordion", { version: "@VERSION", @@ -39,6 +37,7 @@ $.widget( "ui.accordion", { var self = this, options = self.options; + self.lastToggle = {}; self.element.addClass( "ui-accordion ui-widget ui-helper-reset" ); self.headers = self.element.find( options.header ) @@ -378,10 +377,11 @@ $.widget( "ui.accordion", { } animations[ animation ]({ + widget: self, toShow: toShow, toHide: toHide, - prevShow: lastToggle.toShow, - prevHide: lastToggle.toHide, + prevShow: self.lastToggle.toShow, + prevHide: self.lastToggle.toHide, complete: complete, down: toShow.length && ( !toHide.length || ( toShow.index() < toHide.index() ) ) }, additional ); @@ -450,7 +450,7 @@ $.extend( $.ui.accordion, { duration: 300 }, options, additions ); - lastToggle = options; + options.widget.lastToggle = options; if ( !options.toHide.size() ) { originalWidth = options.toShow[0].style.width; @@ -483,11 +483,11 @@ $.extend( $.ui.accordion, { // fix width before calculating height of hidden element var s = options.toShow; originalWidth = s[0].style.width; - s.width( parseInt( s.parent().width(), 10 ) - - parseInt( s.css( "paddingLeft" ), 10 ) - - parseInt( s.css( "paddingRight" ), 10 ) - - ( parseInt( s.css( "borderLeftWidth" ), 10 ) || 0 ) - - ( parseInt( s.css( "borderRightWidth" ), 10) || 0 ) ); + s.width( s.parent().width() + - parseFloat( s.css( "paddingLeft" ) ) + - parseFloat( s.css( "paddingRight" ) ) + - ( parseFloat( s.css( "borderLeftWidth" ) ) || 0 ) + - ( parseFloat( s.css( "borderRightWidth" ) ) || 0 ) ); $.each( fxAttrs, function( i, prop ) { hideProps[ prop ] = "hide"; @@ -628,8 +628,10 @@ if ( $.uiBackCompat !== false ) { var _createIcons = prototype._createIcons; prototype._createIcons = function() { - this.options.icons.activeHeader = this.options.icons.activeHeader || - this.options.icons.headerSelected; + if ( this.options.icons ) { + this.options.icons.activeHeader = this.options.icons.activeHeader || + this.options.icons.headerSelected; + } _createIcons.call( this ); }; }( jQuery, jQuery.ui.accordion.prototype ) ); diff --git a/ui/jquery.ui.autocomplete.js b/ui/jquery.ui.autocomplete.js index 3e0163682..4e528dce4 100644 --- a/ui/jquery.ui.autocomplete.js +++ b/ui/jquery.ui.autocomplete.js @@ -47,11 +47,18 @@ $.widget( "ui.autocomplete", { _create: function() { var self = this, - doc = this.element[ 0 ].ownerDocument, + // 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 suppressKeyPress, + suppressKeyPressRepeat, suppressInput; - this.valueMethod = this.element[ this.element.is( "input" ) ? "val" : "text" ]; + this.valueMethod = this.element[ this.element.is( "input,textarea" ) ? "val" : "text" ]; this.element .addClass( "ui-autocomplete-input" ) @@ -66,11 +73,13 @@ $.widget( "ui.autocomplete", { if ( self.options.disabled || self.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: @@ -110,10 +119,13 @@ $.widget( "ui.autocomplete", { self.menu.select( event ); break; case keyCode.ESCAPE: - self._value( self.term ); - self.close( event ); + if ( self.menu.element.is(":visible") ) { + self._value( self.term ); + self.close( event ); + } break; default: + suppressKeyPressRepeat = true; // search timeout should be triggered before the input value is changed self._searchTimeout( event ); break; @@ -125,6 +137,9 @@ $.widget( "ui.autocomplete", { event.preventDefault(); return; } + if ( suppressKeyPressRepeat ) { + return; + } // replicate some key handlers to allow them to repeat in Firefox and Opera var keyCode = $.ui.keyCode; @@ -181,7 +196,7 @@ $.widget( "ui.autocomplete", { }; this.menu = $( "<ul></ul>" ) .addClass( "ui-autocomplete" ) - .appendTo( $( this.options.appendTo || "body", doc )[0] ) + .appendTo( this.document.find( this.options.appendTo || "body" )[0] ) // prevent the close-on-blur in case of a "slow" click on the menu (long mousedown) .mousedown(function( event ) { // clicking on the scrollbar causes focus to shift to the body @@ -191,7 +206,7 @@ $.widget( "ui.autocomplete", { var menuElement = self.menu.element[ 0 ]; if ( !$( event.target ).closest( ".ui-menu-item" ).length ) { setTimeout(function() { - $( document ).one( 'mousedown', function( event ) { + self.document.one( 'mousedown', function( event ) { if ( event.target !== self.element[ 0 ] && event.target !== menuElement && !$.contains( menuElement, event.target ) ) { @@ -223,7 +238,7 @@ $.widget( "ui.autocomplete", { previous = self.previous; // only trigger when focus was lost (click on menu) - if ( self.element[0] !== doc.activeElement ) { + if ( self.element[0] !== self.document[0].activeElement ) { self.element.focus(); self.previous = previous; // #6109 - IE triggers two focus events and the second @@ -244,22 +259,24 @@ $.widget( "ui.autocomplete", { self.close( event ); self.selectedItem = item; - }, - blur: function( event, ui ) { - // don't set the value of the text field if it's already correct - // this prevents moving the cursor unnecessarily - if ( self.menu.element.is(":visible") && - ( self._value() !== self.term ) ) { - self._value( self.term ); - } } }) .zIndex( this.element.zIndex() + 1 ) .hide() .data( "menu" ); + if ( $.fn.bgiframe ) { this.menu.element.bgiframe(); } + + // turning off autocomplete prevents the browser from remembering the + // value when navigating through history, so we re-enable autocomplete + // if the page is unloaded before the widget is destroyed. #7790 + this._bind( this.window, { + beforeunload: function() { + this.element.removeAttr( "autocomplete" ); + } + }); }, _destroy: function() { @@ -279,7 +296,7 @@ $.widget( "ui.autocomplete", { this._initSource(); } if ( key === "appendTo" ) { - this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] ) + this.menu.element.appendTo( this.document.find( value || "body" )[0] ); } if ( key === "disabled" && value && this.xhr ) { this.xhr.abort(); @@ -325,9 +342,10 @@ $.widget( "ui.autocomplete", { _searchTimeout: function( event ) { var self = this; + clearTimeout( self.searching ); self.searching = setTimeout(function() { // only search if the value has changed - if ( self.term != self.element.val() ) { + if ( self.term !== self._value() ) { self.selectedItem = null; self.search( null, event ); } @@ -384,7 +402,7 @@ $.widget( "ui.autocomplete", { this._trigger( "close", event ); } }, - + _change: function( event ) { if ( this.previous !== this._value() ) { this._trigger( "change", event, { item: this.selectedItem } ); @@ -434,7 +452,9 @@ $.widget( "ui.autocomplete", { _resizeMenu: function() { var ul = this.menu.element; ul.outerWidth( Math.max( - ul.width( "" ).outerWidth(), + // Firefox wraps long text (possibly a rounding bug) + // so we add 1px to avoid the wrapping (#7513) + ul.width( "" ).outerWidth() + 1, this.element.outerWidth() ) ); }, diff --git a/ui/jquery.ui.button.js b/ui/jquery.ui.button.js index 89c52d007..20eb2ca89 100644 --- a/ui/jquery.ui.button.js +++ b/ui/jquery.ui.button.js @@ -174,7 +174,7 @@ $.widget( "ui.button", { } $( this ).addClass( "ui-state-active" ); lastActive = this; - $( document ).one( "mouseup", function() { + self.document.one( "mouseup", function() { lastActive = null; }); }) @@ -319,7 +319,7 @@ $.widget( "ui.button", { return; } var buttonElement = this.buttonElement.removeClass( typeClasses ), - buttonText = $( "<span></span>" ) + buttonText = $( "<span></span>", this.document[0] ) .addClass( "ui-button-text" ) .html( this.options.label ) .appendTo( buttonElement.empty() ) @@ -379,7 +379,7 @@ $.widget( "ui.buttonset", { }, refresh: function() { - var ltr = this.element.css( "direction" ) === "ltr"; + var rtl = this.element.css( "direction" ) === "rtl"; this.buttons = this.element.find( this.options.items ) .filter( ":ui-button" ) @@ -393,10 +393,10 @@ $.widget( "ui.buttonset", { }) .removeClass( "ui-corner-all ui-corner-left ui-corner-right" ) .filter( ":first" ) - .addClass( ltr ? "ui-corner-left" : "ui-corner-right" ) + .addClass( rtl ? "ui-corner-right" : "ui-corner-left" ) .end() .filter( ":last" ) - .addClass( ltr ? "ui-corner-right" : "ui-corner-left" ) + .addClass( rtl ? "ui-corner-left" : "ui-corner-right" ) .end() .end(); }, diff --git a/ui/jquery.ui.datepicker.js b/ui/jquery.ui.datepicker.js index 0b6fb2218..e09dc5a50 100644 --- a/ui/jquery.ui.datepicker.js +++ b/ui/jquery.ui.datepicker.js @@ -636,10 +636,10 @@ $.extend(Datepicker.prototype, { return; var inst = $.datepicker._getInst(input); if ($.datepicker._curInst && $.datepicker._curInst != inst) { - if ( $.datepicker._datepickerShowing ) { - $.datepicker._triggerOnClose($.datepicker._curInst); - } $.datepicker._curInst.dpDiv.stop(true, true); + if ( inst && $.datepicker._datepickerShowing ) { + $.datepicker._hideDatepicker( $.datepicker._curInst.input[0] ); + } } var beforeShow = $.datepicker._get(inst, 'beforeShow'); var beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {}; @@ -790,14 +790,6 @@ $.extend(Datepicker.prototype, { return [position.left, position.top]; }, - /* Trigger custom callback of onClose. */ - _triggerOnClose: function(inst) { - var onClose = this._get(inst, 'onClose'); - if (onClose) - onClose.apply((inst.input ? inst.input[0] : null), - [(inst.input ? inst.input.val() : ''), inst]); - }, - /* Hide the date picker from view. @param input element - the input field attached to the date picker */ _hideDatepicker: function(input) { @@ -820,8 +812,11 @@ $.extend(Datepicker.prototype, { (showAnim == 'fadeIn' ? 'fadeOut' : 'hide'))]((showAnim ? duration : null), postProcess); if (!showAnim) postProcess(); - $.datepicker._triggerOnClose(inst); this._datepickerShowing = false; + var onClose = this._get(inst, 'onClose'); + if (onClose) + onClose.apply((inst.input ? inst.input[0] : null), + [(inst.input ? inst.input.val() : ''), inst]); this._lastInput = null; if (this._inDialog) { this._dialogInput.css({ position: 'absolute', left: '0', top: '-100px' }); @@ -843,12 +838,16 @@ $.extend(Datepicker.prototype, { _checkExternalClick: function(event) { if (!$.datepicker._curInst) return; - var $target = $(event.target); - if ($target[0].id != $.datepicker._mainDivId && + + var $target = $(event.target), + inst = $.datepicker._getInst($target[0]); + + if ( ( ( $target[0].id != $.datepicker._mainDivId && $target.parents('#' + $.datepicker._mainDivId).length == 0 && !$target.hasClass($.datepicker.markerClassName) && !$target.hasClass($.datepicker._triggerClass) && - $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI)) + $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) || + ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst != inst ) ) $.datepicker._hideDatepicker(); }, diff --git a/ui/jquery.ui.dialog.js b/ui/jquery.ui.dialog.js index 493ed07e6..9c9a9b6fc 100644 --- a/ui/jquery.ui.dialog.js +++ b/ui/jquery.ui.dialog.js @@ -293,7 +293,7 @@ $.widget("ui.dialog", { // prevent tabbing out of modal dialogs if ( options.modal ) { - uiDialog.bind( "keypress.ui-dialog", function( event ) { + uiDialog.bind( "keydown.ui-dialog", function( event ) { if ( event.keyCode !== $.ui.keyCode.TAB ) { return; } @@ -372,8 +372,7 @@ $.widget("ui.dialog", { _makeDraggable: function() { var self = this, - options = self.options, - doc = $( document ); + options = self.options; function filteredUi( ui ) { return { @@ -396,8 +395,8 @@ $.widget("ui.dialog", { }, stop: function( event, ui ) { options.position = [ - ui.position.left - doc.scrollLeft(), - ui.position.top - doc.scrollTop() + ui.position.left - self.document.scrollLeft(), + ui.position.top - self.document.scrollTop() ]; $( this ) .removeClass( "ui-dialog-dragging" ); @@ -715,12 +714,11 @@ $.extend( $.ui.dialog.overlay, { $( window ).bind( "resize.dialog-overlay", $.ui.dialog.overlay.resize ); } - var $el = ( this.oldInstances.pop() || $( "<div>" ).addClass( "ui-widget-overlay" ) ) - .appendTo( document.body ) - .css({ - width: this.width(), - height: this.height() - }); + var $el = ( this.oldInstances.pop() || $( "<div>" ).addClass( "ui-widget-overlay" ) ); + $el.appendTo( document.body ).css({ + width: this.width(), + height: this.height() + }); if ( $.fn.bgiframe ) { $el.bgiframe(); diff --git a/ui/jquery.ui.menu.js b/ui/jquery.ui.menu.js index 27e76d909..bf36a77fe 100644 --- a/ui/jquery.ui.menu.js +++ b/ui/jquery.ui.menu.js @@ -20,14 +20,16 @@ $.widget( "ui.menu", { defaultElement: "<ul>", delay: 150, options: { + items: "ul", position: { my: "left top", at: "right top" - } + }, + trigger: null }, _create: function() { - var self = this; this.activeMenu = this.element; + this.isScrolling = false; this.menuId = this.element.attr( "id" ) || "ui-menu-" + idIncrement++; if ( this.element.find( ".ui-icon" ).length ) { this.element.addClass( "ui-menu-icons" ); @@ -38,13 +40,23 @@ $.widget( "ui.menu", { id: this.menuId, role: "menu" }) + // Prevent focus from sticking to links inside menu after clicking + // them (focus should always stay on UL during navigation). + // If the link is clicked, redirect focus to the menu. + // TODO move to _bind below + .bind( "mousedown.menu", function( event ) { + if ( $( event.target).is( "a" ) ) { + event.preventDefault(); + $( this ).focus( 1 ); + } + }) // need to catch all clicks on disabled menu // not possible through _bind - .bind( "click.menu", function( event ) { - if ( self.options.disabled ) { + .bind( "click.menu", $.proxy( function( event ) { + if ( this.options.disabled ) { event.preventDefault(); } - }); + }, this)); this._bind({ "click .ui-menu-item:has(a)": function( event ) { event.stopImmediatePropagation(); @@ -57,143 +69,172 @@ $.widget( "ui.menu", { }, "mouseover .ui-menu-item": function( event ) { event.stopImmediatePropagation(); - var target = $( event.currentTarget ); - // Remove ui-state-active class from siblings of the newly focused menu item to avoid a jump caused by adjacent elements both having a class with a border - target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" ); - this.focus( event, target ); + if ( !this.isScrolling ) { + var target = $( event.currentTarget ); + // Remove ui-state-active class from siblings of the newly focused menu item to avoid a jump caused by adjacent elements both having a class with a border + target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" ); + this.focus( event, target ); + } + this.isScrolling = false; }, + "mouseleave": "collapseAll", + "mouseleave .ui-menu": "collapseAll", "mouseout .ui-menu-item": "blur", "focus": function( event ) { this.focus( event, $( event.target ).children( ".ui-menu-item:first" ) ); }, - "blur": "collapseAll" + blur: function( event ) { + this._delay( function() { + if ( ! $.contains( this.element[0], this.document[0].activeElement ) ) { + this.collapseAll( event ); + } + }, 0); + }, + scroll: function( event ) { + // Keep track of scrolling to prevent mouseover from firing inadvertently when scrolling the menu + this.isScrolling = true; + } }); this.refresh(); - this.element.attr( "tabIndex", 0 ).bind( "keydown.menu", function( event ) { - if ( self.options.disabled ) { - return; - } - switch ( event.keyCode ) { - case $.ui.keyCode.PAGE_UP: - self.previousPage( event ); - event.preventDefault(); - event.stopImmediatePropagation(); - break; - case $.ui.keyCode.PAGE_DOWN: - self.nextPage( event ); - event.preventDefault(); - event.stopImmediatePropagation(); - break; - case $.ui.keyCode.HOME: - self._move( "first", "first", event ); - event.preventDefault(); - event.stopImmediatePropagation(); - break; - case $.ui.keyCode.END: - self._move( "last", "last", event ); - event.preventDefault(); - event.stopImmediatePropagation(); - break; - case $.ui.keyCode.UP: - self.previous( event ); - event.preventDefault(); - event.stopImmediatePropagation(); - break; - case $.ui.keyCode.DOWN: - self.next( event ); - event.preventDefault(); - event.stopImmediatePropagation(); - break; - case $.ui.keyCode.LEFT: - if (self.collapse( event )) { + this.element.attr( "tabIndex", 0 ); + this._bind({ + "keydown": function( event ) { + switch ( event.keyCode ) { + case $.ui.keyCode.PAGE_UP: + this.previousPage( event ); + event.preventDefault(); event.stopImmediatePropagation(); - } - event.preventDefault(); - break; - case $.ui.keyCode.RIGHT: - if (self.expand( event )) { + break; + case $.ui.keyCode.PAGE_DOWN: + this.nextPage( event ); + event.preventDefault(); event.stopImmediatePropagation(); - } - event.preventDefault(); - break; - case $.ui.keyCode.ENTER: - if ( self.active.children( "a[aria-haspopup='true']" ).length ) { - if ( self.expand( event ) ) { - event.stopImmediatePropagation(); - } - } - else { - self.select( event ); + break; + case $.ui.keyCode.HOME: + this._move( "first", "first", event ); + event.preventDefault(); event.stopImmediatePropagation(); - } - event.preventDefault(); - break; - case $.ui.keyCode.ESCAPE: - if ( self.collapse( event ) ) { + break; + case $.ui.keyCode.END: + this._move( "last", "last", event ); + event.preventDefault(); event.stopImmediatePropagation(); - } - event.preventDefault(); - break; - default: - event.stopPropagation(); - clearTimeout( self.filterTimer ); - var match, - prev = self.previousFilter || "", - character = String.fromCharCode( event.keyCode ), - skip = false; - - if (character == prev) { - skip = true; - } else { - character = prev + character; - } - function escape( value ) { - return value.replace( /[-[\]{}()*+?.,\\^$|#\s]/g , "\\$&" ); - } - match = self.activeMenu.children( ".ui-menu-item" ).filter( function() { - return new RegExp("^" + escape(character), "i") - .test( $( this ).children( "a" ).text() ); - }); - match = skip && match.index(self.active.next()) != -1 ? self.active.nextAll(".ui-menu-item") : match; - if ( !match.length ) { - character = String.fromCharCode(event.keyCode); - match = self.activeMenu.children(".ui-menu-item").filter( function() { + break; + case $.ui.keyCode.UP: + this.previous( event ); + event.preventDefault(); + event.stopImmediatePropagation(); + break; + case $.ui.keyCode.DOWN: + this.next( event ); + event.preventDefault(); + event.stopImmediatePropagation(); + break; + case $.ui.keyCode.LEFT: + if (this.collapse( event )) { + event.stopImmediatePropagation(); + } + event.preventDefault(); + break; + case $.ui.keyCode.RIGHT: + if (this.expand( event )) { + event.stopImmediatePropagation(); + } + event.preventDefault(); + break; + case $.ui.keyCode.ENTER: + if ( this.active.children( "a[aria-haspopup='true']" ).length ) { + if ( this.expand( event ) ) { + event.stopImmediatePropagation(); + } + } + else { + this.select( event ); + event.stopImmediatePropagation(); + } + event.preventDefault(); + break; + case $.ui.keyCode.ESCAPE: + if ( this.collapse( event ) ) { + event.stopImmediatePropagation(); + } + event.preventDefault(); + break; + default: + event.stopPropagation(); + clearTimeout( this.filterTimer ); + var match, + prev = this.previousFilter || "", + character = String.fromCharCode( event.keyCode ), + skip = false; + + if (character == prev) { + skip = true; + } else { + character = prev + character; + } + function escape( value ) { + return value.replace( /[-[\]{}()*+?.,\\^$|#\s]/g , "\\$&" ); + } + match = this.activeMenu.children( ".ui-menu-item" ).filter( function() { return new RegExp("^" + escape(character), "i") .test( $( this ).children( "a" ).text() ); }); - } - if ( match.length ) { - self.focus( event, match ); - if (match.length > 1) { - self.previousFilter = character; - self.filterTimer = setTimeout( function() { - delete self.previousFilter; - }, 1000 ); + match = skip && match.index(this.active.next()) != -1 ? this.active.nextAll(".ui-menu-item") : match; + if ( !match.length ) { + character = String.fromCharCode(event.keyCode); + match = this.activeMenu.children(".ui-menu-item").filter( function() { + return new RegExp("^" + escape(character), "i") + .test( $( this ).children( "a" ).text() ); + }); + } + if ( match.length ) { + this.focus( event, match ); + if (match.length > 1) { + this.previousFilter = character; + this.filterTimer = this._delay( function() { + delete this.previousFilter; + }, 1000 ); + } else { + delete this.previousFilter; + } } else { - delete self.previousFilter; + delete this.previousFilter; } - } else { - delete self.previousFilter; } } }); - this._bind( document, { + this._bind( this.document, { click: function( event ) { if ( !$( event.target ).closest( ".ui-menu" ).length ) { this.collapseAll( event ); } } }); + + if ( this.options.trigger ) { + this.element.popup({ + trigger: this.options.trigger, + managed: true, + focusPopup: $.proxy( function( event, ui ) { + this.focus( event, this.element.children( ".ui-menu-item" ).first() ); + this.element.focus( 1 ); + }, this) + }); + } }, _destroy: function() { //destroy (sub)menus + if ( this.options.trigger ) { + this.element.popup( "destroy" ); + } this.element .removeAttr( "aria-activedescendant" ) - .find( "ul" ) + .find( ".ui-menu" ) .andSelf() .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) .removeAttr( "role" ) @@ -219,43 +260,38 @@ $.widget( "ui.menu", { }, refresh: function() { - var self = this, - - // initialize nested menus - submenus = this.element.find( "ul:not(.ui-menu)" ) - .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) - .attr( "role", "menu" ) - .hide() - .attr( "aria-hidden", "true" ) - .attr( "aria-expanded", "false" ), + // initialize nested menus + var submenus = this.element.find( this.options.items + ":not( .ui-menu )" ) + .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) + .attr( "role", "menu" ) + .hide() + .attr( "aria-hidden", "true" ) + .attr( "aria-expanded", "false" ); // don't refresh list items that are already adapted - items = submenus.add( this.element ).children( "li:not(.ui-menu-item):has(a)" ) - .addClass( "ui-menu-item" ) - .attr( "role", "presentation" ); - - items.children( "a" ) - .addClass( "ui-corner-all" ) - .attr( "tabIndex", -1 ) - .attr( "role", "menuitem" ) - .attr( "id", function( i ) { - return self.element.attr( "id" ) + "-" + i; - }); + var menuId = this.menuId; + submenus.add( this.element ).children( ":not( .ui-menu-item ):has( a )" ) + .addClass( "ui-menu-item" ) + .attr( "role", "presentation" ) + .children( "a" ) + .addClass( "ui-corner-all" ) + .attr( "tabIndex", -1 ) + .attr( "role", "menuitem" ) + .attr( "id", function( i ) { + return menuId + "-" + i; + }); submenus.each( function() { var menu = $( this ), item = menu.prev( "a" ); item.attr( "aria-haspopup", "true" ) - .prepend( '<span class="ui-menu-icon ui-icon ui-icon-carat-1-e"></span>' ); + .prepend( '<span class="ui-menu-icon ui-icon ui-icon-carat-1-e"></span>' ); menu.attr( "aria-labelledby", item.attr( "id" ) ); }); }, focus: function( event, item ) { - var nested, - self = this; - this.blur( event ); if ( this._hasScroll() ) { @@ -277,18 +313,18 @@ $.widget( "ui.menu", { .children( "a" ) .addClass( "ui-state-focus" ) .end(); - self.element.attr( "aria-activedescendant", self.active.children("a").attr("id") ); + this.element.attr( "aria-activedescendant", this.active.children( "a" ).attr( "id" ) ); // highlight active parent menu item, if any - this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active"); + this.active.parent().closest( ".ui-menu-item" ).children( "a:first" ).addClass( "ui-state-active" ); - self.timer = setTimeout( function() { - self._close(); - }, self.delay ); + this.timer = this._delay( function() { + this._close(); + }, this.delay ); - nested = $( ">ul", item ); + var nested = $( "> .ui-menu", item ); if ( nested.length && ( /^mouse/.test( event.type ) ) ) { - self._startOpening(nested); + this._startOpening(nested); } this.activeMenu = item.parent(); @@ -317,11 +353,10 @@ $.widget( "ui.menu", { return; } - var self = this; - self.timer = setTimeout( function() { - self._close(); - self._open( submenu ); - }, self.delay ); + this.timer = this._delay( function() { + this._close(); + this._open( submenu ); + }, this.delay ); }, _open: function( submenu ) { @@ -345,23 +380,32 @@ $.widget( "ui.menu", { .position( position ); }, - collapseAll: function( event ) { - this.element - .find( "ul" ) - .hide() - .attr( "aria-hidden", "true" ) - .attr( "aria-expanded", "false" ) - .end() - .find( "a.ui-state-active" ) - .removeClass( "ui-state-active" ); + collapseAll: function( event, all ) { + + // if we were passed an event, look for the submenu that contains the event + var currentMenu = all ? this.element : + $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); + + // if we found no valid submenu ancestor, use the main menu to close all sub menus anyway + if ( !currentMenu.length ) { + currentMenu = this.element; + } + + this._close( currentMenu ); this.blur( event ); - this.activeMenu = this.element; + this.activeMenu = currentMenu; }, - _close: function() { - this.active.parent() - .find( "ul" ) + // With no arguments, closes the currently active menu - if nothing is active + // it closes all menus. If passed an argument, it will search for menus BELOW + _close: function( startMenu ) { + if ( !startMenu ) { + startMenu = this.active ? this.active.parent() : this.element; + } + + startMenu + .find( ".ui-menu" ) .hide() .attr( "aria-hidden", "true" ) .attr( "aria-expanded", "false" ) @@ -371,27 +415,23 @@ $.widget( "ui.menu", { }, collapse: function( event ) { - var newItem = this.active && this.active.parents("li:not(.ui-menubar-item)").first(); + var newItem = this.active && this.active.parent().closest( ".ui-menu-item", this.element ); if ( newItem && newItem.length ) { - this.active.parent() - .attr("aria-hidden", "true") - .attr("aria-expanded", "false") - .hide(); + this._close(); this.focus( event, newItem ); return true; } }, expand: function( event ) { - var self = this, - newItem = this.active && this.active.children("ul").children("li").first(); + var newItem = this.active && this.active.children( ".ui-menu " ).children( ".ui-menu-item" ).first(); if ( newItem && newItem.length ) { this._open( newItem.parent() ); //timeout so Firefox will not hide activedescendant change in expanding submenu from AT - setTimeout( function() { - self.focus( event, newItem ); + this._delay( function() { + this.focus( event, newItem ); }, 20 ); return true; } @@ -487,11 +527,16 @@ $.widget( "ui.menu", { }, select: function( event ) { + // save active reference before collapseAll triggers blur var ui = { item: this.active }; - this.collapseAll( event ); + this.collapseAll( event, true ); + if ( this.options.trigger ) { + $( this.options.trigger ).focus( 1 ); + this.element.popup( "close" ); + } this._trigger( "select", event, ui ); } }); diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 9af3aa080..673493366 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -94,7 +94,7 @@ $.widget( "ui.menubar", { } if ( ( that.open && event.type == "mouseenter" ) || event.type == "click" || that.options.autoExpand ) { if( that.options.autoExpand ) { - clearTimeout( that.timer ); + clearTimeout( that.closeTimer ); } that._open( event, menu ); @@ -123,22 +123,6 @@ $.widget( "ui.menubar", { .attr( "aria-haspopup", "true" ) .wrapInner( "<span class='ui-button-text'></span>" ); - if ( that.options.autoExpand ) { - input.bind( "mouseleave.menubar", function( event ) { - that.timer = setTimeout( function() { - that._close(); - }, 150 ); - }); - menu.bind( "mouseleave.menubar", function( event ) { - that.timer = setTimeout( function() { - that._close(); - }, 150 ); - }) - .bind( "mouseenter.menubar", function( event ) { - clearTimeout( that.timer ); - }); - } - // TODO review if these options are a good choice, maybe they can be merged if ( that.options.menuIcon ) { input.addClass( "ui-state-default" ).append( "<span class='ui-button-icon-secondary ui-icon ui-icon-triangle-1-s'></span>" ); @@ -166,7 +150,17 @@ $.widget( "ui.menubar", { focusout: function( event ) { that.closeTimer = setTimeout( function() { that._close( event ); - }, 100); + }, 150); + }, + "mouseleave .ui-menubar-item": function( event ) { + if ( that.options.autoExpand ) { + that.closeTimer = setTimeout( function() { + that._close( event ); + }, 150); + } + }, + "mouseenter .ui-menubar-item": function( event ) { + clearTimeout( that.closeTimer ); } }); }, diff --git a/ui/jquery.ui.popup.js b/ui/jquery.ui.popup.js index 508209a55..128464cc2 100644 --- a/ui/jquery.ui.popup.js +++ b/ui/jquery.ui.popup.js @@ -14,7 +14,8 @@ */ (function($) { -var idIncrement = 0; +var idIncrement = 0, + suppressExpandOnFocus = false; $.widget( "ui.popup", { version: "@VERSION", @@ -22,6 +23,16 @@ $.widget( "ui.popup", { position: { my: "left top", at: "left bottom" + }, + managed: false, + expandOnFocus: false, + show: { + effect: "slideDown", + duration: "fast" + }, + hide: { + effect: "fadeOut", + duration: "fast" } }, _create: function() { @@ -35,9 +46,10 @@ $.widget( "ui.popup", { } if ( !this.element.attr( "role" ) ) { - // TODO alternatives to tooltip are dialog and menu, all three aren't generic popups - this.element.attr( "role", "dialog" ); - this.generatedRole = true; + if ( !this.options.managed ) { + this.element.attr( "role", "dialog" ); + this.generatedRole = true; + } } this.options.trigger @@ -45,42 +57,92 @@ $.widget( "ui.popup", { .attr( "aria-owns", this.element.attr( "id" ) ); this.element - .addClass( "ui-popup" ) - this.close(); + .addClass( "ui-popup" ); + this._beforeClose(); + this.element.hide(); this._bind(this.options.trigger, { keydown: function( event ) { - // prevent space-to-open to scroll the page, only happens for anchor ui.button - if ( this.options.trigger.is( "a:ui-button" ) && event.keyCode == $.ui.keyCode.SPACE ) { - event.preventDefault(); - } - // TODO handle SPACE to open popup? only when not handled by ui.button - if ( event.keyCode == $.ui.keyCode.SPACE && this.options.trigger.is( "a:not(:ui-button)" ) ) { - this.options.trigger.trigger( "click", event ); - } - // translate keydown to click - // opens popup and let's tooltip hide itself - if ( event.keyCode == $.ui.keyCode.DOWN ) { - // prevent scrolling - event.preventDefault(); - this.options.trigger.trigger( "click", event ); + switch ( event.keyCode ) { + case $.ui.keyCode.TAB: + // Waiting for close() will make popup hide too late, which breaks tab key behavior + this.element.hide(); + this.close( event ); + break; + case $.ui.keyCode.ESCAPE: + if ( this.isOpen ) { + this.close( event ); + } + break; + case $.ui.keyCode.SPACE: + // prevent space-to-open to scroll the page, only happens for anchor ui.button + // TODO check for $.ui.button before using custom selector, once more below + if ( this.options.trigger.is( "a:ui-button" ) ) { + event.preventDefault(); + } + + else if (this.options.trigger.is( "a:not(:ui-button)" ) ) { + this.options.trigger.trigger( "click", event ); + } + break; + case $.ui.keyCode.DOWN: + case $.ui.keyCode.UP: + // prevent scrolling + event.preventDefault(); + clearTimeout( this.closeTimer ); + this._delay(function() { + this.open( event ); + this.focusPopup( event ); + }, 1); + break; } }, click: function( event ) { + event.stopPropagation(); event.preventDefault(); + }, + mousedown: function( event ) { + var noFocus = false; + /* TODO: Determine in which cases focus should stay on the trigger after the popup opens + (should apply for any trigger that has other interaction besides opening the popup, e.g. a text field) */ + if ( $( event.target ).is( "input" ) ) { + noFocus = true; + } if (this.isOpen) { - // let it propagate to close + suppressExpandOnFocus = true; + this.close(); return; } - var that = this; + this.open( event ); clearTimeout( this.closeTimer ); - setTimeout(function() { - that.open( event ); - }, 1); + this._delay( function() { + if ( !noFocus ) { + this.focusPopup(); + } + }, 1 ); } }); - if ( !this.element.is( ":ui-menu" ) ) { + if ( this.options.expandOnFocus ) { + this._bind( this.options.trigger, { + focus : function( event ) { + if ( !suppressExpandOnFocus ) { + this._delay( function() { + if ( !this.isOpen ) { + this.open( event ); + } + }, 1); + } + this._delay( function() { + suppressExpandOnFocus = false; + }, 100); + }, + blur: function( event ) { + suppressExpandOnFocus = false; + } + }); + } + if ( !this.options.managed ) { //default use case, wrap tab order in popup this._bind({ keydown : function( event ) { if ( event.keyCode !== $.ui.keyCode.TAB ) { @@ -102,38 +164,36 @@ $.widget( "ui.popup", { this._bind({ focusout: function( event ) { - var that = this; // use a timer to allow click to clear it and letting that // handle the closing instead of opening again - that.closeTimer = setTimeout( function() { - that.close( event ); - }, 100); + this.closeTimer = this._delay( function() { + this.close( event ); + }, 150); }, focusin: function( event ) { clearTimeout( this.closeTimer ); + }, + mouseup: function( event ) { + clearTimeout( this.closeTimer ); } }); this._bind({ - // TODO only triggered on element if it can receive focus - // bind to document instead? - // either element itself or a child should be focusable keyup: function( event ) { if ( event.keyCode == $.ui.keyCode.ESCAPE && this.element.is( ":visible" ) ) { this.close( event ); - // TODO move this to close()? would allow menu.select to call popup.close, and get focus back to trigger - this.options.trigger.focus(); + this.focusTrigger(); } } }); - this._bind(document, { + this._bind( this.document, { click: function( event ) { - if ( this.isOpen && !$(event.target).closest(".ui-popup").length ) { + if ( this.isOpen && !$( event.target ).closest( this.element.add( this.options.trigger ) ).length ) { this.close( event ); } } - }) + }); }, _destroy: function() { @@ -161,16 +221,20 @@ $.widget( "ui.popup", { of: this.options.trigger }, this.options.position ); + this._show( this.element, this.options.show ); this.element - .show() .attr( "aria-hidden", "false" ) .attr( "aria-expanded", "true" ) .position( position ); - if (this.element.is( ":ui-menu" )) { //popup is a menu - this.element.menu( "focus", event, this.element.children( "li" ).first() ); - this.element.focus(); - } else { + // take trigger out of tab order to allow shift-tab to skip trigger + this.options.trigger.attr( "tabindex", -1 ); + this.isOpen = true; + this._trigger( "open", event ); + }, + + focusPopup: function( event ) { + if ( !this.options.managed ) { // set focus to the first tabbable element in the popup container // if there are no tabbable elements, set focus on the popup itself var tabbables = this.element.find( ":tabbable" ); @@ -184,18 +248,18 @@ $.widget( "ui.popup", { } tabbables.first().focus( 1 ); } + this._trigger( "focusPopup", event ); + }, - // take trigger out of tab order to allow shift-tab to skip trigger - this.options.trigger.attr( "tabindex", -1 ); - this.isOpen = true; - this._trigger( "open", event ); + focusTrigger: function( event ) { + suppressExpandOnFocus = true; + this.options.trigger.focus(); + this._trigger( "focusTrigger", event ); }, close: function( event ) { - this.element - .hide() - .attr( "aria-hidden", "true" ) - .attr( "aria-expanded", "false" ); + this._beforeClose(); + this._hide( this.element, this.options.hide ); this.options.trigger.attr( "tabindex" , 0 ); if ( this.removeTabIndex ) { @@ -203,6 +267,12 @@ $.widget( "ui.popup", { } this.isOpen = false; this._trigger( "close", event ); + }, + + _beforeClose: function() { + this.element + .attr( "aria-hidden", "true" ) + .attr( "aria-expanded", "false" ); } }); diff --git a/ui/jquery.ui.position.js b/ui/jquery.ui.position.js index 23a98b491..9a520f845 100644 --- a/ui/jquery.ui.position.js +++ b/ui/jquery.ui.position.js @@ -39,15 +39,16 @@ $.position = { return w1 - w2; }, - getScrollInfo: function( within ) { - var that = within[0], - scrollHeight = within.height() < that.scrollHeight, - scrollWidth = within.width() < that.scrollWidth, - scrollbarWidth = $.position.scrollbarWidth(); + getScrollInfo: function(within) { + var notWindow = within[0] !== window, + overflowX = notWindow ? within.css( "overflow-x" ) : "", + overflowY = notWindow ? within.css( "overflow-y" ) : "", + scrollbarWidth = overflowX === "auto" || overflowX === "scroll" ? $.position.scrollbarWidth() : 0, + scrollbarHeight = overflowY === "auto" || overflowY === "scroll" ? $.position.scrollbarWidth() : 0; return { - height: scrollHeight ? scrollbarWidth : 0, - width : scrollWidth ? scrollbarWidth : 0 + height: within.height() < within[0].scrollHeight ? scrollbarHeight : 0, + width: within.width() < within[0].scrollWidth ? scrollbarWidth : 0 }; } }; @@ -182,13 +183,15 @@ $.fn.position = function( options ) { position.left += myOffset[ 0 ]; position.top += myOffset[ 1 ]; - // prevent fractions (see #5280) - position.left = Math.round( position.left ); - position.top = Math.round( position.top ); + // if the browser doesn't support fractions, then round for consistent results + if ( !$.support.offsetFractions ) { + position.left = Math.round( position.left ); + position.top = Math.round( position.top ); + } collisionPosition = { - left: position.left - marginLeft, - top: position.top - marginTop + marginLeft: marginLeft, + marginTop: marginTop }; $.each( [ "left", "top" ], function( i, dir ) { @@ -225,18 +228,38 @@ $.ui.position = { isWindow = $.isWindow( data.within[0] ), withinOffset = isWindow ? win.scrollLeft() : within.offset().left, outerWidth = isWindow ? win.width() : within.outerWidth(), - overLeft = withinOffset - data.collisionPosition.left, - overRight = data.collisionPosition.left + data.collisionWidth - outerWidth - withinOffset; - - // element is wider than window or too far left -> align with left edge - if ( data.collisionWidth > outerWidth || overLeft > 0 ) { + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = withinOffset - collisionPosLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, + newOverRight, + newOverLeft; + + // element is wider than within + if ( data.collisionWidth > outerWidth ) { + // element is initially over the left side of within + if ( overLeft > 0 && overRight <= 0 ) { + newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset; + position.left += overLeft - newOverRight; + // element is initially over right side of within + } else if ( overRight > 0 && overLeft <= 0 ) { + position.left = withinOffset; + // element is initially over both left and right sides of within + } else { + if ( overLeft > overRight ) { + position.left = withinOffset + outerWidth - data.collisionWidth; + } else { + position.left = withinOffset; + } + } + // too far left -> align with left edge + } else if ( overLeft > 0 ) { position.left += overLeft; // too far right -> align with right edge } else if ( overRight > 0 ) { position.left -= overRight; // adjust based on position and margin } else { - position.left = Math.max( position.left - data.collisionPosition.left, position.left ); + position.left = Math.max( position.left - collisionPosLeft, position.left ); } }, top: function( position, data ) { @@ -245,18 +268,38 @@ $.ui.position = { isWindow = $.isWindow( data.within[0] ), withinOffset = isWindow ? win.scrollTop() : within.offset().top, outerHeight = isWindow ? win.height() : within.outerHeight(), - overTop = withinOffset - data.collisionPosition.top, - overBottom = data.collisionPosition.top + data.collisionHeight - outerHeight - withinOffset; - - // element is taller than window or too far up -> align with top edge - if ( data.collisionHeight > outerHeight || overTop > 0 ) { + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = withinOffset - collisionPosTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, + newOverTop, + newOverBottom; + + // element is taller than within + if ( data.collisionHeight > outerHeight ) { + // element is initially over the top of within + if ( overTop > 0 && overBottom <= 0 ) { + newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset; + position.top += overTop - newOverBottom; + // element is initially over bottom of within + } else if ( overBottom > 0 && overTop <= 0 ) { + position.top = withinOffset; + // element is initially over both top and bottom of within + } else { + if ( overTop > overBottom ) { + position.top = withinOffset + outerHeight - data.collisionHeight; + } else { + position.top = withinOffset; + } + } + // too far up -> align with top + } else if ( overTop > 0 ) { position.top += overTop; // too far down -> align with bottom edge } else if ( overBottom > 0 ) { position.top -= overBottom; // adjust based on position and margin } else { - position.top = Math.max( position.top - data.collisionPosition.top, position.top ); + position.top = Math.max( position.top - collisionPosTop, position.top ); } } }, @@ -268,14 +311,15 @@ $.ui.position = { data.elem .removeClass( "ui-flipped-left ui-flipped-right" ); - + var within = data.within, win = $( window ), isWindow = $.isWindow( data.within[0] ), - withinOffset = isWindow ? 0 : within.offset().left, + withinOffset = ( isWindow ? 0 : within.offset().left ) + within.scrollLeft(), outerWidth = isWindow ? within.width() : within.outerWidth(), - overLeft = data.collisionPosition.left - withinOffset, - overRight = data.collisionPosition.left + data.collisionWidth - outerWidth - withinOffset, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = collisionPosLeft - withinOffset, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, left = data.my[ 0 ] === "left", myOffset = data.my[ 0 ] === "left" ? -data.elemWidth : @@ -285,30 +329,45 @@ $.ui.position = { atOffset = data.at[ 0 ] === "left" ? data.targetWidth : -data.targetWidth, - offset = -2 * data.offset[ 0 ]; - if ( overLeft < 0 || overRight > 0 ) { - - data.elem - .addClass( "ui-flipped-" + ( overLeft < 0 ? "right" : "left" ) ); - - position.left += myOffset + atOffset + offset; + offset = -2 * data.offset[ 0 ], + newOverRight, + newOverLeft; + + if ( overLeft < 0 ) { + newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset; + if ( newOverRight < 0 || newOverRight < Math.abs( overLeft ) ) { + data.elem + .addClass( "ui-flipped-right" ); + + position.left += myOffset + atOffset + offset; + } + } + else if ( overRight > 0 ) { + newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - withinOffset; + if ( newOverLeft > 0 || Math.abs( newOverLeft ) < overRight ) { + data.elem + .addClass( "ui-flipped-left" ); + + position.left += myOffset + atOffset + offset; + } } }, top: function( position, data ) { if ( data.at[ 1 ] === center ) { return; } - + data.elem .removeClass( "ui-flipped-top ui-flipped-bottom" ); - + var within = data.within, win = $( window ), isWindow = $.isWindow( data.within[0] ), - withinOffset = isWindow ? 0 : within.offset().top, + withinOffset = ( isWindow ? 0 : within.offset().top ) + within.scrollTop(), outerHeight = isWindow ? within.height() : within.outerHeight(), - overTop = data.collisionPosition.top - withinOffset, - overBottom = data.collisionPosition.top + data.collisionHeight - outerHeight - withinOffset, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = collisionPosTop - withinOffset, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, top = data.my[ 1 ] === "top", myOffset = top ? -data.elemHeight : @@ -318,18 +377,80 @@ $.ui.position = { atOffset = data.at[ 1 ] === "top" ? data.targetHeight : -data.targetHeight, - offset = -2 * data.offset[ 1 ]; - if ( overTop < 0 || overBottom > 0 ) { - - data.elem - .addClass( "ui-flipped-" + ( overTop < 0 ? "bottom" : "top" ) ); - - position.top += myOffset + atOffset + offset; + offset = -2 * data.offset[ 1 ], + newOverTop, + newOverBottom; + if ( overTop < 0 ) { + newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset; + if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < Math.abs( overTop ) ) ) { + data.elem + .addClass( "ui-flipped-bottom" ); + + position.top += myOffset + atOffset + offset; + } + } + else if ( overBottom > 0 ) { + newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - withinOffset; + if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || Math.abs( newOverTop ) < overBottom ) ) { + data.elem + .addClass( "ui-flipped-top" ); + + position.top += myOffset + atOffset + offset; + } } } + }, + flipfit: { + left: function() { + $.ui.position.flip.left.apply( this, arguments ); + $.ui.position.fit.left.apply( this, arguments ); + }, + top: function() { + $.ui.position.flip.top.apply( this, arguments ); + $.ui.position.fit.top.apply( this, arguments ); + } } }; +// fraction support test +(function () { + var testElement, testElementParent, testElementStyle, offsetLeft, i + body = document.getElementsByTagName( "body" )[ 0 ], + div = document.createElement( "div" ); + + //Create a "fake body" for testing based on method used in jQuery.support + testElement = document.createElement( body ? "div" : "body" ); + testElementStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0, + background: "none" + }; + if ( body ) { + jQuery.extend( testElementStyle, { + position: "absolute", + left: "-1000px", + top: "-1000px" + }); + } + for ( i in testElementStyle ) { + testElement.style[ i ] = testElementStyle[ i ]; + } + testElement.appendChild( div ); + testElementParent = body || document.documentElement; + testElementParent.insertBefore( testElement, testElementParent.firstChild ); + + div.style.cssText = "position: absolute; left: 10.7432222px;"; + + offsetLeft = $( div ).offset().left; + $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11; + + testElement.innerHTML = ""; + testElementParent.removeChild( testElement ); +})(); + // DEPRECATED if ( $.uiBackCompat !== false ) { // offset option diff --git a/ui/jquery.ui.selectable.js b/ui/jquery.ui.selectable.js index 75f1cee66..d7b24d4e3 100644 --- a/ui/jquery.ui.selectable.js +++ b/ui/jquery.ui.selectable.js @@ -34,6 +34,7 @@ $.widget("ui.selectable", $.ui.mouse, { var selectees; this.refresh = function() { selectees = $(self.options.filter, self.element[0]); + selectees.addClass("ui-selectee"); selectees.each(function() { var $this = $(this); var pos = $this.offset(); diff --git a/ui/jquery.ui.sortable.js b/ui/jquery.ui.sortable.js index 99798a915..62d227a3d 100644 --- a/ui/jquery.ui.sortable.js +++ b/ui/jquery.ui.sortable.js @@ -63,13 +63,11 @@ $.widget("ui.sortable", $.ui.mouse, { destroy: function() { this.element - .removeClass("ui-sortable ui-sortable-disabled") - .removeData("sortable") - .unbind(".sortable"); + .removeClass("ui-sortable ui-sortable-disabled"); this._mouseDestroy(); for ( var i = this.items.length - 1; i >= 0; i-- ) - this.items[i].item.removeData("sortable-item"); + this.items[i].item.removeData(this.widgetName + "-item"); return this; }, @@ -86,6 +84,7 @@ $.widget("ui.sortable", $.ui.mouse, { }, _mouseCapture: function(event, overrideHandle) { + var that = this; if (this.reverting) { return false; @@ -98,12 +97,12 @@ $.widget("ui.sortable", $.ui.mouse, { //Find out if the clicked node (or one of its parents) is a actual item in this.items var currentItem = null, self = this, nodes = $(event.target).parents().each(function() { - if($.data(this, 'sortable-item') == self) { + if($.data(this, that.widgetName + '-item') == self) { currentItem = $(this); return false; } }); - if($.data(event.target, 'sortable-item') == self) currentItem = $(event.target); + if($.data(event.target, that.widgetName + '-item') == self) currentItem = $(event.target); if(!currentItem) return false; if(this.options.handle && !overrideHandle) { @@ -528,7 +527,7 @@ $.widget("ui.sortable", $.ui.mouse, { for (var i = connectWith.length - 1; i >= 0; i--){ var cur = $(connectWith[i]); for (var j = cur.length - 1; j >= 0; j--){ - var inst = $.data(cur[j], 'sortable'); + var inst = $.data(cur[j], this.widgetName); if(inst && inst != this && !inst.options.disabled) { queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), inst]); } @@ -550,7 +549,7 @@ $.widget("ui.sortable", $.ui.mouse, { _removeCurrentsFromItems: function() { - var list = this.currentItem.find(":data(sortable-item)"); + var list = this.currentItem.find(":data(" + this.widgetName + "-item)"); for (var i=0; i < this.items.length; i++) { @@ -576,7 +575,7 @@ $.widget("ui.sortable", $.ui.mouse, { for (var i = connectWith.length - 1; i >= 0; i--){ var cur = $(connectWith[i]); for (var j = cur.length - 1; j >= 0; j--){ - var inst = $.data(cur[j], 'sortable'); + var inst = $.data(cur[j], this.widgetName); if(inst && inst != this && !inst.options.disabled) { queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]); this.containers.push(inst); @@ -592,7 +591,7 @@ $.widget("ui.sortable", $.ui.mouse, { for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) { var item = $(_queries[j]); - item.data('sortable-item', targetData); // Data for target checking (mouse manager) + item.data(this.widgetName + '-item', targetData); // Data for target checking (mouse manager) items.push({ item: item, diff --git a/ui/jquery.ui.spinner.js b/ui/jquery.ui.spinner.js index 9f4a6b921..5623722db 100644 --- a/ui/jquery.ui.spinner.js +++ b/ui/jquery.ui.spinner.js @@ -10,15 +10,16 @@ * Depends: * jquery.ui.core.js * jquery.ui.widget.js + * jquery.ui.button.js */ (function( $ ) { function modifier( fn ) { return function() { - var previous = this.options.value; + var previous = this.element.val(); fn.apply( this, arguments ); this._refresh(); - if ( previous !== this.options.value ) { + if ( previous !== this.element.val() ) { this._trigger( "change" ); } }; @@ -29,13 +30,13 @@ $.widget( "ui.spinner", { defaultElement: "<input>", widgetEventPrefix: "spin", options: { + culture: null, incremental: true, - max: Number.MAX_VALUE, - min: -Number.MAX_VALUE, + max: null, + min: null, numberFormat: null, page: 10, step: 1, - value: 0, change: null, spin: null, @@ -44,17 +45,26 @@ $.widget( "ui.spinner", { }, _create: function() { - this._value( this.options.value ); + this._value( this.element.val(), true ); this._draw(); - this._mousewheel(); + this._bind( this._events ); this._refresh(); + + // turning off autocomplete prevents the browser from remembering the + // value when navigating through history, so we re-enable autocomplete + // if the page is unloaded before the widget is destroyed. #7790 + this._bind( this.window, { + beforeunload: function() { + this.element.removeAttr( "autocomplete" ); + } + }); }, _getCreateOptions: function() { var options = {}, element = this.element; - $.each( [ "min", "max", "step", "value" ], function( i, option ) { + $.each( [ "min", "max", "step" ], function( i, option ) { var value = element.attr( option ); if ( value !== undefined && value.length ) { options[ option ] = value; @@ -64,6 +74,72 @@ $.widget( "ui.spinner", { return options; }, + _events: { + keydown: function( event ) { + if ( this._start( event ) && this._keydown( event ) ) { + event.preventDefault(); + } + }, + keyup: "_stop", + focus: function() { + this.uiSpinner.addClass( "ui-state-active" ); + this.previous = this.element.val(); + }, + blur: function( event ) { + this._refresh(); + this.uiSpinner.removeClass( "ui-state-active" ); + if ( this.previous !== this.element.val() ) { + this._trigger( "change", event ); + } + }, + mousewheel: function( event, delta ) { + if ( !delta ) { + return; + } + if ( !this.spinning && !this._start( event ) ) { + return false; + } + + this._spin( (delta > 0 ? 1 : -1) * this.options.step, event ); + clearTimeout( this.mousewheelTimer ); + this.mousewheelTimer = this._delay(function() { + if ( this.spinning ) { + this._stop( event ); + } + }, 100 ); + event.preventDefault(); + }, + "mousedown .ui-spinner-button": function( event ) { + // ensure focus is on (or stays on) the text field + event.preventDefault(); + if ( this.document[0].activeElement !== this.element[ 0 ] ) { + this.element.focus(); + } + + if ( this._start( event ) === false ) { + return; + } + + this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event ); + }, + "mouseup .ui-spinner-button": "_stop", + "mouseenter .ui-spinner-button": function( event ) { + // button will add ui-state-active if mouse was down while mouseleave and kept down + if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) { + return; + } + + if ( this._start( event ) === false ) { + return false; + } + this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event ); + }, + // TODO: do we really want to consider this a stop? + // shouldn't we just stop the repeater and wait until mouseup before + // we trigger the stop event? + "mouseleave .ui-spinner-button": "_stop" + }, + _draw: function() { var uiSpinner = this.uiSpinner = this.element .addClass( "ui-spinner-input" ) @@ -75,69 +151,19 @@ $.widget( "ui.spinner", { this._hoverable( uiSpinner ); this.element.attr( "role", "spinbutton" ); - this._bind({ - keydown: function( event ) { - if ( this._start( event ) && this._keydown( event ) ) { - event.preventDefault(); - } - }, - keyup: "_stop", - focus: function() { - uiSpinner.addClass( "ui-state-active" ); - this.previous = this.element.val(); - }, - blur: function( event ) { - // don't clear invalid values on blur - var value = this.element.val(); - this._value( value ); - if ( this.element.val() === "" ) { - this.element.val( value ); - } - uiSpinner.removeClass( "ui-state-active" ); - // TODO: what should trigger change? - // element.val() or options.value? - if ( this.previous !== this.element.val() ) { - this._trigger( "change", event ); - } - } - }); // button bindings this.buttons = uiSpinner.find( ".ui-spinner-button" ) .attr( "tabIndex", -1 ) .button() .removeClass( "ui-corner-all" ); - this._bind( this.buttons, { - mousedown: function( event ) { - // ensure focus is on (or stays on) the text field - event.preventDefault(); - if ( document.activeElement !== this.element[ 0 ] ) { - this.element.focus(); - } - - if ( this._start( event ) === false ) { - return; - } - this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event ); - }, - mouseup: "_stop", - mouseenter: function( event ) { - // button will add ui-state-active if mouse was down while mouseleave and kept down - if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) { - return; - } - - if ( this._start( event ) === false ) { - return false; - } - this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event ); - }, - // TODO: do we really want to consider this a stop? - // shouldn't we just stop the repeater and wait until mouseup before - // we trigger the stop event? - mouseleave: "_stop" - }); + // IE 6 doesn't understand height: 50% for the buttons + // unless the wrapper has an explicit height + if ( this.buttons.height() > Math.ceil( uiSpinner.height() * 0.5 ) && + uiSpinner.height() > 0 ) { + uiSpinner.height( uiSpinner.height() ); + } // disable spinner if element was already disabled if ( this.options.disabled ) { @@ -162,39 +188,11 @@ $.widget( "ui.spinner", { case keyCode.PAGE_DOWN: this._repeat( null, -options.page, event ); return true; - case keyCode.ENTER: - this._value( this.element.val() ); } return false; }, - _mousewheel: function() { - // need the delta normalization that mousewheel plugin provides - if ( !$.fn.mousewheel ) { - return; - } - this._bind({ - mousewheel: function( event, delta ) { - if ( !delta ) { - return; - } - if ( !this.spinning && !this._start( event ) ) { - return false; - } - - this._spin( (delta > 0 ? 1 : -1) * this.options.step, event ); - clearTimeout( this.mousewheelTimer ); - this.mousewheelTimer = setTimeout(function() { - if ( this.spinning ) { - this._stop( event ); - } - }, 100 ); - event.preventDefault(); - } - }); - }, - _uiSpinnerHtml: function() { return "<span class='ui-spinner ui-state-default ui-widget ui-widget-content ui-corner-all'></span>"; }, @@ -222,47 +220,78 @@ $.widget( "ui.spinner", { }, _repeat: function( i, steps, event ) { - var that = this; i = i || 500; clearTimeout( this.timer ); - this.timer = setTimeout(function() { - that._repeat( 40, steps, event ); + this.timer = this._delay(function() { + this._repeat( 40, steps, event ); }, i ); this._spin( steps * this.options.step, event ); }, _spin: function( step, event ) { + var value = this.value() || 0; + if ( !this.counter ) { this.counter = 1; } - var newVal = this.value() + step * this._increment( this.counter ); + value = this._adjustValue( value + step * this._increment( this.counter ) ); - // clamp the new value - newVal = this._trimValue( newVal ); - - if ( !this.spinning || this._trigger( "spin", event, { value: newVal } ) !== false) { - this._value( newVal ); + if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false) { + this._value( value ); this.counter++; } }, _increment: function( i ) { - return this.options.incremental ? - Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 ) : - 1; + var incremental = this.options.incremental; + + if ( incremental ) { + return $.isFunction( incremental ) ? + incremental( i ) : + Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 ); + } + + return 1; }, - _trimValue: function( value ) { - var options = this.options; + _precision: function() { + var precision = this._precisionOf( this.options.step ); + if ( this.options.min !== null ) { + precision = Math.max( precision, this._precisionOf( this.options.min ) ); + } + return precision; + }, + + _precisionOf: function( num ) { + var str = num.toString(), + decimal = str.indexOf( "." ); + return decimal === -1 ? 0 : str.length - decimal - 1; + }, - if ( value > options.max) { + _adjustValue: function( value ) { + var base, aboveMin, + options = this.options; + + // make sure we're at a valid step + // - find out where we are relative to the base (min or 0) + base = options.min !== null ? options.min : 0; + aboveMin = value - base; + // - round to the nearest step + aboveMin = Math.round(aboveMin / options.step) * options.step; + // - rounding is based on 0, so adjust back to our base + value = base + aboveMin; + + // fix precision from bad JS floating point math + value = parseFloat( value.toFixed( this._precision() ) ); + + // clamp the value + if ( options.max !== null && value > options.max) { return options.max; } - - if ( value < options.min ) { + if ( options.min !== null && value < options.min ) { return options.min; } @@ -282,8 +311,11 @@ $.widget( "ui.spinner", { }, _setOption: function( key, value ) { - if ( key === "value" ) { - return this._value( value ); + if ( key === "culture" || key === "numberFormat" ) { + var prevValue = this._parse( this.element.val() ); + this.options[ key ] = value; + this.element.val( this._format( prevValue ) ); + return; } this._super( "_setOption", key, value ); @@ -301,36 +333,48 @@ $.widget( "ui.spinner", { _setOptions: modifier(function( options ) { this._super( "_setOptions", options ); - - // handle any options that might cause value to change, e.g., min - this._value( this._trimValue( this.options.value ) ); + this._value( this.element.val() ); }), _parse: function( val ) { - if ( typeof val === "string" ) { - val = window.Globalize && this.options.numberFormat ? Globalize.parseFloat( val ) : +val; + if ( typeof val === "string" && val !== "" ) { + val = window.Globalize && this.options.numberFormat ? + Globalize.parseFloat( val, 10, this.options.culture ) : +val; } - return isNaN( val ) ? null : val; + return val === "" || isNaN( val ) ? null : val; }, - _format: function() { - var num = this.options.value; - return window.Globalize && this.options.numberFormat ? Globalize.format( num, this.options.numberFormat ) : num; + _format: function( value ) { + if ( value === "" ) { + return ""; + } + return window.Globalize && this.options.numberFormat ? + Globalize.format( value, this.options.numberFormat, this.options.culture ) : + value; }, _refresh: function() { - this.element - .val( this._format() ) - .attr({ - "aria-valuemin": this.options.min, - "aria-valuemax": this.options.max, - "aria-valuenow": this.options.value - }); + this.element.attr({ + "aria-valuemin": this.options.min, + "aria-valuemax": this.options.max, + // TODO: what should we do with values that can't be parsed? + "aria-valuenow": this._parse( this.element.val() ) + }); }, // update the value without triggering change - _value: function( value ) { - this.options.value = this._trimValue( this._parse(value) ); + _value: function( value, allowAny ) { + var parsed; + if ( value !== "" ) { + parsed = this._parse( value ); + if ( parsed !== null ) { + if ( !allowAny ) { + parsed = this._adjustValue( parsed ); + } + value = this._format( parsed ); + } + } + this.element.val( value ); this._refresh(); }, @@ -371,9 +415,9 @@ $.widget( "ui.spinner", { value: function( newVal ) { if ( !arguments.length ) { - return this.options.value; + return this._parse( this.element.val() ); } - this.option( "value", newVal ); + modifier( this._value ).call( this, newVal ); }, widget: function() { diff --git a/ui/jquery.ui.tabs.js b/ui/jquery.ui.tabs.js index 62bab2d9f..5eaab1aae 100644 --- a/ui/jquery.ui.tabs.js +++ b/ui/jquery.ui.tabs.js @@ -18,6 +18,19 @@ function getNextTabId() { return ++tabId; } +var isLocal = (function() { + var rhash = /#.*$/, + currentPage = location.href.replace( rhash, "" ); + + return function( anchor ) { + // clone the node to work around IE 6 not normalizing the href property + // if it's manually set, i.e., a.href = "#foo" kills the normalization + anchor = anchor.cloneNode( false ); + return anchor.hash.length > 1 && + anchor.href.replace( rhash, "" ) === currentPage; + }; +})(); + $.widget( "ui.tabs", { version: "@VERSION", options: { @@ -197,8 +210,7 @@ $.widget( "ui.tabs", { }, _processTabs: function() { - var self = this, - fragmentId = /^#.+/; // Safari 2 reports '#' for an empty hash + var self = this; this.list = this.element.find( "ol,ul" ).eq( 0 ); this.lis = $( " > li:has(a[href])", this.list ); @@ -208,30 +220,14 @@ $.widget( "ui.tabs", { this.panels = $( [] ); this.anchors.each(function( i, a ) { - var href = $( a ).attr( "href" ), - hrefBase = href.split( "#" )[ 0 ], - selector, - panel, - baseEl; - - // For dynamically created HTML that contains a hash as href IE < 8 expands - // such href to the full page url with hash and then misinterprets tab as ajax. - // Same consideration applies for an added tab with a fragment identifier - // since a[href=#fragment-identifier] does unexpectedly not match. - // Thus normalize href attribute... - if ( hrefBase && ( hrefBase === location.toString().split( "#" )[ 0 ] || - ( baseEl = $( "base" )[ 0 ]) && hrefBase === baseEl.href ) ) { - href = a.hash; - a.href = href; - } + var selector, panel; // inline tab - if ( fragmentId.test( href ) ) { - selector = href; + if ( isLocal( a ) ) { + selector = a.hash; panel = self.element.find( self._sanitizeSelector( selector ) ); // remote tab - // prevent loading the page itself if href is just "#" - } else if ( href && href !== "#" ) { + } else { var id = self._tabId( a ); selector = "#" + id; panel = self.element.find( selector ); @@ -239,9 +235,6 @@ $.widget( "ui.tabs", { panel = self._createPanel( id ); panel.insertAfter( self.panels[ i - 1 ] || self.list ); } - // invalid tab href - } else { - self.options.disabled.push( i ); } if ( panel.length) { @@ -525,21 +518,18 @@ $.widget( "ui.tabs", { options = this.options, anchor = this.anchors.eq( index ), panel = self._getPanelForTab( anchor ), - // TODO until #3808 is fixed strip fragment identifier from url - // (IE fails to load from such url) - url = anchor.attr( "href" ).replace( /#.*$/, "" ), eventData = { tab: anchor, panel: panel }; // not remote - if ( !url ) { + if ( isLocal( anchor[ 0 ] ) ) { return; } this.xhr = $.ajax({ - url: url, + url: anchor.attr( "href" ), beforeSend: function( jqXHR, settings ) { return self._trigger( "beforeLoad", event, $.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) ); @@ -551,19 +541,27 @@ $.widget( "ui.tabs", { this.xhr .success(function( response ) { - panel.html( response ); - self._trigger( "load", event, eventData ); + // TODO: IE resolves cached XHRs immediately + // remove when core #10467 is fixed + setTimeout(function() { + panel.html( response ); + self._trigger( "load", event, eventData ); + }, 1 ); }) .complete(function( jqXHR, status ) { - if ( status === "abort" ) { - self.panels.stop( false, true ); - } - - self.lis.eq( index ).removeClass( "ui-tabs-loading" ); - - if ( jqXHR === self.xhr ) { - delete self.xhr; - } + // TODO: IE resolves cached XHRs immediately + // remove when core #10467 is fixed + setTimeout(function() { + if ( status === "abort" ) { + self.panels.stop( false, true ); + } + + self.lis.eq( index ).removeClass( "ui-tabs-loading" ); + + if ( jqXHR === self.xhr ) { + delete self.xhr; + } + }, 1 ); }); } @@ -802,11 +800,14 @@ if ( $.uiBackCompat !== false ) { index = this._getIndex( index ); var options = this.options, tab = this.lis.eq( index ).remove(), - panel = this.panels.eq( index ).remove(); + panel = this._getPanelForTab( tab.find( "a[aria-controls]" ) ).remove(); // If selected tab was removed focus tab to the right or // in case the last tab was removed the tab to the left. - if ( tab.hasClass( "ui-tabs-active" ) && this.anchors.length > 1) { + // We check for more than 2 tabs, because if there are only 2, + // then when we remove this tab, there will only be one tab left + // so we don't need to detect which tab to activate. + if ( tab.hasClass( "ui-tabs-active" ) && this.anchors.length > 2 ) { this._activate( index + ( index + 1 < this.anchors.length ? 1 : -1 ) ); } diff --git a/ui/jquery.ui.tooltip.js b/ui/jquery.ui.tooltip.js index 5e32459fc..35b6f9b50 100644 --- a/ui/jquery.ui.tooltip.js +++ b/ui/jquery.ui.tooltip.js @@ -27,7 +27,7 @@ $.widget( "ui.tooltip", { position: { my: "left+15 center", at: "right center", - collision: "flip fit" + collision: "flipfit flipfit" }, show: true, tooltipClass: null, @@ -166,7 +166,7 @@ $.widget( "ui.tooltip", { // don't close if the element has focus // this prevents the tooltip from closing if you hover while focused - if ( !force && document.activeElement === target[0] ) { + if ( !force && this.document[0].activeElement === target[0] ) { return; } @@ -200,7 +200,7 @@ $.widget( "ui.tooltip", { $( "<div>" ) .addClass( "ui-tooltip-content" ) .appendTo( tooltip ); - tooltip.appendTo( document.body ); + tooltip.appendTo( this.document[0].body ); if ( $.fn.bgiframe ) { tooltip.bgiframe(); } diff --git a/ui/jquery.ui.widget.js b/ui/jquery.ui.widget.js index 4c538d020..294e321a9 100644 --- a/ui/jquery.ui.widget.js +++ b/ui/jquery.ui.widget.js @@ -195,6 +195,12 @@ $.Widget.prototype = { if ( element !== this ) { $.data( element, this.widgetName, this ); this._bind({ remove: "destroy" }); + this.document = $( element.style ? + // element within the document + element.ownerDocument : + // element is window or document + element.document || element ); + this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); } this._create(); @@ -270,10 +276,11 @@ $.Widget.prototype = { return this; }, _setOptions: function( options ) { - var self = this; - $.each( options, function( key, value ) { - self._setOption( key, value ); - }); + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } return this; }, @@ -326,13 +333,22 @@ $.Widget.prototype = { eventName = match[1] + "." + instance.widgetName, selector = match[2]; if ( selector ) { - element.delegate( selector, eventName, handlerProxy ); + instance.widget().delegate( selector, eventName, handlerProxy ); } else { element.bind( eventName, handlerProxy ); } }); }, + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + _hoverable: function( element ) { this.hoverable = this.hoverable.add( element ); this._bind( element, { @@ -377,6 +393,10 @@ $.Widget.prototype = { } } + // the original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[0]; + this.element.trigger( event, data ); args = $.isArray( data ) ? |