bootstrap-input-spinner 4.0.4 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bootstrap-input-spinner",
3
- "version": "4.0.4",
3
+ "version": "4.1.0",
4
4
  "description": "A Bootstrap 5 / jQuery plugin to create input spinner elements for number input.",
5
5
  "browser": "./src/bootstrap-input-spinner.js",
6
6
  "scripts": {
@@ -1,499 +0,0 @@
1
- <!DOCTYPE html>
2
- <!--suppress HtmlFormInputWithoutLabel -->
3
- <html lang="en">
4
- <head>
5
- <meta charset="UTF-8">
6
- <title>bootstrap-input-spinner</title>
7
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
8
- <link rel="stylesheet" href="../node_modules/prismjs/themes/prism-tomorrow.css"/>
9
- <meta name="viewport" content="width=device-width, initial-scale=1"/>
10
- <style type="text/css">
11
- h2 {
12
- margin-top: 3rem;
13
- margin-bottom: 1rem;
14
- border-bottom: 1px solid rgba(0, 0, 0, 0.5);
15
- }
16
-
17
- h3, h4 {
18
- margin-top: 2rem;
19
- }
20
-
21
- .input-group, input.test-value-input {
22
- max-width: 250px;
23
- }
24
- </style>
25
- <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
26
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script>
27
- <script src="src/bootstrap-input-spinner.js"></script>
28
- <script src="./src/custom-editors.js"></script>
29
- <script src="../node_modules/prismjs/prism.js"></script>
30
- </head>
31
- <body>
32
- <section class="container py-5">
33
- <h1>bootstrap-input-spinner</h1>
34
- <p>
35
- A Bootstrap / jQuery plugin to create input spinner elements for number input, by
36
- <a href="https://shaack.com/en">shaack.com</a> engineering. For now it needs jQuery, but I am working on it.
37
- </p>
38
- <p>This version is compatible with Bootstrap 5, but we remain a Bootstrap 4 compatible version with the branch
39
- <a href="https://github.com/shaack/bootstrap-input-spinner/tree/bootstrap4-compatible">bootstrap4-compatible</a>. npm versions 3.x are Bootstrap 5 compatible, versions 2.x Bootstrap 4 compatible.</p>
40
- <p>
41
- License: <a href="https://github.com/shaack/bootstrap-input-spinner/blob/master/LICENSE">MIT</a>
42
- </p>
43
- <h2>Features</h2>
44
- <p>The Bootstrap InputSpinner</p>
45
- <ul>
46
- <li>
47
- is <b>mobile friendly</b> and <b>responsive</b>,
48
- </li>
49
- <li>
50
- has <b>internationalized</b> number formatting,
51
- </li>
52
- <li>
53
- automatically changes the value when <b>holding a button</b>,
54
- </li>
55
- <li>
56
- allows setting a <b>prefix</b> or <b>suffix</b> text in the input,
57
- </li>
58
- <li>
59
- handles <code>val()</code> like the native element,
60
- </li>
61
- <li>
62
- <b>dynamically handles</b> changing <b>attribute values</b> like <code>disabled</code> oder
63
- <code>class</code>,
64
- </li>
65
- <li>
66
- dispatches <code>change</code> and <code>input</code> <b>events on value
67
- change</b> like the native element,
68
- </li>
69
- <li>
70
- <b>needs no extra css</b>, just Bootstrap 5.
71
- </li>
72
- </ul>
73
- <h2>Usage</h2>
74
- <p>
75
- This script enables the InputSpinner for all inputs with <code>type='number'</code>.
76
- <b>No extra css needed</b>, just Bootstrap 5.
77
- </p>
78
- <pre><code class="language-html">&lt;script src="./src/bootstrap-input-spinner.js">&lt;/script>
79
- &lt;script>
80
- $("input[type='number']").inputSpinner()
81
- &lt;/script></code></pre>
82
- <h2>Repository, documentation and npm package</h2>
83
- <p>Find the source code, more documentation and the npm package at</p>
84
- <ul>
85
- <li><a href="https://github.com/shaack/bootstrap-input-spinner">GitHub repository and documentation</a></li>
86
- <li><a href="https://www.npmjs.com/package/bootstrap-input-spinner">npm package</a></li>
87
- </ul>
88
- <h2>Examples</h2>
89
- <p>The following contains examples of the InputSpinner's main features</p>
90
-
91
- <h3>No attributes</h3>
92
- <p>
93
- <input type="number"/>
94
- </p>
95
- <pre><code class="language-html">&lt;input type="number"/></code></pre>
96
- <h3>Simple Integer</h3>
97
- <p>
98
- <input type="number" value="500" min="0" max="1000" step="10"/>
99
- </p>
100
- <pre><code
101
- class="language-html">&lt;input type="number" value="500" min="0" max="1000" step="10"/></code></pre>
102
- <h3>Floating Point</h3>
103
- <p>
104
- <input type="number" value="4.5" data-decimals="2" min="0" max="9" step="0.1"/>
105
- </p>
106
- <pre><code
107
- class="language-html">&lt;input type="number" value="4.5" data-decimals="2" min="0" max="9" step="0.1"/></code></pre>
108
-
109
- <h3>Handle <code>change</code> and <code>input</code> events and
110
- read the value from JavaScript
111
- with <code>val()</code></h3>
112
- <p>
113
- Type in a number to see the difference between <code>change</code> and <code>input</code> events.
114
- </p>
115
- <p>
116
- <input type="number" id="changedInput" value="2500" min="0" max="5000000" data-decimals="2"/>
117
- </p>
118
- <p>
119
- Value on input: <span id="valueOnInput"></span><br/>
120
- Value on change: <span id="valueOnChange"></span>
121
- </p>
122
- <script>
123
- var $changedInput = $("#changedInput")
124
- var $valueOnInput = $("#valueOnInput")
125
- var $valueOnChange = $("#valueOnChange")
126
- $changedInput.on("input", function (event) {
127
- console.log("on input", event)
128
- $valueOnInput.html($(event.target).val())
129
- // or $valueOnInput.html(event.target.value) // in vanilla js
130
- // or $valueOnInput.html($changedInput.val())
131
- })
132
- $changedInput.on("change", function (event) {
133
- console.log("on change", event)
134
- $valueOnChange.html($(event.target).val())
135
- })
136
- </script>
137
- <pre><code class="language-javascript">var $changedInput = $("#changedInput")
138
- var $valueOnInput = $("#valueOnInput")
139
- var $valueOnChange = $("#valueOnChange")
140
- $changedInput.on("input", function (event) {
141
- $valueOnInput.html($(event.target).val())
142
- // or $valueOnInput.html(event.target.value) // in vanilla js
143
- // or $valueOnInput.html($changedInput.val())
144
- })
145
- $changedInput.on("change", function (event) {
146
- $valueOnChange.html($(event.target).val())
147
- })</code></pre>
148
- <h3>Programmatic changing the value with <code>val()</code></h3>
149
- <p>
150
- <label for="inputNet">Net</label>
151
- <input type="number" id="inputNet" value="100" min="0" max="10000" step="0.01" data-decimals="2"/>
152
- </p>
153
- <p>
154
- <label for="inputGross">Gross (+19%)</label>
155
- <input type="number" id="inputGross" value="100" min="0" max="11900" step="0.01" data-decimals="2"/>
156
- </p>
157
- <script>
158
- var $inputNet = $("#inputNet")
159
- var $inputGross = $("#inputGross")
160
- $inputNet.on("input", function (event) {
161
- $inputGross.val($(event.target).val() * 1.19)
162
- })
163
- $inputGross.on("input", function (event) {
164
- $inputNet.val($(event.target).val() / 1.19)
165
- })
166
- $inputGross.val($inputNet.val() * 1.19)
167
- </script>
168
- <pre><code class="language-javascript">$inputNet.on("input", function (event) {
169
- $inputGross.val($(event.target).val() * 1.19)
170
- // or $inputGross[0].setValue(event.target.value * 1.19) // in vanilla js
171
- // or $inputGross.val($inputNet.val() * 1.19)
172
- // do all the same
173
- })
174
- $inputGross.on("input", function (event) {
175
- $inputNet.val($(event.target).val() / 1.19)
176
- })</code></pre>
177
- <h3>Attributes <code>placeholder</code> and <code>required</code></h3>
178
- <form>
179
- <p>
180
- <input placeholder="Enter a number" required type="number" value="" min="-100" max="100"/>
181
- </p>
182
- <pre><code
183
- class="language-html">&lt;input <strong>placeholder</strong>="Enter a number" <strong>required</strong> type="number" value="" min="-100" max="100"/></code></pre>
184
- <input type="submit" id="submitButton" class="btn btn-primary mb-4" value="Submit to test empty input"/>
185
- </form>
186
-
187
- <h3>Attribute <code>disabled</code>, dynamically changing</h3>
188
- <p>Attributes are handled dynamically.</p>
189
- <form>
190
- <p>
191
- <input id="inputDisabled" disabled type="number" value="50"/>
192
- </p>
193
- <div class="form-check">
194
- <input type="checkbox" checked class="form-check-input" id="disabledSwitch">
195
- <label class="form-check-label" for="disabledSwitch">Disabled</label>
196
- </div>
197
- <script>
198
- var $inputDisabled = $("#inputDisabled")
199
- var $disabledSwitch = $("#disabledSwitch")
200
- $disabledSwitch.on("change", function () {
201
- $inputDisabled.prop("disabled", $(this).prop("checked"))
202
- })
203
- </script>
204
- <pre><code class="language-html">&lt;input id="inputDisabled" disabled type="number" value="50"/>
205
- &lt;div class="form-check">
206
- &lt;input type="checkbox" checked class="form-check-input" id="disabledSwitch"/>
207
- &lt;label class="form-check-label" for="disabledSwitch">Disabled&lt;/label>
208
- &lt;/div>
209
- &lt;script>
210
- var $inputDisabled = $("#inputDisabled")
211
- var $disabledSwitch = $("#disabledSwitch")
212
- $disabledSwitch.on("change", function () {
213
- $inputDisabled.prop("disabled", $(this).prop("checked"))
214
- })
215
- &lt;/script></code></pre>
216
- </form>
217
-
218
- <h3><code>buttonsOnly</code> mode and disabled <code>autoInterval</code></h3>
219
- <p>
220
- In <code>buttonsOnly</code> mode no direct text input is allowed, the text-input
221
- gets the attribute <code>readonly</code>. But the plus and minus buttons still allow to change the value.
222
- <br/><code>autoInterval: undefined</code> additionally disables the auto increase/decrease, when you hold the button.
223
- </p>
224
- <p>
225
- <input class="buttons-only" value="5" min="1" max="10"/>
226
- </p>
227
- <pre><code class="language-js">$(".buttons-only").inputSpinner({buttonsOnly: true, autoInterval: undefined})</code></pre>
228
-
229
- <h3>Dynamically handling of the <code>class</code> attribute</h3>
230
- <p>
231
- <input id="inputChangeClass" class="is-valid" type="number" value="50"/>
232
- </p>
233
- <p>
234
- <label for="classInput">CSS Class</label>
235
- <input id="classInput" type="text" class="form-control test-value-input" value="is-valid"/>
236
- Try to change the class to "is-invalid" or "text-info".
237
- </p>
238
- <script>
239
- var $inputChangeClass = $("#inputChangeClass")
240
- var $classInput = $("#classInput")
241
- $classInput.on("input", function () {
242
- $inputChangeClass.prop("class", this.value)
243
- })
244
- </script>
245
- <pre><code class="language-html">&lt;input id="inputChangeClass" class="is-valid" type="number" value="50"/>
246
- &lt;label for="classInput">CSS Class&lt;/label>
247
- &lt;input id="classInput" type="text" class="form-control" value="is-valid"/>
248
- &lt;script>
249
- var $inputChangeClass = $("#inputChangeClass")
250
- var $classInput = $("#classInput")
251
- $classInput.on("input", function() {
252
- $inputChangeClass.prop("class", this.value);
253
- })
254
- &lt;/script></code></pre>
255
-
256
- <h3>Sizing</h3>
257
- <p>Sizing works out of the box. Just set the original inputs class to <code>form-control-sm</code> or
258
- <code>form-control-lg</code>, and
259
- the resulting group gets the class <code>input-group-sm</code> or <code>input-group-lg</code>.</p>
260
- <p>
261
- <label for="inputSmall">Small</label>
262
- <input id="inputSmall" class="form-control-sm" type="number" value="0.0" data-decimals="4" min="-1" max="1" step="0.0001"/>
263
- </p>
264
- <pre><code class="language-html">&lt;input class="form-control-sm" type="number" value="0.0" data-decimals="4" min="-1" max="1" step="0.0001"/></code></pre>
265
- <p>
266
- <label for="inputLarge">Large</label>
267
- <input id="inputLarge" class="form-control-lg" type="number" value="1000000" data-decimals="0" min="0" max="2000000" step="1"/>
268
- </p>
269
- <pre><code class="language-html">&lt;input class="form-control-lg" type="number" value="1000000" data-decimals="0" min="0" max="2000000" step="1"/></code></pre>
270
-
271
-
272
- <h3>Dynamically handling of <code>min</code>, <code>max</code>,
273
- <code>step</code> and <code>data-decimals</code></h3>
274
- <div class="row">
275
- <div class="col-lg-3">
276
- <p>
277
- <label for="minInput">min</label>
278
- <input id="minInput" type="text" class="form-control test-value-input" value="0"/>
279
- </p>
280
- </div>
281
- <div class="col-lg-3">
282
- <p>
283
- <label for="maxInput">max</label>
284
- <input id="maxInput" type="text" class="form-control test-value-input" value="100"/>
285
- </p>
286
- </div>
287
- <div class="col-lg-3">
288
- <p>
289
- <label for="stepInput">step</label>
290
- <input id="stepInput" type="text" class="form-control test-value-input" value="0.05"/>
291
- </p>
292
- </div>
293
- <div class="col-lg-3">
294
- <p>
295
- <label for="dataDecimalsInput">data-decimals</label>
296
- <input id="dataDecimalsInput" type="text" class="form-control test-value-input" value="2"/>
297
- </p>
298
- </div>
299
- </div>
300
- <p>
301
- <label for="minMaxTester">Try here</label>
302
- <input id="minMaxTester" type="number" value="50" min="0" max="100" step="0.05" data-decimals="2"/>
303
- </p>
304
- <script>
305
- var $minInput = $("#minInput")
306
- var $maxInput = $("#maxInput")
307
- var $stepInput = $("#stepInput")
308
- var $dataDecimalsInput = $("#dataDecimalsInput")
309
- var $minMaxTester = $("#minMaxTester")
310
- $minInput.on("change", function (event) {
311
- console.log("on change", event)
312
- $minMaxTester.attr("min", $minInput.val())
313
- })
314
- $maxInput.on("change", function (event) {
315
- console.log("on change", event)
316
- $minMaxTester.attr("max", $maxInput.val())
317
- })
318
- $stepInput.on("change", function (event) {
319
- console.log("on change", event)
320
- $minMaxTester.attr("step", $stepInput.val())
321
- })
322
- $dataDecimalsInput.on("change", function (event) {
323
- console.log("on change", event)
324
- $minMaxTester.attr("data-decimals", $dataDecimalsInput.val())
325
- })
326
- </script>
327
- <pre><code class="language-javascript">var $minInput = $("#minInput")
328
- var $maxInput = $("#maxInput")
329
- var $stepInput = $("#stepInput")
330
- var $dataDecimalsInput = $("#dataDecimalsInput")
331
- var $minMaxTester = $("#minMaxTester")
332
- $minInput.on("change", function (event) {
333
- $minMaxTester.attr("min", $minInput.val())
334
- })
335
- $maxInput.on("change", function (event) {
336
- $minMaxTester.attr("max", $maxInput.val())
337
- })
338
- $stepInput.on("change", function (event) {
339
- $minMaxTester.attr("step", $stepInput.val())
340
- })
341
- $dataDecimalsInput.on("change", function (event) {
342
- $minMaxTester.attr("data-decimals", $dataDecimalsInput.val())
343
- })
344
- </code></pre>
345
-
346
- <h3>Prefix and Suffix</h3>
347
- <p>
348
- <label for="inputPrefix">Prefix</label>
349
- <input id="inputPrefix" data-prefix="$" value="100.0" data-decimals="2" min="0" max="1000" step="0.1" type="number"/>
350
- </p>
351
- <pre><code class="language-html">&lt;input data-prefix="$" value="100.0" data-decimals="2" min="0" max="1000" step="0.1" type="number" /></code></pre>
352
- <p>
353
- <label for="inputSuffix">Suffix</label>
354
- <input id="inputSuffix" data-suffix="°C" value="50" min="0" max="100" type="number"/>
355
- </p>
356
- <pre><code
357
- class="language-html">&lt;input data-suffix="°C" value="50" min="0" max="100" type="number" /></code></pre>
358
-
359
-
360
- <h3>Looping the value</h3>
361
- <p>This input starts from 0 when reaching 360.</p>
362
- <p>
363
- <input type="number" id="inputLoop" value="0" data-decimals="0" min="-10" max="360" step="10"/>
364
- </p>
365
- <script>
366
- var $inputLoop = $("#inputLoop")
367
- $inputLoop.on("input", function(ignored) {
368
- var value = $inputLoop.val()
369
- value = (value < 0) ? 360 + parseInt(value, 10) : value % 360
370
- $inputLoop.val(value)
371
- })
372
- </script>
373
- <pre><code class="language-html">&lt;input step="10" type="number" id="inputLoop" value="0" data-decimals="0" min="-10" max="360"/&gt;</code></pre>
374
- <p>"Loop" the value between 0 and 360 with the <code>change</code> event in JavaScript.</p>
375
- <pre><code class="language-javascript">var $inputLoop = $("#inputLoop")
376
- $inputLoop.on("input", function(event) {
377
- var value = $inputLoop.val()
378
- value = (value < 0) ? 360 + parseInt(value, 10) : value % 360
379
- $inputLoop.val(value)
380
- })</code></pre>
381
-
382
- <h3>Custom Editors</h3>
383
-
384
- <p>An Editor defines, how the input is parsed and rendered. The inputSpinner is shipped with some custom Editors in
385
- <code>/src/custom-editors.js</code>.</p>
386
-
387
- <h4>RawEditor</h4>
388
-
389
- <p>The simplest custom Editor is the <code>RawEditor</code>, it renders just the value und parses just the value,
390
- without any
391
- changes, like a native number input. No internationalization, no digit grouping.</p>
392
- <p>
393
- <input id="rawEditor" value="1000"/>
394
- </p>
395
- <script>
396
- $("#rawEditor").inputSpinner({editor: customEditors.RawEditor})
397
- </script>
398
- <pre><code
399
- class="language-js">$("#rawEditor").inputSpinner({editor: customEditors.RawEditor})</code></pre>
400
-
401
- <h4>TimeEditor</h4>
402
-
403
- <p>The <code>TimeEditor</code> renders the number as time in hours and minutes, separated by a colon.</p>
404
- <input id="timeEditor" value="60" step="5"/>
405
- <div class="mt-1">value: <span id="timeValue"></span></div>
406
-
407
- <script>
408
- var $timeEditorInput = $("#timeEditor")
409
- $timeEditorInput.inputSpinner({editor: customEditors.TimeEditor})
410
- $("#timeValue").text($timeEditorInput.val())
411
- $timeEditorInput.on("input", function() {
412
- $("#timeValue").text($timeEditorInput.val())
413
- })
414
- </script>
415
- <pre><code
416
- class="language-js">$("#rawEditor").inputSpinner({editor: customEditors.TimeEditor})</code></pre>
417
-
418
- <h3>Styling with templates (<i>new!</i>)</h3>
419
- <p>With the new templating feature, you can almost do <b>anything, when it comes to layout</b>.</p>
420
- <h5>How about... buttons right</h5>
421
- <p>
422
- <input data-prefix="¥" id="templateButtonsRight" value="1000"/>
423
- </p>
424
- <p>
425
- This is the template for "buttons right":
426
- </p>
427
- <script>
428
- $("#templateButtonsRight").inputSpinner({
429
- template:
430
- '<div class="input-group ${groupClass}">' +
431
- '<input type="text" inputmode="decimal" style="text-align: ${textAlign}" class="form-control"/>' +
432
- '<button style="min-width: ${buttonsWidth}" class="btn btn-decrement ${buttonsClass}" type="button">${decrementButton}</button>' +
433
- '<button style="min-width: ${buttonsWidth}" class="btn btn-increment ${buttonsClass}" type="button">${incrementButton}</button>' +
434
- '</div>'
435
- })
436
- </script>
437
- <pre><code class="language-html">&lt;div class="input-group ${groupClass}">
438
- &lt;input type="text" inputmode="decimal" style="text-align: ${textAlign}" class="form-control"/>
439
- &lt;button style="min-width: ${buttonsWidth}" class="btn btn-decrement ${buttonsClass}" type="button">${decrementButton}&lt;/button>
440
- &lt;button style="min-width: ${buttonsWidth}" class="btn btn-increment ${buttonsClass}" type="button">${incrementButton}&lt;/button>
441
- &lt;/div></code></pre>
442
- <p>You can... or must use the following variables in your template:</p>
443
- <ul>
444
- <li>${groupClass}</li>
445
- <li>${textAlign}</li>
446
- <li>${buttonsWidth}</li>
447
- <li>${buttonsClass}</li>
448
- <li>${decrementButton}</li>
449
- <li>${incrementButton}</li>
450
- </ul>
451
- <p>Provide the template as configuration parameter:</p>
452
- <pre><code class="language-js">$(element).inputSpinner({template: '&lt;div class...'})</code></pre>
453
-
454
- <h3>Destroying the spinner</h3>
455
- <p>To Remove the InputSpinner and show the original input element, use</p>
456
- <pre><code class="language-javascript">$(element).inputSpinner("destroy")</code></pre>
457
- <div class="mb-3">
458
- <label for="inputDestroyCreate">Label `for` switches dynamically:</label>
459
- <input type="number" id="inputDestroyCreate" value="50"/>
460
- </div>
461
- <button id="buttonDestroy" class="btn btn-primary">destroy</button>
462
- <button id="buttonCreate" disabled="disabled" class="btn btn-primary">re-create</button>
463
- <script>
464
- var $buttonDestroy = $("#buttonDestroy")
465
- var $buttonCreate = $("#buttonCreate")
466
- var $inputDestroyCreate = $("#inputDestroyCreate")
467
- $buttonDestroy.click(function () {
468
- $inputDestroyCreate.inputSpinner("destroy")
469
- $buttonDestroy.attr("disabled", true)
470
- $buttonCreate.attr("disabled", false)
471
- })
472
- $buttonCreate.click(function () {
473
- $inputDestroyCreate.inputSpinner()
474
- $buttonDestroy.attr("disabled", false)
475
- $buttonCreate.attr("disabled", true)
476
- })
477
- </script>
478
-
479
- <div class="card my-5 border-info">
480
- <a href="https://shaack.com/works">
481
- <div class="card-body">
482
- <h4 class="mb-2 mt-0">More Bootstrap components (from shaack.com)</h4>
483
- You may want to check out our further Bootstrap extensions,
484
- <b>bootstrap-show-modal</b> and
485
- <b>bootstrap-detect-breakpoint</b>.
486
- </div>
487
- </a>
488
- </div>
489
- <p>If you find bugs or have suggestions, you may write an
490
- <a href="https://github.com/shaack/bootstrap-input-spinner/issues">issue</a>.</p>
491
- <br/><br/>
492
- </section>
493
- <!-- bootstrap needs jQuery -->
494
- <script>
495
- $("input[type='number']").inputSpinner()
496
- $(".buttons-only").inputSpinner({buttonsOnly: true, autoInterval: undefined })
497
- </script>
498
- </body>
499
- </html>
@@ -1,375 +0,0 @@
1
- /**
2
- * Author and copyright: Stefan Haack (https://shaack.com)
3
- * Repository: https://github.com/shaack/bootstrap-input-spinner
4
- * License: MIT, see file 'LICENSE'
5
- */
6
-
7
- ;(function ($) {
8
- "use strict"
9
-
10
- // the default editor for parsing and rendering
11
- const I18nEditor = function (props, element) {
12
- const locale = props.locale || "en-US"
13
-
14
- this.parse = function (customFormat) {
15
- const numberFormat = new Intl.NumberFormat(locale)
16
- const thousandSeparator = numberFormat.format(11111).replace(/1/g, '') || '.'
17
- const decimalSeparator = numberFormat.format(1.1).replace(/1/g, '')
18
- return parseFloat(customFormat
19
- .replace(new RegExp(' ', 'g'), '')
20
- .replace(new RegExp('\\' + thousandSeparator, 'g'), '')
21
- .replace(new RegExp('\\' + decimalSeparator), '.')
22
- )
23
- }
24
-
25
- this.render = function (number) {
26
- const decimals = parseInt(element.getAttribute("data-decimals")) || 0
27
- const digitGrouping = !(element.getAttribute("data-digit-grouping") === "false")
28
- const numberFormat = new Intl.NumberFormat(locale, {
29
- minimumFractionDigits: decimals,
30
- maximumFractionDigits: decimals,
31
- useGrouping: digitGrouping
32
- })
33
- return numberFormat.format(number)
34
- }
35
- }
36
-
37
- let triggerKeyPressed = false
38
- const originalVal = $.fn.val
39
- $.fn.val = function (value) {
40
- if (arguments.length >= 1) {
41
- for (let i = 0; i < this.length; i++) {
42
- if (this[i]["bootstrap-input-spinner"] && this[i].setValue) {
43
- const element = this[i]
44
- setTimeout(function () {
45
- element.setValue(value)
46
- })
47
- }
48
- }
49
- }
50
- return originalVal.apply(this, arguments)
51
- }
52
-
53
- $.fn.inputSpinner = function (methodOrProps) {
54
-
55
- if (methodOrProps === "destroy") {
56
- this.each(function () {
57
- if (this["bootstrap-input-spinner"]) {
58
- this.destroyInputSpinner()
59
- } else {
60
- console.warn("element", this, "is no bootstrap-input-spinner")
61
- }
62
- })
63
- return this
64
- }
65
-
66
- const props = {
67
- decrementButton: "<strong>&minus;</strong>", // button text
68
- incrementButton: "<strong>&plus;</strong>", // ..
69
- groupClass: "", // css class of the resulting input-group
70
- buttonsClass: "btn-outline-secondary",
71
- buttonsWidth: "2.5rem",
72
- textAlign: "center", // alignment of the entered number
73
- autoDelay: 500, // ms threshold before auto value change
74
- autoInterval: 50, // speed of auto value change, set to `undefined` to disable auto-change
75
- buttonsOnly: false, // set this `true` to disable the possibility to enter or paste the number via keyboard
76
- keyboardStepping: true, // set this to `false` to disallow the use of the up and down arrow keys to step
77
- locale: navigator.language, // the locale, per default detected automatically from the browser
78
- editor: I18nEditor, // the editor (parsing and rendering of the input)
79
- template: // the template of the input
80
- '<div class="input-group ${groupClass}">' +
81
- '<button style="min-width: ${buttonsWidth}" class="btn btn-decrement ${buttonsClass} btn-minus" type="button">${decrementButton}</button>' +
82
- '<input type="text" inputmode="decimal" style="text-align: ${textAlign}" class="form-control form-control-text-input"/>' +
83
- '<button style="min-width: ${buttonsWidth}" class="btn btn-increment ${buttonsClass} btn-plus" type="button">${incrementButton}</button>' +
84
- '</div>'
85
- }
86
-
87
- for (let option in methodOrProps) {
88
- // noinspection JSUnfilteredForInLoop
89
- props[option] = methodOrProps[option]
90
- }
91
-
92
- const html = props.template
93
- .replace(/\${groupClass}/g, props.groupClass)
94
- .replace(/\${buttonsWidth}/g, props.buttonsWidth)
95
- .replace(/\${buttonsClass}/g, props.buttonsClass)
96
- .replace(/\${decrementButton}/g, props.decrementButton)
97
- .replace(/\${incrementButton}/g, props.incrementButton)
98
- .replace(/\${textAlign}/g, props.textAlign)
99
-
100
- this.each(function () {
101
-
102
- if (this["bootstrap-input-spinner"]) {
103
- console.warn("element", this, "is already a bootstrap-input-spinner")
104
- } else {
105
-
106
- var $original = $(this)
107
- $original[0]["bootstrap-input-spinner"] = true
108
- $original.hide()
109
- $original[0].inputSpinnerEditor = new props.editor(props, this)
110
-
111
- var autoDelayHandler = null
112
- var autoIntervalHandler = null
113
-
114
- var $inputGroup = $(html)
115
- var $buttonDecrement = $inputGroup.find(".btn-decrement")
116
- var $buttonIncrement = $inputGroup.find(".btn-increment")
117
- var $input = $inputGroup.find("input")
118
- var $label = $("label[for='" + $original.attr("id") + "']")
119
- if (!$label[0]) {
120
- $label = $original.closest("label")
121
- }
122
-
123
- var min = null
124
- var max = null
125
- var step = null
126
-
127
- updateAttributes()
128
-
129
- var value = parseFloat($original[0].value)
130
- let pointerState = false
131
-
132
- const prefix = $original.attr("data-prefix") || ""
133
- const suffix = $original.attr("data-suffix") || ""
134
-
135
- if (prefix) {
136
- const prefixElement = $('<span class="input-group-text">' + prefix + '</span>')
137
- $inputGroup.find("input").before(prefixElement)
138
- }
139
- if (suffix) {
140
- const suffixElement = $('<span class="input-group-text">' + suffix + '</span>')
141
- $inputGroup.find("input").after(suffixElement)
142
- }
143
-
144
- $original[0].setValue = function (newValue) {
145
- setValue(newValue)
146
- }
147
- $original[0].destroyInputSpinner = function () {
148
- destroy()
149
- }
150
-
151
- var observer = new MutationObserver(function () {
152
- updateAttributes()
153
- setValue(value, true)
154
- })
155
- observer.observe($original[0], {attributes: true})
156
-
157
- $original.after($inputGroup)
158
-
159
- setValue(value)
160
-
161
- $input.on("paste input change focusout", function (event) {
162
- let newValue = $input[0].value
163
- const focusOut = event.type === "focusout"
164
- newValue = $original[0].inputSpinnerEditor.parse(newValue)
165
- setValue(newValue, focusOut)
166
- dispatchEvent($original, event.type)
167
- if (props.keyboardStepping && focusOut) { // stop stepping
168
- resetTimer()
169
- }
170
- }).on("keydown", function (event) {
171
- if (props.keyboardStepping) {
172
- if (event.which === 38) { // up arrow pressed
173
- event.preventDefault()
174
- if (!$buttonDecrement.prop("disabled")) {
175
- stepHandling(step)
176
- }
177
- } else if (event.which === 40) { // down arrow pressed
178
- event.preventDefault()
179
- if (!$buttonIncrement.prop("disabled")) {
180
- stepHandling(-step)
181
- }
182
- }
183
- }
184
- }).on("keyup", function (event) {
185
- // up/down arrow released
186
- if (props.keyboardStepping && (event.which === 38 || event.which === 40)) {
187
- event.preventDefault()
188
- resetTimer()
189
- }
190
- })
191
-
192
- // decrement button
193
- onPointerDown($buttonDecrement[0], function () {
194
- if (!$buttonDecrement.prop("disabled")) {
195
- pointerState = true
196
- stepHandling(-step)
197
- }
198
- })
199
- // increment button
200
- onPointerDown($buttonIncrement[0], function () {
201
- if (!$buttonIncrement.prop("disabled")) {
202
- pointerState = true
203
- stepHandling(step)
204
- }
205
- })
206
- onPointerUp(document.body, function () {
207
- if(pointerState === true) {
208
- resetTimer()
209
- dispatchEvent($original, "change")
210
- pointerState = false
211
- }
212
- })
213
- }
214
-
215
- function setValue(newValue, updateInput) {
216
- if (updateInput === undefined) {
217
- updateInput = true
218
- }
219
- if (isNaN(newValue) || newValue === "") {
220
- $original[0].value = ""
221
- if (updateInput) {
222
- $input[0].value = ""
223
- }
224
- value = NaN
225
- } else {
226
- newValue = parseFloat(newValue)
227
- newValue = Math.min(Math.max(newValue, min), max)
228
- $original[0].value = newValue
229
- if (updateInput) {
230
- $input[0].value = $original[0].inputSpinnerEditor.render(newValue)
231
- }
232
- value = newValue
233
- }
234
- }
235
-
236
- function destroy() {
237
- $original.prop("required", $input.prop("required"))
238
- observer.disconnect()
239
- resetTimer()
240
- $input.off("paste input change focusout")
241
- $inputGroup.remove()
242
- $original.show()
243
- $original[0]["bootstrap-input-spinner"] = undefined
244
- if ($label[0]) {
245
- $label.attr("for", $original.attr("id"))
246
- }
247
- }
248
-
249
- function dispatchEvent($element, type) {
250
- if (type) {
251
- setTimeout(function () {
252
- let event
253
- if (typeof (Event) === 'function') {
254
- event = new Event(type, {bubbles: true})
255
- } else { // IE
256
- event = document.createEvent('Event')
257
- event.initEvent(type, true, true)
258
- }
259
- $element[0].dispatchEvent(event)
260
- })
261
- }
262
- }
263
-
264
- function stepHandling(step) {
265
- calcStep(step)
266
- resetTimer()
267
- if(props.autoInterval !== undefined) {
268
- autoDelayHandler = setTimeout(function () {
269
- autoIntervalHandler = setInterval(function () {
270
- calcStep(step)
271
- }, props.autoInterval)
272
- }, props.autoDelay)
273
- }
274
- }
275
-
276
- function calcStep(step) {
277
- if (isNaN(value)) {
278
- value = 0
279
- }
280
- setValue(Math.round(value / step) * step + step)
281
- dispatchEvent($original, "input")
282
- }
283
-
284
- function resetTimer() {
285
- clearTimeout(autoDelayHandler)
286
- clearTimeout(autoIntervalHandler)
287
- }
288
-
289
- function updateAttributes() {
290
- // copy properties from original to the new input
291
- if ($original.prop("required")) {
292
- $input.prop("required", $original.prop("required"))
293
- $original.removeAttr('required')
294
- }
295
- $input.prop("placeholder", $original.prop("placeholder"))
296
- $input.attr("inputmode", $original.attr("inputmode") || "decimal")
297
- const disabled = $original.prop("disabled")
298
- const readonly = $original.prop("readonly")
299
- $input.prop("disabled", disabled)
300
- $input.prop("readonly", readonly || props.buttonsOnly)
301
- $buttonIncrement.prop("disabled", disabled || readonly)
302
- $buttonDecrement.prop("disabled", disabled || readonly)
303
- if (disabled || readonly) {
304
- resetTimer()
305
- }
306
- const originalClass = $original.prop("class")
307
- let groupClass = ""
308
- // sizing
309
- if (/form-control-sm/g.test(originalClass)) {
310
- groupClass = "input-group-sm"
311
- } else if (/form-control-lg/g.test(originalClass)) {
312
- groupClass = "input-group-lg"
313
- }
314
- const inputClass = originalClass.replace(/form-control(-(sm|lg))?/g, "")
315
- $inputGroup.prop("class", "input-group " + groupClass + " " + props.groupClass)
316
- $input.prop("class", "form-control " + inputClass)
317
-
318
- // update the main attributes
319
- min = isNaN($original.prop("min")) || $original.prop("min") === "" ? -Infinity : parseFloat($original.prop("min"))
320
- max = isNaN($original.prop("max")) || $original.prop("max") === "" ? Infinity : parseFloat($original.prop("max"))
321
- step = parseFloat($original.prop("step")) || 1
322
- if ($original.attr("hidden")) {
323
- $inputGroup.attr("hidden", $original.attr("hidden"))
324
- } else {
325
- $inputGroup.removeAttr("hidden")
326
- }
327
- if ($original.attr("id")) {
328
- $input.attr("id", $original.attr("id") + ":input_spinner") // give the spinner a unique id...
329
- if ($label[0]) {
330
- $label.attr("for", $input.attr("id")) // ...to rewire the label
331
- }
332
- }
333
- }
334
- })
335
-
336
- return this
337
- }
338
-
339
- function onPointerUp(element, callback) {
340
- element.addEventListener("mouseup", function (e) {
341
- callback(e)
342
- })
343
- element.addEventListener("touchend", function (e) {
344
- callback(e)
345
- })
346
- element.addEventListener("keyup", function (e) {
347
- if ((e.keyCode === 32 || e.keyCode === 13)) {
348
- triggerKeyPressed = false
349
- callback(e)
350
- }
351
- })
352
- }
353
-
354
- function onPointerDown(element, callback) {
355
- element.addEventListener("mousedown", function (e) {
356
- if (e.button === 0) {
357
- e.preventDefault()
358
- callback(e)
359
- }
360
- })
361
- element.addEventListener("touchstart", function (e) {
362
- if (e.cancelable) {
363
- e.preventDefault()
364
- }
365
- callback(e)
366
- }, {passive: false})
367
- element.addEventListener("keydown", function (e) {
368
- if ((e.keyCode === 32 || e.keyCode === 13) && !triggerKeyPressed) {
369
- triggerKeyPressed = true
370
- callback(e)
371
- }
372
- })
373
- }
374
-
375
- }(jQuery))
@@ -1,51 +0,0 @@
1
- /**
2
- * Author and copyright: Stefan Haack (https://shaack.com)
3
- * Repository: https://github.com/shaack/bootstrap-input-spinner
4
- * License: MIT, see file 'LICENSE'
5
- */
6
- const customEditors = {
7
- RawEditor: function (props, element) {
8
- this.parse = function (customFormat) {
9
- // parse nothing
10
- return customFormat
11
- }
12
- this.render = function (number) {
13
- // render raw
14
- return number
15
- }
16
- },
17
- TimeEditor: function (props, element) {
18
- // could be implemented more elegant maybe, but works
19
- this.parse = function (customFormat) {
20
- let trimmed = customFormat.trim()
21
- let sign = 1
22
- if (trimmed.charAt(0) === "-") {
23
- sign = -1
24
- trimmed = trimmed.replace("-", "")
25
- }
26
- const parts = trimmed.split(":")
27
- let hours = 0, minutes
28
- if (parts[1]) {
29
- hours = parseInt(parts[0], 10)
30
- minutes = parseInt(parts[1], 10)
31
- } else {
32
- minutes = parseInt(parts[0], 10)
33
- }
34
- return (hours * 60 + minutes) * sign
35
- }
36
- this.render = function (number) {
37
- let minutes = Math.abs(number % 60)
38
- if (minutes < 10) {
39
- minutes = "0" + minutes
40
- }
41
- let hours
42
- if (number >= 0) {
43
- hours = Math.floor(number / 60)
44
- return hours + ":" + minutes
45
- } else {
46
- hours = Math.ceil(number / 60)
47
- return "-" + Math.abs(hours) + ":" + minutes
48
- }
49
- }
50
- }
51
- }
@@ -1,17 +0,0 @@
1
- import {describe, it, assert} from "../node_modules/teevi/src/teevi.js"
2
-
3
- describe('bootstrap-input-spinner', function () {
4
- it('Should display and destroy the spinner', function () {
5
- addInput()
6
- const $input = $("input[type='number']")
7
- $input.inputSpinner()
8
- $input.inputSpinner("destroy")
9
- })
10
- })
11
-
12
- function addInput() {
13
- var testContainer = document.getElementById("testContainer")
14
- var input = document.createElement("input")
15
- input.type = "number"
16
- testContainer.append(input)
17
- }
@@ -1,19 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
6
- <title>bootstrap-input-spinner test</title>
7
- </head>
8
- <body>
9
- <div id="testContainer"></div>
10
- <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
11
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script>
12
- <script src="../src/bootstrap-input-spinner.js"></script>
13
- <script type="module">
14
- import {teevi} from "../../node_modules/teevi/src/teevi.js"
15
- import "./Test.js"
16
- teevi.run()
17
- </script>
18
- </body>
19
- </html>
@@ -1,55 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <title>input type="number"</title>
5
- <link rel="stylesheet" href="../../node_modules/bootstrap/dist/css/bootstrap.min.css"/>
6
- <link rel="stylesheet" href="test.css"/>
7
- <meta name="viewport" content="width=device-width, initial-scale=1"/>
8
- <body>
9
- <h1>This is for testing the behaviour of the native number input, compared to the bootstrap-input-spinner.</h1>
10
- First: Native Element, second: bootstrap-input-spinner.
11
-
12
- <h3>No attributes</h3>
13
- <p>
14
- <input type="number"/>
15
- <input type="number" class="bootstrap-input-spinner"/>
16
- </p>
17
-
18
-
19
- <h3>step 1, value 1.01</h3>
20
- <p>
21
- <input type="number" step="1" value="1.01"/>
22
- <input type="number" class="bootstrap-input-spinner" step="1" value="1.01"/>
23
- </p>
24
-
25
- <h3>step 2, value 6, min 5.01</h3>
26
- <p>
27
- <input type="number" step="2" min="5.01" value="6"/>
28
- <input type="number" class="bootstrap-input-spinner" step="2" min="5.01" value="6"/>
29
- </p>
30
-
31
- <h3>step 2, min 0, max 9.5</h3>
32
- <p>
33
- <input type="number" step="2" min="0" max="9.5"/>
34
- <input type="number" class="bootstrap-input-spinner" step="2" min="0" max="9.5"/>
35
- </p>
36
-
37
- <h3>step 10, min 1, max 49</h3>
38
- <p>
39
- <input type="number" step="10" min="1" max="49"/>
40
- <input type="number" class="bootstrap-input-spinner" step="10" min="1" max="49"/>
41
- </p>
42
- <h3>step 20, min -99, max 100</h3>
43
- <p>
44
- <input type="number" step="20" min="-99" max="100"/>
45
- <input type="number" class="bootstrap-input-spinner" step="20" min="-99" max="100"/>
46
- </p>
47
-
48
- <script src="../../node_modules/jquery/dist/jquery.min.js"></script>
49
- <script src="../../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
50
- <script src="../../src/bootstrap-input-spinner.js"></script>
51
- <script>
52
- $(".bootstrap-input-spinner").inputSpinner()
53
- </script>
54
- </body>
55
- </html>
@@ -1,43 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <title>Test bug #98</title>
5
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
6
- <link rel="stylesheet" href="test.css"/>
7
- <meta name="viewport" content="width=device-width, initial-scale=1"/>
8
- </head>
9
- <body>
10
- <h1>Test page for fixing <a href="https://github.com/shaack/bootstrap-input-spinner/issues/98">issue #98</a></h1>
11
- <div style="max-width: 520px">
12
- <div class="form-row">
13
- <label class="col-4"><b>Change this:</b></label>
14
- <div class="col-8 mb-1">
15
- <input type="number" value="0" id="source_b" class="form-control"/>
16
- </div>
17
- </div>
18
- <div class="box_b">
19
- <div class="form-row">
20
- <label class="col-4">Mirror 1:</label>
21
- <div class="col-8 mb-1">
22
- <input type="number" value="0" id="mirror_1b" class="form-control"/>
23
- </div>
24
- </div>
25
- <div class="form-row">
26
- <label class="col-4">Mirror 2:</label>
27
- <div class="col-8">
28
- <input type="number" value="0" id="mirror_2b" class="form-control"/>
29
- </div>
30
- </div>
31
- </div>
32
- </div>
33
- <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
34
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script>
35
- <script src="../../src/bootstrap-input-spinner.js"></script>
36
- <script>
37
- $('input').inputSpinner()
38
- $(document).on('change', 'input#source_b', function (e) {
39
- $('.box_b input').val($(e.target).val())
40
- })
41
- </script>
42
- </body>
43
- </html>
@@ -1,20 +0,0 @@
1
- body {
2
- padding: 2rem;
3
- }
4
-
5
- h1 {
6
- margin-bottom: 2rem;
7
- }
8
-
9
- h3 {
10
- margin-top: 2rem;
11
- }
12
-
13
- .input-group, input.test-value-input {
14
- max-width: 250px;
15
- }
16
-
17
- p input:first-child {
18
- margin-bottom: 0.5rem;
19
- width: 250px;
20
- }