-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathincrementable.mjs
143 lines (109 loc) · 3.32 KB
/
incrementable.mjs
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
/**
* Make numbers in a textfield incrementable/decrementable (like in dev tools)
* @author Lea Verou
* @license MIT
* @version 2.0.0
*/
const NUMBER = /-?(\d*\.?\d+)/;
const PREFIX_SUFFIX = /[%\w.-]/;
const PARTIAL_TOKEN = RegExp(`^${PREFIX_SUFFIX.source}*${NUMBER.source}?${PREFIX_SUFFIX.source}*$`);
export default class Incrementable {
constructor (target, options = {}) {
this.target = target;
this.options = Object.assign({}, options, Incrementable.defaultOptions);
this.step = +target.getAttribute('step') ||
+target.getAttribute('data-step') || 1;
this.multiplier = this.options.multiplier;
this.prefixes = this.options.prefixes;
this.suffixes = this.options.units;
let value = 'value' in target? 'value' : 'textContent';
this.target.addEventListener('keydown', evt => {
let target = this.target;
let multiplier = this.multiplier(evt);
if (multiplier && (evt.key == "ArrowUp" || evt.key == "ArrowDown")) {
let content = target[value];
let caretStart = target.selectionStart;
let caretEnd = target.selectionEnd;
let selection = content.substring(caretStart, caretEnd);
if (!PARTIAL_TOKEN.test(selection)) {
return;
}
let i;
// Find potential beginning and end
for (i = caretStart - 1; i > 0; i--) {
let char = content[i];
if (!PREFIX_SUFFIX.test(char)) {
i++;
break;
}
}
let start = i;
for (i = caretEnd; i < content.length; i++) {
let char = content[i];
if (!PREFIX_SUFFIX.test(char)) {
break;
}
}
let end = i;
let token = content.substring(start, end);
if (!NUMBER.test(token)) {
// There is no number to increment here
return;
}
target.focus();
if (target.selectionStart != start || target.selectionEnd != end) {
target.selectionStart = start;
target.selectionEnd = end;
target.dispatchEvent(new Event("select"));
}
let adjusted = Incrementable.value(token, {
decrement: evt.key == "ArrowDown",
multiplier,
step: this.step
});
if (adjusted !== token) {
evt.preventDefault();
evt.stopPropagation();
}
document.execCommand("insertText", false, adjusted);
target.selectionStart = start;
target.selectionEnd = start + adjusted.length;
}
});
}
static value(token, {decrement = false, multiplier = 1, step = 1} = {}) {
// Extract number
let number = token.match(NUMBER);
let index = number.index;
let before = token.substring(0, index);
let after = token.substring(index + number[0].length);
let val = +number[0];
let offset = (decrement? -1 : 1) * (multiplier || 1) * step;
let valPrecision = precision(val);
let offsetPrecision = precision(offset);
// Prevent rounding errors
let newVal = (parseFloat((val + offset).toPrecision(
Math.max(valPrecision.integer, offsetPrecision.integer) +
Math.max(valPrecision.decimals, offsetPrecision.decimals)
)));
return before + newVal + after;
}
static defaultOptions = {
multiplier: evt => evt.shiftKey? 10 : (evt.ctrlKey? .1 : 1)
}
}
function precision (number) {
number = (number + '').replace(/^0+/, '');
var dot = number.indexOf('.');
if (dot === -1) {
return {
integer: number.length,
decimals: 0
};
}
return {
integer: dot,
decimals: number.length - 1 - dot
};
}
window.Incrementable = Incrementable;