[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/jquery/ui/ -> spinner.js (source)

   1  /*!
   2   * jQuery UI Spinner 1.13.3
   3   * https://jqueryui.com
   4   *
   5   * Copyright OpenJS Foundation and other contributors
   6   * Released under the MIT license.
   7   * https://jquery.org/license
   8   */
   9  
  10  //>>label: Spinner
  11  //>>group: Widgets
  12  //>>description: Displays buttons to easily input numbers via the keyboard or mouse.
  13  //>>docs: https://api.jqueryui.com/spinner/
  14  //>>demos: https://jqueryui.com/spinner/
  15  //>>css.structure: ../../themes/base/core.css
  16  //>>css.structure: ../../themes/base/spinner.css
  17  //>>css.theme: ../../themes/base/theme.css
  18  
  19  ( function( factory ) {
  20      "use strict";
  21  
  22      if ( typeof define === "function" && define.amd ) {
  23  
  24          // AMD. Register as an anonymous module.
  25          define( [
  26              "jquery",
  27              "./button",
  28              "../version",
  29              "../keycode",
  30              "../safe-active-element",
  31              "../widget"
  32          ], factory );
  33      } else {
  34  
  35          // Browser globals
  36          factory( jQuery );
  37      }
  38  } )( function( $ ) {
  39  "use strict";
  40  
  41  function spinnerModifier( fn ) {
  42      return function() {
  43          var previous = this.element.val();
  44          fn.apply( this, arguments );
  45          this._refresh();
  46          if ( previous !== this.element.val() ) {
  47              this._trigger( "change" );
  48          }
  49      };
  50  }
  51  
  52  $.widget( "ui.spinner", {
  53      version: "1.13.3",
  54      defaultElement: "<input>",
  55      widgetEventPrefix: "spin",
  56      options: {
  57          classes: {
  58              "ui-spinner": "ui-corner-all",
  59              "ui-spinner-down": "ui-corner-br",
  60              "ui-spinner-up": "ui-corner-tr"
  61          },
  62          culture: null,
  63          icons: {
  64              down: "ui-icon-triangle-1-s",
  65              up: "ui-icon-triangle-1-n"
  66          },
  67          incremental: true,
  68          max: null,
  69          min: null,
  70          numberFormat: null,
  71          page: 10,
  72          step: 1,
  73  
  74          change: null,
  75          spin: null,
  76          start: null,
  77          stop: null
  78      },
  79  
  80      _create: function() {
  81  
  82          // handle string values that need to be parsed
  83          this._setOption( "max", this.options.max );
  84          this._setOption( "min", this.options.min );
  85          this._setOption( "step", this.options.step );
  86  
  87          // Only format if there is a value, prevents the field from being marked
  88          // as invalid in Firefox, see #9573.
  89          if ( this.value() !== "" ) {
  90  
  91              // Format the value, but don't constrain.
  92              this._value( this.element.val(), true );
  93          }
  94  
  95          this._draw();
  96          this._on( this._events );
  97          this._refresh();
  98  
  99          // Turning off autocomplete prevents the browser from remembering the
 100          // value when navigating through history, so we re-enable autocomplete
 101          // if the page is unloaded before the widget is destroyed. #7790
 102          this._on( this.window, {
 103              beforeunload: function() {
 104                  this.element.removeAttr( "autocomplete" );
 105              }
 106          } );
 107      },
 108  
 109      _getCreateOptions: function() {
 110          var options = this._super();
 111          var element = this.element;
 112  
 113          $.each( [ "min", "max", "step" ], function( i, option ) {
 114              var value = element.attr( option );
 115              if ( value != null && value.length ) {
 116                  options[ option ] = value;
 117              }
 118          } );
 119  
 120          return options;
 121      },
 122  
 123      _events: {
 124          keydown: function( event ) {
 125              if ( this._start( event ) && this._keydown( event ) ) {
 126                  event.preventDefault();
 127              }
 128          },
 129          keyup: "_stop",
 130          focus: function() {
 131              this.previous = this.element.val();
 132          },
 133          blur: function( event ) {
 134              if ( this.cancelBlur ) {
 135                  delete this.cancelBlur;
 136                  return;
 137              }
 138  
 139              this._stop();
 140              this._refresh();
 141              if ( this.previous !== this.element.val() ) {
 142                  this._trigger( "change", event );
 143              }
 144          },
 145          mousewheel: function( event, delta ) {
 146              var activeElement = $.ui.safeActiveElement( this.document[ 0 ] );
 147              var isActive = this.element[ 0 ] === activeElement;
 148  
 149              if ( !isActive || !delta ) {
 150                  return;
 151              }
 152  
 153              if ( !this.spinning && !this._start( event ) ) {
 154                  return false;
 155              }
 156  
 157              this._spin( ( delta > 0 ? 1 : -1 ) * this.options.step, event );
 158              clearTimeout( this.mousewheelTimer );
 159              this.mousewheelTimer = this._delay( function() {
 160                  if ( this.spinning ) {
 161                      this._stop( event );
 162                  }
 163              }, 100 );
 164              event.preventDefault();
 165          },
 166          "mousedown .ui-spinner-button": function( event ) {
 167              var previous;
 168  
 169              // We never want the buttons to have focus; whenever the user is
 170              // interacting with the spinner, the focus should be on the input.
 171              // If the input is focused then this.previous is properly set from
 172              // when the input first received focus. If the input is not focused
 173              // then we need to set this.previous based on the value before spinning.
 174              previous = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] ) ?
 175                  this.previous : this.element.val();
 176  			function checkFocus() {
 177                  var isActive = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] );
 178                  if ( !isActive ) {
 179                      this.element.trigger( "focus" );
 180                      this.previous = previous;
 181  
 182                      // support: IE
 183                      // IE sets focus asynchronously, so we need to check if focus
 184                      // moved off of the input because the user clicked on the button.
 185                      this._delay( function() {
 186                          this.previous = previous;
 187                      } );
 188                  }
 189              }
 190  
 191              // Ensure focus is on (or stays on) the text field
 192              event.preventDefault();
 193              checkFocus.call( this );
 194  
 195              // Support: IE
 196              // IE doesn't prevent moving focus even with event.preventDefault()
 197              // so we set a flag to know when we should ignore the blur event
 198              // and check (again) if focus moved off of the input.
 199              this.cancelBlur = true;
 200              this._delay( function() {
 201                  delete this.cancelBlur;
 202                  checkFocus.call( this );
 203              } );
 204  
 205              if ( this._start( event ) === false ) {
 206                  return;
 207              }
 208  
 209              this._repeat( null, $( event.currentTarget )
 210                  .hasClass( "ui-spinner-up" ) ? 1 : -1, event );
 211          },
 212          "mouseup .ui-spinner-button": "_stop",
 213          "mouseenter .ui-spinner-button": function( event ) {
 214  
 215              // button will add ui-state-active if mouse was down while mouseleave and kept down
 216              if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
 217                  return;
 218              }
 219  
 220              if ( this._start( event ) === false ) {
 221                  return false;
 222              }
 223              this._repeat( null, $( event.currentTarget )
 224                  .hasClass( "ui-spinner-up" ) ? 1 : -1, event );
 225          },
 226  
 227          // TODO: do we really want to consider this a stop?
 228          // shouldn't we just stop the repeater and wait until mouseup before
 229          // we trigger the stop event?
 230          "mouseleave .ui-spinner-button": "_stop"
 231      },
 232  
 233      // Support mobile enhanced option and make backcompat more sane
 234      _enhance: function() {
 235          this.uiSpinner = this.element
 236              .attr( "autocomplete", "off" )
 237              .wrap( "<span>" )
 238              .parent()
 239  
 240                  // Add buttons
 241                  .append(
 242                      "<a></a><a></a>"
 243                  );
 244      },
 245  
 246      _draw: function() {
 247          this._enhance();
 248  
 249          this._addClass( this.uiSpinner, "ui-spinner", "ui-widget ui-widget-content" );
 250          this._addClass( "ui-spinner-input" );
 251  
 252          this.element.attr( "role", "spinbutton" );
 253  
 254          // Button bindings
 255          this.buttons = this.uiSpinner.children( "a" )
 256              .attr( "tabIndex", -1 )
 257              .attr( "aria-hidden", true )
 258              .button( {
 259                  classes: {
 260                      "ui-button": ""
 261                  }
 262              } );
 263  
 264          // TODO: Right now button does not support classes this is already updated in button PR
 265          this._removeClass( this.buttons, "ui-corner-all" );
 266  
 267          this._addClass( this.buttons.first(), "ui-spinner-button ui-spinner-up" );
 268          this._addClass( this.buttons.last(), "ui-spinner-button ui-spinner-down" );
 269          this.buttons.first().button( {
 270              "icon": this.options.icons.up,
 271              "showLabel": false
 272          } );
 273          this.buttons.last().button( {
 274              "icon": this.options.icons.down,
 275              "showLabel": false
 276          } );
 277  
 278          // IE 6 doesn't understand height: 50% for the buttons
 279          // unless the wrapper has an explicit height
 280          if ( this.buttons.height() > Math.ceil( this.uiSpinner.height() * 0.5 ) &&
 281                  this.uiSpinner.height() > 0 ) {
 282              this.uiSpinner.height( this.uiSpinner.height() );
 283          }
 284      },
 285  
 286      _keydown: function( event ) {
 287          var options = this.options,
 288              keyCode = $.ui.keyCode;
 289  
 290          switch ( event.keyCode ) {
 291          case keyCode.UP:
 292              this._repeat( null, 1, event );
 293              return true;
 294          case keyCode.DOWN:
 295              this._repeat( null, -1, event );
 296              return true;
 297          case keyCode.PAGE_UP:
 298              this._repeat( null, options.page, event );
 299              return true;
 300          case keyCode.PAGE_DOWN:
 301              this._repeat( null, -options.page, event );
 302              return true;
 303          }
 304  
 305          return false;
 306      },
 307  
 308      _start: function( event ) {
 309          if ( !this.spinning && this._trigger( "start", event ) === false ) {
 310              return false;
 311          }
 312  
 313          if ( !this.counter ) {
 314              this.counter = 1;
 315          }
 316          this.spinning = true;
 317          return true;
 318      },
 319  
 320      _repeat: function( i, steps, event ) {
 321          i = i || 500;
 322  
 323          clearTimeout( this.timer );
 324          this.timer = this._delay( function() {
 325              this._repeat( 40, steps, event );
 326          }, i );
 327  
 328          this._spin( steps * this.options.step, event );
 329      },
 330  
 331      _spin: function( step, event ) {
 332          var value = this.value() || 0;
 333  
 334          if ( !this.counter ) {
 335              this.counter = 1;
 336          }
 337  
 338          value = this._adjustValue( value + step * this._increment( this.counter ) );
 339  
 340          if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false ) {
 341              this._value( value );
 342              this.counter++;
 343          }
 344      },
 345  
 346      _increment: function( i ) {
 347          var incremental = this.options.incremental;
 348  
 349          if ( incremental ) {
 350              return typeof incremental === "function" ?
 351                  incremental( i ) :
 352                  Math.floor( i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1 );
 353          }
 354  
 355          return 1;
 356      },
 357  
 358      _precision: function() {
 359          var precision = this._precisionOf( this.options.step );
 360          if ( this.options.min !== null ) {
 361              precision = Math.max( precision, this._precisionOf( this.options.min ) );
 362          }
 363          return precision;
 364      },
 365  
 366      _precisionOf: function( num ) {
 367          var str = num.toString(),
 368              decimal = str.indexOf( "." );
 369          return decimal === -1 ? 0 : str.length - decimal - 1;
 370      },
 371  
 372      _adjustValue: function( value ) {
 373          var base, aboveMin,
 374              options = this.options;
 375  
 376          // Make sure we're at a valid step
 377          // - find out where we are relative to the base (min or 0)
 378          base = options.min !== null ? options.min : 0;
 379          aboveMin = value - base;
 380  
 381          // - round to the nearest step
 382          aboveMin = Math.round( aboveMin / options.step ) * options.step;
 383  
 384          // - rounding is based on 0, so adjust back to our base
 385          value = base + aboveMin;
 386  
 387          // Fix precision from bad JS floating point math
 388          value = parseFloat( value.toFixed( this._precision() ) );
 389  
 390          // Clamp the value
 391          if ( options.max !== null && value > options.max ) {
 392              return options.max;
 393          }
 394          if ( options.min !== null && value < options.min ) {
 395              return options.min;
 396          }
 397  
 398          return value;
 399      },
 400  
 401      _stop: function( event ) {
 402          if ( !this.spinning ) {
 403              return;
 404          }
 405  
 406          clearTimeout( this.timer );
 407          clearTimeout( this.mousewheelTimer );
 408          this.counter = 0;
 409          this.spinning = false;
 410          this._trigger( "stop", event );
 411      },
 412  
 413      _setOption: function( key, value ) {
 414          var prevValue, first, last;
 415  
 416          if ( key === "culture" || key === "numberFormat" ) {
 417              prevValue = this._parse( this.element.val() );
 418              this.options[ key ] = value;
 419              this.element.val( this._format( prevValue ) );
 420              return;
 421          }
 422  
 423          if ( key === "max" || key === "min" || key === "step" ) {
 424              if ( typeof value === "string" ) {
 425                  value = this._parse( value );
 426              }
 427          }
 428          if ( key === "icons" ) {
 429              first = this.buttons.first().find( ".ui-icon" );
 430              this._removeClass( first, null, this.options.icons.up );
 431              this._addClass( first, null, value.up );
 432              last = this.buttons.last().find( ".ui-icon" );
 433              this._removeClass( last, null, this.options.icons.down );
 434              this._addClass( last, null, value.down );
 435          }
 436  
 437          this._super( key, value );
 438      },
 439  
 440      _setOptionDisabled: function( value ) {
 441          this._super( value );
 442  
 443          this._toggleClass( this.uiSpinner, null, "ui-state-disabled", !!value );
 444          this.element.prop( "disabled", !!value );
 445          this.buttons.button( value ? "disable" : "enable" );
 446      },
 447  
 448      _setOptions: spinnerModifier( function( options ) {
 449          this._super( options );
 450      } ),
 451  
 452      _parse: function( val ) {
 453          if ( typeof val === "string" && val !== "" ) {
 454              val = window.Globalize && this.options.numberFormat ?
 455                  Globalize.parseFloat( val, 10, this.options.culture ) : +val;
 456          }
 457          return val === "" || isNaN( val ) ? null : val;
 458      },
 459  
 460      _format: function( value ) {
 461          if ( value === "" ) {
 462              return "";
 463          }
 464          return window.Globalize && this.options.numberFormat ?
 465              Globalize.format( value, this.options.numberFormat, this.options.culture ) :
 466              value;
 467      },
 468  
 469      _refresh: function() {
 470          this.element.attr( {
 471              "aria-valuemin": this.options.min,
 472              "aria-valuemax": this.options.max,
 473  
 474              // TODO: what should we do with values that can't be parsed?
 475              "aria-valuenow": this._parse( this.element.val() )
 476          } );
 477      },
 478  
 479      isValid: function() {
 480          var value = this.value();
 481  
 482          // Null is invalid
 483          if ( value === null ) {
 484              return false;
 485          }
 486  
 487          // If value gets adjusted, it's invalid
 488          return value === this._adjustValue( value );
 489      },
 490  
 491      // Update the value without triggering change
 492      _value: function( value, allowAny ) {
 493          var parsed;
 494          if ( value !== "" ) {
 495              parsed = this._parse( value );
 496              if ( parsed !== null ) {
 497                  if ( !allowAny ) {
 498                      parsed = this._adjustValue( parsed );
 499                  }
 500                  value = this._format( parsed );
 501              }
 502          }
 503          this.element.val( value );
 504          this._refresh();
 505      },
 506  
 507      _destroy: function() {
 508          this.element
 509              .prop( "disabled", false )
 510              .removeAttr( "autocomplete role aria-valuemin aria-valuemax aria-valuenow" );
 511  
 512          this.uiSpinner.replaceWith( this.element );
 513      },
 514  
 515      stepUp: spinnerModifier( function( steps ) {
 516          this._stepUp( steps );
 517      } ),
 518      _stepUp: function( steps ) {
 519          if ( this._start() ) {
 520              this._spin( ( steps || 1 ) * this.options.step );
 521              this._stop();
 522          }
 523      },
 524  
 525      stepDown: spinnerModifier( function( steps ) {
 526          this._stepDown( steps );
 527      } ),
 528      _stepDown: function( steps ) {
 529          if ( this._start() ) {
 530              this._spin( ( steps || 1 ) * -this.options.step );
 531              this._stop();
 532          }
 533      },
 534  
 535      pageUp: spinnerModifier( function( pages ) {
 536          this._stepUp( ( pages || 1 ) * this.options.page );
 537      } ),
 538  
 539      pageDown: spinnerModifier( function( pages ) {
 540          this._stepDown( ( pages || 1 ) * this.options.page );
 541      } ),
 542  
 543      value: function( newVal ) {
 544          if ( !arguments.length ) {
 545              return this._parse( this.element.val() );
 546          }
 547          spinnerModifier( this._value ).call( this, newVal );
 548      },
 549  
 550      widget: function() {
 551          return this.uiSpinner;
 552      }
 553  } );
 554  
 555  // DEPRECATED
 556  // TODO: switch return back to widget declaration at top of file when this is removed
 557  if ( $.uiBackCompat !== false ) {
 558  
 559      // Backcompat for spinner html extension points
 560      $.widget( "ui.spinner", $.ui.spinner, {
 561          _enhance: function() {
 562              this.uiSpinner = this.element
 563                  .attr( "autocomplete", "off" )
 564                  .wrap( this._uiSpinnerHtml() )
 565                  .parent()
 566  
 567                      // Add buttons
 568                      .append( this._buttonHtml() );
 569          },
 570          _uiSpinnerHtml: function() {
 571              return "<span>";
 572          },
 573  
 574          _buttonHtml: function() {
 575              return "<a></a><a></a>";
 576          }
 577      } );
 578  }
 579  
 580  return $.ui.spinner;
 581  
 582  } );


Generated : Sat Nov 23 08:20:01 2024 Cross-referenced by PHPXref