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


Generated : Sun Jul 26 08:20:18 2026 Cross-referenced by PHPXref