bootstrap-input-spinner 4.1.2 → 5.0.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/README.md CHANGED
@@ -1,8 +1,8 @@
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
- > 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`.
5
+ > 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
6
 
7
7
  ![bootstrap-input-spinner](https://shaack.com/projekte/assets/img/bootstrap-input-spinner-floatingpoint-and-i18n.png)
8
8
  *Examples with floating-point and german localization*
@@ -72,7 +72,7 @@ Create the element in HTML. The attributes are compatible to the native `input[t
72
72
  </script>
73
73
  ```
74
74
 
75
- That's it. **No extra css needed**, just Bootstrap 5 and jQuery. jQuery is still a runtime dependency and is planned to be removed in a future release.
75
+ That's it. **No extra css needed**, just Bootstrap 5.
76
76
 
77
77
  ## API Reference
78
78
 
@@ -225,19 +225,14 @@ To modify the look completely, you can use the template parameter. There is an e
225
225
 
226
226
  ### Programmatic change and read of value
227
227
 
228
- In vanilla JavaScript, read via `element.value` and write via `element.setValue(newValue)`:
228
+ Read via `element.value`, write via `element.setValue(newValue)`:
229
229
 
230
230
  ```javascript
231
231
  const currentValue = element.value // read
232
232
  element.setValue(newValue) // write
233
233
  ```
234
234
 
235
- The jQuery `val()` function also works in both directions:
236
-
237
- ```javascript
238
- const currentValue = $(element).val() // read
239
- $(element).val(newValue) // write
240
- ```
235
+ > Writing directly with `element.value = 5` bypasses the editor rendering, so always use `setValue` to update the spinner value programmatically.
241
236
 
242
237
  ### Handling attributes
243
238
 
@@ -252,21 +247,11 @@ is dynamically set to `input-group-sm` or `input-group-lg`.
252
247
 
253
248
  ### Events
254
249
 
255
- The InputSpinner handles `input` and `change` events like the native element.
256
-
257
- #### Event handling with vanilla JavaScript
250
+ The InputSpinner dispatches native `input` and `change` events on the original element, just like a native number input.
258
251
 
259
252
  ```javascript
260
253
  element.addEventListener("change", function (event) {
261
- newValue = this.value
262
- })
263
- ```
264
-
265
- #### Event handling with jQuery syntax
266
-
267
- ```javascript
268
- $(element).on("change", function (event) {
269
- newValue = $(this).val()
254
+ const newValue = event.target.value
270
255
  })
271
256
  ```
272
257
 
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 / jQuery plugin to create input spinner elements for number input, by
37
- <a href="https://shaack.com/en">shaack.com</a> engineering. For now it needs jQuery, but I am working on it.
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, but we remain a Bootstrap 4 compatible version with the branch
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 <code>val()</code> like the native element,
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">&lt;script src="./src/InputSpinner.js">&lt;/script>
81
- &lt;script>
82
- $("input[type='number']").inputSpinner()
73
+ <pre><code class="language-html">&lt;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
  &lt;/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">&lt;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
- var $changedInput = $("#changedInput")
126
- var $valueOnInput = $("#valueOnInput")
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
- $changedInput.on("change", function (event) {
135
- console.log("on change", event)
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">var $changedInput = $("#changedInput")
140
- var $valueOnInput = $("#valueOnInput")
141
- var $valueOnChange = $("#valueOnChange")
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
- $changedInput.on("change", function (event) {
148
- $valueOnChange.html($(event.target).val())
130
+ changedInput.addEventListener("change", function (event) {
131
+ document.getElementById("valueOnChange").textContent = event.target.value
149
132
  })</code></pre>
150
- <h3>Programmatic changing the value with <code>val()</code></h3>
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
- var $inputNet = $("#inputNet")
161
- var $inputGross = $("#inputGross")
162
- $inputNet.on("input", function (event) {
163
- $inputGross.val($(event.target).val() * 1.19)
164
- })
165
- $inputGross.on("input", function (event) {
166
- $inputNet.val($(event.target).val() / 1.19)
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">$inputNet.on("input", function (event) {
171
- $inputGross.val($(event.target).val() * 1.19)
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
- $inputGross.on("input", function (event) {
177
- $inputNet.val($(event.target).val() / 1.19)
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
- var $inputDisabled = $("#inputDisabled")
201
- var $disabledSwitch = $("#disabledSwitch")
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">&lt;input id="inputDisabled" disabled type="number" value="50"/>
@@ -209,10 +189,8 @@ $inputGross.on("input", function (event) {
209
189
  &lt;label class="form-check-label" for="disabledSwitch">Disabled&lt;/label>
210
190
  &lt;/div>
211
191
  &lt;script>
212
- var $inputDisabled = $("#inputDisabled")
213
- var $disabledSwitch = $("#disabledSwitch")
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
  &lt;/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
- const element = document.getElementById("buttons-only")
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">$(".buttons-only").inputSpinner({buttonsOnly: true, autoInterval: undefined})</code></pre>
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
- var $inputChangeClass = $("#inputChangeClass")
249
- var $classInput = $("#classInput")
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">&lt;input id="inputChangeClass" class="is-valid" type="number" value="50"/>
255
230
  &lt;label for="classInput">CSS Class&lt;/label>
256
231
  &lt;input id="classInput" type="text" class="form-control" value="is-valid"/>
257
232
  &lt;script>
258
- var $inputChangeClass = $("#inputChangeClass")
259
- var $classInput = $("#classInput")
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
  &lt;/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
- var $minInput = $("#minInput")
317
- var $maxInput = $("#maxInput")
318
- var $stepInput = $("#stepInput")
319
- var $dataDecimalsInput = $("#dataDecimalsInput")
320
- var $minMaxTester = $("#minMaxTester")
321
- $minInput.on("change", function (event) {
322
- console.log("on change", event)
323
- $minMaxTester.attr("min", $minInput.val())
324
- })
325
- $maxInput.on("change", function (event) {
326
- console.log("on change", event)
327
- $minMaxTester.attr("max", $maxInput.val())
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">var $minInput = $("#minInput")
339
- var $maxInput = $("#maxInput")
340
- var $stepInput = $("#stepInput")
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
- var $inputLoop = $("#inputLoop")
379
- $inputLoop.on("input", function (ignored) {
380
- var value = $inputLoop.val()
381
- value = (value < 0) ? 360 + parseInt(value, 10) : value % 360
382
- $inputLoop.val(value)
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">&lt;input step="10" type="number" id="inputLoop" value="0" data-decimals="0" min="-10" max="360"/&gt;</code></pre>
386
- <p>"Loop" the value between 0 and 360 with the <code>change</code> event in JavaScript.</p>
387
- <pre><code class="language-javascript">var $inputLoop = $("#inputLoop")
388
- $inputLoop.on("input", function(event) {
389
- var value = $inputLoop.val()
390
- value = (value < 0) ? 360 + parseInt(value, 10) : value % 360
391
- $inputLoop.val(value)
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, how the input is parsed and rendered. The inputSpinner is shipped with some custom Editors in
397
- <code>/src/custom-editors.js</code>.</p>
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 und parses 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
- class="language-js">$("#rawEditor").inputSpinner({editor: customEditors.RawEditor})</code></pre>
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: customEditors.TimeEditor})
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
- class="language-js">$("#rawEditor").inputSpinner({editor: customEditors.TimeEditor})</code></pre>
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 (<i>new!</i>)</h3>
435
- <p>With the new templating feature, you can almost do <b>anything, when it comes to layout</b>.</p>
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">$(element).inputSpinner({template: '&lt;div class...'})</code></pre>
427
+ <pre><code class="language-js">new InputSpinner(element, {template: '&lt;div class...'})</code></pre>
472
428
 
473
429
  <h3>Destroying the spinner</h3>
474
- <p>To Remove the InputSpinner and show the original input element, use</p>
475
- <pre><code class="language-javascript">$(element).inputSpinner("destroy")</code></pre>
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
- var $buttonDestroy = $("#buttonDestroy")
485
- var $buttonCreate = $("#buttonCreate")
486
- var $inputDestroyCreate = $("#inputDestroyCreate")
487
- $buttonDestroy.click(function () {
488
- $inputDestroyCreate[0].destroyInputSpinner()
489
- $buttonDestroy.attr("disabled", true)
490
- $buttonCreate.attr("disabled", false)
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
- $buttonCreate.click(function () {
493
- new InputSpinner($inputDestroyCreate[0])
494
- $buttonDestroy.attr("disabled", false)
495
- $buttonCreate.attr("disabled", true)
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 inputSpinnerElements = document.querySelectorAll("input[type='number']")
518
- for (const inputSpinnerElement of inputSpinnerElements) {
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.1.2",
4
- "description": "A Bootstrap 5 / jQuery plugin to create input spinner elements for number input.",
5
- "browser": "./src/bootstrap-input-spinner.js",
3
+ "version": "5.0.0",
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
  },
@@ -6,45 +6,45 @@
6
6
 
7
7
  // the default editor for parsing and rendering
8
8
  const I18nEditor = function (props, element) {
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
- }
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
- 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)
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
- const originalVal = $.fn.val
36
- $.fn.val = function (value) {
37
- if (arguments.length >= 1) {
38
- for (let i = 0; i < this.length; i++) {
39
- if (this[i]["bootstrap-input-spinner"] && this[i].setValue) {
40
- const element = this[i]
41
- setTimeout(function () {
42
- element.setValue(value)
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 originalVal.apply(this, arguments)
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>&minus;</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 (this.element["bootstrap-input-spinner"]) {
101
- console.warn("element", this.element, "is already a bootstrap-input-spinner")
102
- } else {
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
- this.min = null
122
- this.max = null
123
- this.step = null
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
- updateAttributes()
98
+ this.autoDelayHandler = null
99
+ this.autoIntervalHandler = null
126
100
 
127
- this.value = parseFloat(this.$original[0].value)
128
- let pointerState = false
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
- const prefix = this.$original.attr("data-prefix") || ""
131
- const suffix = this.$original.attr("data-suffix") || ""
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
- if (prefix) {
134
- const prefixElement = $('<span class="input-group-text">' + prefix + '</span>')
135
- this.$inputGroup.find("input").before(prefixElement)
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
- this.$original[0].setValue = function (newValue) {
143
- setValue(newValue)
144
- }
145
- this.$original[0].destroyInputSpinner = function () {
146
- destroy()
147
- }
118
+ updateAttributes()
148
119
 
149
- this.observer = new MutationObserver(function () {
150
- updateAttributes()
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
- this.$original.after(this.$inputGroup)
123
+ const prefix = this.original.getAttribute("data-prefix") || ""
124
+ const suffix = this.original.getAttribute("data-suffix") || ""
156
125
 
157
- setValue(this.value)
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
- this.$input.on("paste input change focusout", function (event) {
160
- let newValue = self.$input[0].value
161
- const focusOut = event.type === "focusout"
162
- if(!self.props.buttonsOnly) {
163
- newValue = self.$original[0].inputSpinnerEditor.parse(newValue)
164
- setValue(newValue, focusOut)
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
- // decrement button
193
- onPointerDown(self.$buttonDecrement[0], function () {
194
- if (!self.$buttonDecrement.prop("disabled")) {
195
- pointerState = true
196
- stepHandling(-self.step)
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
- // increment button
200
- onPointerDown(self.$buttonIncrement[0], function () {
201
- if (!self.$buttonIncrement.prop("disabled")) {
202
- pointerState = true
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
- onPointerUp(document.body, function () {
207
- if (pointerState === true) {
208
- resetTimer()
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.$original[0].value = ""
232
+ self.original.value = ""
221
233
  if (updateInput) {
222
- self.$input[0].value = ""
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.$original[0].value = newValue
240
+ self.original.value = newValue
229
241
  if (updateInput) {
230
- self.$input[0].value = self.$original[0].inputSpinnerEditor.render(newValue)
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.$original.prop("required", self.$input.prop("required"))
249
+ if (self.input.required) {
250
+ self.original.required = true
251
+ }
238
252
  self.observer.disconnect()
239
253
  resetTimer()
240
- self.$input.off("paste input change focusout")
241
- self.$inputGroup.remove()
242
- self.$original.show()
243
- self.$original[0]["bootstrap-input-spinner"] = undefined
244
- if (self.$label[0]) {
245
- self.$label.attr("for", self.$original.attr("id"))
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($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 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.$original, "input")
291
+ dispatchEvent(self.original, "input")
282
292
  }
283
293
 
284
294
  function resetTimer() {
285
295
  clearTimeout(self.autoDelayHandler)
286
- clearTimeout(self.autoIntervalHandler)
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.$original.prop("required")) {
292
- self.$input.prop("required", self.$original.prop("required"))
293
- self.$original.removeAttr('required')
301
+ if (self.original.required) {
302
+ self.input.required = true
303
+ self.original.removeAttribute("required")
294
304
  }
295
- self.$input.prop("placeholder", self.$original.prop("placeholder"))
296
- self.$input.attr("inputmode", self.$original.attr("inputmode") || "decimal")
297
- const disabled = self.$original.prop("disabled")
298
- const readonly = self.$original.prop("readonly")
299
- self.$input.prop("disabled", disabled)
300
- self.$input.prop("readonly", readonly || self.props.buttonsOnly)
301
- self.$buttonIncrement.prop("disabled", disabled || readonly)
302
- self.$buttonDecrement.prop("disabled", disabled || readonly)
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.$original.prop("class")
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.$inputGroup.prop("class", "input-group " + groupClass + " " + self.props.groupClass)
316
- self.$input.prop("class", "form-control " + inputClass)
317
-
318
- // update the main attributes
319
- self.min = isNaN(self.$original.prop("min")) || self.$original.prop("min") === "" ? -Infinity : parseFloat(self.$original.prop("min"))
320
- self.max = isNaN(self.$original.prop("max")) || self.$original.prop("max") === "" ? Infinity : parseFloat(self.$original.prop("max"))
321
- self.step = parseFloat(self.$original.prop("step")) || 1
322
- if (self.$original.attr("hidden")) {
323
- self.$inputGroup.attr("hidden", self.$original.attr("hidden"))
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.$inputGroup.removeAttr("hidden")
334
+ self.inputGroup.removeAttribute("hidden")
326
335
  }
327
- if (self.$original.attr("id")) {
328
- self.$input.attr("id", self.$original.attr("id") + ":input_spinner") // give the spinner a unique id...
329
- if (self.$label[0]) {
330
- self.$label.attr("for", self.$input.attr("id")) // ...to rewire the label
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(element, callback) {
336
- element.addEventListener("mouseup", function (e) {
344
+ function onPointerUp(el, callback) {
345
+ bind(el, "mouseup", function (e) {
337
346
  callback(e)
338
347
  })
339
- element.addEventListener("touchend", function (e) {
348
+ bind(el, "touchend", function (e) {
340
349
  callback(e)
341
350
  })
342
- element.addEventListener("keyup", function (e) {
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(element, callback) {
351
- element.addEventListener("mousedown", function (e) {
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
- element.addEventListener("touchstart", function (e) {
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
- element.addEventListener("keydown", function (e) {
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
-
@@ -125,10 +125,9 @@ describe("InputSpinner setValue", () => {
125
125
  assert.equal(group.querySelector("input").value, "")
126
126
  clear()
127
127
  })
128
- it("is reachable via jQuery val() monkey patch", async () => {
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
- window.$(el).val(42)
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", async () => {
172
+ it("arrow-up key steps the value", () => {
174
173
  const {el, group} = spin({value: "5", min: "0", max: "10", step: "1"})
175
- window.$(group.querySelector("input")).trigger(window.$.Event("keydown", {which: 38}))
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", async () => {
180
+ it("arrow-down key steps the value", () => {
180
181
  const {el, group} = spin({value: "5", min: "0", max: "10", step: "1"})
181
- window.$(group.querySelector("input")).trigger(window.$.Event("keydown", {which: 40}))
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>