xyc
2024-05-17 6b24f642b01cf3cd1be0d5833273fa2867d389e1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
/**
 * SpinButton control
 *
 * Adds bells and whistles to any ordinary textbox to
 * make it look and feel like a SpinButton Control.
 *
 * Supplies {@link external:jQuery.fn.SpinButton} (and also {@link external:jQuery.loadingStylesheets})
 *
 * Originally written by George Adamson, Software Unity (george.jquery@softwareunity.com) August 2006.
 * - Added min/max options
 * - Added step size option
 * - Added bigStep (page up/down) option
 *
 * Modifications made by Mark Gibson, (mgibson@designlinks.net) September 2006:
 * - Converted to jQuery plugin
 * - Allow limited or unlimited min/max values
 * - Allow custom class names, and add class to input element
 * - Removed global vars
 * - Reset (to original or through config) when invalid value entered
 * - Repeat whilst holding mouse button down (with initial pause, like keyboard repeat)
 * - Support mouse wheel in Firefox
 * - Fix double click in IE
 * - Refactored some code and renamed some vars
 *
 * Modifications by Jeff Schiller, June 2009:
 * - provide callback function for when the value changes based on the following
 *   {@link https://www.mail-archive.com/jquery-en@googlegroups.com/msg36070.html}
 *
 * Modifications by Jeff Schiller, July 2009:
 * - improve styling for widget in Opera
 * - consistent key-repeat handling cross-browser
 *
 * Modifications by Alexis Deveria, October 2009:
 * - provide "stepfunc" callback option to allow custom function to run when changing a value
 * - Made adjustValue(0) only run on certain keyup events, not all.
 *
 * Tested in IE6, Opera9, Firefox 1.5
 *
 * | Version | Date | Author | Notes
 * |---------|------|--------|------|
 * | v1.0 | 11 Aug 2006 | George Adamson | First release
 * | v1.1 | Aug 2006 | George Adamson | Minor enhancements
 * | v1.2 | 27 Sep 2006 | Mark Gibson | Major enhancements
 * | v1.3a | 28 Sep 2006 | George Adamson | Minor enhancements
 * | v1.4 | 18 Jun 2009 | Jeff Schiller | Added callback function
 * | v1.5 | 06 Jul 2009 | Jeff Schiller | Fixes for Opera.
 * | v1.6 | 13 Oct 2009 | Alexis Deveria | Added stepfunc function
 * | v1.7 | 21 Oct 2009 | Alexis Deveria | Minor fixes.<br />Fast-repeat for keys and live updating as you type.
 * | v1.8 | 12 Jan 2010 | Benjamin Thomas | Fixes for mouseout behavior.<br />Added smallStep
 * | v1.9 | 20 May 2018 | Brett Zamir | Avoid SVGEdit dependency via `stateObj` config;<br />convert to ES6 module |
 * @module jQuerySpinButton
 * @example
 
  // Create group of settings to initialise spinbutton(s). (Optional)
  const myOptions = {
    min: 0, // Set lower limit.
    max: 100, // Set upper limit.
    step: 1, // Set increment size.
    smallStep: 0.5, // Set shift-click increment size.
    stateObj: {tool_scale: 1}, // Object to allow passing in live-updating scale
    spinClass: mySpinBtnClass, // CSS class to style the spinbutton. (Class also specifies url of the up/down button image.)
    upClass: mySpinUpClass, // CSS class for style when mouse over up button.
    downClass: mySpinDnClass // CSS class for style when mouse over down button.
  };
 
  $(function () {
    // Initialise INPUT element(s) as SpinButtons: (passing options if desired)
    $("#myInputElement").SpinButton(myOptions);
  });
 */
