@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/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
+ };