bootstrap-input-spinner 4.1.0 → 4.1.2
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 +44 -25
- package/package.json +3 -3
- package/src/custom-editors.js +60 -40
- package/test/TestCustomEditors.js +54 -0
- package/test/TestInputSpinner.js +301 -0
- package/test/index.html +34 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# bootstrap-input-spinner
|
|
2
2
|
|
|
3
|
-
A Bootstrap extension to create input spinner elements for number input.
|
|
3
|
+
A Bootstrap 5 extension to create input spinner elements for number input.
|
|
4
4
|
|
|
5
|
-
> Note: bootstrap-input-spinner is now
|
|
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`.
|
|
6
6
|
|
|
7
7
|

|
|
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.
|
|
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.
|
|
76
76
|
|
|
77
77
|
## API Reference
|
|
78
78
|
|
|
@@ -98,11 +98,13 @@ The InputSpinner also handles the standard input attributes `required`, `disable
|
|
|
98
98
|
|
|
99
99
|
### Create an instance in JavaScript
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
Instantiate the `InputSpinner` class on any `<input type="number">` element. You may provide additional configuration
|
|
102
|
+
in an object as a second parameter.
|
|
103
103
|
|
|
104
104
|
```js
|
|
105
|
-
|
|
105
|
+
import {InputSpinner} from "bootstrap-input-spinner/src/InputSpinner.js"
|
|
106
|
+
|
|
107
|
+
new InputSpinner(element, config)
|
|
106
108
|
```
|
|
107
109
|
|
|
108
110
|
#### Configuration (props)
|
|
@@ -177,12 +179,20 @@ step.
|
|
|
177
179
|
Used to format the number in the UI. Detected automatically from the user's browser, can be set to "de", "en",… or "
|
|
178
180
|
de_DE", "en_GB",….
|
|
179
181
|
|
|
180
|
-
##### editor
|
|
182
|
+
##### editor
|
|
183
|
+
|
|
184
|
+
An Editor defines how the input is parsed and rendered. The default editor is the internal `I18nEditor`, which
|
|
185
|
+
parses and renders an internationalized number. Additional editors live in `src/custom-editors.js` and are available
|
|
186
|
+
as named ES exports:
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
import {RawEditor, TimeEditor} from "bootstrap-input-spinner/src/custom-editors.js"
|
|
190
|
+
|
|
191
|
+
new InputSpinner(element, {editor: TimeEditor})
|
|
192
|
+
```
|
|
181
193
|
|
|
182
|
-
An Editor
|
|
183
|
-
|
|
184
|
-
must implement the two functions `parse(customValue)`, to parse the input to a number and `render(number)` to render the
|
|
185
|
-
number to the spinner input.
|
|
194
|
+
An Editor must implement two functions: `parse(customFormat)` to turn the input string into a number, and
|
|
195
|
+
`render(number)` to format the number back for display.
|
|
186
196
|
|
|
187
197
|
The simplest custom Editor is the `RawEditor`, it renders just the value und parses just the value, without any changes,
|
|
188
198
|
like a native number input. It looks like this:
|
|
@@ -215,14 +225,19 @@ To modify the look completely, you can use the template parameter. There is an e
|
|
|
215
225
|
|
|
216
226
|
### Programmatic change and read of value
|
|
217
227
|
|
|
218
|
-
|
|
228
|
+
In vanilla JavaScript, read via `element.value` and write via `element.setValue(newValue)`:
|
|
219
229
|
|
|
220
230
|
```javascript
|
|
221
|
-
|
|
222
|
-
|
|
231
|
+
const currentValue = element.value // read
|
|
232
|
+
element.setValue(newValue) // write
|
|
223
233
|
```
|
|
224
234
|
|
|
225
|
-
|
|
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
|
+
```
|
|
226
241
|
|
|
227
242
|
### Handling attributes
|
|
228
243
|
|
|
@@ -257,14 +272,12 @@ $(element).on("change", function (event) {
|
|
|
257
272
|
|
|
258
273
|
### Methods
|
|
259
274
|
|
|
260
|
-
Methods are passed as string values instead of the options object.
|
|
261
|
-
|
|
262
275
|
#### destroy
|
|
263
276
|
|
|
264
277
|
Removes the InputSpinner and shows the original input element.
|
|
265
278
|
|
|
266
279
|
```javascript
|
|
267
|
-
|
|
280
|
+
element.destroyInputSpinner()
|
|
268
281
|
```
|
|
269
282
|
|
|
270
283
|
## Minified version
|
|
@@ -280,21 +293,27 @@ Just install uglify
|
|
|
280
293
|
npm install uglify-js -g
|
|
281
294
|
```
|
|
282
295
|
|
|
283
|
-
and then in the src
|
|
296
|
+
and then in the `src` folder
|
|
284
297
|
|
|
285
298
|
```bash
|
|
286
|
-
uglifyjs
|
|
299
|
+
uglifyjs InputSpinner.js --compress --mangle > InputSpinner.min.js
|
|
287
300
|
```
|
|
288
301
|
|
|
289
302
|
Violà! :)
|
|
290
303
|
|
|
291
|
-
##
|
|
304
|
+
## Testing
|
|
305
|
+
|
|
306
|
+
There is a [Teevi](https://github.com/shaack/teevi) based browser test suite under `test/`. Serve the repo with any
|
|
307
|
+
static server and open `test/index.html` in a browser to run it:
|
|
292
308
|
|
|
293
|
-
|
|
309
|
+
```bash
|
|
310
|
+
npx http-server -p 8080
|
|
311
|
+
# then open http://localhost:8080/test/
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Browser support
|
|
294
315
|
|
|
295
|
-
|
|
296
|
-
**"Intl is not defined"** (See [issue #34](https://github.com/shaack/bootstrap-input-spinner/issues/34)), just use a
|
|
297
|
-
shim or polyfill like [Intl.js](https://github.com/andyearnshaw/Intl.js), and it works.
|
|
316
|
+
The spinner works in all modern browsers that support ES modules.
|
|
298
317
|
|
|
299
318
|
---
|
|
300
319
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bootstrap-input-spinner",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"description": "A Bootstrap 5 / jQuery plugin to create input spinner elements for number input.",
|
|
5
5
|
"browser": "./src/bootstrap-input-spinner.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://shaack.com/en/open-source-components",
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"prismjs": "^1.
|
|
29
|
-
"teevi": "^2.
|
|
28
|
+
"prismjs": "^1.30.0",
|
|
29
|
+
"teevi": "^2.3.0"
|
|
30
30
|
}
|
|
31
31
|
}
|
package/src/custom-editors.js
CHANGED
|
@@ -3,49 +3,69 @@
|
|
|
3
3
|
* Repository: https://github.com/shaack/bootstrap-input-spinner
|
|
4
4
|
* License: MIT, see file 'LICENSE'
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
|
|
7
|
+
export const RawEditor = function (props, element) {
|
|
8
|
+
this.parse = function (customFormat) {
|
|
9
|
+
// parse nothing
|
|
10
|
+
return customFormat
|
|
11
|
+
}
|
|
12
|
+
this.render = function (number) {
|
|
13
|
+
// render raw
|
|
14
|
+
return number
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const TimeEditor = function (props, element) {
|
|
19
|
+
// could be implemented more elegant maybe, but works
|
|
20
|
+
this.parse = function (customFormat) {
|
|
21
|
+
let trimmed = customFormat.trim()
|
|
22
|
+
let sign = 1
|
|
23
|
+
if (trimmed.charAt(0) === "-") {
|
|
24
|
+
sign = -1
|
|
25
|
+
trimmed = trimmed.replace("-", "")
|
|
11
26
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
27
|
+
const parts = trimmed.split(":")
|
|
28
|
+
let hours = 0, minutes
|
|
29
|
+
if (parts[1]) {
|
|
30
|
+
hours = parseInt(parts[0], 10)
|
|
31
|
+
minutes = parseInt(parts[1], 10)
|
|
32
|
+
} else {
|
|
33
|
+
minutes = parseInt(parts[0], 10)
|
|
15
34
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (trimmed.charAt(0) === "-") {
|
|
23
|
-
sign = -1
|
|
24
|
-
trimmed = trimmed.replace("-", "")
|
|
25
|
-
}
|
|
26
|
-
const parts = trimmed.split(":")
|
|
27
|
-
let hours = 0, minutes
|
|
28
|
-
if (parts[1]) {
|
|
29
|
-
hours = parseInt(parts[0], 10)
|
|
30
|
-
minutes = parseInt(parts[1], 10)
|
|
31
|
-
} else {
|
|
32
|
-
minutes = parseInt(parts[0], 10)
|
|
33
|
-
}
|
|
34
|
-
return (hours * 60 + minutes) * sign
|
|
35
|
+
return (hours * 60 + minutes) * sign
|
|
36
|
+
}
|
|
37
|
+
this.render = function (number) {
|
|
38
|
+
let minutes = Math.abs(number % 60)
|
|
39
|
+
if (minutes < 10) {
|
|
40
|
+
minutes = "0" + minutes
|
|
35
41
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
hours = Math.floor(number / 60)
|
|
44
|
-
return hours + ":" + minutes
|
|
45
|
-
} else {
|
|
46
|
-
hours = Math.ceil(number / 60)
|
|
47
|
-
return "-" + Math.abs(hours) + ":" + minutes
|
|
48
|
-
}
|
|
42
|
+
let hours
|
|
43
|
+
if (number >= 0) {
|
|
44
|
+
hours = Math.floor(number / 60)
|
|
45
|
+
return hours + ":" + minutes
|
|
46
|
+
} else {
|
|
47
|
+
hours = Math.ceil(number / 60)
|
|
48
|
+
return "-" + Math.abs(hours) + ":" + minutes
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
// Deprecated: global `window.customEditors` is kept for backwards compatibility
|
|
54
|
+
// with users who load this file via a classic <script> tag. Prefer the named
|
|
55
|
+
// ES module exports above.
|
|
56
|
+
if (typeof window !== "undefined") {
|
|
57
|
+
let warned = false
|
|
58
|
+
const editors = {RawEditor, TimeEditor}
|
|
59
|
+
window.customEditors = new Proxy(editors, {
|
|
60
|
+
get(target, prop) {
|
|
61
|
+
if (!warned && prop in target) {
|
|
62
|
+
warned = true
|
|
63
|
+
console.warn(
|
|
64
|
+
"bootstrap-input-spinner: window.customEditors is deprecated, " +
|
|
65
|
+
"import {RawEditor, TimeEditor} from 'bootstrap-input-spinner/src/custom-editors.js' instead."
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
return target[prop]
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author and copyright: Stefan Haack (https://shaack.com)
|
|
3
|
+
* Repository: https://github.com/shaack/bootstrap-input-spinner
|
|
4
|
+
* License: MIT, see file 'LICENSE'
|
|
5
|
+
*/
|
|
6
|
+
import {describe, it, assert} from "../node_modules/teevi/src/teevi.js"
|
|
7
|
+
import {RawEditor, TimeEditor} from "../src/custom-editors.js"
|
|
8
|
+
|
|
9
|
+
describe("RawEditor", () => {
|
|
10
|
+
it("parses input unchanged", () => {
|
|
11
|
+
const editor = new RawEditor({}, document.createElement("input"))
|
|
12
|
+
assert.equal(editor.parse("42"), "42")
|
|
13
|
+
assert.equal(editor.parse("abc"), "abc")
|
|
14
|
+
})
|
|
15
|
+
it("renders number unchanged", () => {
|
|
16
|
+
const editor = new RawEditor({}, document.createElement("input"))
|
|
17
|
+
assert.equal(editor.render(3.14), 3.14)
|
|
18
|
+
assert.equal(editor.render(0), 0)
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe("TimeEditor", () => {
|
|
23
|
+
const editor = new TimeEditor({}, document.createElement("input"))
|
|
24
|
+
|
|
25
|
+
it("parses H:MM into total minutes", () => {
|
|
26
|
+
assert.equal(editor.parse("1:30"), 90)
|
|
27
|
+
assert.equal(editor.parse("0:05"), 5)
|
|
28
|
+
assert.equal(editor.parse("10:00"), 600)
|
|
29
|
+
})
|
|
30
|
+
it("parses bare minutes", () => {
|
|
31
|
+
assert.equal(editor.parse("45"), 45)
|
|
32
|
+
})
|
|
33
|
+
it("parses negative times", () => {
|
|
34
|
+
assert.equal(editor.parse("-1:30"), -90)
|
|
35
|
+
assert.equal(editor.parse("-0:15"), -15)
|
|
36
|
+
})
|
|
37
|
+
it("renders positive total minutes as H:MM", () => {
|
|
38
|
+
assert.equal(editor.render(90), "1:30")
|
|
39
|
+
assert.equal(editor.render(5), "0:05")
|
|
40
|
+
assert.equal(editor.render(600), "10:00")
|
|
41
|
+
})
|
|
42
|
+
it("renders zero", () => {
|
|
43
|
+
assert.equal(editor.render(0), "0:00")
|
|
44
|
+
})
|
|
45
|
+
it("renders negative total minutes with leading minus", () => {
|
|
46
|
+
assert.equal(editor.render(-90), "-1:30")
|
|
47
|
+
assert.equal(editor.render(-15), "-0:15")
|
|
48
|
+
})
|
|
49
|
+
it("is round-trip stable for positive values", () => {
|
|
50
|
+
for (const v of [0, 5, 59, 60, 61, 125, 600]) {
|
|
51
|
+
assert.equal(editor.parse(editor.render(v)), v)
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author and copyright: Stefan Haack (https://shaack.com)
|
|
3
|
+
* Repository: https://github.com/shaack/bootstrap-input-spinner
|
|
4
|
+
* License: MIT, see file 'LICENSE'
|
|
5
|
+
*/
|
|
6
|
+
import {describe, it, assert} from "../node_modules/teevi/src/teevi.js"
|
|
7
|
+
import {InputSpinner} from "../src/InputSpinner.js"
|
|
8
|
+
import {RawEditor} from "../src/custom-editors.js"
|
|
9
|
+
|
|
10
|
+
const fixture = document.getElementById("fixture")
|
|
11
|
+
|
|
12
|
+
function createInput(attrs = {}) {
|
|
13
|
+
const input = document.createElement("input")
|
|
14
|
+
input.type = "number"
|
|
15
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
16
|
+
input.setAttribute(k, String(v))
|
|
17
|
+
}
|
|
18
|
+
fixture.appendChild(input)
|
|
19
|
+
return input
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function spin(attrs = {}, props) {
|
|
23
|
+
const el = createInput(attrs)
|
|
24
|
+
const spinner = new InputSpinner(el, props)
|
|
25
|
+
return {el, spinner, group: el.nextElementSibling}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function clear() {
|
|
29
|
+
fixture.innerHTML = ""
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function wait(ms = 0) {
|
|
33
|
+
return new Promise(r => setTimeout(r, ms))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function pressButton(btn) {
|
|
37
|
+
btn.dispatchEvent(new MouseEvent("mousedown", {button: 0, cancelable: true, bubbles: true}))
|
|
38
|
+
document.body.dispatchEvent(new MouseEvent("mouseup", {button: 0, bubbles: true}))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("InputSpinner construction", () => {
|
|
42
|
+
it("inserts an input-group after the original element", () => {
|
|
43
|
+
const {el, group} = spin({value: "5"})
|
|
44
|
+
assert.true(group !== null)
|
|
45
|
+
assert.true(group.classList.contains("input-group"))
|
|
46
|
+
assert.equal(group.querySelectorAll("button").length, 2)
|
|
47
|
+
assert.equal(group.querySelectorAll("input").length, 1)
|
|
48
|
+
clear()
|
|
49
|
+
})
|
|
50
|
+
it("hides the original input", () => {
|
|
51
|
+
const {el} = spin({value: "5"})
|
|
52
|
+
assert.equal(el.style.display, "none")
|
|
53
|
+
clear()
|
|
54
|
+
})
|
|
55
|
+
it("marks the original element as a spinner", () => {
|
|
56
|
+
const {el} = spin({value: "5"})
|
|
57
|
+
assert.equal(el["bootstrap-input-spinner"], true)
|
|
58
|
+
assert.equal(typeof el.setValue, "function")
|
|
59
|
+
assert.equal(typeof el.destroyInputSpinner, "function")
|
|
60
|
+
clear()
|
|
61
|
+
})
|
|
62
|
+
it("sets the initial value from the value attribute", () => {
|
|
63
|
+
const {el, group} = spin({value: "7"})
|
|
64
|
+
assert.equal(el.value, "7")
|
|
65
|
+
assert.equal(group.querySelector("input").value, "7")
|
|
66
|
+
clear()
|
|
67
|
+
})
|
|
68
|
+
it("leaves value empty (NaN) when no value attribute is given", () => {
|
|
69
|
+
const {el, group} = spin()
|
|
70
|
+
assert.equal(el.value, "")
|
|
71
|
+
assert.equal(group.querySelector("input").value, "")
|
|
72
|
+
clear()
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe("InputSpinner I18n rendering (default editor)", () => {
|
|
77
|
+
it("renders with data-decimals", () => {
|
|
78
|
+
const {group} = spin({value: "4.5", "data-decimals": "2"}, {locale: "en-US"})
|
|
79
|
+
assert.equal(group.querySelector("input").value, "4.50")
|
|
80
|
+
clear()
|
|
81
|
+
})
|
|
82
|
+
it("uses thousands grouping by default", () => {
|
|
83
|
+
const {group} = spin({value: "12345"}, {locale: "en-US"})
|
|
84
|
+
assert.equal(group.querySelector("input").value, "12,345")
|
|
85
|
+
clear()
|
|
86
|
+
})
|
|
87
|
+
it("disables grouping when data-digit-grouping=false", () => {
|
|
88
|
+
const {group} = spin({value: "12345", "data-digit-grouping": "false"}, {locale: "en-US"})
|
|
89
|
+
assert.equal(group.querySelector("input").value, "12345")
|
|
90
|
+
clear()
|
|
91
|
+
})
|
|
92
|
+
it("honors the German locale separators", () => {
|
|
93
|
+
const {group} = spin({value: "1234.5", "data-decimals": "1"}, {locale: "de-DE"})
|
|
94
|
+
// German: '.' thousands, ',' decimal
|
|
95
|
+
assert.equal(group.querySelector("input").value, "1.234,5")
|
|
96
|
+
clear()
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe("InputSpinner setValue", () => {
|
|
101
|
+
it("clamps above max", () => {
|
|
102
|
+
const {el, group} = spin({value: "5", min: "0", max: "10"})
|
|
103
|
+
el.setValue(99)
|
|
104
|
+
assert.equal(el.value, "10")
|
|
105
|
+
assert.equal(group.querySelector("input").value, "10")
|
|
106
|
+
clear()
|
|
107
|
+
})
|
|
108
|
+
it("clamps below min", () => {
|
|
109
|
+
const {el, group} = spin({value: "5", min: "0", max: "10"})
|
|
110
|
+
el.setValue(-99)
|
|
111
|
+
assert.equal(el.value, "0")
|
|
112
|
+
assert.equal(group.querySelector("input").value, "0")
|
|
113
|
+
clear()
|
|
114
|
+
})
|
|
115
|
+
it("accepts floats", () => {
|
|
116
|
+
const {el} = spin({value: "0", "data-decimals": "2"}, {locale: "en-US"})
|
|
117
|
+
el.setValue(3.14)
|
|
118
|
+
assert.equal(el.value, "3.14")
|
|
119
|
+
clear()
|
|
120
|
+
})
|
|
121
|
+
it("clears value on NaN", () => {
|
|
122
|
+
const {el, group} = spin({value: "5"})
|
|
123
|
+
el.setValue(NaN)
|
|
124
|
+
assert.equal(el.value, "")
|
|
125
|
+
assert.equal(group.querySelector("input").value, "")
|
|
126
|
+
clear()
|
|
127
|
+
})
|
|
128
|
+
it("is reachable via jQuery val() monkey patch", async () => {
|
|
129
|
+
const {el, group} = spin({value: "1", min: "0", max: "100"})
|
|
130
|
+
window.$(el).val(42)
|
|
131
|
+
await wait()
|
|
132
|
+
assert.equal(el.value, "42")
|
|
133
|
+
assert.equal(group.querySelector("input").value, "42")
|
|
134
|
+
clear()
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe("InputSpinner stepping", () => {
|
|
139
|
+
it("increments on the plus button", () => {
|
|
140
|
+
const {el, group} = spin({value: "5", min: "0", max: "100", step: "1"})
|
|
141
|
+
pressButton(group.querySelector(".btn-increment"))
|
|
142
|
+
assert.equal(el.value, "6")
|
|
143
|
+
clear()
|
|
144
|
+
})
|
|
145
|
+
it("decrements on the minus button", () => {
|
|
146
|
+
const {el, group} = spin({value: "5", min: "0", max: "100", step: "1"})
|
|
147
|
+
pressButton(group.querySelector(".btn-decrement"))
|
|
148
|
+
assert.equal(el.value, "4")
|
|
149
|
+
clear()
|
|
150
|
+
})
|
|
151
|
+
it("honors a custom step size", () => {
|
|
152
|
+
const {el, group} = spin({value: "0", min: "0", max: "100", step: "10"})
|
|
153
|
+
pressButton(group.querySelector(".btn-increment"))
|
|
154
|
+
assert.equal(el.value, "10")
|
|
155
|
+
pressButton(group.querySelector(".btn-increment"))
|
|
156
|
+
assert.equal(el.value, "20")
|
|
157
|
+
clear()
|
|
158
|
+
})
|
|
159
|
+
it("does not step past max", () => {
|
|
160
|
+
const {el, group} = spin({value: "9", min: "0", max: "10", step: "1"})
|
|
161
|
+
pressButton(group.querySelector(".btn-increment"))
|
|
162
|
+
pressButton(group.querySelector(".btn-increment"))
|
|
163
|
+
assert.equal(el.value, "10")
|
|
164
|
+
clear()
|
|
165
|
+
})
|
|
166
|
+
it("does not step below min", () => {
|
|
167
|
+
const {el, group} = spin({value: "1", min: "0", max: "10", step: "1"})
|
|
168
|
+
pressButton(group.querySelector(".btn-decrement"))
|
|
169
|
+
pressButton(group.querySelector(".btn-decrement"))
|
|
170
|
+
assert.equal(el.value, "0")
|
|
171
|
+
clear()
|
|
172
|
+
})
|
|
173
|
+
it("arrow-up key steps the value", async () => {
|
|
174
|
+
const {el, group} = spin({value: "5", min: "0", max: "10", step: "1"})
|
|
175
|
+
window.$(group.querySelector("input")).trigger(window.$.Event("keydown", {which: 38}))
|
|
176
|
+
assert.equal(el.value, "6")
|
|
177
|
+
clear()
|
|
178
|
+
})
|
|
179
|
+
it("arrow-down key steps the value", async () => {
|
|
180
|
+
const {el, group} = spin({value: "5", min: "0", max: "10", step: "1"})
|
|
181
|
+
window.$(group.querySelector("input")).trigger(window.$.Event("keydown", {which: 40}))
|
|
182
|
+
assert.equal(el.value, "4")
|
|
183
|
+
clear()
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
describe("InputSpinner events", () => {
|
|
188
|
+
it("dispatches 'input' when stepping", async () => {
|
|
189
|
+
const {el, group} = spin({value: "5", min: "0", max: "10"})
|
|
190
|
+
let fired = 0
|
|
191
|
+
el.addEventListener("input", () => fired++)
|
|
192
|
+
pressButton(group.querySelector(".btn-increment"))
|
|
193
|
+
await wait()
|
|
194
|
+
assert.true(fired >= 1)
|
|
195
|
+
clear()
|
|
196
|
+
})
|
|
197
|
+
it("dispatches 'change' on pointer release", async () => {
|
|
198
|
+
const {el, group} = spin({value: "5", min: "0", max: "10"})
|
|
199
|
+
let fired = 0
|
|
200
|
+
el.addEventListener("change", () => fired++)
|
|
201
|
+
pressButton(group.querySelector(".btn-increment"))
|
|
202
|
+
await wait()
|
|
203
|
+
assert.equal(fired, 1)
|
|
204
|
+
clear()
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
describe("InputSpinner attribute observation", () => {
|
|
209
|
+
it("reflects min/max changes", async () => {
|
|
210
|
+
const {el} = spin({value: "5", min: "0", max: "10"})
|
|
211
|
+
el.setAttribute("max", "7")
|
|
212
|
+
await wait()
|
|
213
|
+
el.setValue(99)
|
|
214
|
+
assert.equal(el.value, "7")
|
|
215
|
+
clear()
|
|
216
|
+
})
|
|
217
|
+
it("reflects disabled attribute", async () => {
|
|
218
|
+
const {el, group} = spin({value: "5"})
|
|
219
|
+
el.setAttribute("disabled", "disabled")
|
|
220
|
+
await wait()
|
|
221
|
+
assert.true(group.querySelector(".btn-increment").disabled)
|
|
222
|
+
assert.true(group.querySelector(".btn-decrement").disabled)
|
|
223
|
+
assert.true(group.querySelector("input").disabled)
|
|
224
|
+
clear()
|
|
225
|
+
})
|
|
226
|
+
it("reflects readonly attribute on visible input", async () => {
|
|
227
|
+
const {el, group} = spin({value: "5"})
|
|
228
|
+
el.setAttribute("readonly", "readonly")
|
|
229
|
+
await wait()
|
|
230
|
+
assert.true(group.querySelector("input").readOnly)
|
|
231
|
+
assert.true(group.querySelector(".btn-increment").disabled)
|
|
232
|
+
clear()
|
|
233
|
+
})
|
|
234
|
+
it("maps form-control-sm to input-group-sm", async () => {
|
|
235
|
+
const el = createInput({value: "5"})
|
|
236
|
+
el.className = "form-control form-control-sm"
|
|
237
|
+
new InputSpinner(el)
|
|
238
|
+
await wait()
|
|
239
|
+
assert.true(el.nextElementSibling.classList.contains("input-group-sm"))
|
|
240
|
+
clear()
|
|
241
|
+
})
|
|
242
|
+
it("maps form-control-lg to input-group-lg", async () => {
|
|
243
|
+
const el = createInput({value: "5"})
|
|
244
|
+
el.className = "form-control form-control-lg"
|
|
245
|
+
new InputSpinner(el)
|
|
246
|
+
await wait()
|
|
247
|
+
assert.true(el.nextElementSibling.classList.contains("input-group-lg"))
|
|
248
|
+
clear()
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
describe("InputSpinner prefix/suffix", () => {
|
|
253
|
+
it("renders a prefix element", () => {
|
|
254
|
+
const {group} = spin({value: "5", "data-prefix": "$"})
|
|
255
|
+
const prefix = group.querySelector(".input-group-text")
|
|
256
|
+
assert.true(prefix !== null)
|
|
257
|
+
assert.equal(prefix.textContent, "$")
|
|
258
|
+
clear()
|
|
259
|
+
})
|
|
260
|
+
it("renders a suffix element", () => {
|
|
261
|
+
const {group} = spin({value: "5", "data-suffix": "kg"})
|
|
262
|
+
const suffix = group.querySelector(".input-group-text")
|
|
263
|
+
assert.true(suffix !== null)
|
|
264
|
+
assert.equal(suffix.textContent, "kg")
|
|
265
|
+
clear()
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
describe("InputSpinner buttonsOnly mode", () => {
|
|
270
|
+
it("makes the input readonly", () => {
|
|
271
|
+
const {group} = spin({value: "5"}, {buttonsOnly: true})
|
|
272
|
+
assert.true(group.querySelector("input").readOnly)
|
|
273
|
+
clear()
|
|
274
|
+
})
|
|
275
|
+
it("still allows button stepping", () => {
|
|
276
|
+
const {el, group} = spin({value: "5", min: "0", max: "10"}, {buttonsOnly: true})
|
|
277
|
+
pressButton(group.querySelector(".btn-increment"))
|
|
278
|
+
assert.equal(el.value, "6")
|
|
279
|
+
clear()
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
describe("InputSpinner custom editor", () => {
|
|
284
|
+
it("uses the supplied editor for rendering", () => {
|
|
285
|
+
const {group} = spin({value: "3.14159"}, {editor: RawEditor})
|
|
286
|
+
assert.equal(group.querySelector("input").value, "3.14159")
|
|
287
|
+
clear()
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
describe("InputSpinner destroy", () => {
|
|
292
|
+
it("removes the input-group and un-hides the original", () => {
|
|
293
|
+
const {el, group} = spin({value: "5"})
|
|
294
|
+
assert.true(group.isConnected)
|
|
295
|
+
el.destroyInputSpinner()
|
|
296
|
+
assert.false(group.isConnected)
|
|
297
|
+
assert.notEqual(el.style.display, "none")
|
|
298
|
+
assert.equal(el["bootstrap-input-spinner"], undefined)
|
|
299
|
+
clear()
|
|
300
|
+
})
|
|
301
|
+
})
|
package/test/index.html
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>bootstrap-input-spinner tests</title>
|
|
6
|
+
<link rel="stylesheet"
|
|
7
|
+
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
|
|
8
|
+
crossorigin="anonymous">
|
|
9
|
+
<script src="https://code.jquery.com/jquery-3.6.0.min.js" crossorigin="anonymous"></script>
|
|
10
|
+
<style>
|
|
11
|
+
body { font-family: sans-serif; padding: 1rem; }
|
|
12
|
+
#fixture { position: absolute; left: -10000px; top: -10000px; }
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<h1>bootstrap-input-spinner tests</h1>
|
|
17
|
+
<div id="fixture"></div>
|
|
18
|
+
|
|
19
|
+
<script src="https://cdn.jsdelivr.net/npm/es-module-shims@1.7.2/dist/es-module-shims.min.js"></script>
|
|
20
|
+
<script type="importmap">
|
|
21
|
+
{
|
|
22
|
+
"imports": {
|
|
23
|
+
"teevi/": "../node_modules/teevi/"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
<script type="module">
|
|
28
|
+
import {teevi} from "teevi/src/teevi.js"
|
|
29
|
+
import "./TestInputSpinner.js"
|
|
30
|
+
import "./TestCustomEditors.js"
|
|
31
|
+
teevi.run()
|
|
32
|
+
</script>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|