-
Notifications
You must be signed in to change notification settings - Fork 1
/
delimited-input.js
139 lines (114 loc) · 5.26 KB
/
delimited-input.js
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
function DelimitedInput(separator, spread, direction, prefill, overwrite, eraseCharacter) {
if (typeof(separator) !== 'string' || separator.length !== 1) {
throw new Error('Delimiter must be a single character string, got ' +
typeof(separator) + ' "' + separator + '"');
}
const format = DelimitedInput.formatter(separator, spread, direction);
// "xxx-yyy" -> segmentLength === 4
const segmentLength = spread + separator.length;
return function(event) {
const el = event.target;
const priorPosition = el.selectionStart;
const key = String.fromCharCode(event.keyCode);
if (/[A-z0-9]/.test(key)) {
event.preventDefault();
const selectionLength = el.selectionEnd - el.selectionStart;
if ((el.value.length >= el.size && selectionLength === 0 && !overwrite) ||
(priorPosition >= el.size && overwrite)) {
return; // after preventDefault() to avoid changing input value
}
const value = DelimitedInput.subtract(
el.value, el.selectionStart, el.selectionEnd);
const nextValue = format(DelimitedInput.inject(value, event.shiftKey ? key : key.toLowerCase(), priorPosition, overwrite));
const predict = (prefill && nextValue.length < el.size && (nextValue.length + 1) % segmentLength == 0) ? separator : "";
el.value = nextValue + predict;
if (direction === DelimitedInput.ltr) {
// +1 as input is not yet reflected in position
const next_to_separator = (priorPosition + 1) % segmentLength == 0
// left of separator, adds +1 more
|| ((priorPosition + 2) % segmentLength == 0);
const shift = (priorPosition + (next_to_separator ? 2 : 1));
el.selectionStart = el.selectionEnd = shift;
} else {
// Accounts also additional or removed delimiters
const shift = el.value.length - value.length;
el.selectionStart = el.selectionEnd = (priorPosition + shift);
}
} else if (event.keyCode === 8) {
event.preventDefault();
const result = DelimitedInput.backspace(el, separator, direction, eraseCharacter);
const newValue = format(result.value);
el.value = newValue;
el.selectionStart = el.selectionEnd = (direction === DelimitedInput.ltr)
? result.positionFromStart
: newValue.length - result.positionFromEnd;
}
}
}
DelimitedInput.ltr = {}; // Apply rules left-to-right
DelimitedInput.rtl = {}; // Apply rules right-to-left
DelimitedInput.reverse = function(string) {
return string.split("").reverse().join("");
};
DelimitedInput.formatter = function(separator, spread, direction) {
const re = new RegExp(".{1," + spread + "}", "g");
const reStrip = new RegExp(separator, "g");
return function(value) {
return value.length === 0
? ""
: (direction === DelimitedInput.rtl
? DelimitedInput.reverse(DelimitedInput.reverse(
value.replace(reStrip, "")).match(re).join(separator))
: value.replace(reStrip, "").match(re).join(separator));
}
};
DelimitedInput.strip = function(string) {
return string.replace(/[^A-z0-9]/g, '');
};
DelimitedInput.inject = function(string, character, positionFromLeft, overwrite) {
return string.slice(0, positionFromLeft) +
character + string.slice(positionFromLeft + (overwrite ? 1 : 0));
};
DelimitedInput.subtract = function(string, start, end) {
const head = string.slice(0, start);
const tail = string.slice(end, string.length);
return head + tail;
};
DelimitedInput.backspace = function(el, separator, direction, eraseCharacter) {
const value = el.value;
const selStart = el.selectionStart;
const selEnd = el.selectionEnd;
const selLength = selEnd - selStart;
const cursorRightOfSeparator = selStart > 0
&& value[selStart - 1] === separator[separator.length - 1];
const cursorPosition = Math.max(0, selStart - (selLength ? 0 : 1));
// In RTL delimiters never change on right side of the cursor when erasing
const positionFromEnd = value.length - selEnd;
// In LTR at most additional separator is removed, delimiters may change on
// right hand side of the cursor
const positionFromStart = Math.max(0, selStart - (cursorRightOfSeparator ? 2 : 1));
const modifiedValue = this.subtract(value, cursorPosition - (cursorRightOfSeparator
// Erase char before separator if cursor right after of separator
? separator.length
: 0),
selEnd);
return {
positionFromEnd: positionFromEnd,
positionFromStart: positionFromStart,
value: (eraseCharacter && selStart > 0)
? this.inject(modifiedValue, eraseCharacter, positionFromStart, false)
: modifiedValue
};
};
// Export functionality, sampled from baconjs <3
if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
define([], function() { return DelimitedInput; });
if (typeof this !== "undefined" && this !== null) {
this.DelimitedInput = DelimitedInput;
}
} else if ((typeof module !== "undefined" && module !== null) && (module.exports != null)) {
module.exports = DelimitedInput; // for DelimitedInput = require 'delimited-input'
DelimitedInput.DelimitedInput = DelimitedInput; // for {DelimitedInput} = require 'delimited-input'
} else {
this.DelimitedInput = DelimitedInput; // otherwise for execution context
}