PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 All 205 releases
booking / vendors / imask / esm / masked / number.js

number.js in Booking Calendar 11.9, at vendors/imask/esm/masked/number.js

315 lines 11.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { escapeRegExp, DIRECTION } from '../core/utils.js';
2 import ChangeDetails from '../core/change-details.js';
3 import Masked from './base.js';
4 import IMask from '../core/holder.js';
5 import '../core/continuous-tail-details.js';
6
7 var _MaskedNumber;
8 /** Number mask */
9 class MaskedNumber extends Masked {
10 /** Single char */
11
12 /** Single char */
13
14 /** Array of single chars */
15
16 /** */
17
18 /** */
19
20 /** Digits after point */
21
22 /** Flag to remove leading and trailing zeros in the end of editing */
23
24 /** Flag to pad trailing zeros after point in the end of editing */
25
26 /** Enable characters overwriting */
27
28 /** */
29
30 /** */
31
32 /** */
33
34 /** Format typed value to string */
35
36 /** Parse string to get typed value */
37
38 constructor(opts) {
39 super({
40 ...MaskedNumber.DEFAULTS,
41 ...opts
42 });
43 }
44 updateOptions(opts) {
45 super.updateOptions(opts);
46 }
47 _update(opts) {
48 super._update(opts);
49 this._updateRegExps();
50 }
51 _updateRegExps() {
52 const start = '^' + (this.allowNegative ? '[+|\\-]?' : '');
53 const mid = '\\d*';
54 const end = (this.scale ? "(" + escapeRegExp(this.radix) + "\\d{0," + this.scale + "})?" : '') + '$';
55 this._numberRegExp = new RegExp(start + mid + end);
56 this._mapToRadixRegExp = new RegExp("[" + this.mapToRadix.map(escapeRegExp).join('') + "]", 'g');
57 this._thousandsSeparatorRegExp = new RegExp(escapeRegExp(this.thousandsSeparator), 'g');
58 }
59 _removeThousandsSeparators(value) {
60 return value.replace(this._thousandsSeparatorRegExp, '');
61 }
62 _insertThousandsSeparators(value) {
63 // https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript
64 const parts = value.split(this.radix);
65 parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandsSeparator);
66 return parts.join(this.radix);
67 }
68 doPrepareChar(ch, flags) {
69 if (flags === void 0) {
70 flags = {};
71 }
72 const [prepCh, details] = super.doPrepareChar(this._removeThousandsSeparators(this.scale && this.mapToRadix.length && (
73 /*
74 radix should be mapped when
75 1) input is done from keyboard = flags.input && flags.raw
76 2) unmasked value is set = !flags.input && !flags.raw
77 and should not be mapped when
78 1) value is set = flags.input && !flags.raw
79 2) raw value is set = !flags.input && flags.raw
80 */
81 flags.input && flags.raw || !flags.input && !flags.raw) ? ch.replace(this._mapToRadixRegExp, this.radix) : ch), flags);
82 if (ch && !prepCh) details.skip = true;
83 if (prepCh && !this.allowPositive && !this.value && prepCh !== '-') details.aggregate(this._appendChar('-'));
84 return [prepCh, details];
85 }
86 _separatorsCount(to, extendOnSeparators) {
87 if (extendOnSeparators === void 0) {
88 extendOnSeparators = false;
89 }
90 let count = 0;
91 for (let pos = 0; pos < to; ++pos) {
92 if (this._value.indexOf(this.thousandsSeparator, pos) === pos) {
93 ++count;
94 if (extendOnSeparators) to += this.thousandsSeparator.length;
95 }
96 }
97 return count;
98 }
99 _separatorsCountFromSlice(slice) {
100 if (slice === void 0) {
101 slice = this._value;
102 }
103 return this._separatorsCount(this._removeThousandsSeparators(slice).length, true);
104 }
105 extractInput(fromPos, toPos, flags) {
106 if (fromPos === void 0) {
107 fromPos = 0;
108 }
109 if (toPos === void 0) {
110 toPos = this.displayValue.length;
111 }
112 [fromPos, toPos] = this._adjustRangeWithSeparators(fromPos, toPos);
113 return this._removeThousandsSeparators(super.extractInput(fromPos, toPos, flags));
114 }
115 _appendCharRaw(ch, flags) {
116 if (flags === void 0) {
117 flags = {};
118 }
119 const prevBeforeTailValue = flags.tail && flags._beforeTailState ? flags._beforeTailState._value : this._value;
120 const prevBeforeTailSeparatorsCount = this._separatorsCountFromSlice(prevBeforeTailValue);
121 this._value = this._removeThousandsSeparators(this.value);
122 const oldValue = this._value;
123 this._value += ch;
124 const num = this.number;
125 let accepted = !isNaN(num);
126 let skip = false;
127 if (accepted) {
128 let fixedNum;
129 if (this.min != null && this.min < 0 && this.number < this.min) fixedNum = this.min;
130 if (this.max != null && this.max > 0 && this.number > this.max) fixedNum = this.max;
131 if (fixedNum != null) {
132 if (this.autofix) {
133 this._value = this.format(fixedNum, this).replace(MaskedNumber.UNMASKED_RADIX, this.radix);
134 skip || (skip = oldValue === this._value && !flags.tail); // if not changed on tail it's still ok to proceed
135 } else {
136 accepted = false;
137 }
138 }
139 accepted && (accepted = Boolean(this._value.match(this._numberRegExp)));
140 }
141 let appendDetails;
142 if (!accepted) {
143 this._value = oldValue;
144 appendDetails = new ChangeDetails();
145 } else {
146 appendDetails = new ChangeDetails({
147 inserted: this._value.slice(oldValue.length),
148 rawInserted: skip ? '' : ch,
149 skip
150 });
151 }
152 this._value = this._insertThousandsSeparators(this._value);
153 const beforeTailValue = flags.tail && flags._beforeTailState ? flags._beforeTailState._value : this._value;
154 const beforeTailSeparatorsCount = this._separatorsCountFromSlice(beforeTailValue);
155 appendDetails.tailShift += (beforeTailSeparatorsCount - prevBeforeTailSeparatorsCount) * this.thousandsSeparator.length;
156 return appendDetails;
157 }
158 _findSeparatorAround(pos) {
159 if (this.thousandsSeparator) {
160 const searchFrom = pos - this.thousandsSeparator.length + 1;
161 const separatorPos = this.value.indexOf(this.thousandsSeparator, searchFrom);
162 if (separatorPos <= pos) return separatorPos;
163 }
164 return -1;
165 }
166 _adjustRangeWithSeparators(from, to) {
167 const separatorAroundFromPos = this._findSeparatorAround(from);
168 if (separatorAroundFromPos >= 0) from = separatorAroundFromPos;
169 const separatorAroundToPos = this._findSeparatorAround(to);
170 if (separatorAroundToPos >= 0) to = separatorAroundToPos + this.thousandsSeparator.length;
171 return [from, to];
172 }
173 remove(fromPos, toPos) {
174 if (fromPos === void 0) {
175 fromPos = 0;
176 }
177 if (toPos === void 0) {
178 toPos = this.displayValue.length;
179 }
180 [fromPos, toPos] = this._adjustRangeWithSeparators(fromPos, toPos);
181 const valueBeforePos = this.value.slice(0, fromPos);
182 const valueAfterPos = this.value.slice(toPos);
183 const prevBeforeTailSeparatorsCount = this._separatorsCount(valueBeforePos.length);
184 this._value = this._insertThousandsSeparators(this._removeThousandsSeparators(valueBeforePos + valueAfterPos));
185 const beforeTailSeparatorsCount = this._separatorsCountFromSlice(valueBeforePos);
186 return new ChangeDetails({
187 tailShift: (beforeTailSeparatorsCount - prevBeforeTailSeparatorsCount) * this.thousandsSeparator.length
188 });
189 }
190 nearestInputPos(cursorPos, direction) {
191 if (!this.thousandsSeparator) return cursorPos;
192 switch (direction) {
193 case DIRECTION.NONE:
194 case DIRECTION.LEFT:
195 case DIRECTION.FORCE_LEFT:
196 {
197 const separatorAtLeftPos = this._findSeparatorAround(cursorPos - 1);
198 if (separatorAtLeftPos >= 0) {
199 const separatorAtLeftEndPos = separatorAtLeftPos + this.thousandsSeparator.length;
200 if (cursorPos < separatorAtLeftEndPos || this.value.length <= separatorAtLeftEndPos || direction === DIRECTION.FORCE_LEFT) {
201 return separatorAtLeftPos;
202 }
203 }
204 break;
205 }
206 case DIRECTION.RIGHT:
207 case DIRECTION.FORCE_RIGHT:
208 {
209 const separatorAtRightPos = this._findSeparatorAround(cursorPos);
210 if (separatorAtRightPos >= 0) {
211 return separatorAtRightPos + this.thousandsSeparator.length;
212 }
213 }
214 }
215 return cursorPos;
216 }
217 doCommit() {
218 if (this.value) {
219 const number = this.number;
220 let validnum = number;
221
222 // check bounds
223 if (this.min != null) validnum = Math.max(validnum, this.min);
224 if (this.max != null) validnum = Math.min(validnum, this.max);
225 if (validnum !== number) this.unmaskedValue = this.format(validnum, this);
226 let formatted = this.value;
227 if (this.normalizeZeros) formatted = this._normalizeZeros(formatted);
228 if (this.padFractionalZeros && this.scale > 0) formatted = this._padFractionalZeros(formatted);
229 this._value = formatted;
230 }
231 super.doCommit();
232 }
233 _normalizeZeros(value) {
234 const parts = this._removeThousandsSeparators(value).split(this.radix);
235
236 // remove leading zeros
237 parts[0] = parts[0].replace(/^(\D*)(0*)(\d*)/, (match, sign, zeros, num) => sign + num);
238 // add leading zero
239 if (value.length && !/\d$/.test(parts[0])) parts[0] = parts[0] + '0';
240 if (parts.length > 1) {
241 parts[1] = parts[1].replace(/0*$/, ''); // remove trailing zeros
242 if (!parts[1].length) parts.length = 1; // remove fractional
243 }
244 return this._insertThousandsSeparators(parts.join(this.radix));
245 }
246 _padFractionalZeros(value) {
247 if (!value) return value;
248 const parts = value.split(this.radix);
249 if (parts.length < 2) parts.push('');
250 parts[1] = parts[1].padEnd(this.scale, '0');
251 return parts.join(this.radix);
252 }
253 doSkipInvalid(ch, flags, checkTail) {
254 if (flags === void 0) {
255 flags = {};
256 }
257 const dropFractional = this.scale === 0 && ch !== this.thousandsSeparator && (ch === this.radix || ch === MaskedNumber.UNMASKED_RADIX || this.mapToRadix.includes(ch));
258 return super.doSkipInvalid(ch, flags, checkTail) && !dropFractional;
259 }
260 get unmaskedValue() {
261 return this._removeThousandsSeparators(this._normalizeZeros(this.value)).replace(this.radix, MaskedNumber.UNMASKED_RADIX);
262 }
263 set unmaskedValue(unmaskedValue) {
264 super.unmaskedValue = unmaskedValue;
265 }
266 get typedValue() {
267 return this.parse(this.unmaskedValue, this);
268 }
269 set typedValue(n) {
270 this.rawInputValue = this.format(n, this).replace(MaskedNumber.UNMASKED_RADIX, this.radix);
271 }
272
273 /** Parsed Number */
274 get number() {
275 return this.typedValue;
276 }
277 set number(number) {
278 this.typedValue = number;
279 }
280 get allowNegative() {
281 return this.min != null && this.min < 0 || this.max != null && this.max < 0;
282 }
283 get allowPositive() {
284 return this.min != null && this.min > 0 || this.max != null && this.max > 0;
285 }
286 typedValueEquals(value) {
287 // handle 0 -> '' case (typed = 0 even if value = '')
288 // for details see https://github.com/uNmAnNeR/imaskjs/issues/134
289 return (super.typedValueEquals(value) || MaskedNumber.EMPTY_VALUES.includes(value) && MaskedNumber.EMPTY_VALUES.includes(this.typedValue)) && !(value === 0 && this.value === '');
290 }
291 }
292 _MaskedNumber = MaskedNumber;
293 MaskedNumber.UNMASKED_RADIX = '.';
294 MaskedNumber.EMPTY_VALUES = [...Masked.EMPTY_VALUES, 0];
295 MaskedNumber.DEFAULTS = {
296 ...Masked.DEFAULTS,
297 mask: Number,
298 radix: ',',
299 thousandsSeparator: '',
300 mapToRadix: [_MaskedNumber.UNMASKED_RADIX],
301 min: Number.MIN_SAFE_INTEGER,
302 max: Number.MAX_SAFE_INTEGER,
303 scale: 2,
304 normalizeZeros: true,
305 padFractionalZeros: false,
306 parse: Number,
307 format: n => n.toLocaleString('en-US', {
308 useGrouping: false,
309 maximumFractionDigits: 20
310 })
311 };
312 IMask.MaskedNumber = MaskedNumber;
313
314 export { MaskedNumber as default };
315