@player-ui/common-types-plugin 0.0.1-next.1
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/dist/index.cjs.js +756 -0
- package/dist/index.d.ts +250 -0
- package/dist/index.esm.js +743 -0
- package/package.json +19 -0
- package/src/data-types/refs.ts +33 -0
- package/src/data-types/types.ts +113 -0
- package/src/formats/index.ts +425 -0
- package/src/formats/utils.ts +192 -0
- package/src/index.ts +27 -0
- package/src/validators/index.ts +366 -0
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@player-ui/common-types-plugin",
|
|
3
|
+
"version": "0.0.1-next.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"registry": "https://registry.npmjs.org"
|
|
7
|
+
},
|
|
8
|
+
"peerDependencies": {
|
|
9
|
+
"@player-ui/binding-grammar": "0.0.1-next.1"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@player-ui/utils": "0.0.1-next.1",
|
|
13
|
+
"@player-ui/binding-grammar": "0.0.1-next.1",
|
|
14
|
+
"@babel/runtime": "7.15.4"
|
|
15
|
+
},
|
|
16
|
+
"main": "dist/index.cjs.js",
|
|
17
|
+
"module": "dist/index.esm.js",
|
|
18
|
+
"typings": "dist/index.d.ts"
|
|
19
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Language } from '@player-ui/types';
|
|
2
|
+
|
|
3
|
+
export const BooleanTypeRef: Language.DataTypeRef = {
|
|
4
|
+
type: 'BooleanType',
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const IntegerTypeRef: Language.DataTypeRef = {
|
|
8
|
+
type: 'IntegerType',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const IntegerPosTypeRef: Language.DataTypeRef = {
|
|
12
|
+
type: 'IntegerPosType',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const IntegerNNTypeRef: Language.DataTypeRef = {
|
|
16
|
+
type: 'IntegerNNType',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const StringTypeRef: Language.DataTypeRef = {
|
|
20
|
+
type: 'StringType',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const CollectionTypeRef: Language.DataTypeRef = {
|
|
24
|
+
type: 'CollectionType',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const DateTypeRef: Language.DataTypeRef = {
|
|
28
|
+
type: 'DateType',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const PhoneTypeRef: Language.DataTypeRef = {
|
|
32
|
+
type: 'PhoneType',
|
|
33
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Schema } from '@player-ui/types';
|
|
2
|
+
import {
|
|
3
|
+
BooleanTypeRef,
|
|
4
|
+
CollectionTypeRef,
|
|
5
|
+
DateTypeRef,
|
|
6
|
+
IntegerPosTypeRef,
|
|
7
|
+
IntegerTypeRef,
|
|
8
|
+
PhoneTypeRef,
|
|
9
|
+
StringTypeRef,
|
|
10
|
+
IntegerNNTypeRef,
|
|
11
|
+
} from './refs';
|
|
12
|
+
|
|
13
|
+
export const BooleanType: Schema.DataType<boolean> = {
|
|
14
|
+
...BooleanTypeRef,
|
|
15
|
+
default: false,
|
|
16
|
+
validation: [
|
|
17
|
+
{
|
|
18
|
+
type: 'oneOf',
|
|
19
|
+
message: 'Value must be true or false',
|
|
20
|
+
options: [true, false],
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const IntegerType: Schema.DataType<number> = {
|
|
26
|
+
...IntegerTypeRef,
|
|
27
|
+
validation: [
|
|
28
|
+
{
|
|
29
|
+
type: 'integer',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
format: {
|
|
33
|
+
type: 'integer',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const IntegerPosType: Schema.DataType<number> = {
|
|
38
|
+
...IntegerPosTypeRef,
|
|
39
|
+
validation: [
|
|
40
|
+
{
|
|
41
|
+
type: 'integer',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'min',
|
|
45
|
+
value: 1,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
format: {
|
|
49
|
+
type: 'integer',
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const IntegerNNType: Schema.DataType<number> = {
|
|
54
|
+
...IntegerNNTypeRef,
|
|
55
|
+
validation: [
|
|
56
|
+
{
|
|
57
|
+
type: 'integer',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: 'min',
|
|
61
|
+
value: 0,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
format: {
|
|
65
|
+
type: 'integer',
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const StringType: Schema.DataType<string> = {
|
|
70
|
+
...StringTypeRef,
|
|
71
|
+
default: '',
|
|
72
|
+
validation: [
|
|
73
|
+
{
|
|
74
|
+
type: 'string',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
format: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const CollectionType: Schema.DataType<Array<unknown>> = {
|
|
83
|
+
...CollectionTypeRef,
|
|
84
|
+
validation: [
|
|
85
|
+
{
|
|
86
|
+
type: 'collection',
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const DateType: Schema.DataType<string> = {
|
|
92
|
+
...DateTypeRef,
|
|
93
|
+
validation: [
|
|
94
|
+
{
|
|
95
|
+
type: 'string',
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
format: {
|
|
99
|
+
type: 'date',
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const PhoneType: Schema.DataType<string> = {
|
|
104
|
+
...PhoneTypeRef,
|
|
105
|
+
validation: [
|
|
106
|
+
{
|
|
107
|
+
type: 'phone',
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
format: {
|
|
111
|
+
type: 'phone',
|
|
112
|
+
},
|
|
113
|
+
};
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import type { FormatType } from '@player-ui/schema';
|
|
2
|
+
import { createMaskedNumericFormatter } from './utils';
|
|
3
|
+
|
|
4
|
+
const LENGTH_OF_MAX_INT = String(Number.MAX_SAFE_INTEGER).split('').length;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Converts an integer to and from a string for display
|
|
8
|
+
*/
|
|
9
|
+
export const integer: FormatType<number, string> = {
|
|
10
|
+
name: 'integer',
|
|
11
|
+
|
|
12
|
+
/** Converts any integer to a string */
|
|
13
|
+
format: (value) => {
|
|
14
|
+
if (value === '-') {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const formatted = integer.deformat?.(value) ?? value;
|
|
19
|
+
|
|
20
|
+
if (typeof formatted === 'number') {
|
|
21
|
+
return String(formatted);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return '';
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
/** Converts any string or number to an integer */
|
|
28
|
+
deformat: (value) => {
|
|
29
|
+
if (typeof value === 'number') {
|
|
30
|
+
// Handle different zeros. Math.floor(-0) is still -0
|
|
31
|
+
return Math.floor(value) + 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (typeof value !== 'string') {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isNeg = value.replace(/[^0-9.-]/g, '').charAt(0) === '-';
|
|
39
|
+
|
|
40
|
+
// Remove everything but digits and decimal
|
|
41
|
+
let digits = value.replace(/[^0-9.]/g, '');
|
|
42
|
+
const decimalPlace = digits.indexOf('.');
|
|
43
|
+
|
|
44
|
+
if (decimalPlace > -1) {
|
|
45
|
+
digits = digits.substring(0, decimalPlace);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (digits.length === 0) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Can't be longer than the biggest int
|
|
53
|
+
digits = digits.substr(0, LENGTH_OF_MAX_INT);
|
|
54
|
+
|
|
55
|
+
const num = Number(`${isNeg ? '-' : ''}${digits}`);
|
|
56
|
+
|
|
57
|
+
// Handle different zeros. Math.floor(-0) is still -0
|
|
58
|
+
return Math.floor(num) + 0;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Converts a number to/from a comma separated version */
|
|
63
|
+
export const commaNumber: FormatType<
|
|
64
|
+
number,
|
|
65
|
+
string,
|
|
66
|
+
{
|
|
67
|
+
/** The number of decimal places to show */
|
|
68
|
+
precision?: number;
|
|
69
|
+
}
|
|
70
|
+
> = {
|
|
71
|
+
name: 'commaNumber',
|
|
72
|
+
|
|
73
|
+
/** Go from number to number w/ commas */
|
|
74
|
+
format: (_value, options) => {
|
|
75
|
+
if (_value === undefined || _value === '') {
|
|
76
|
+
return _value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (typeof _value !== 'string' && typeof _value !== 'number') {
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const value = String(_value);
|
|
84
|
+
|
|
85
|
+
// Check to see if first valid char is a negative
|
|
86
|
+
const isNeg = value.replace(/[^0-9.-]/g, '').charAt(0) === '-';
|
|
87
|
+
// Remove everything but digits and decimal
|
|
88
|
+
let digitAndDecimal = value.replace(/[^0-9.]/g, '');
|
|
89
|
+
// Remove extra leading zeros
|
|
90
|
+
digitAndDecimal = digitAndDecimal.replace(/^(0*)((0.)?\d)/g, '$2');
|
|
91
|
+
|
|
92
|
+
// Find index of first decimal point, for insertion later
|
|
93
|
+
const firstDecimal = digitAndDecimal.indexOf('.');
|
|
94
|
+
|
|
95
|
+
// Remove all non-digits i.e. extra decimal points
|
|
96
|
+
const digitsOnly = digitAndDecimal.replace(/[^0-9]/g, '');
|
|
97
|
+
|
|
98
|
+
let preDecDigits = digitsOnly;
|
|
99
|
+
let postDecDigits = '';
|
|
100
|
+
|
|
101
|
+
if (firstDecimal >= 0) {
|
|
102
|
+
preDecDigits = digitsOnly
|
|
103
|
+
.substring(0, firstDecimal)
|
|
104
|
+
.substr(0, LENGTH_OF_MAX_INT);
|
|
105
|
+
postDecDigits = digitsOnly.substring(firstDecimal);
|
|
106
|
+
} else {
|
|
107
|
+
preDecDigits = preDecDigits.substr(0, LENGTH_OF_MAX_INT);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (options?.precision !== undefined) {
|
|
111
|
+
postDecDigits = postDecDigits
|
|
112
|
+
.substring(0, options.precision)
|
|
113
|
+
.padEnd(options.precision, '0');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Beautify
|
|
117
|
+
preDecDigits = preDecDigits.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
118
|
+
|
|
119
|
+
if (preDecDigits === '' && firstDecimal === 0) {
|
|
120
|
+
preDecDigits = '0';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Put pieces together
|
|
124
|
+
let retVal = preDecDigits;
|
|
125
|
+
|
|
126
|
+
if (isNeg) {
|
|
127
|
+
retVal = `-${retVal}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (firstDecimal >= 0 || options?.precision !== undefined) {
|
|
131
|
+
retVal += `.${postDecDigits}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return retVal;
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
/** Go from string with comma's to numbers */
|
|
138
|
+
deformat: (value) => {
|
|
139
|
+
if (typeof value !== 'string') {
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const strValue = value.replace(/,/g, '');
|
|
144
|
+
|
|
145
|
+
if (strValue === '') {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const number = Number(strValue);
|
|
150
|
+
|
|
151
|
+
return isNaN(number) ||
|
|
152
|
+
number > Number.MAX_SAFE_INTEGER ||
|
|
153
|
+
number < Number.MIN_SAFE_INTEGER
|
|
154
|
+
? undefined
|
|
155
|
+
: number;
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const date: FormatType<
|
|
160
|
+
string,
|
|
161
|
+
string,
|
|
162
|
+
{
|
|
163
|
+
/** The mask to use to format the date */
|
|
164
|
+
mask?: string;
|
|
165
|
+
}
|
|
166
|
+
> = {
|
|
167
|
+
name: 'date',
|
|
168
|
+
|
|
169
|
+
format: (_value, options) => {
|
|
170
|
+
let value = typeof _value === 'number' ? String(_value) : _value;
|
|
171
|
+
if (_value === undefined) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (typeof value !== 'string' || value === '') {
|
|
176
|
+
return '';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// matching anything in DDDD-DD-DD format, including invalid date like 1111-99-99
|
|
180
|
+
if (value.match(/^\d{4}[-]\d{1,2}[-]\d{1,2}$/)) {
|
|
181
|
+
const tempVal = value.split('-');
|
|
182
|
+
value = `${tempVal[1]}/${tempVal[2]}/${tempVal[0]}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const dateFormat = options?.mask?.toUpperCase() ?? 'MM/DD/YYYY';
|
|
186
|
+
|
|
187
|
+
const delimiter = dateFormat.replace(/[^/.-]/g, '').charAt(0);
|
|
188
|
+
const formatParts = dateFormat.split(delimiter);
|
|
189
|
+
const valueParts = value.split(delimiter);
|
|
190
|
+
const processedValueParts = [];
|
|
191
|
+
let lastMatchIsFull = true;
|
|
192
|
+
|
|
193
|
+
for (let index = 0; index < valueParts.length; index++) {
|
|
194
|
+
let part = valueParts[index];
|
|
195
|
+
|
|
196
|
+
if (lastMatchIsFull && index < formatParts.length) {
|
|
197
|
+
// Remove all non-digits
|
|
198
|
+
part = part.replace(/[^0-9]/g, '');
|
|
199
|
+
const isLastExpectedField = formatParts.length - 1 === index;
|
|
200
|
+
const hasDelimiterAfter = valueParts.length - 1 > index;
|
|
201
|
+
const curFormat = formatParts[index];
|
|
202
|
+
|
|
203
|
+
if (curFormat === 'YYYY') {
|
|
204
|
+
if (part.length > 4) {
|
|
205
|
+
valueParts[index + 1] = [
|
|
206
|
+
'*',
|
|
207
|
+
part.substring(4),
|
|
208
|
+
valueParts[index + 1],
|
|
209
|
+
].join('');
|
|
210
|
+
part = part.substring(0, 4);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (part.length === 4) {
|
|
214
|
+
lastMatchIsFull = true;
|
|
215
|
+
processedValueParts.push(part);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (part.length === 3) {
|
|
219
|
+
if (isLastExpectedField || !hasDelimiterAfter) {
|
|
220
|
+
lastMatchIsFull = false;
|
|
221
|
+
processedValueParts.push(part);
|
|
222
|
+
} else {
|
|
223
|
+
valueParts[index + 1] = `*${part.substring(2)}${
|
|
224
|
+
valueParts[index + 1]
|
|
225
|
+
}`;
|
|
226
|
+
part = part.substring(0, 2);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (part.length === 2) {
|
|
231
|
+
// Autocomplete completes 2 digit years based on rule:
|
|
232
|
+
// If autocompleted year is in this millennium up to this year + 2
|
|
233
|
+
// Else put it in the last millennium
|
|
234
|
+
// 19 and 20 aren't autocompleted unless there is a separator after
|
|
235
|
+
let autocomplete;
|
|
236
|
+
|
|
237
|
+
// If user didn't enter 2 digits, don't autocomplete YYYY
|
|
238
|
+
// Otherwise, 19 and 20 aren't autocompleted unless there is a separator after
|
|
239
|
+
if (
|
|
240
|
+
part.length === 2 &&
|
|
241
|
+
(hasDelimiterAfter ||
|
|
242
|
+
(isLastExpectedField && part !== '19' && part !== '20'))
|
|
243
|
+
) {
|
|
244
|
+
autocomplete = `20${part}`;
|
|
245
|
+
|
|
246
|
+
if (
|
|
247
|
+
part > (new Date().getFullYear() + 5).toString().substring(2)
|
|
248
|
+
) {
|
|
249
|
+
autocomplete = `19${part}`;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (autocomplete) {
|
|
254
|
+
lastMatchIsFull = true;
|
|
255
|
+
processedValueParts.push(autocomplete);
|
|
256
|
+
} else {
|
|
257
|
+
lastMatchIsFull = false;
|
|
258
|
+
processedValueParts.push(part);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (part.length === 1 || part.length === 0) {
|
|
263
|
+
lastMatchIsFull = false;
|
|
264
|
+
processedValueParts.push(part);
|
|
265
|
+
}
|
|
266
|
+
} else if (curFormat === 'YY') {
|
|
267
|
+
if (part.length > 2) {
|
|
268
|
+
valueParts[index + 1] = [
|
|
269
|
+
'*',
|
|
270
|
+
part.substring(2),
|
|
271
|
+
valueParts[index + 1],
|
|
272
|
+
].join('');
|
|
273
|
+
part = part.substring(0, 2);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (part.length === 2) {
|
|
277
|
+
lastMatchIsFull = true;
|
|
278
|
+
processedValueParts.push(part);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (part.length === 1 || part.length === 0) {
|
|
282
|
+
lastMatchIsFull = false;
|
|
283
|
+
processedValueParts.push(part);
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
// Only MM and DD left
|
|
287
|
+
if (part.length > 2) {
|
|
288
|
+
valueParts[index + 1] = [
|
|
289
|
+
'*',
|
|
290
|
+
part.substring(2),
|
|
291
|
+
valueParts[index + 1],
|
|
292
|
+
].join('');
|
|
293
|
+
part = part.substring(0, 2);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (part.length === 2) {
|
|
297
|
+
// 00 isn't a valid month or day,
|
|
298
|
+
// but if they typed in a delimiter,
|
|
299
|
+
// let them deal with it being wrong
|
|
300
|
+
if (part === '00' && !hasDelimiterAfter) {
|
|
301
|
+
lastMatchIsFull = false;
|
|
302
|
+
processedValueParts.push('0');
|
|
303
|
+
} else {
|
|
304
|
+
lastMatchIsFull = true;
|
|
305
|
+
processedValueParts.push(part);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (part.length === 1) {
|
|
310
|
+
if (hasDelimiterAfter) {
|
|
311
|
+
lastMatchIsFull = true;
|
|
312
|
+
processedValueParts.push(`0${part}`);
|
|
313
|
+
} else {
|
|
314
|
+
lastMatchIsFull = false;
|
|
315
|
+
processedValueParts.push(part);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (part.length === 0) {
|
|
320
|
+
lastMatchIsFull = false;
|
|
321
|
+
processedValueParts.push(part);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return processedValueParts.join(delimiter);
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
export const currency: FormatType<
|
|
332
|
+
number,
|
|
333
|
+
string,
|
|
334
|
+
{
|
|
335
|
+
/** The symbol to use for currency */
|
|
336
|
+
currencySymbol?: string;
|
|
337
|
+
|
|
338
|
+
/** Use parenthesis instead of a - sign for negative values */
|
|
339
|
+
useParensForNeg?: boolean;
|
|
340
|
+
|
|
341
|
+
/** The number of decimal places to show */
|
|
342
|
+
precision?: number;
|
|
343
|
+
}
|
|
344
|
+
> = {
|
|
345
|
+
name: 'currency',
|
|
346
|
+
format: (_value, options) => {
|
|
347
|
+
const value = typeof _value === 'number' ? String(_value) : _value;
|
|
348
|
+
const {
|
|
349
|
+
currencySymbol = '',
|
|
350
|
+
useParensForNeg = false,
|
|
351
|
+
precision = 2,
|
|
352
|
+
} = options ?? {};
|
|
353
|
+
|
|
354
|
+
if (value === undefined || value === '') {
|
|
355
|
+
return value;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (typeof value !== 'string') {
|
|
359
|
+
return value;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const sign = /^\s*-/.test(value) ? -1 : 1;
|
|
363
|
+
const dotIndex = value.indexOf('.');
|
|
364
|
+
|
|
365
|
+
let preDecimal: string;
|
|
366
|
+
let postDecimal: string;
|
|
367
|
+
|
|
368
|
+
// Strip out non-digits
|
|
369
|
+
// Check if first non-empty character is a minus sign
|
|
370
|
+
if (dotIndex >= 0) {
|
|
371
|
+
preDecimal = value.substr(0, dotIndex).replace(/\D+/g, '');
|
|
372
|
+
postDecimal = value.substr(dotIndex + 1).replace(/\D+/g, '');
|
|
373
|
+
} else {
|
|
374
|
+
preDecimal = value.replace(/\D+/g, '');
|
|
375
|
+
postDecimal = '0';
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const numericalValue = sign * Number(`${preDecimal}.${postDecimal}`);
|
|
379
|
+
|
|
380
|
+
const fixedString = numericalValue.toFixed(precision);
|
|
381
|
+
|
|
382
|
+
// Beautify - add commas between groups of 3 digits
|
|
383
|
+
// Would need to split the string first if we had more than 3 decimal places
|
|
384
|
+
const prettyString = fixedString.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
385
|
+
|
|
386
|
+
if (prettyString.charAt(0) === '-') {
|
|
387
|
+
if (useParensForNeg) {
|
|
388
|
+
return `(${currencySymbol}${prettyString.substring(1)})`;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return `-${currencySymbol}${prettyString.substring(1)}`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return currencySymbol + prettyString;
|
|
395
|
+
},
|
|
396
|
+
deformat: (value, options) => {
|
|
397
|
+
if (typeof value === 'number') {
|
|
398
|
+
return value;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (typeof value !== 'string') {
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
let deformatted = value;
|
|
406
|
+
|
|
407
|
+
if (options?.currencySymbol) {
|
|
408
|
+
deformatted = value.replace(options.currencySymbol, '');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return commaNumber.deformat?.(deformatted);
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const basePhoneFormatter = createMaskedNumericFormatter(
|
|
416
|
+
'phone',
|
|
417
|
+
'(###) ###-####'
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
export const phone: FormatType<string> = {
|
|
421
|
+
...basePhoneFormatter,
|
|
422
|
+
deformat: (value) => basePhoneFormatter.deformat?.(value),
|
|
423
|
+
format: (value) =>
|
|
424
|
+
basePhoneFormatter.format?.(value === '(' ? '' : value) ?? value,
|
|
425
|
+
};
|