/**
 * @function module:jQuerySpinButton.jQuerySpinButton
 * @param {external:jQuery} $ The jQuery object to which to add the plug-in
 * @returns {external:jQuery}
*/
export default function ($) {
  if (!$.loadingStylesheets) {
    $.loadingStylesheets = [];
  }
  const stylesheet = 'spinbtn/jQuery.SpinButton.css';
  if (!$.loadingStylesheets.includes(stylesheet)) {
    $.loadingStylesheets.push(stylesheet);
  }
  /**
  * @callback module:jQuerySpinButton.StepCallback
  * @param {external:jQuery} thisArg Value of `this`
  */
  /**
  * @callback module:jQuerySpinButton.ValueCallback
  * @param {external:jQuery} thisArg Value of `this`
  * @param {Float} value Value that was changed
  */
  /**
   * @typedef {PlainObject} module:jQuerySpinButton.SpinButtonConfig
   * @property {Float} min Set lower limit
   * @property {Float} max Set upper limit.
   * @property {Float} step Set increment size.
   * @property {module:jQuerySpinButton.StepCallback} stepfunc Custom function to run when changing a value; called with `this` of object and the value to adjust
   * @property {module:jQuerySpinButton.ValueCallback} callback Called after value adjusted (with `this` of object)
   * @property {Float} smallStep Set shift-click increment size.
   * @property {PlainObject} stateObj Object to allow passing in live-updating scale
   * @property {Float} stateObj.tool_scale
   * @property {string} spinClass CSS class to style the spinbutton. (Class also specifies url of the up/down button image.)
   * @property {string} upClass CSS class for style when mouse over up button.
   * @property {string} downClass CSS class for style when mouse over down button.
   * @property {Float} page Value to be adjusted on page up/page down
   * @property {Float} reset Reset value when invalid value entered
   * @property {Float} delay Millisecond delay
   * @property {Float} interval Millisecond interval
  */
  /**
  * @function external:jQuery.fn.SpinButton
  * @param {module:jQuerySpinButton.SpinButtonConfig} cfg
  * @returns {external:jQuery}
  */
  $.fn.SpinButton = function (cfg) {
    cfg = cfg || {};
    function coord (el, prop) {
      const b = document.body;
 
      let c = el[prop];
      while ((el = el.offsetParent) && (el !== b)) {
        if (!$.browser.msie || (el.currentStyle.position !== 'relative')) {
          c += el[prop];
        }
      }
 
      return c;
    }
 
    return this.each(function () {
      this.repeating = false;
 
      // Apply specified options or defaults:
      // (Ought to refactor this some day to use $.extend() instead)
      this.spinCfg = {
        // min: cfg.min ? Number(cfg.min) : null,
        // max: cfg.max ? Number(cfg.max) : null,
        min: !isNaN(parseFloat(cfg.min)) ? Number(cfg.min) : null, // Fixes bug with min:0
        max: !isNaN(parseFloat(cfg.max)) ? Number(cfg.max) : null,
        step: cfg.step ? Number(cfg.step) : 1,
        stepfunc: cfg.stepfunc || false,
        page: cfg.page ? Number(cfg.page) : 10,
        upClass: cfg.upClass || 'up',
        downClass: cfg.downClass || 'down',
        reset: cfg.reset || this.value,
        delay: cfg.delay ? Number(cfg.delay) : 500,
        interval: cfg.interval ? Number(cfg.interval) : 100,
        _btn_width: 20,
        _direction: null,
        _delay: null,
        _repeat: null,
        callback: cfg.callback || null
      };
 
      // if a smallStep isn't supplied, use half the regular step
      this.spinCfg.smallStep = cfg.smallStep || this.spinCfg.step / 2;
 
      this.adjustValue = function (i) {
        let v;
        if (isNaN(this.value)) {
          v = this.spinCfg.reset;
        } else if (typeof this.spinCfg.stepfunc === 'function') {
          v = this.spinCfg.stepfunc(this, i);
        } else {
          // weirdest JavaScript bug ever: 5.1 + 0.1 = 5.199999999
          v = Number((Number(this.value) + Number(i)).toFixed(5));
        }
        if (this.spinCfg.min !== null) { v = Math.max(v, this.spinCfg.min); }
        if (this.spinCfg.max !== null) { v = Math.min(v, this.spinCfg.max); }
        this.value = v;
        if (typeof this.spinCfg.callback === 'function') { this.spinCfg.callback(this); }
      };
 
      $(this)
        .addClass(cfg.spinClass || 'spin-button')
 
        .mousemove(function (e) {
          // Determine which button mouse is over, or not (spin direction):
          const x = e.pageX || e.x;
          const y = e.pageY || e.y;
          const el = e.target;
          const scale = cfg.stateObj.tool_scale || 1;
          const height = $(el).height() / 2;
 
          const direction =
            (x > coord(el, 'offsetLeft') +
              el.offsetWidth * scale - this.spinCfg._btn_width)
              ? ((y < coord(el, 'offsetTop') + height * scale) ? 1 : -1) : 0;
 
          if (direction !== this.spinCfg._direction) {
            // Style up/down buttons:
            switch (direction) {
            case 1: // Up arrow:
              $(this).removeClass(this.spinCfg.downClass).addClass(this.spinCfg.upClass);
              break;
            case -1: // Down arrow:
              $(this).removeClass(this.spinCfg.upClass).addClass(this.spinCfg.downClass);
              break;
            default: // Mouse is elsewhere in the textbox
              $(this).removeClass(this.spinCfg.upClass).removeClass(this.spinCfg.downClass);
            }
 
            // Set spin direction:
            this.spinCfg._direction = direction;
          }
        })
 
        .mouseout(function () {
          // Reset up/down buttons to their normal appearance when mouse moves away:
          $(this).removeClass(this.spinCfg.upClass).removeClass(this.spinCfg.downClass);
          this.spinCfg._direction = null;
          window.clearInterval(this.spinCfg._repeat);
          window.clearTimeout(this.spinCfg._delay);
        })
 
        .mousedown(function (e) {
          if (e.button === 0 && this.spinCfg._direction !== 0) {
            // Respond to click on one of the buttons:
            const stepSize = e.shiftKey ? this.spinCfg.smallStep : this.spinCfg.step;
 
            const adjust = () => {
              this.adjustValue(this.spinCfg._direction * stepSize);
            };
 
            adjust();
 
            // Initial delay before repeating adjustment
            this.spinCfg._delay = window.setTimeout(() => {
              adjust();
              // Repeat adjust at regular intervals
              this.spinCfg._repeat = window.setInterval(adjust, this.spinCfg.interval);
            }, this.spinCfg.delay);
          }
        })
 
        .mouseup(function (e) {
          // Cancel repeating adjustment
          window.clearInterval(this.spinCfg._repeat);
          window.clearTimeout(this.spinCfg._delay);
        })
 
        .dblclick(function (e) {
          if ($.browser.msie) {
            this.adjustValue(this.spinCfg._direction * this.spinCfg.step);
          }
        })
 
        .keydown(function (e) {
          // Respond to up/down arrow keys.
          switch (e.keyCode) {
          case 38: this.adjustValue(this.spinCfg.step); break; // Up
          case 40: this.adjustValue(-this.spinCfg.step); break; // Down
          case 33: this.adjustValue(this.spinCfg.page); break; // PageUp
          case 34: this.adjustValue(-this.spinCfg.page); break; // PageDown
          }
        })
 
        /*
        http://unixpapa.com/js/key.html describes the current state-of-affairs for
        key repeat events:
        - Safari 3.1 changed their model so that keydown is reliably repeated going forward
        - Firefox and Opera still only repeat the keypress event, not the keydown
        */
        .keypress(function (e) {
          if (this.repeating) {
            // Respond to up/down arrow keys.
            switch (e.keyCode) {
            case 38: this.adjustValue(this.spinCfg.step); break; // Up
            case 40: this.adjustValue(-this.spinCfg.step); break; // Down
            case 33: this.adjustValue(this.spinCfg.page); break; // PageUp
            case 34: this.adjustValue(-this.spinCfg.page); break; // PageDown
            }
          // we always ignore the first keypress event (use the keydown instead)
          } else {
            this.repeating = true;
          }
        })
 
        // clear the 'repeating' flag
        .keyup(function (e) {
          this.repeating = false;
          switch (e.keyCode) {
          case 38: // Up
          case 40: // Down
          case 33: // PageUp
          case 34: // PageDown
          case 13: this.adjustValue(0); break; // Enter/Return
          }
        })
 
        .bind('mousewheel', function (e) {
          // Respond to mouse wheel in IE. (It returns up/dn motion in multiples of 120)
          if (e.wheelDelta >= 120) {
            this.adjustValue(this.spinCfg.step);
          } else if (e.wheelDelta <= -120) {
            this.adjustValue(-this.spinCfg.step);
          }
 
          e.preventDefault();
        })
 
        .change(function (e) {
          this.adjustValue(0);
        });
 
      if (this.addEventListener) {
        // Respond to mouse wheel in Firefox
        this.addEventListener('DOMMouseScroll', function (e) {
          if (e.detail > 0) {
            this.adjustValue(-this.spinCfg.step);
          } else if (e.detail < 0) {
            this.adjustValue(this.spinCfg.step);
          }
 
          e.preventDefault();
        });
      }
    });
  };
  return $;
}