bootstrap-input-spinner 4.1.2 → 5.0.1
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/README.md +9 -22
- package/index.html +106 -152
- package/package.json +8 -3
- package/src/InputSpinner.js +215 -208
- package/test/TestInputSpinner.js +10 -7
- package/test/index.html +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# bootstrap-input-spinner
|
|
2
2
|
|
|
3
|
-
A Bootstrap 5 extension to create input spinner elements for number input.
|
|
3
|
+
A Bootstrap 5 extension to create input spinner elements for number input. Zero dependencies other than Bootstrap 5 — **no jQuery required** since v5.0.0.
|
|
4
4
|
|
|
5
|
-
>
|
|
5
|
+
> **Unofficial third-party component** — `bootstrap-input-spinner` is maintained by [shaack.com](https://shaack.com) and is not affiliated with or endorsed by the Bootstrap team.
|
|
6
|
+
|
|
7
|
+
> Note: bootstrap-input-spinner is now an ES6 module. The legacy ES5 version has been removed; if you still need it, pin to npm `3.x`. If you still need jQuery integration, pin to `4.x`.
|
|
6
8
|
|
|
7
9
|

|
|
8
10
|
*Examples with floating-point and german localization*
|
|
@@ -72,7 +74,7 @@ Create the element in HTML. The attributes are compatible to the native `input[t
|
|
|
72
74
|
</script>
|
|
73
75
|
```
|
|
74
76
|
|
|
75
|
-
That's it. **No extra css needed**, just Bootstrap 5
|
|
77
|
+
That's it. **No extra css needed**, just Bootstrap 5.
|
|
76
78
|
|
|
77
79
|
## API Reference
|
|
78
80
|
|
|
@@ -225,19 +227,14 @@ To modify the look completely, you can use the template parameter. There is an e
|
|
|
225
227
|
|
|
226
228
|
### Programmatic change and read of value
|
|
227
229
|
|
|
228
|
-
|
|
230
|
+
Read via `element.value`, write via `element.setValue(newValue)`:
|
|
229
231
|
|
|
230
232
|
```javascript
|
|
231
233
|
const currentValue = element.value // read
|
|
232
234
|
element.setValue(newValue) // write
|
|
233
235
|
```
|
|
234
236
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
```javascript
|
|
238
|
-
const currentValue = $(element).val() // read
|
|
239
|
-
$(element).val(newValue) // write
|
|
240
|
-
```
|
|
237
|
+
> Writing directly with `element.value = 5` bypasses the editor rendering, so always use `setValue` to update the spinner value programmatically.
|
|
241
238
|
|
|
242
239
|
### Handling attributes
|
|
243
240
|
|
|
@@ -252,21 +249,11 @@ is dynamically set to `input-group-sm` or `input-group-lg`.
|
|
|
252
249
|
|
|
253
250
|
### Events
|
|
254
251
|
|
|
255
|
-
The InputSpinner
|
|
256
|
-
|
|
257
|
-
#### Event handling with vanilla JavaScript
|
|
252
|
+
The InputSpinner dispatches native `input` and `change` events on the original element, just like a native number input.
|
|
258
253
|
|
|
259
254
|
```javascript
|
|
260
255
|
element.addEventListener("change", function (event) {
|
|
261
|
-
newValue =
|
|
262
|
-
})
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
#### Event handling with jQuery syntax
|
|
266
|
-
|
|
267
|
-
```javascript
|
|
268
|
-
$(element).on("change", function (event) {
|
|
269
|
-
newValue = $(this).val()
|
|
256
|
+
const newValue = event.target.value
|
|
270
257
|
})
|
|
271
258
|
```
|
|
272
259
|
|
package/index.html
CHANGED
|
@@ -22,23 +22,19 @@
|
|
|
22
22
|
max-width: 250px;
|
|
23
23
|
}
|
|
24
24
|
</style>
|
|
25
|
-
<!-- TODO remove jQuery -->
|
|
26
|
-
<script src="https://code.jquery.com/jquery-3.6.0.min.js"
|
|
27
|
-
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
|
28
25
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
|
|
29
|
-
<script src="src/custom-editors.js"></script>
|
|
30
26
|
<script src="./node_modules/prismjs/prism.js"></script>
|
|
31
27
|
</head>
|
|
32
28
|
<body>
|
|
33
29
|
<section class="container py-5">
|
|
34
30
|
<h1>bootstrap-input-spinner</h1>
|
|
35
31
|
<p>
|
|
36
|
-
A Bootstrap
|
|
37
|
-
<a href="https://shaack.com/en">shaack.com</a> engineering.
|
|
32
|
+
A Bootstrap 5 plugin to create input spinner elements for number input, by
|
|
33
|
+
<a href="https://shaack.com/en">shaack.com</a> engineering. Zero dependencies other than Bootstrap 5.
|
|
38
34
|
</p>
|
|
39
|
-
<p>This version is compatible with Bootstrap 5
|
|
40
|
-
<a href="https://github.com/shaack/bootstrap-input-spinner/tree/bootstrap4-compatible">bootstrap4-compatible</a
|
|
41
|
-
npm versions 3.x are Bootstrap 5 compatible, versions 2.x Bootstrap 4 compatible.</p>
|
|
35
|
+
<p>This version is compatible with Bootstrap 5. For Bootstrap 4 use the
|
|
36
|
+
<a href="https://github.com/shaack/bootstrap-input-spinner/tree/bootstrap4-compatible">bootstrap4-compatible</a>
|
|
37
|
+
branch. npm versions 3.x are Bootstrap 5 compatible, versions 2.x Bootstrap 4 compatible.</p>
|
|
42
38
|
<p>
|
|
43
39
|
License: <a href="https://github.com/shaack/bootstrap-input-spinner/blob/master/LICENSE">MIT</a>
|
|
44
40
|
</p>
|
|
@@ -58,10 +54,7 @@
|
|
|
58
54
|
allows setting a <b>prefix</b> or <b>suffix</b> text in the input,
|
|
59
55
|
</li>
|
|
60
56
|
<li>
|
|
61
|
-
handles <
|
|
62
|
-
</li>
|
|
63
|
-
<li>
|
|
64
|
-
<b>dynamically handles</b> changing <b>attribute values</b> like <code>disabled</code> oder
|
|
57
|
+
<b>dynamically handles</b> changing <b>attribute values</b> like <code>disabled</code> or
|
|
65
58
|
<code>class</code>,
|
|
66
59
|
</li>
|
|
67
60
|
<li>
|
|
@@ -77,9 +70,11 @@
|
|
|
77
70
|
This script enables the InputSpinner for all inputs with <code>type='number'</code>.
|
|
78
71
|
<b>No extra css needed</b>, just Bootstrap 5.
|
|
79
72
|
</p>
|
|
80
|
-
<pre><code class="language-html"><script
|
|
81
|
-
|
|
82
|
-
|
|
73
|
+
<pre><code class="language-html"><script type="module">
|
|
74
|
+
import {InputSpinner} from "./src/InputSpinner.js"
|
|
75
|
+
for (const el of document.querySelectorAll("input[type='number']")) {
|
|
76
|
+
new InputSpinner(el)
|
|
77
|
+
}
|
|
83
78
|
</script></code></pre>
|
|
84
79
|
<h2>Repository, documentation and npm package</h2>
|
|
85
80
|
<p>Find the source code, more documentation and the npm package at</p>
|
|
@@ -109,8 +104,7 @@
|
|
|
109
104
|
class="language-html"><input type="number" value="4.5" data-decimals="2" min="0" max="9" step="0.1"/></code></pre>
|
|
110
105
|
|
|
111
106
|
<h3>Handle <code>change</code> and <code>input</code> events and
|
|
112
|
-
read the value from JavaScript
|
|
113
|
-
with <code>val()</code></h3>
|
|
107
|
+
read the value from JavaScript</h3>
|
|
114
108
|
<p>
|
|
115
109
|
Type in a number to see the difference between <code>change</code> and <code>input</code> events.
|
|
116
110
|
</p>
|
|
@@ -122,32 +116,21 @@
|
|
|
122
116
|
Value on change: <span id="valueOnChange"></span>
|
|
123
117
|
</p>
|
|
124
118
|
<script>
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
var $valueOnChange = $("#valueOnChange")
|
|
128
|
-
$changedInput.on("input", function (event) {
|
|
129
|
-
console.log("on input", event)
|
|
130
|
-
$valueOnInput.html($(event.target).val())
|
|
131
|
-
// or $valueOnInput.html(event.target.value) // in vanilla js
|
|
132
|
-
// or $valueOnInput.html($changedInput.val())
|
|
119
|
+
document.getElementById("changedInput").addEventListener("input", function (event) {
|
|
120
|
+
document.getElementById("valueOnInput").textContent = event.target.value
|
|
133
121
|
})
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
$valueOnChange.html($(event.target).val())
|
|
122
|
+
document.getElementById("changedInput").addEventListener("change", function (event) {
|
|
123
|
+
document.getElementById("valueOnChange").textContent = event.target.value
|
|
137
124
|
})
|
|
138
125
|
</script>
|
|
139
|
-
<pre><code class="language-javascript">
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
$changedInput.on("input", function (event) {
|
|
143
|
-
$valueOnInput.html($(event.target).val())
|
|
144
|
-
// or $valueOnInput.html(event.target.value) // in vanilla js
|
|
145
|
-
// or $valueOnInput.html($changedInput.val())
|
|
126
|
+
<pre><code class="language-javascript">const changedInput = document.getElementById("changedInput")
|
|
127
|
+
changedInput.addEventListener("input", function (event) {
|
|
128
|
+
document.getElementById("valueOnInput").textContent = event.target.value
|
|
146
129
|
})
|
|
147
|
-
|
|
148
|
-
|
|
130
|
+
changedInput.addEventListener("change", function (event) {
|
|
131
|
+
document.getElementById("valueOnChange").textContent = event.target.value
|
|
149
132
|
})</code></pre>
|
|
150
|
-
<h3>Programmatic changing the value
|
|
133
|
+
<h3>Programmatic changing the value</h3>
|
|
151
134
|
<p>
|
|
152
135
|
<label for="inputNet">Net</label>
|
|
153
136
|
<input type="number" id="inputNet" value="100" min="0" max="10000" step="0.01" data-decimals="2"/>
|
|
@@ -157,24 +140,23 @@ $changedInput.on("change", function (event) {
|
|
|
157
140
|
<input type="number" id="inputGross" value="100" min="0" max="11900" step="0.01" data-decimals="2"/>
|
|
158
141
|
</p>
|
|
159
142
|
<script>
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
143
|
+
window.addEventListener("load", function () {
|
|
144
|
+
const inputNet = document.getElementById("inputNet")
|
|
145
|
+
const inputGross = document.getElementById("inputGross")
|
|
146
|
+
inputNet.addEventListener("input", function (event) {
|
|
147
|
+
inputGross.setValue(event.target.value * 1.19)
|
|
148
|
+
})
|
|
149
|
+
inputGross.addEventListener("input", function (event) {
|
|
150
|
+
inputNet.setValue(event.target.value / 1.19)
|
|
151
|
+
})
|
|
152
|
+
inputGross.setValue(inputNet.value * 1.19)
|
|
167
153
|
})
|
|
168
|
-
$inputGross.val($inputNet.val() * 1.19)
|
|
169
154
|
</script>
|
|
170
|
-
<pre><code class="language-javascript"
|
|
171
|
-
|
|
172
|
-
// or $inputGross[0].setValue(event.target.value * 1.19) // in vanilla js
|
|
173
|
-
// or $inputGross.val($inputNet.val() * 1.19)
|
|
174
|
-
// do all the same
|
|
155
|
+
<pre><code class="language-javascript">inputNet.addEventListener("input", function (event) {
|
|
156
|
+
inputGross.setValue(event.target.value * 1.19)
|
|
175
157
|
})
|
|
176
|
-
|
|
177
|
-
|
|
158
|
+
inputGross.addEventListener("input", function (event) {
|
|
159
|
+
inputNet.setValue(event.target.value / 1.19)
|
|
178
160
|
})</code></pre>
|
|
179
161
|
<h3>Attributes <code>placeholder</code> and <code>required</code></h3>
|
|
180
162
|
<form>
|
|
@@ -197,10 +179,8 @@ $inputGross.on("input", function (event) {
|
|
|
197
179
|
<label class="form-check-label" for="disabledSwitch">Disabled</label>
|
|
198
180
|
</div>
|
|
199
181
|
<script>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
$disabledSwitch.on("change", function () {
|
|
203
|
-
$inputDisabled.prop("disabled", $(this).prop("checked"))
|
|
182
|
+
document.getElementById("disabledSwitch").addEventListener("change", function (event) {
|
|
183
|
+
document.getElementById("inputDisabled").disabled = event.target.checked
|
|
204
184
|
})
|
|
205
185
|
</script>
|
|
206
186
|
<pre><code class="language-html"><input id="inputDisabled" disabled type="number" value="50"/>
|
|
@@ -209,10 +189,8 @@ $inputGross.on("input", function (event) {
|
|
|
209
189
|
<label class="form-check-label" for="disabledSwitch">Disabled</label>
|
|
210
190
|
</div>
|
|
211
191
|
<script>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
$disabledSwitch.on("change", function () {
|
|
215
|
-
$inputDisabled.prop("disabled", $(this).prop("checked"))
|
|
192
|
+
document.getElementById("disabledSwitch").addEventListener("change", function (event) {
|
|
193
|
+
document.getElementById("inputDisabled").disabled = event.target.checked
|
|
216
194
|
})
|
|
217
195
|
</script></code></pre>
|
|
218
196
|
</form>
|
|
@@ -229,11 +207,10 @@ $inputGross.on("input", function (event) {
|
|
|
229
207
|
</p>
|
|
230
208
|
<script type="module">
|
|
231
209
|
import {InputSpinner} from "./src/InputSpinner.js"
|
|
232
|
-
|
|
233
|
-
new InputSpinner(element,
|
|
210
|
+
new InputSpinner(document.getElementById("buttons-only"),
|
|
234
211
|
{buttonsOnly: true, autoInterval: undefined})
|
|
235
212
|
</script>
|
|
236
|
-
<pre><code class="language-js"
|
|
213
|
+
<pre><code class="language-js">new InputSpinner(element, {buttonsOnly: true, autoInterval: undefined})</code></pre>
|
|
237
214
|
|
|
238
215
|
<h3>Dynamically handling of the <code>class</code> attribute</h3>
|
|
239
216
|
<p>
|
|
@@ -245,20 +222,16 @@ $inputGross.on("input", function (event) {
|
|
|
245
222
|
Try to change the class to "is-invalid" or "text-info".
|
|
246
223
|
</p>
|
|
247
224
|
<script>
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
$classInput.on("input", function () {
|
|
251
|
-
$inputChangeClass.prop("class", this.value)
|
|
225
|
+
document.getElementById("classInput").addEventListener("input", function (event) {
|
|
226
|
+
document.getElementById("inputChangeClass").className = event.target.value
|
|
252
227
|
})
|
|
253
228
|
</script>
|
|
254
229
|
<pre><code class="language-html"><input id="inputChangeClass" class="is-valid" type="number" value="50"/>
|
|
255
230
|
<label for="classInput">CSS Class</label>
|
|
256
231
|
<input id="classInput" type="text" class="form-control" value="is-valid"/>
|
|
257
232
|
<script>
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
$classInput.on("input", function() {
|
|
261
|
-
$inputChangeClass.prop("class", this.value);
|
|
233
|
+
document.getElementById("classInput").addEventListener("input", function (event) {
|
|
234
|
+
document.getElementById("inputChangeClass").className = event.target.value
|
|
262
235
|
})
|
|
263
236
|
</script></code></pre>
|
|
264
237
|
|
|
@@ -313,45 +286,24 @@ $inputGross.on("input", function (event) {
|
|
|
313
286
|
<input id="minMaxTester" type="number" value="50" min="0" max="100" step="0.05" data-decimals="2"/>
|
|
314
287
|
</p>
|
|
315
288
|
<script>
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
})
|
|
329
|
-
$stepInput.on("change", function (event) {
|
|
330
|
-
console.log("on change", event)
|
|
331
|
-
$minMaxTester.attr("step", $stepInput.val())
|
|
332
|
-
})
|
|
333
|
-
$dataDecimalsInput.on("change", function (event) {
|
|
334
|
-
console.log("on change", event)
|
|
335
|
-
$minMaxTester.attr("data-decimals", $dataDecimalsInput.val())
|
|
336
|
-
})
|
|
289
|
+
(function () {
|
|
290
|
+
const tester = document.getElementById("minMaxTester")
|
|
291
|
+
const bindAttr = function (inputId, attrName) {
|
|
292
|
+
document.getElementById(inputId).addEventListener("change", function (event) {
|
|
293
|
+
tester.setAttribute(attrName, event.target.value)
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
bindAttr("minInput", "min")
|
|
297
|
+
bindAttr("maxInput", "max")
|
|
298
|
+
bindAttr("stepInput", "step")
|
|
299
|
+
bindAttr("dataDecimalsInput", "data-decimals")
|
|
300
|
+
})()
|
|
337
301
|
</script>
|
|
338
|
-
<pre><code class="language-javascript">
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
var $dataDecimalsInput = $("#dataDecimalsInput")
|
|
342
|
-
var $minMaxTester = $("#minMaxTester")
|
|
343
|
-
$minInput.on("change", function (event) {
|
|
344
|
-
$minMaxTester.attr("min", $minInput.val())
|
|
345
|
-
})
|
|
346
|
-
$maxInput.on("change", function (event) {
|
|
347
|
-
$minMaxTester.attr("max", $maxInput.val())
|
|
348
|
-
})
|
|
349
|
-
$stepInput.on("change", function (event) {
|
|
350
|
-
$minMaxTester.attr("step", $stepInput.val())
|
|
351
|
-
})
|
|
352
|
-
$dataDecimalsInput.on("change", function (event) {
|
|
353
|
-
$minMaxTester.attr("data-decimals", $dataDecimalsInput.val())
|
|
302
|
+
<pre><code class="language-javascript">const tester = document.getElementById("minMaxTester")
|
|
303
|
+
document.getElementById("minInput").addEventListener("change", function (event) {
|
|
304
|
+
tester.setAttribute("min", event.target.value)
|
|
354
305
|
})
|
|
306
|
+
// ...same for max, step, data-decimals
|
|
355
307
|
</code></pre>
|
|
356
308
|
|
|
357
309
|
<h3>Prefix and Suffix</h3>
|
|
@@ -375,30 +327,32 @@ $dataDecimalsInput.on("change", function (event) {
|
|
|
375
327
|
<input type="number" id="inputLoop" value="0" data-decimals="0" min="-10" max="360" step="10"/>
|
|
376
328
|
</p>
|
|
377
329
|
<script>
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
330
|
+
window.addEventListener("load", function () {
|
|
331
|
+
const inputLoop = document.getElementById("inputLoop")
|
|
332
|
+
inputLoop.addEventListener("input", function () {
|
|
333
|
+
let value = parseInt(inputLoop.value, 10)
|
|
334
|
+
value = (value < 0) ? 360 + value : value % 360
|
|
335
|
+
inputLoop.setValue(value)
|
|
336
|
+
})
|
|
383
337
|
})
|
|
384
338
|
</script>
|
|
385
339
|
<pre><code class="language-html"><input step="10" type="number" id="inputLoop" value="0" data-decimals="0" min="-10" max="360"/></code></pre>
|
|
386
|
-
<p>"Loop" the value between 0 and 360 with the <code>
|
|
387
|
-
<pre><code class="language-javascript">
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
value = (value < 0) ? 360 +
|
|
391
|
-
|
|
340
|
+
<p>"Loop" the value between 0 and 360 with the <code>input</code> event in JavaScript.</p>
|
|
341
|
+
<pre><code class="language-javascript">const inputLoop = document.getElementById("inputLoop")
|
|
342
|
+
inputLoop.addEventListener("input", function () {
|
|
343
|
+
let value = parseInt(inputLoop.value, 10)
|
|
344
|
+
value = (value < 0) ? 360 + value : value % 360
|
|
345
|
+
inputLoop.setValue(value)
|
|
392
346
|
})</code></pre>
|
|
393
347
|
|
|
394
348
|
<h3>Custom Editors</h3>
|
|
395
349
|
|
|
396
|
-
<p>An Editor defines
|
|
397
|
-
<code>/src/custom-editors.js</code
|
|
350
|
+
<p>An Editor defines how the input is parsed and rendered. The inputSpinner ships some custom Editors in
|
|
351
|
+
<code>/src/custom-editors.js</code>, available as named ES exports.</p>
|
|
398
352
|
|
|
399
353
|
<h4>RawEditor</h4>
|
|
400
354
|
|
|
401
|
-
<p>The simplest custom Editor is the <code>RawEditor</code>, it renders just the value
|
|
355
|
+
<p>The simplest custom Editor is the <code>RawEditor</code>, it renders just the value and parses just the value,
|
|
402
356
|
without any
|
|
403
357
|
changes, like a native number input. No internationalization, no digit grouping.</p>
|
|
404
358
|
<p>
|
|
@@ -406,12 +360,12 @@ $inputLoop.on("input", function(event) {
|
|
|
406
360
|
</p>
|
|
407
361
|
<script type="module">
|
|
408
362
|
import {InputSpinner} from "./src/InputSpinner.js"
|
|
409
|
-
|
|
410
|
-
new InputSpinner(document.getElementById("rawEditor"),
|
|
411
|
-
{editor: customEditors.RawEditor})
|
|
363
|
+
import {RawEditor} from "./src/custom-editors.js"
|
|
364
|
+
new InputSpinner(document.getElementById("rawEditor"), {editor: RawEditor})
|
|
412
365
|
</script>
|
|
413
|
-
<pre><code
|
|
414
|
-
|
|
366
|
+
<pre><code class="language-js">import {InputSpinner} from "./src/InputSpinner.js"
|
|
367
|
+
import {RawEditor} from "./src/custom-editors.js"
|
|
368
|
+
new InputSpinner(document.getElementById("rawEditor"), {editor: RawEditor})</code></pre>
|
|
415
369
|
|
|
416
370
|
<h4>TimeEditor</h4>
|
|
417
371
|
|
|
@@ -420,19 +374,21 @@ $inputLoop.on("input", function(event) {
|
|
|
420
374
|
<div class="mt-1">value: <span id="timeValue"></span></div>
|
|
421
375
|
<script type="module">
|
|
422
376
|
import {InputSpinner} from "./src/InputSpinner.js"
|
|
377
|
+
import {TimeEditor} from "./src/custom-editors.js"
|
|
423
378
|
|
|
424
379
|
const element = document.getElementById("timeEditor")
|
|
425
|
-
new InputSpinner(element, {editor:
|
|
380
|
+
new InputSpinner(element, {editor: TimeEditor})
|
|
426
381
|
element.addEventListener("input", () => {
|
|
427
382
|
document.getElementById("timeValue").textContent = element.value
|
|
428
383
|
})
|
|
429
384
|
document.getElementById("timeValue").textContent = element.value
|
|
430
385
|
</script>
|
|
431
|
-
<pre><code
|
|
432
|
-
|
|
386
|
+
<pre><code class="language-js">import {InputSpinner} from "./src/InputSpinner.js"
|
|
387
|
+
import {TimeEditor} from "./src/custom-editors.js"
|
|
388
|
+
new InputSpinner(document.getElementById("timeEditor"), {editor: TimeEditor})</code></pre>
|
|
433
389
|
|
|
434
|
-
<h3>Styling with templates
|
|
435
|
-
<p>With the
|
|
390
|
+
<h3>Styling with templates</h3>
|
|
391
|
+
<p>With the templating feature, you can almost do <b>anything, when it comes to layout</b>.</p>
|
|
436
392
|
<h5>How about... buttons right</h5>
|
|
437
393
|
<p>
|
|
438
394
|
<input data-prefix="¥" id="templateButtonsRight" value="1000"/>
|
|
@@ -468,11 +424,11 @@ $inputLoop.on("input", function(event) {
|
|
|
468
424
|
<li>${incrementButton}</li>
|
|
469
425
|
</ul>
|
|
470
426
|
<p>Provide the template as configuration parameter:</p>
|
|
471
|
-
<pre><code class="language-js"
|
|
427
|
+
<pre><code class="language-js">new InputSpinner(element, {template: '<div class...'})</code></pre>
|
|
472
428
|
|
|
473
429
|
<h3>Destroying the spinner</h3>
|
|
474
|
-
<p>To
|
|
475
|
-
<pre><code class="language-javascript"
|
|
430
|
+
<p>To remove the InputSpinner and show the original input element, use</p>
|
|
431
|
+
<pre><code class="language-javascript">element.destroyInputSpinner()</code></pre>
|
|
476
432
|
<div class="mb-3">
|
|
477
433
|
<label for="inputDestroyCreate">Label `for` switches dynamically:</label>
|
|
478
434
|
<input type="number" id="inputDestroyCreate" value="50"/>
|
|
@@ -481,18 +437,18 @@ $inputLoop.on("input", function(event) {
|
|
|
481
437
|
<button id="buttonCreate" disabled="disabled" class="btn btn-primary">re-create</button>
|
|
482
438
|
<script type="module">
|
|
483
439
|
import {InputSpinner} from "./src/InputSpinner.js"
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
440
|
+
const buttonDestroy = document.getElementById("buttonDestroy")
|
|
441
|
+
const buttonCreate = document.getElementById("buttonCreate")
|
|
442
|
+
const inputDestroyCreate = document.getElementById("inputDestroyCreate")
|
|
443
|
+
buttonDestroy.addEventListener("click", function () {
|
|
444
|
+
inputDestroyCreate.destroyInputSpinner()
|
|
445
|
+
buttonDestroy.disabled = true
|
|
446
|
+
buttonCreate.disabled = false
|
|
491
447
|
})
|
|
492
|
-
|
|
493
|
-
new InputSpinner(
|
|
494
|
-
|
|
495
|
-
|
|
448
|
+
buttonCreate.addEventListener("click", function () {
|
|
449
|
+
new InputSpinner(inputDestroyCreate)
|
|
450
|
+
buttonDestroy.disabled = false
|
|
451
|
+
buttonCreate.disabled = true
|
|
496
452
|
})
|
|
497
453
|
</script>
|
|
498
454
|
|
|
@@ -510,13 +466,11 @@ $inputLoop.on("input", function(event) {
|
|
|
510
466
|
<a href="https://github.com/shaack/bootstrap-input-spinner/issues">issue</a>.</p>
|
|
511
467
|
<br/><br/>
|
|
512
468
|
</section>
|
|
513
|
-
<!-- bootstrap needs jQuery -->
|
|
514
469
|
<script type="module">
|
|
515
470
|
import {InputSpinner} from "./src/InputSpinner.js"
|
|
516
471
|
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
new InputSpinner(inputSpinnerElement)
|
|
472
|
+
for (const el of document.querySelectorAll("input[type='number']")) {
|
|
473
|
+
new InputSpinner(el)
|
|
520
474
|
}
|
|
521
475
|
</script>
|
|
522
476
|
</body>
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bootstrap-input-spinner",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A Bootstrap 5
|
|
5
|
-
"browser": "./src/
|
|
3
|
+
"version": "5.0.1",
|
|
4
|
+
"description": "A Bootstrap 5 plugin to create input spinner elements for number input.",
|
|
5
|
+
"browser": "./src/InputSpinner.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/InputSpinner.js",
|
|
9
|
+
"./custom-editors": "./src/custom-editors.js"
|
|
10
|
+
},
|
|
6
11
|
"scripts": {
|
|
7
12
|
"test": "tput setaf 4;echo open test/index.html in your browser for testing."
|
|
8
13
|
},
|
package/src/InputSpinner.js
CHANGED
|
@@ -6,45 +6,45 @@
|
|
|
6
6
|
|
|
7
7
|
// the default editor for parsing and rendering
|
|
8
8
|
const I18nEditor = function (props, element) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
const locale = props.locale || "en-US"
|
|
10
|
+
|
|
11
|
+
this.parse = function (customFormat) {
|
|
12
|
+
const numberFormat = new Intl.NumberFormat(locale)
|
|
13
|
+
const thousandSeparator = numberFormat.format(11111).replace(/1/g, '') || '.'
|
|
14
|
+
const decimalSeparator = numberFormat.format(1.1).replace(/1/g, '')
|
|
15
|
+
return parseFloat(customFormat
|
|
16
|
+
.replace(new RegExp(' ', 'g'), '')
|
|
17
|
+
.replace(new RegExp('\\' + thousandSeparator, 'g'), '')
|
|
18
|
+
.replace(new RegExp('\\' + decimalSeparator), '.')
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
22
|
+
this.render = function (number) {
|
|
23
|
+
const decimals = parseInt(element.getAttribute("data-decimals")) || 0
|
|
24
|
+
const digitGrouping = !(element.getAttribute("data-digit-grouping") === "false")
|
|
25
|
+
const numberFormat = new Intl.NumberFormat(locale, {
|
|
26
|
+
minimumFractionDigits: decimals,
|
|
27
|
+
maximumFractionDigits: decimals,
|
|
28
|
+
useGrouping: digitGrouping
|
|
29
|
+
})
|
|
30
|
+
return numberFormat.format(number)
|
|
32
31
|
}
|
|
32
|
+
}
|
|
33
33
|
|
|
34
34
|
let triggerKeyPressed = false
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
|
|
36
|
+
function parseTemplate(html) {
|
|
37
|
+
const tpl = document.createElement("template")
|
|
38
|
+
tpl.innerHTML = html.trim()
|
|
39
|
+
return tpl.content.firstElementChild
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseNumberAttr(el, name, fallback) {
|
|
43
|
+
const raw = el.getAttribute(name)
|
|
44
|
+
if (raw === null || raw === "" || isNaN(parseFloat(raw))) {
|
|
45
|
+
return fallback
|
|
46
46
|
}
|
|
47
|
-
return
|
|
47
|
+
return parseFloat(raw)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export class InputSpinner {
|
|
@@ -53,18 +53,6 @@ export class InputSpinner {
|
|
|
53
53
|
|
|
54
54
|
const self = this
|
|
55
55
|
this.element = element
|
|
56
|
-
/*
|
|
57
|
-
if (props === "destroy") { // todo replace with method
|
|
58
|
-
this.each(function () {
|
|
59
|
-
if (this["bootstrap-input-spinner"]) {
|
|
60
|
-
this.destroyInputSpinner()
|
|
61
|
-
} else {
|
|
62
|
-
console.warn("element", this, "is no bootstrap-input-spinner")
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
return this
|
|
66
|
-
}
|
|
67
|
-
*/
|
|
68
56
|
|
|
69
57
|
this.props = {
|
|
70
58
|
decrementButton: "<strong>−</strong>", // button text
|
|
@@ -97,168 +85,190 @@ export class InputSpinner {
|
|
|
97
85
|
.replace(/\${incrementButton}/g, this.props.incrementButton)
|
|
98
86
|
.replace(/\${textAlign}/g, this.props.textAlign)
|
|
99
87
|
|
|
100
|
-
if (
|
|
101
|
-
console.warn("element",
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.$original = $(this.element)
|
|
105
|
-
this.$original[0]["bootstrap-input-spinner"] = true
|
|
106
|
-
this.$original.hide()
|
|
107
|
-
this.$original[0].inputSpinnerEditor = new this.props.editor(this.props, this.element)
|
|
108
|
-
|
|
109
|
-
this.autoDelayHandler = null
|
|
110
|
-
this.autoIntervalHandler = null
|
|
111
|
-
|
|
112
|
-
this.$inputGroup = $(html)
|
|
113
|
-
this.$buttonDecrement = this.$inputGroup.find(".btn-decrement")
|
|
114
|
-
this.$buttonIncrement = this.$inputGroup.find(".btn-increment")
|
|
115
|
-
this.$input = this.$inputGroup.find("input")
|
|
116
|
-
this.$label = $("label[for='" + this.$original.attr("id") + "']")
|
|
117
|
-
if (!this.$label[0]) {
|
|
118
|
-
this.$label = this.$original.closest("label")
|
|
119
|
-
}
|
|
88
|
+
if (element["bootstrap-input-spinner"]) {
|
|
89
|
+
console.warn("element", element, "is already a bootstrap-input-spinner")
|
|
90
|
+
return
|
|
91
|
+
}
|
|
120
92
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
93
|
+
this.original = element
|
|
94
|
+
this.original["bootstrap-input-spinner"] = true
|
|
95
|
+
this.original.style.display = "none"
|
|
96
|
+
this.original.inputSpinnerEditor = new this.props.editor(this.props, element)
|
|
124
97
|
|
|
125
|
-
|
|
98
|
+
this.autoDelayHandler = null
|
|
99
|
+
this.autoIntervalHandler = null
|
|
126
100
|
|
|
127
|
-
|
|
128
|
-
|
|
101
|
+
this.inputGroup = parseTemplate(html)
|
|
102
|
+
this.buttonDecrement = this.inputGroup.querySelector(".btn-decrement")
|
|
103
|
+
this.buttonIncrement = this.inputGroup.querySelector(".btn-increment")
|
|
104
|
+
this.input = this.inputGroup.querySelector("input")
|
|
129
105
|
|
|
130
|
-
|
|
131
|
-
|
|
106
|
+
this.label = null
|
|
107
|
+
if (this.original.id) {
|
|
108
|
+
this.label = document.querySelector("label[for='" + this.original.id + "']")
|
|
109
|
+
}
|
|
110
|
+
if (!this.label) {
|
|
111
|
+
this.label = this.original.closest("label")
|
|
112
|
+
}
|
|
132
113
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
if (suffix) {
|
|
138
|
-
const suffixElement = $('<span class="input-group-text">' + suffix + '</span>')
|
|
139
|
-
this.$inputGroup.find("input").after(suffixElement)
|
|
140
|
-
}
|
|
114
|
+
this.min = null
|
|
115
|
+
this.max = null
|
|
116
|
+
this.step = null
|
|
141
117
|
|
|
142
|
-
|
|
143
|
-
setValue(newValue)
|
|
144
|
-
}
|
|
145
|
-
this.$original[0].destroyInputSpinner = function () {
|
|
146
|
-
destroy()
|
|
147
|
-
}
|
|
118
|
+
updateAttributes()
|
|
148
119
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
setValue(self.value, true)
|
|
152
|
-
})
|
|
153
|
-
this.observer.observe(this.$original[0], {attributes: true})
|
|
120
|
+
this.value = parseFloat(this.original.value)
|
|
121
|
+
let pointerState = false
|
|
154
122
|
|
|
155
|
-
|
|
123
|
+
const prefix = this.original.getAttribute("data-prefix") || ""
|
|
124
|
+
const suffix = this.original.getAttribute("data-suffix") || ""
|
|
156
125
|
|
|
157
|
-
|
|
126
|
+
if (prefix) {
|
|
127
|
+
const prefixElement = document.createElement("span")
|
|
128
|
+
prefixElement.className = "input-group-text"
|
|
129
|
+
prefixElement.textContent = prefix
|
|
130
|
+
this.input.parentNode.insertBefore(prefixElement, this.input)
|
|
131
|
+
}
|
|
132
|
+
if (suffix) {
|
|
133
|
+
const suffixElement = document.createElement("span")
|
|
134
|
+
suffixElement.className = "input-group-text"
|
|
135
|
+
suffixElement.textContent = suffix
|
|
136
|
+
this.input.parentNode.insertBefore(suffixElement, this.input.nextSibling)
|
|
137
|
+
}
|
|
158
138
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
dispatchEvent(self.$original, event.type)
|
|
166
|
-
}
|
|
167
|
-
if (self.props.keyboardStepping && focusOut) { // stop stepping
|
|
168
|
-
resetTimer()
|
|
169
|
-
}
|
|
170
|
-
}).on("keydown", function (event) {
|
|
171
|
-
if (self.props.keyboardStepping) {
|
|
172
|
-
if (event.which === 38) { // up arrow pressed
|
|
173
|
-
event.preventDefault()
|
|
174
|
-
if (!self.$buttonDecrement.prop("disabled")) {
|
|
175
|
-
stepHandling(self.step)
|
|
176
|
-
}
|
|
177
|
-
} else if (event.which === 40) { // down arrow pressed
|
|
178
|
-
event.preventDefault()
|
|
179
|
-
if (!self.$buttonIncrement.prop("disabled")) {
|
|
180
|
-
stepHandling(-self.step)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}).on("keyup", function (event) {
|
|
185
|
-
// up/down arrow released
|
|
186
|
-
if (self.props.keyboardStepping && (event.which === 38 || event.which === 40)) {
|
|
187
|
-
event.preventDefault()
|
|
188
|
-
resetTimer()
|
|
189
|
-
}
|
|
190
|
-
})
|
|
139
|
+
this.original.setValue = function (newValue) {
|
|
140
|
+
setValue(newValue)
|
|
141
|
+
}
|
|
142
|
+
this.original.destroyInputSpinner = function () {
|
|
143
|
+
destroy()
|
|
144
|
+
}
|
|
191
145
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
146
|
+
this.observer = new MutationObserver(function () {
|
|
147
|
+
updateAttributes()
|
|
148
|
+
setValue(self.value, true)
|
|
149
|
+
})
|
|
150
|
+
this.observer.observe(this.original, {attributes: true})
|
|
151
|
+
|
|
152
|
+
this.original.parentNode.insertBefore(this.inputGroup, this.original.nextSibling)
|
|
153
|
+
|
|
154
|
+
setValue(this.value)
|
|
155
|
+
|
|
156
|
+
// Track listeners so destroy() can detach them cleanly.
|
|
157
|
+
this._teardown = []
|
|
158
|
+
const bind = function (target, type, handler, options) {
|
|
159
|
+
target.addEventListener(type, handler, options)
|
|
160
|
+
self._teardown.push(function () {
|
|
161
|
+
target.removeEventListener(type, handler, options)
|
|
198
162
|
})
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const onInputEvent = function (event) {
|
|
166
|
+
let newValue = self.input.value
|
|
167
|
+
const focusOut = event.type === "focusout"
|
|
168
|
+
if (!self.props.buttonsOnly) {
|
|
169
|
+
newValue = self.original.inputSpinnerEditor.parse(newValue)
|
|
170
|
+
setValue(newValue, focusOut)
|
|
171
|
+
dispatchEvent(self.original, event.type)
|
|
172
|
+
}
|
|
173
|
+
if (self.props.keyboardStepping && focusOut) { // stop stepping
|
|
174
|
+
resetTimer()
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
bind(this.input, "paste", onInputEvent)
|
|
178
|
+
bind(this.input, "input", onInputEvent)
|
|
179
|
+
bind(this.input, "change", onInputEvent)
|
|
180
|
+
bind(this.input, "focusout", onInputEvent)
|
|
181
|
+
|
|
182
|
+
bind(this.input, "keydown", function (event) {
|
|
183
|
+
if (!self.props.keyboardStepping) return
|
|
184
|
+
if (event.key === "ArrowUp" || event.keyCode === 38) {
|
|
185
|
+
event.preventDefault()
|
|
186
|
+
if (!self.buttonIncrement.disabled) {
|
|
203
187
|
stepHandling(self.step)
|
|
204
188
|
}
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
dispatchEvent(self.$original, "change")
|
|
210
|
-
pointerState = false
|
|
189
|
+
} else if (event.key === "ArrowDown" || event.keyCode === 40) {
|
|
190
|
+
event.preventDefault()
|
|
191
|
+
if (!self.buttonDecrement.disabled) {
|
|
192
|
+
stepHandling(-self.step)
|
|
211
193
|
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
bind(this.input, "keyup", function (event) {
|
|
197
|
+
if (self.props.keyboardStepping &&
|
|
198
|
+
(event.key === "ArrowUp" || event.key === "ArrowDown" ||
|
|
199
|
+
event.keyCode === 38 || event.keyCode === 40)) {
|
|
200
|
+
event.preventDefault()
|
|
201
|
+
resetTimer()
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// decrement button
|
|
206
|
+
onPointerDown(self.buttonDecrement, function () {
|
|
207
|
+
if (!self.buttonDecrement.disabled) {
|
|
208
|
+
pointerState = true
|
|
209
|
+
stepHandling(-self.step)
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
// increment button
|
|
213
|
+
onPointerDown(self.buttonIncrement, function () {
|
|
214
|
+
if (!self.buttonIncrement.disabled) {
|
|
215
|
+
pointerState = true
|
|
216
|
+
stepHandling(self.step)
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
onPointerUp(document.body, function () {
|
|
220
|
+
if (pointerState === true) {
|
|
221
|
+
resetTimer()
|
|
222
|
+
dispatchEvent(self.original, "change")
|
|
223
|
+
pointerState = false
|
|
224
|
+
}
|
|
225
|
+
})
|
|
214
226
|
|
|
215
227
|
function setValue(newValue, updateInput) {
|
|
216
228
|
if (updateInput === undefined) {
|
|
217
229
|
updateInput = true
|
|
218
230
|
}
|
|
219
231
|
if (isNaN(newValue) || newValue === "") {
|
|
220
|
-
self
|
|
232
|
+
self.original.value = ""
|
|
221
233
|
if (updateInput) {
|
|
222
|
-
self
|
|
234
|
+
self.input.value = ""
|
|
223
235
|
}
|
|
224
236
|
self.value = NaN
|
|
225
237
|
} else {
|
|
226
238
|
newValue = parseFloat(newValue)
|
|
227
239
|
newValue = Math.min(Math.max(newValue, self.min), self.max)
|
|
228
|
-
self
|
|
240
|
+
self.original.value = newValue
|
|
229
241
|
if (updateInput) {
|
|
230
|
-
self
|
|
242
|
+
self.input.value = self.original.inputSpinnerEditor.render(newValue)
|
|
231
243
|
}
|
|
232
244
|
self.value = newValue
|
|
233
245
|
}
|
|
234
246
|
}
|
|
235
247
|
|
|
236
248
|
function destroy() {
|
|
237
|
-
self
|
|
249
|
+
if (self.input.required) {
|
|
250
|
+
self.original.required = true
|
|
251
|
+
}
|
|
238
252
|
self.observer.disconnect()
|
|
239
253
|
resetTimer()
|
|
240
|
-
|
|
241
|
-
self
|
|
242
|
-
self
|
|
243
|
-
self
|
|
244
|
-
|
|
245
|
-
|
|
254
|
+
for (const off of self._teardown) off()
|
|
255
|
+
self._teardown = []
|
|
256
|
+
self.inputGroup.remove()
|
|
257
|
+
self.original.style.display = ""
|
|
258
|
+
self.original["bootstrap-input-spinner"] = undefined
|
|
259
|
+
delete self.original.setValue
|
|
260
|
+
delete self.original.destroyInputSpinner
|
|
261
|
+
delete self.original.inputSpinnerEditor
|
|
262
|
+
if (self.label) {
|
|
263
|
+
self.label.setAttribute("for", self.original.id || "")
|
|
246
264
|
}
|
|
247
265
|
}
|
|
248
266
|
|
|
249
|
-
function dispatchEvent(
|
|
250
|
-
if (type)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
event = new Event(type, {bubbles: true})
|
|
255
|
-
} else { // IE todo remove
|
|
256
|
-
event = document.createEvent('Event')
|
|
257
|
-
event.initEvent(type, true, true)
|
|
258
|
-
}
|
|
259
|
-
$element[0].dispatchEvent(event)
|
|
260
|
-
})
|
|
261
|
-
}
|
|
267
|
+
function dispatchEvent(target, type) {
|
|
268
|
+
if (!type) return
|
|
269
|
+
setTimeout(function () {
|
|
270
|
+
target.dispatchEvent(new Event(type, {bubbles: true}))
|
|
271
|
+
})
|
|
262
272
|
}
|
|
263
273
|
|
|
264
274
|
function stepHandling(step) {
|
|
@@ -278,68 +288,67 @@ export class InputSpinner {
|
|
|
278
288
|
self.value = 0
|
|
279
289
|
}
|
|
280
290
|
setValue(Math.round(self.value / step) * step + step)
|
|
281
|
-
dispatchEvent(self
|
|
291
|
+
dispatchEvent(self.original, "input")
|
|
282
292
|
}
|
|
283
293
|
|
|
284
294
|
function resetTimer() {
|
|
285
295
|
clearTimeout(self.autoDelayHandler)
|
|
286
|
-
|
|
296
|
+
clearInterval(self.autoIntervalHandler)
|
|
287
297
|
}
|
|
288
298
|
|
|
289
299
|
function updateAttributes() {
|
|
290
300
|
// copy properties from original to the new input
|
|
291
|
-
if (self
|
|
292
|
-
self
|
|
293
|
-
self
|
|
301
|
+
if (self.original.required) {
|
|
302
|
+
self.input.required = true
|
|
303
|
+
self.original.removeAttribute("required")
|
|
294
304
|
}
|
|
295
|
-
self
|
|
296
|
-
self
|
|
297
|
-
const disabled = self
|
|
298
|
-
const readonly = self
|
|
299
|
-
self
|
|
300
|
-
self
|
|
301
|
-
self
|
|
302
|
-
self
|
|
305
|
+
self.input.placeholder = self.original.placeholder
|
|
306
|
+
self.input.setAttribute("inputmode", self.original.getAttribute("inputmode") || "decimal")
|
|
307
|
+
const disabled = self.original.disabled
|
|
308
|
+
const readonly = self.original.readOnly
|
|
309
|
+
self.input.disabled = disabled
|
|
310
|
+
self.input.readOnly = readonly || self.props.buttonsOnly
|
|
311
|
+
self.buttonIncrement.disabled = disabled || readonly
|
|
312
|
+
self.buttonDecrement.disabled = disabled || readonly
|
|
303
313
|
if (disabled || readonly) {
|
|
304
314
|
resetTimer()
|
|
305
315
|
}
|
|
306
|
-
const originalClass = self
|
|
316
|
+
const originalClass = self.original.className || ""
|
|
307
317
|
let groupClass = ""
|
|
308
|
-
// sizing
|
|
309
318
|
if (/form-control-sm/g.test(originalClass)) {
|
|
310
319
|
groupClass = "input-group-sm"
|
|
311
320
|
} else if (/form-control-lg/g.test(originalClass)) {
|
|
312
321
|
groupClass = "input-group-lg"
|
|
313
322
|
}
|
|
314
323
|
const inputClass = originalClass.replace(/form-control(-(sm|lg))?/g, "")
|
|
315
|
-
self
|
|
316
|
-
self
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
self.
|
|
320
|
-
self.
|
|
321
|
-
|
|
322
|
-
if (self
|
|
323
|
-
self
|
|
324
|
+
self.inputGroup.className = "input-group " + groupClass + " " + self.props.groupClass
|
|
325
|
+
self.input.className = "form-control " + inputClass
|
|
326
|
+
|
|
327
|
+
self.min = parseNumberAttr(self.original, "min", -Infinity)
|
|
328
|
+
self.max = parseNumberAttr(self.original, "max", Infinity)
|
|
329
|
+
self.step = parseNumberAttr(self.original, "step", 1) || 1
|
|
330
|
+
|
|
331
|
+
if (self.original.hasAttribute("hidden")) {
|
|
332
|
+
self.inputGroup.setAttribute("hidden", self.original.getAttribute("hidden") || "")
|
|
324
333
|
} else {
|
|
325
|
-
self
|
|
334
|
+
self.inputGroup.removeAttribute("hidden")
|
|
326
335
|
}
|
|
327
|
-
if (self
|
|
328
|
-
self
|
|
329
|
-
if (self
|
|
330
|
-
self
|
|
336
|
+
if (self.original.id) {
|
|
337
|
+
self.input.id = self.original.id + ":input_spinner" // give the spinner a unique id...
|
|
338
|
+
if (self.label) {
|
|
339
|
+
self.label.setAttribute("for", self.input.id) // ...to rewire the label
|
|
331
340
|
}
|
|
332
341
|
}
|
|
333
342
|
}
|
|
334
343
|
|
|
335
|
-
function onPointerUp(
|
|
336
|
-
|
|
344
|
+
function onPointerUp(el, callback) {
|
|
345
|
+
bind(el, "mouseup", function (e) {
|
|
337
346
|
callback(e)
|
|
338
347
|
})
|
|
339
|
-
|
|
348
|
+
bind(el, "touchend", function (e) {
|
|
340
349
|
callback(e)
|
|
341
350
|
})
|
|
342
|
-
|
|
351
|
+
bind(el, "keyup", function (e) {
|
|
343
352
|
if ((e.keyCode === 32 || e.keyCode === 13)) {
|
|
344
353
|
triggerKeyPressed = false
|
|
345
354
|
callback(e)
|
|
@@ -347,20 +356,20 @@ export class InputSpinner {
|
|
|
347
356
|
})
|
|
348
357
|
}
|
|
349
358
|
|
|
350
|
-
function onPointerDown(
|
|
351
|
-
|
|
359
|
+
function onPointerDown(el, callback) {
|
|
360
|
+
bind(el, "mousedown", function (e) {
|
|
352
361
|
if (e.button === 0) {
|
|
353
362
|
e.preventDefault()
|
|
354
363
|
callback(e)
|
|
355
364
|
}
|
|
356
365
|
})
|
|
357
|
-
|
|
366
|
+
bind(el, "touchstart", function (e) {
|
|
358
367
|
if (e.cancelable) {
|
|
359
368
|
e.preventDefault()
|
|
360
369
|
}
|
|
361
370
|
callback(e)
|
|
362
371
|
}, {passive: false})
|
|
363
|
-
|
|
372
|
+
bind(el, "keydown", function (e) {
|
|
364
373
|
if ((e.keyCode === 32 || e.keyCode === 13) && !triggerKeyPressed) {
|
|
365
374
|
triggerKeyPressed = true
|
|
366
375
|
callback(e)
|
|
@@ -368,6 +377,4 @@ export class InputSpinner {
|
|
|
368
377
|
})
|
|
369
378
|
}
|
|
370
379
|
}
|
|
371
|
-
|
|
372
380
|
}
|
|
373
|
-
|
package/test/TestInputSpinner.js
CHANGED
|
@@ -125,10 +125,9 @@ describe("InputSpinner setValue", () => {
|
|
|
125
125
|
assert.equal(group.querySelector("input").value, "")
|
|
126
126
|
clear()
|
|
127
127
|
})
|
|
128
|
-
it("
|
|
128
|
+
it("element.setValue updates both the original and the visible input", () => {
|
|
129
129
|
const {el, group} = spin({value: "1", min: "0", max: "100"})
|
|
130
|
-
|
|
131
|
-
await wait()
|
|
130
|
+
el.setValue(42)
|
|
132
131
|
assert.equal(el.value, "42")
|
|
133
132
|
assert.equal(group.querySelector("input").value, "42")
|
|
134
133
|
clear()
|
|
@@ -170,15 +169,19 @@ describe("InputSpinner stepping", () => {
|
|
|
170
169
|
assert.equal(el.value, "0")
|
|
171
170
|
clear()
|
|
172
171
|
})
|
|
173
|
-
it("arrow-up key steps the value",
|
|
172
|
+
it("arrow-up key steps the value", () => {
|
|
174
173
|
const {el, group} = spin({value: "5", min: "0", max: "10", step: "1"})
|
|
175
|
-
|
|
174
|
+
group.querySelector("input").dispatchEvent(
|
|
175
|
+
new KeyboardEvent("keydown", {key: "ArrowUp", bubbles: true, cancelable: true})
|
|
176
|
+
)
|
|
176
177
|
assert.equal(el.value, "6")
|
|
177
178
|
clear()
|
|
178
179
|
})
|
|
179
|
-
it("arrow-down key steps the value",
|
|
180
|
+
it("arrow-down key steps the value", () => {
|
|
180
181
|
const {el, group} = spin({value: "5", min: "0", max: "10", step: "1"})
|
|
181
|
-
|
|
182
|
+
group.querySelector("input").dispatchEvent(
|
|
183
|
+
new KeyboardEvent("keydown", {key: "ArrowDown", bubbles: true, cancelable: true})
|
|
184
|
+
)
|
|
182
185
|
assert.equal(el.value, "4")
|
|
183
186
|
clear()
|
|
184
187
|
})
|
package/test/index.html
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
<link rel="stylesheet"
|
|
7
7
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
|
|
8
8
|
crossorigin="anonymous">
|
|
9
|
-
<script src="https://code.jquery.com/jquery-3.6.0.min.js" crossorigin="anonymous"></script>
|
|
10
9
|
<style>
|
|
11
10
|
body { font-family: sans-serif; padding: 1rem; }
|
|
12
11
|
#fixture { position: absolute; left: -10000px; top: -10000px; }
|
|
12
|
+
h2 { margin-top: 1rem; }
|
|
13
13
|
</style>
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|