-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstepbox.js
More file actions
159 lines (139 loc) · 5.85 KB
/
stepbox.js
File metadata and controls
159 lines (139 loc) · 5.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Stepboxes: replace default spinboxes with GTK-style spinboxes.
document.addEventListener('DOMContentLoaded', () => {
let spinboxes = document.querySelectorAll('input[type="number"]');
for (let input of spinboxes) {
let min = parseFloat(input.min);
let max = parseFloat(input.max);
let step = parseFloat(input.step);
if (!step || step <= 0) step = 1;
// Properties of valid input:
let integer = Number.isInteger(step);
let nonnegative = min >= 0;
let wrapper = document.createElement('div');
wrapper.className = 'stepbox';
let decr = document.createElement('button');
decr.className = 'decrement';
decr.addEventListener('click', stepDown.bind(input, 1));
decr.textContent = "\u2212"; // MINUS SIGN
let incr = document.createElement('button');
incr.className = 'increment';
incr.addEventListener('click', stepUp.bind(input, 1));
incr.textContent = "+";
input.type = 'text';
input.setAttribute('inputmode', 'numeric');
input.pattern =
(nonnegative ? '' : '-?') +
(integer ? '[0-9]+'
: '(?:[0-9]+[.]?[0-9]*|[.][0-9]+)');
input.addEventListener('change', checkValidity.bind(input));
input.parentNode.replaceChild(wrapper, input);
wrapper.appendChild(input);
wrapper.appendChild(decr);
wrapper.appendChild(incr);
// Ugh, I don't know how to do width-for-height aspect ratios in CSS.
wrapper.style.setProperty('--height',
getComputedStyle(decr).getPropertyValue('height'));
}
});
function checkValidity() {
this.setCustomValidity(isSufferingStepMismatch.call(this) ?
"stepMismatch" : '');
}
function isSufferingStepMismatch() {
if (/^any$/i.test(this.step)) return false;
let step = parseFloat(this.step);
if (!this.hasAttribute('step')) step = 1;
if (!step || step <= 0) step = 1;
let value = parseFloat(this.value);
if (value === undefined) return false;
let step_base =
(this.hasAttribute('min')
&& parseFloat(this.min))
|| (this.hasAttribute('value')
&& parseFloat(this.getAttribute('value')))
|| 0;
if ((step_base - value) % step === 0) return false;
return true;
}
function stepUp(n) { return step.call(this, n, +1); }
function stepDown(n) { return step.call(this, n, -1); }
function step(n, direction) {
let value = parseFloat(this.value) || 0;
let originalValue = value;
let step_base =
(this.hasAttribute('min')
&& parseFloat(this.min))
|| (this.hasAttribute('value')
&& parseFloat(this.getAttribute('value')))
|| 0;
let step = parseFloat(this.step);
if (!this.hasAttribute('step')) step = 1;
if (!step || step <= 0) step = 1;
let min;
if (this.hasAttribute('min')) {
min = parseFloat(this.getAttribute('min'));
// We want the smallest integer k such that
// step_base + k * step >= min
// k * step >= min - step_base
// k >= (min - step_base) / step [note: step > 0]
let k = Math.ceil((min - step_base) / step);
min = step_base + k * step;
}
let max;
if (this.hasAttribute('max')) {
max = parseFloat(this.getAttribute('max'));
// We want the largest integer k such that
// step_base + k * step <= max
// k * step <= max - step_base
// k <= (max - step_base) / step
let k = Math.floor((max - step_base) / step);
max = step_base + k * step;
}
if (max !== undefined && min !== undefined && max < min) return;
let op = direction > 0 ? Math.ceil : Math.floor;
let k = op((value - step_base) / step);
value = step_base + k * step;
if (value === originalValue) {
value += n * step * direction;
}
/*
// Note: step is always strictly greater than zero.
let rem = (step_base - value) % step;
if (rem !== 0) {
value += rem;
if (direction === -1) value -= step;
} else {
value += n * step * direction;
}
*/
if (min !== undefined && value < min) value = min;
if (max !== undefined && value > max) value = max;
if (direction === -1 && value > originalValue) return;
if (direction === +1 && value < originalValue) return;
this.value = value;
this.dispatchEvent(new Event('input', {bubbles: true}));
this.dispatchEvent(new Event('change', {bubbles: true}));
}
// I am pretty sure that the HTML5 "rules for parsing floating-point number
// values" are very close to what parseFloat does, including the stopping at
// unexpected characters behaviour. The differences are as follows: parseFloat
// parse the non-finite values ±Infinity and NaN; parseFloat will skip more
// kinds of whitespace (both skip TAB, LF, FF, CR, and SPACE but parseFloat
// additionally will skip VT, NBSP, ZWNBSP, LINE SEPARATOR, PARAGRAPH
// SEPARATOR, and any other Unicode category 'Zs' Space_Separator code point);
// parseFloat will return -0 for "-0", "-.0e1" and other input mathematically
// equal to zero starting with "-" whereas the HTML5 rules can never return -0.
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-floating-point-number-values
// https://tc39.github.io/ecma262/#sec-parsefloat-string
function parseFloat(s) {
// We'll ignore the extra whitespace permissiveness.
let es_num = Number.parseFloat(s);
// Input that ES makes non-finite values would be an error in HTML.
if (!Number.isFinite(es_num)) return undefined;
// Convert -0 to +0 if needed.
if (es_num === 0) return 0;
return es_num;
}