@es-joy/jsoe 0.12.4 → 0.13.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/.nyc_output/out.json +11754 -11508
- package/CHANGES.md +8 -0
- package/demo/index.js +38 -29
- package/dist/formatAndTypeChoices.d.ts +11 -2
- package/dist/formatAndTypeChoices.d.ts.map +1 -1
- package/dist/formats/structuredCloning.d.ts.map +1 -1
- package/dist/formats.d.ts +52 -10
- package/dist/formats.d.ts.map +1 -1
- package/dist/fundamentalTypes/arrayType.d.ts.map +1 -1
- package/dist/fundamentalTypes/dateType.d.ts.map +1 -1
- package/dist/fundamentalTypes/errorType.d.ts.map +1 -1
- package/dist/fundamentalTypes/regexpType.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +795 -620
- package/dist/superTypes/errorsSpecialType.d.ts.map +1 -1
- package/dist/typeChoices.d.ts +4 -0
- package/dist/typeChoices.d.ts.map +1 -1
- package/dist/types.d.ts +308 -74
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/formatAndTypeChoices.js +20 -5
- package/src/formats/indexedDBKey.js +1 -1
- package/src/formats/json.js +1 -1
- package/src/formats/schemaAndArbitrary.js +1 -1
- package/src/formats/schemaOnly.js +1 -1
- package/src/formats/structuredCloning.js +22 -13
- package/src/formats.js +87 -26
- package/src/fundamentalTypes/arrayReferenceType.js +2 -2
- package/src/fundamentalTypes/arrayType.js +43 -23
- package/src/fundamentalTypes/dateType.js +2 -3
- package/src/fundamentalTypes/errorType.js +5 -3
- package/src/fundamentalTypes/regexpType.js +2 -3
- package/src/index.js +1 -1
- package/src/superTypes/errorsSpecialType.js +12 -5
- package/src/typeChoices.js +21 -10
- package/src/types.js +579 -527
package/src/types.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import Formats from './formats.js';
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
// jamilih
|
|
3
5
|
jml,
|
|
@@ -5,8 +7,6 @@ import {
|
|
|
5
7
|
Typeson, getJSONType, structuredCloningThrowing
|
|
6
8
|
} from './vendor-imports.js';
|
|
7
9
|
|
|
8
|
-
import Formats from './formats.js';
|
|
9
|
-
|
|
10
10
|
import {$e, $$e} from './utils/templateUtils.js';
|
|
11
11
|
|
|
12
12
|
import nullType from './fundamentalTypes/nullType.js';
|
|
@@ -66,6 +66,11 @@ export const getPropertyValueFromLegend = (legend) => {
|
|
|
66
66
|
return String(Number.parseInt(propElem.textContent) - 1);
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Any other possibilities than `div`?
|
|
71
|
+
* @typedef {HTMLDivElement} RootElement
|
|
72
|
+
*/
|
|
73
|
+
|
|
69
74
|
/**
|
|
70
75
|
* Utility to retrieve the type out of a type root element.
|
|
71
76
|
* @callback GetTypeForRoot
|
|
@@ -78,7 +83,7 @@ export const getPropertyValueFromLegend = (legend) => {
|
|
|
78
83
|
* state and path.
|
|
79
84
|
* @callback GetValueForRoot
|
|
80
85
|
* @param {RootElement} root
|
|
81
|
-
* @param {StateObject}
|
|
86
|
+
* @param {StateObject} stateObj
|
|
82
87
|
* @param {string} [currentPath]
|
|
83
88
|
* @returns {StructuredCloneValue}
|
|
84
89
|
*/
|
|
@@ -95,7 +100,7 @@ export const getPropertyValueFromLegend = (legend) => {
|
|
|
95
100
|
* Utility to get the value for a root using its ancestor and state.
|
|
96
101
|
* @callback GetValueFromRootAncestor
|
|
97
102
|
* @param {string|HTMLElement} selOrEl
|
|
98
|
-
* @param {StateObject}
|
|
103
|
+
* @param {StateObject} stateObj
|
|
99
104
|
* @returns {StructuredCloneValue}
|
|
100
105
|
*/
|
|
101
106
|
|
|
@@ -106,13 +111,6 @@ export const getPropertyValueFromLegend = (legend) => {
|
|
|
106
111
|
* HTMLButtonElement}
|
|
107
112
|
*/
|
|
108
113
|
|
|
109
|
-
/**
|
|
110
|
-
* @callback GetTypesForFormatAndState
|
|
111
|
-
* @param {string} format
|
|
112
|
-
* @param {string} [state]
|
|
113
|
-
* @returns {AvailableType[]|undefined}
|
|
114
|
-
*/
|
|
115
|
-
|
|
116
114
|
/**
|
|
117
115
|
* @callback GetOptionForType
|
|
118
116
|
* @param {AvailableType} type
|
|
@@ -121,11 +119,40 @@ export const getPropertyValueFromLegend = (legend) => {
|
|
|
121
119
|
|
|
122
120
|
/**
|
|
123
121
|
* @callback GetTypeOptionsForFormatAndState
|
|
124
|
-
* @param {
|
|
122
|
+
* @param {import('./formats.js').AvailableFormat} format
|
|
125
123
|
* @param {string} [parserState]
|
|
126
124
|
* @returns {[string, {value: AvailableType, title?: string}][]}
|
|
127
125
|
*/
|
|
128
126
|
|
|
127
|
+
/**
|
|
128
|
+
* @typedef {(
|
|
129
|
+
* keypath: string,
|
|
130
|
+
* parentPath: string,
|
|
131
|
+
* arrayOrObjectPropertyName: string,
|
|
132
|
+
* valueType: string
|
|
133
|
+
* ) => StateObject} GetPossibleSchemasForPathAndType
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @typedef {{
|
|
138
|
+
* typeNamespace?: string,
|
|
139
|
+
* "readonly"?: boolean,
|
|
140
|
+
* format?: import('./formats.js').AvailableFormat,
|
|
141
|
+
* formats?: import('./formats.js').default,
|
|
142
|
+
* types?: Types,
|
|
143
|
+
* error?: Error,
|
|
144
|
+
* rootUI?: Element,
|
|
145
|
+
* schema?: string,
|
|
146
|
+
* schemaContent?: object,
|
|
147
|
+
* getPossibleSchemasForPathAndType?: GetPossibleSchemasForPathAndType,
|
|
148
|
+
* paths?: {[currentPath: string]: {
|
|
149
|
+
* referentPath: string,
|
|
150
|
+
* expectArrayReferent: boolean
|
|
151
|
+
* }},
|
|
152
|
+
* handlingReference?: boolean
|
|
153
|
+
* }} StateObject
|
|
154
|
+
*/
|
|
155
|
+
|
|
129
156
|
/* eslint-disable jsdoc/valid-types -- readonly reserved */
|
|
130
157
|
/**
|
|
131
158
|
* @typedef {(cfg: {
|
|
@@ -204,35 +231,6 @@ export const getPropertyValueFromLegend = (legend) => {
|
|
|
204
231
|
* ) => void} CustomValidateAllReferences
|
|
205
232
|
*/
|
|
206
233
|
|
|
207
|
-
/**
|
|
208
|
-
* @type {{
|
|
209
|
-
* availableTypes: {
|
|
210
|
-
* [key in AvailableType]: Partial<TypeObject>|string[]
|
|
211
|
-
* },
|
|
212
|
-
* getTypeForRoot: GetTypeForRoot,
|
|
213
|
-
* getValueForRoot: GetValueForRoot,
|
|
214
|
-
* getFormControlForRoot: GetFormControlForRoot,
|
|
215
|
-
* getValueFromRootAncestor: GetValueFromRootAncestor,
|
|
216
|
-
* getFormControlFromRootAncestor: GetFormControlFromRootAncestor,
|
|
217
|
-
* getTypesForFormatAndState: GetTypesForFormatAndState,
|
|
218
|
-
* getOptionForType: GetOptionForType,
|
|
219
|
-
* getTypeOptionsForFormatAndState: GetTypeOptionsForFormatAndState,
|
|
220
|
-
* getUIForModeAndType: GetUIForModeAndType,
|
|
221
|
-
* contexts: {
|
|
222
|
-
* [key: string]: {
|
|
223
|
-
* [key: string]: {type: AvailableType, after: AvailableType}[]
|
|
224
|
-
* }
|
|
225
|
-
* },
|
|
226
|
-
* validValuesSet: ValidValuesSet,
|
|
227
|
-
* validateAllReferences: ValidateAllReferences,
|
|
228
|
-
* validate: Validate,
|
|
229
|
-
* setValue: SetValue,
|
|
230
|
-
* getValueForString: GetValueForString,
|
|
231
|
-
* customValidateAllReferences?: CustomValidateAllReferences
|
|
232
|
-
* }}
|
|
233
|
-
*/
|
|
234
|
-
const Types = {};
|
|
235
|
-
|
|
236
234
|
/**
|
|
237
235
|
* @typedef {import('./formats.js').StructuredCloneValue} StructuredCloneValue
|
|
238
236
|
*/
|
|
@@ -254,7 +252,8 @@ const Types = {};
|
|
|
254
252
|
* path: string
|
|
255
253
|
* ][],
|
|
256
254
|
* parent?: object,
|
|
257
|
-
* parentPath?: string|number
|
|
255
|
+
* parentPath?: string|number,
|
|
256
|
+
* types?: Types
|
|
258
257
|
* }} RootInfo
|
|
259
258
|
*/
|
|
260
259
|
|
|
@@ -319,13 +318,16 @@ const Types = {};
|
|
|
319
318
|
* type?: AvailableType,
|
|
320
319
|
* topRoot?: HTMLDivElement,
|
|
321
320
|
* resultType?: "keys"|"values"|"both",
|
|
322
|
-
* format: import('./formats.js').AvailableFormat
|
|
321
|
+
* format: import('./formats.js').AvailableFormat,
|
|
322
|
+
* types: Types
|
|
323
323
|
* }) => JamilihArray} viewUI
|
|
324
324
|
* @property {(info: {
|
|
325
325
|
* value?: StructuredCloneValue,
|
|
326
326
|
* typeNamespace?: string,
|
|
327
327
|
* bringIntoFocus?: boolean,
|
|
328
328
|
* format?: import('./formats.js').AvailableFormat,
|
|
329
|
+
* formats?: import('./formats.js').default,
|
|
330
|
+
* types: Types,
|
|
329
331
|
* resultType?: "keys"|"values"|"both",
|
|
330
332
|
* type?: AvailableType,
|
|
331
333
|
* arrayState?: string,
|
|
@@ -345,7 +347,10 @@ const Types = {};
|
|
|
345
347
|
* message?: string,
|
|
346
348
|
* valid: boolean
|
|
347
349
|
* }} [validate] Message will be used if validity is false.
|
|
348
|
-
* @property {(info: {
|
|
350
|
+
* @property {(info: {
|
|
351
|
+
* topRoot: HTMLDivElement,
|
|
352
|
+
* types: Types
|
|
353
|
+
* }) => void} [validateAll] For
|
|
349
354
|
* validation of array and object references only.
|
|
350
355
|
* @property {{
|
|
351
356
|
* structuredCloning: {
|
|
@@ -356,308 +361,566 @@ const Types = {};
|
|
|
356
361
|
* context types
|
|
357
362
|
*/
|
|
358
363
|
|
|
359
|
-
Types.availableTypes = {
|
|
360
|
-
null: nullType,
|
|
361
|
-
true: trueType,
|
|
362
|
-
false: falseType,
|
|
363
|
-
number: numberType,
|
|
364
|
-
bigint: bigintType,
|
|
365
|
-
string: stringType,
|
|
366
|
-
arrayReference: arrayReferenceType,
|
|
367
|
-
objectReference: objectReferenceType,
|
|
368
|
-
array: arrayType,
|
|
369
|
-
// Note: We don't do for BooleanObject/NumberObject/StringObject, date,
|
|
370
|
-
// regexp, as added properties on them are not being cloned (in Chrome
|
|
371
|
-
// at least)
|
|
372
|
-
object: objectType,
|
|
373
|
-
date: dateType,
|
|
374
|
-
|
|
375
|
-
// This type is only for throwing upon cloning errors:
|
|
376
|
-
// 'checkDataCloneException'
|
|
377
|
-
// This type might be supported by evaluable JS or config
|
|
378
|
-
// passed in:
|
|
379
|
-
userObject: ['User objects'],
|
|
380
|
-
undef: undefinedType,
|
|
381
|
-
SpecialRealNumber: SpecialRealNumberSuperType,
|
|
382
|
-
SpecialNumber: SpecialNumberSuperType,
|
|
383
|
-
|
|
384
|
-
error: errorType,
|
|
385
|
-
errors: errorsSpecialType,
|
|
386
|
-
|
|
387
|
-
regexp: regexpType,
|
|
388
|
-
BooleanObject: BooleanObjectType,
|
|
389
|
-
NumberObject: NumberObjectType,
|
|
390
|
-
StringObject: StringObjectType,
|
|
391
|
-
|
|
392
|
-
map: mapType,
|
|
393
|
-
set: setType,
|
|
394
|
-
|
|
395
|
-
file: fileType,
|
|
396
|
-
filelist: filelistType,
|
|
397
|
-
blob: blobType,
|
|
398
|
-
blobHTML: blobHTMLType,
|
|
399
|
-
|
|
400
|
-
domexception: domexceptionType,
|
|
401
|
-
domrect: domrectType,
|
|
402
|
-
dompoint: dompointType,
|
|
403
|
-
dommatrix: dommatrixType,
|
|
404
|
-
|
|
405
|
-
arraybuffer: {
|
|
406
|
-
option: ['ArrayBuffer']
|
|
407
|
-
},
|
|
408
|
-
arraybufferview: {
|
|
409
|
-
option: ['ArrayBufferView']
|
|
410
|
-
},
|
|
411
|
-
dataview: {
|
|
412
|
-
option: ['DataView']
|
|
413
|
-
},
|
|
414
|
-
imagedata: {
|
|
415
|
-
option: ['ImageData']
|
|
416
|
-
},
|
|
417
|
-
imagebitmap: {
|
|
418
|
-
option: ['ImageBitmap']
|
|
419
|
-
},
|
|
420
|
-
|
|
421
|
-
// Typed Arrays
|
|
422
|
-
int8array: {
|
|
423
|
-
option: ['Int8Array']
|
|
424
|
-
},
|
|
425
|
-
uint8array: {
|
|
426
|
-
option: ['Uint8Array']
|
|
427
|
-
},
|
|
428
|
-
uint8clampedarray: {
|
|
429
|
-
option: ['Uint8ClampedArray']
|
|
430
|
-
},
|
|
431
|
-
int16array: {
|
|
432
|
-
option: ['Int16Array']
|
|
433
|
-
},
|
|
434
|
-
uint16array: {
|
|
435
|
-
option: ['Uint16Array']
|
|
436
|
-
},
|
|
437
|
-
int32array: {
|
|
438
|
-
option: ['Int32Array']
|
|
439
|
-
},
|
|
440
|
-
uint32array: {
|
|
441
|
-
option: ['Uint32Array']
|
|
442
|
-
},
|
|
443
|
-
float32array: {
|
|
444
|
-
option: ['Float32Array']
|
|
445
|
-
},
|
|
446
|
-
float64array: {
|
|
447
|
-
option: ['Float64Array']
|
|
448
|
-
},
|
|
449
|
-
|
|
450
|
-
// We're catching this instead of using this
|
|
451
|
-
// sparseUndefined: sparseUndefinedType,
|
|
452
|
-
|
|
453
|
-
ValidDate: {
|
|
454
|
-
valid: true
|
|
455
|
-
},
|
|
456
|
-
/*
|
|
457
|
-
sparseArrays: {
|
|
458
|
-
sparse: true
|
|
459
|
-
},
|
|
460
|
-
*/
|
|
461
|
-
arrayNonindexKeys: {
|
|
462
|
-
sparse: true
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
|
-
|
|
466
364
|
/**
|
|
467
365
|
* @typedef {"null"|"true"|"false"|"number"|"bigint"|"string"|"arrayReference"|
|
|
468
|
-
* "objectReference"|"array"|"object"|"date"|"userObject"|"undef"|
|
|
469
|
-
* "SpecialRealNumber"|"SpecialNumber"|"regexp"|"BooleanObject"|
|
|
470
|
-
* "NumberObject"|"StringObject"|"map"|"set"|"file"|"filelist"|"blobHTML"|
|
|
471
|
-
* "arraybuffer"|"arraybufferview"|"dataview"|"imagedata"|"imagebitmap"|
|
|
472
|
-
* "int8array"|"uint8array"|"uint8clampedarray"|"int16array"|"uint16array"|
|
|
473
|
-
* "int32array"|"uint32array"|"float32array"|"float64array"|"ValidDate"|
|
|
474
|
-
* "arrayNonindexKeys"|"error"|"errors"|"blob"|"domexception"|"domrect"|
|
|
475
|
-
* "dompoint"|"dommatrix"} AvailableType
|
|
476
|
-
*/
|
|
366
|
+
* "objectReference"|"array"|"object"|"date"|"userObject"|"undef"|
|
|
367
|
+
* "SpecialRealNumber"|"SpecialNumber"|"regexp"|"BooleanObject"|
|
|
368
|
+
* "NumberObject"|"StringObject"|"map"|"set"|"file"|"filelist"|"blobHTML"|
|
|
369
|
+
* "arraybuffer"|"arraybufferview"|"dataview"|"imagedata"|"imagebitmap"|
|
|
370
|
+
* "int8array"|"uint8array"|"uint8clampedarray"|"int16array"|"uint16array"|
|
|
371
|
+
* "int32array"|"uint32array"|"float32array"|"float64array"|"ValidDate"|
|
|
372
|
+
* "arrayNonindexKeys"|"error"|"errors"|"blob"|"domexception"|"domrect"|
|
|
373
|
+
* "dompoint"|"dommatrix"} AvailableType
|
|
374
|
+
*/
|
|
477
375
|
|
|
478
376
|
/**
|
|
479
|
-
*
|
|
480
|
-
* copyFrom: AvailableType, copyTo: AvailableType
|
|
481
|
-
* ][]} replacements
|
|
482
|
-
* @returns {void}
|
|
377
|
+
*
|
|
483
378
|
*/
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
379
|
+
class Types {
|
|
380
|
+
/**
|
|
381
|
+
*
|
|
382
|
+
*/
|
|
383
|
+
constructor () {
|
|
384
|
+
this.formats = new Formats(); // Todo: Make customizable and test
|
|
385
|
+
|
|
386
|
+
/** @type {CustomValidateAllReferences|undefined} */
|
|
387
|
+
this.customValidateAllReferences = undefined;
|
|
388
|
+
/**
|
|
389
|
+
* @type {{
|
|
390
|
+
* [key in AvailableType]: Partial<TypeObject>|string[]
|
|
391
|
+
* }}
|
|
392
|
+
*/
|
|
393
|
+
this.availableTypes = {
|
|
394
|
+
null: nullType,
|
|
395
|
+
true: trueType,
|
|
396
|
+
false: falseType,
|
|
397
|
+
number: numberType,
|
|
398
|
+
bigint: bigintType,
|
|
399
|
+
string: stringType,
|
|
400
|
+
arrayReference: arrayReferenceType,
|
|
401
|
+
objectReference: objectReferenceType,
|
|
402
|
+
array: arrayType,
|
|
403
|
+
// Note: We don't do for BooleanObject/NumberObject/StringObject, date,
|
|
404
|
+
// regexp, as added properties on them are not being cloned (in Chrome
|
|
405
|
+
// at least)
|
|
406
|
+
object: objectType,
|
|
407
|
+
date: dateType,
|
|
408
|
+
|
|
409
|
+
// This type is only for throwing upon cloning errors:
|
|
410
|
+
// 'checkDataCloneException'
|
|
411
|
+
// This type might be supported by evaluable JS or config
|
|
412
|
+
// passed in:
|
|
413
|
+
userObject: ['User objects'],
|
|
414
|
+
undef: undefinedType,
|
|
415
|
+
SpecialRealNumber: SpecialRealNumberSuperType,
|
|
416
|
+
SpecialNumber: SpecialNumberSuperType,
|
|
417
|
+
|
|
418
|
+
error: errorType,
|
|
419
|
+
errors: errorsSpecialType,
|
|
420
|
+
|
|
421
|
+
regexp: regexpType,
|
|
422
|
+
BooleanObject: BooleanObjectType,
|
|
423
|
+
NumberObject: NumberObjectType,
|
|
424
|
+
StringObject: StringObjectType,
|
|
425
|
+
|
|
426
|
+
map: mapType,
|
|
427
|
+
set: setType,
|
|
428
|
+
|
|
429
|
+
file: fileType,
|
|
430
|
+
filelist: filelistType,
|
|
431
|
+
blob: blobType,
|
|
432
|
+
blobHTML: blobHTMLType,
|
|
433
|
+
|
|
434
|
+
domexception: domexceptionType,
|
|
435
|
+
domrect: domrectType,
|
|
436
|
+
dompoint: dompointType,
|
|
437
|
+
dommatrix: dommatrixType,
|
|
438
|
+
|
|
439
|
+
arraybuffer: {
|
|
440
|
+
option: ['ArrayBuffer']
|
|
441
|
+
},
|
|
442
|
+
arraybufferview: {
|
|
443
|
+
option: ['ArrayBufferView']
|
|
444
|
+
},
|
|
445
|
+
dataview: {
|
|
446
|
+
option: ['DataView']
|
|
447
|
+
},
|
|
448
|
+
imagedata: {
|
|
449
|
+
option: ['ImageData']
|
|
450
|
+
},
|
|
451
|
+
imagebitmap: {
|
|
452
|
+
option: ['ImageBitmap']
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
// Typed Arrays
|
|
456
|
+
int8array: {
|
|
457
|
+
option: ['Int8Array']
|
|
458
|
+
},
|
|
459
|
+
uint8array: {
|
|
460
|
+
option: ['Uint8Array']
|
|
461
|
+
},
|
|
462
|
+
uint8clampedarray: {
|
|
463
|
+
option: ['Uint8ClampedArray']
|
|
464
|
+
},
|
|
465
|
+
int16array: {
|
|
466
|
+
option: ['Int16Array']
|
|
467
|
+
},
|
|
468
|
+
uint16array: {
|
|
469
|
+
option: ['Uint16Array']
|
|
470
|
+
},
|
|
471
|
+
int32array: {
|
|
472
|
+
option: ['Int32Array']
|
|
473
|
+
},
|
|
474
|
+
uint32array: {
|
|
475
|
+
option: ['Uint32Array']
|
|
476
|
+
},
|
|
477
|
+
float32array: {
|
|
478
|
+
option: ['Float32Array']
|
|
479
|
+
},
|
|
480
|
+
float64array: {
|
|
481
|
+
option: ['Float64Array']
|
|
482
|
+
},
|
|
483
|
+
|
|
484
|
+
// We're catching this instead of using this
|
|
485
|
+
// sparseUndefined: sparseUndefinedType,
|
|
486
|
+
|
|
487
|
+
ValidDate: {
|
|
488
|
+
valid: true
|
|
489
|
+
},
|
|
490
|
+
/*
|
|
491
|
+
sparseArrays: {
|
|
492
|
+
sparse: true
|
|
493
|
+
},
|
|
494
|
+
*/
|
|
495
|
+
arrayNonindexKeys: {
|
|
496
|
+
sparse: true
|
|
497
|
+
}
|
|
498
|
+
};
|
|
495
499
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
[
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
)
|
|
500
|
+
/**
|
|
501
|
+
* @param {[
|
|
502
|
+
* copyFrom: AvailableType, copyTo: AvailableType
|
|
503
|
+
* ][]} replacements
|
|
504
|
+
* @returns {void}
|
|
505
|
+
*/
|
|
506
|
+
const copyTypeObjs = (replacements) => {
|
|
507
|
+
replacements.forEach(([copyFrom, copyTo]) => {
|
|
508
|
+
Object.assign(this.availableTypes[
|
|
509
|
+
/** @type {AvailableType} */
|
|
510
|
+
(copyTo)
|
|
511
|
+
], this.availableTypes[
|
|
512
|
+
/** @type {AvailableType} */
|
|
513
|
+
(copyFrom)
|
|
514
|
+
]);
|
|
515
|
+
});
|
|
516
|
+
};
|
|
507
517
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
518
|
+
copyTypeObjs(
|
|
519
|
+
[
|
|
520
|
+
/** @type {[AvailableType, AvailableType]} */ (
|
|
521
|
+
['date', 'ValidDate']
|
|
522
|
+
),
|
|
523
|
+
// 'sparseArrays'
|
|
524
|
+
/** @type {[AvailableType, AvailableType]} */ (
|
|
525
|
+
['array', 'arrayNonindexKeys']
|
|
526
|
+
)
|
|
527
|
+
]
|
|
528
|
+
);
|
|
511
529
|
|
|
512
|
-
/**
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
530
|
+
/**
|
|
531
|
+
* @type {{
|
|
532
|
+
* [key: string]: {
|
|
533
|
+
* [key: string]: {type: AvailableType, after: AvailableType}[]
|
|
534
|
+
* }
|
|
535
|
+
* }}
|
|
536
|
+
*/
|
|
537
|
+
this.contexts = {};
|
|
538
|
+
Object.entries(this.availableTypes).forEach(([typ, typeObj]) => {
|
|
539
|
+
const type = /** @type {AvailableType} */ (typ);
|
|
540
|
+
const {stateDependent} = /** @type {TypeObject} */ (typeObj);
|
|
541
|
+
if (stateDependent) {
|
|
542
|
+
Object.entries(stateDependent).forEach(([
|
|
543
|
+
format, formatStateDependent
|
|
544
|
+
]) => {
|
|
545
|
+
if (!this.contexts[format]) {
|
|
546
|
+
this.contexts[format] = {};
|
|
547
|
+
}
|
|
548
|
+
const {contexts, after} = formatStateDependent;
|
|
549
|
+
contexts.forEach((context) => {
|
|
550
|
+
if (!this.contexts[format][context]) {
|
|
551
|
+
this.contexts[format][context] = [];
|
|
552
|
+
}
|
|
553
|
+
this.contexts[format][context].push({type, after});
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
}
|
|
520
559
|
|
|
521
|
-
/**
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
* }},
|
|
535
|
-
* handlingReference?: boolean
|
|
536
|
-
* }} StateObject
|
|
537
|
-
*/
|
|
560
|
+
/**
|
|
561
|
+
* @param {import('./formats.js').AvailableFormat} format
|
|
562
|
+
* @param {StructuredCloneValue} record
|
|
563
|
+
* @param {StateObject} stateObj
|
|
564
|
+
* @returns {Promise<Element>}
|
|
565
|
+
*/
|
|
566
|
+
async getControlsForFormatAndValue (
|
|
567
|
+
format, record, stateObj
|
|
568
|
+
) {
|
|
569
|
+
return await this.formats.getControlsForFormatAndValue(
|
|
570
|
+
this, format, record, stateObj
|
|
571
|
+
);
|
|
572
|
+
}
|
|
538
573
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
574
|
+
/**
|
|
575
|
+
* @param {import('./formats.js').AvailableFormat} format
|
|
576
|
+
* @param {string} context
|
|
577
|
+
* @returns {{
|
|
578
|
+
* type: AvailableType
|
|
579
|
+
* after: AvailableType
|
|
580
|
+
* }[]}
|
|
581
|
+
*/
|
|
582
|
+
getContextInfo (format, context) {
|
|
583
|
+
return this.contexts[format][context];
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/** @type {GetValueForRoot} */
|
|
587
|
+
getValueForRoot (root, stateObj, currentPath) {
|
|
588
|
+
const typeObject = /** @type {TypeObject} */ (
|
|
589
|
+
this.availableTypes[
|
|
590
|
+
/** @type {AvailableType} */
|
|
591
|
+
(Types.getTypeForRoot(root))
|
|
592
|
+
]
|
|
593
|
+
);
|
|
594
|
+
return typeObject.getValue({
|
|
595
|
+
root, stateObj, currentPath
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** @type {GetFormControlForRoot} */
|
|
600
|
+
getFormControlForRoot (root) {
|
|
601
|
+
const typeObj = /** @type {TypeObject} */ (this.availableTypes[
|
|
542
602
|
/** @type {AvailableType} */
|
|
543
603
|
(Types.getTypeForRoot(root))
|
|
544
|
-
]
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
};
|
|
604
|
+
]);
|
|
605
|
+
/* istanbul ignore if -- All have except aliases */
|
|
606
|
+
if (!typeObj.getInput) {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
return typeObj.getInput({root});
|
|
610
|
+
}
|
|
550
611
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
612
|
+
/** @type {GetValueFromRootAncestor} */
|
|
613
|
+
getValueFromRootAncestor (selOrEl, stateObj) {
|
|
614
|
+
return this.getValueForRoot(
|
|
615
|
+
/** @type {RootElement} */
|
|
616
|
+
($e(selOrEl, 'div[data-type]')),
|
|
617
|
+
{
|
|
618
|
+
...stateObj,
|
|
619
|
+
formats: this.formats
|
|
620
|
+
}
|
|
621
|
+
);
|
|
559
622
|
}
|
|
560
|
-
return typeObj.getInput({root});
|
|
561
|
-
};
|
|
562
623
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
/** @type {RootElement} */
|
|
566
|
-
(
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
624
|
+
/** @type {GetFormControlFromRootAncestor} */
|
|
625
|
+
getFormControlFromRootAncestor (selOrEl) {
|
|
626
|
+
const root = /** @type {RootElement} */ ($e(selOrEl, 'div[data-type]'));
|
|
627
|
+
if (!root) {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
return this.getFormControlForRoot(root);
|
|
631
|
+
}
|
|
570
632
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
633
|
+
/** @type {GetOptionForType} */
|
|
634
|
+
getOptionForType (type) {
|
|
635
|
+
/** @type {[string, {value?: AvailableType, title?: string}?]} */
|
|
636
|
+
const optInfo = [
|
|
637
|
+
...(/** @type {TypeObject} */ (
|
|
638
|
+
this.availableTypes[type]
|
|
639
|
+
)).option
|
|
640
|
+
];
|
|
641
|
+
optInfo[1] = {value: type, ...(optInfo[1])};
|
|
642
|
+
return /** @type {[string, {value: AvailableType, title?: string}]} */ (
|
|
643
|
+
optInfo
|
|
644
|
+
);
|
|
575
645
|
}
|
|
576
|
-
return Types.getFormControlForRoot(root);
|
|
577
|
-
};
|
|
578
646
|
|
|
579
|
-
|
|
580
|
-
format,
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
return /** @type {[string, {value: AvailableType, title?: string}]} */ (
|
|
592
|
-
optInfo
|
|
593
|
-
);
|
|
594
|
-
};
|
|
647
|
+
/** @type {GetTypeOptionsForFormatAndState} */
|
|
648
|
+
getTypeOptionsForFormatAndState (format, parserState) {
|
|
649
|
+
const typesForFormatAndState = this.formats.getTypesForFormatAndState(
|
|
650
|
+
this, format, parserState
|
|
651
|
+
);
|
|
652
|
+
if (!typesForFormatAndState) {
|
|
653
|
+
throw new Error('Unexpected type for format and state');
|
|
654
|
+
}
|
|
655
|
+
return typesForFormatAndState.map((type) => {
|
|
656
|
+
return this.getOptionForType(type);
|
|
657
|
+
});
|
|
658
|
+
}
|
|
595
659
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
660
|
+
/** @type {GetUIForModeAndType} */
|
|
661
|
+
getUIForModeAndType ({
|
|
662
|
+
readonly, resultType, typeNamespace, type, topRoot, bringIntoFocus,
|
|
663
|
+
buildTypeChoices, format, schemaContent, schemaState, value, hasValue,
|
|
664
|
+
replaced
|
|
665
|
+
}) {
|
|
666
|
+
const typeObj = /** @type {TypeObject} */ (this.availableTypes[type]);
|
|
667
|
+
const arg = hasValue
|
|
668
|
+
? {
|
|
669
|
+
typeNamespace, type, buildTypeChoices,
|
|
670
|
+
format, schemaContent, schemaState,
|
|
671
|
+
resultType, topRoot, bringIntoFocus, value,
|
|
672
|
+
replaced,
|
|
673
|
+
types: this
|
|
674
|
+
}
|
|
675
|
+
: {
|
|
676
|
+
typeNamespace, type, buildTypeChoices,
|
|
677
|
+
format, schemaContent, schemaState,
|
|
678
|
+
resultType, topRoot, bringIntoFocus,
|
|
679
|
+
replaced,
|
|
680
|
+
types: this
|
|
681
|
+
};
|
|
682
|
+
const root = /** @type {HTMLDivElement} */ (jml(
|
|
683
|
+
...(readonly
|
|
684
|
+
? typeObj.viewUI(arg)
|
|
685
|
+
: typeObj.editUI(arg))
|
|
686
|
+
));
|
|
687
|
+
if (!readonly && typeObj.validate) {
|
|
688
|
+
const formControl = typeObj.getInput({root});
|
|
689
|
+
formControl.addEventListener('input', () => {
|
|
690
|
+
this.validate({type, root, topRoot});
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
return root;
|
|
602
694
|
}
|
|
603
|
-
return typesForFormatAndState.map((type) => {
|
|
604
|
-
return Types.getOptionForType(type);
|
|
605
|
-
});
|
|
606
|
-
};
|
|
607
695
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const arg = hasValue
|
|
615
|
-
? {
|
|
616
|
-
typeNamespace, type, buildTypeChoices,
|
|
617
|
-
format, schemaContent, schemaState,
|
|
618
|
-
resultType, topRoot, bringIntoFocus, value,
|
|
619
|
-
replaced
|
|
696
|
+
/** @type {ValidateAllReferences} */
|
|
697
|
+
validateAllReferences ({topRoot}) {
|
|
698
|
+
/* istanbul ignore if -- Unreachable? */
|
|
699
|
+
if (!topRoot) {
|
|
700
|
+
console.log('No references present');
|
|
701
|
+
return;
|
|
620
702
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
? typeObj.viewUI(arg)
|
|
630
|
-
: typeObj.editUI(arg))
|
|
631
|
-
));
|
|
632
|
-
if (!readonly && typeObj.validate) {
|
|
633
|
-
const formControl = typeObj.getInput({root});
|
|
634
|
-
formControl.addEventListener('input', () => {
|
|
635
|
-
Types.validate({type, root, topRoot});
|
|
703
|
+
|
|
704
|
+
// Could just hard-code arrayReference and objectReference,
|
|
705
|
+
// but we'll try to avoid depending on specific types
|
|
706
|
+
Object.values(this.availableTypes).forEach((typeObj) => {
|
|
707
|
+
const typeObject = /** @type {TypeObject} */ (typeObj);
|
|
708
|
+
if (typeObject.validateAll) {
|
|
709
|
+
typeObject.validateAll({topRoot, types: this});
|
|
710
|
+
}
|
|
636
711
|
});
|
|
712
|
+
|
|
713
|
+
if (this.customValidateAllReferences) {
|
|
714
|
+
this.customValidateAllReferences({topRoot});
|
|
715
|
+
}
|
|
637
716
|
}
|
|
638
|
-
return root;
|
|
639
|
-
};
|
|
640
717
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
718
|
+
/** @type {Validate} */
|
|
719
|
+
validate ({type, root, topRoot}) {
|
|
720
|
+
const typeObj = /** @type {TypeObject} */ (this.availableTypes[type]);
|
|
721
|
+
// Todo (low): We limit for now to input boxes which have `validate`
|
|
722
|
+
if (typeObj.validate) {
|
|
723
|
+
const {valid, message} = typeObj.validate({root, topRoot});
|
|
724
|
+
const formControl = typeObj.getInput({root});
|
|
725
|
+
formControl.setCustomValidity(
|
|
726
|
+
valid
|
|
727
|
+
? ''
|
|
728
|
+
/* istanbul ignore next -- Should always have a message */
|
|
729
|
+
: message || 'Invalid'
|
|
730
|
+
);
|
|
731
|
+
formControl.reportValidity();
|
|
732
|
+
return valid;
|
|
733
|
+
}
|
|
734
|
+
return true;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/** @type {SetValue} */
|
|
738
|
+
setValue ({type, root, value}) {
|
|
739
|
+
const typeObj = /** @type {TypeObject} */ (this.availableTypes[type]);
|
|
740
|
+
if (typeObj.setValue) {
|
|
741
|
+
typeObj.setValue({root, value});
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Todo (low): Should really add real parser
|
|
746
|
+
// Todo (low): Implement `getStringForValue` (e.g., to expose feature for
|
|
747
|
+
// bookmarking object value currently in view); would not be
|
|
748
|
+
// enough to iterate DOM to get string URL as we'd also like
|
|
749
|
+
// the ability to have arbitrary JSON/structuredCloning sent to this
|
|
750
|
+
// URL from other sites/programs (can currently pass in JSON
|
|
751
|
+
// format to the URL, but that is still expecting our Router
|
|
752
|
+
// string syntax)
|
|
753
|
+
|
|
754
|
+
/** @type {GetValueForString} */
|
|
755
|
+
getValueForString (s, {
|
|
756
|
+
format, state, endMatchTypeObjs = [], firstRun = true,
|
|
757
|
+
rootHolder = [], parent, parentPath
|
|
758
|
+
}) {
|
|
759
|
+
const allowedTypes = this.formats.getTypesForFormatAndState(
|
|
760
|
+
this, format, state
|
|
761
|
+
);
|
|
762
|
+
if (!allowedTypes) {
|
|
763
|
+
throw new Error('Could not get types for format and state');
|
|
764
|
+
}
|
|
765
|
+
const allowedTypeObjs = Object.entries(
|
|
766
|
+
this.availableTypes
|
|
767
|
+
).filter(([type]) => {
|
|
768
|
+
return allowedTypes.includes(/** @type {AvailableType} */ (type));
|
|
769
|
+
});
|
|
770
|
+
const allowedTypeObjsVals = allowedTypeObjs.map(([, arr]) => arr);
|
|
771
|
+
|
|
772
|
+
const reduced = allowedTypeObjsVals.reduce((array, typObj) => {
|
|
773
|
+
const typeObj = /** @type {TypeObject} */ (typObj);
|
|
774
|
+
let arr = /** @type {string[]} */ (array);
|
|
775
|
+
if (typeObj.regexEndings) {
|
|
776
|
+
arr.push(...typeObj.regexEndings);
|
|
777
|
+
arr = [...new Set(arr)];
|
|
778
|
+
}
|
|
779
|
+
return arr;
|
|
780
|
+
}, []);
|
|
781
|
+
const endings = '|' + /** @type {string[]} */ (
|
|
782
|
+
reduced
|
|
783
|
+
).map((str) => escapeRegex(str)).join('|');
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* @type {RegExpMatchArray|boolean|null}
|
|
787
|
+
*/
|
|
788
|
+
let match = null;
|
|
789
|
+
let found = allowedTypeObjs.find(([/* _type */, typObj]) => {
|
|
790
|
+
const typeObj = /** @type {TypeObject} */ (typObj);
|
|
791
|
+
let {stringRegex} = typeObj;
|
|
792
|
+
if (typeof typeObj.stringRegex === 'function') {
|
|
793
|
+
stringRegex = typeObj.stringRegex(true);
|
|
649
794
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
795
|
+
stringRegex = stringRegex
|
|
796
|
+
// Strip off terminal (dollar sign) when matching substrings
|
|
797
|
+
? new RegExp(
|
|
798
|
+
/** @type {RegExp} */ (
|
|
799
|
+
stringRegex
|
|
800
|
+
).source.slice(0, -1) + '(?=$' + endings + ')',
|
|
801
|
+
'u'
|
|
802
|
+
)
|
|
803
|
+
: stringRegex;
|
|
804
|
+
match = Boolean(stringRegex && s) && s.match(
|
|
805
|
+
/** @type {RegExp} */ (stringRegex)
|
|
806
|
+
);
|
|
807
|
+
return match;
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
let beginOnly = false;
|
|
811
|
+
if (found === undefined) {
|
|
812
|
+
found = allowedTypeObjs.find(([/* _type */, typObj]) => {
|
|
813
|
+
const typeObj = /** @type {TypeObject} */ (typObj);
|
|
814
|
+
const {stringRegexBegin} = typeObj;
|
|
815
|
+
match = Boolean(stringRegexBegin && s) && s.match(
|
|
816
|
+
/** @type {RegExp} */ (stringRegexBegin)
|
|
817
|
+
);
|
|
818
|
+
if (match) {
|
|
819
|
+
beginOnly = true;
|
|
820
|
+
endMatchTypeObjs.push(typeObj);
|
|
654
821
|
}
|
|
655
|
-
|
|
822
|
+
return match;
|
|
656
823
|
});
|
|
657
|
-
}
|
|
824
|
+
}
|
|
825
|
+
let assign = true;
|
|
826
|
+
if (found !== undefined) {
|
|
827
|
+
// The `found` is evaluated again, so sets `match` to non-null
|
|
828
|
+
const mtch = /** @type {RegExpMatchArray} */ (
|
|
829
|
+
/** @type {unknown} */ (match)
|
|
830
|
+
);
|
|
831
|
+
const [content, innerContent] = mtch;
|
|
832
|
+
let remnant = s.slice(content.length);
|
|
833
|
+
s = s.slice(0, content.length);
|
|
834
|
+
// console.log('s0', s, '::', remnant, match);
|
|
835
|
+
let valObj;
|
|
836
|
+
try {
|
|
837
|
+
valObj = /** @type {TypeObject} */ (found[1]).toValue(
|
|
838
|
+
mtch.groups?.innerContent || innerContent || s, {
|
|
839
|
+
types: this,
|
|
840
|
+
format,
|
|
841
|
+
match,
|
|
842
|
+
endMatchTypeObjs,
|
|
843
|
+
remnant,
|
|
844
|
+
rootHolder,
|
|
845
|
+
parent,
|
|
846
|
+
parentPath
|
|
847
|
+
}
|
|
848
|
+
);
|
|
849
|
+
/* istanbul ignore next -- Good regexes should prevent */
|
|
850
|
+
} catch (e) {
|
|
851
|
+
/* istanbul ignore next -- Good regexes should prevent */
|
|
852
|
+
console.log('eee', e);
|
|
853
|
+
/* istanbul ignore next -- Good regexes should prevent */
|
|
854
|
+
throw e;
|
|
855
|
+
}
|
|
856
|
+
if (valObj.assign === false) {
|
|
857
|
+
assign = false;
|
|
858
|
+
}
|
|
859
|
+
const {value} = valObj;
|
|
860
|
+
if (valObj.remnant !== undefined) {
|
|
861
|
+
({remnant} = valObj);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (beginOnly && endMatchTypeObjs.length) {
|
|
865
|
+
const endMatch = remnant.match(
|
|
866
|
+
/** @type {RegExp} */ (/** @type {TypeObject} */ (
|
|
867
|
+
endMatchTypeObjs.at(-1)
|
|
868
|
+
).stringRegexEnd)
|
|
869
|
+
);
|
|
870
|
+
if (endMatch) {
|
|
871
|
+
endMatchTypeObjs.pop(); // Safe now to extract
|
|
872
|
+
remnant = remnant.slice(endMatch[0].length);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
if (firstRun) {
|
|
876
|
+
const typeson = new Typeson().register(
|
|
877
|
+
structuredCloningThrowing
|
|
878
|
+
);
|
|
879
|
+
try {
|
|
880
|
+
const topRoot = typeson.revive(value);
|
|
881
|
+
rootHolder.forEach(([type, parent, parentPath, path]) => {
|
|
882
|
+
const typeObject = this.availableTypes[
|
|
883
|
+
/** @type {AvailableType} */ (type + 'Reference')
|
|
884
|
+
];
|
|
885
|
+
|
|
886
|
+
// @ts-expect-error Reference method exists
|
|
887
|
+
const val = typeObject.resolveReference(path, topRoot);
|
|
888
|
+
const basicType = getJSONType(val);
|
|
889
|
+
// eslint-disable-next-line @stylistic/max-len -- Long
|
|
890
|
+
/* istanbul ignore else -- Successful reference always an object/array? */
|
|
891
|
+
if (
|
|
892
|
+
['array', 'object'].includes(type) && basicType === type
|
|
893
|
+
) {
|
|
894
|
+
/** @type {{[key: string]: any}} */ (
|
|
895
|
+
parent
|
|
896
|
+
)[/** @type {string|number} */ (parentPath)] = val;
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
return [topRoot, remnant, beginOnly, assign];
|
|
900
|
+
} catch (err) {
|
|
901
|
+
console.log('failed Typeson revival', err);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return [value, remnant, beginOnly, assign];
|
|
905
|
+
}
|
|
906
|
+
throw new Error('Bad parsing data');
|
|
658
907
|
}
|
|
659
|
-
});
|
|
660
908
|
|
|
909
|
+
/**
|
|
910
|
+
* @param {AvailableType} type
|
|
911
|
+
* @returns {Partial<TypeObject>|string[]}
|
|
912
|
+
*/
|
|
913
|
+
getTypeObject (type) {
|
|
914
|
+
return this.availableTypes[type];
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/** @type {GetTypeForRoot} */
|
|
919
|
+
Types.getTypeForRoot = (root) => {
|
|
920
|
+
return String(root ? root.dataset.type : root);
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
/** @type {ValidValuesSet} */
|
|
661
924
|
Types.validValuesSet = ({form, typeNamespace, keySelectClass}) => {
|
|
662
925
|
// If form is hidden, don't list errors by default
|
|
663
926
|
if (!form.offsetParent ||
|
|
@@ -691,57 +954,6 @@ Types.validValuesSet = ({form, typeNamespace, keySelectClass}) => {
|
|
|
691
954
|
);
|
|
692
955
|
};
|
|
693
956
|
|
|
694
|
-
/**
|
|
695
|
-
* Any other possibilities than `div`?
|
|
696
|
-
* @typedef {HTMLDivElement} RootElement
|
|
697
|
-
*/
|
|
698
|
-
|
|
699
|
-
Types.validateAllReferences = ({topRoot}) => {
|
|
700
|
-
/* istanbul ignore if -- Unreachable? */
|
|
701
|
-
if (!topRoot) {
|
|
702
|
-
console.log('No references present');
|
|
703
|
-
return;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// Could just hard-code arrayReference and objectReference,
|
|
707
|
-
// but we'll try to avoid depending on specific types
|
|
708
|
-
Object.values(Types.availableTypes).forEach((typeObj) => {
|
|
709
|
-
const typeObject = /** @type {TypeObject} */ (typeObj);
|
|
710
|
-
if (typeObject.validateAll) {
|
|
711
|
-
typeObject.validateAll({topRoot});
|
|
712
|
-
}
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
if (Types.customValidateAllReferences) {
|
|
716
|
-
Types.customValidateAllReferences({topRoot});
|
|
717
|
-
}
|
|
718
|
-
};
|
|
719
|
-
|
|
720
|
-
Types.validate = ({type, root, topRoot}) => {
|
|
721
|
-
const typeObj = /** @type {TypeObject} */ (Types.availableTypes[type]);
|
|
722
|
-
// Todo (low): We limit for now to input boxes which have `validate`
|
|
723
|
-
if (typeObj.validate) {
|
|
724
|
-
const {valid, message} = typeObj.validate({root, topRoot});
|
|
725
|
-
const formControl = typeObj.getInput({root});
|
|
726
|
-
formControl.setCustomValidity(
|
|
727
|
-
valid
|
|
728
|
-
? ''
|
|
729
|
-
/* istanbul ignore next -- Should always have a message */
|
|
730
|
-
: message || 'Invalid'
|
|
731
|
-
);
|
|
732
|
-
formControl.reportValidity();
|
|
733
|
-
return valid;
|
|
734
|
-
}
|
|
735
|
-
return true;
|
|
736
|
-
};
|
|
737
|
-
|
|
738
|
-
Types.setValue = ({type, root, value}) => {
|
|
739
|
-
const typeObj = /** @type {TypeObject} */ (Types.availableTypes[type]);
|
|
740
|
-
if (typeObj.setValue) {
|
|
741
|
-
typeObj.setValue({root, value});
|
|
742
|
-
}
|
|
743
|
-
};
|
|
744
|
-
|
|
745
957
|
/**
|
|
746
958
|
*
|
|
747
959
|
* @param {string} str
|
|
@@ -752,164 +964,4 @@ function escapeRegex (str) {
|
|
|
752
964
|
replaceAll(/[.\\+*?^[\]$(){}=!<>|:-]/gu, '\\$&');
|
|
753
965
|
}
|
|
754
966
|
|
|
755
|
-
// Todo (low): Should really add real parser
|
|
756
|
-
// Todo (low): Implement `getStringForValue` (e.g., to expose feature for
|
|
757
|
-
// bookmarking object value currently in view); would not be
|
|
758
|
-
// enough to iterate DOM to get string URL as we'd also like
|
|
759
|
-
// the ability to have arbitrary JSON/structuredCloning sent to this
|
|
760
|
-
// URL from other sites/programs (can currently pass in JSON
|
|
761
|
-
// format to the URL, but that is still expecting our Router
|
|
762
|
-
// string syntax)
|
|
763
|
-
|
|
764
|
-
Types.getValueForString = (s, {
|
|
765
|
-
format, state, endMatchTypeObjs = [], firstRun = true,
|
|
766
|
-
rootHolder = [], parent, parentPath
|
|
767
|
-
}) => {
|
|
768
|
-
const allowedTypes = Types.getTypesForFormatAndState(format, state);
|
|
769
|
-
if (!allowedTypes) {
|
|
770
|
-
throw new Error('Could not get types for format and state');
|
|
771
|
-
}
|
|
772
|
-
const allowedTypeObjs = Object.entries(
|
|
773
|
-
Types.availableTypes
|
|
774
|
-
).filter(([type]) => {
|
|
775
|
-
return allowedTypes.includes(/** @type {AvailableType} */ (type));
|
|
776
|
-
});
|
|
777
|
-
const allowedTypeObjsVals = allowedTypeObjs.map(([, arr]) => arr);
|
|
778
|
-
|
|
779
|
-
const reduced = allowedTypeObjsVals.reduce((array, typObj) => {
|
|
780
|
-
const typeObj = /** @type {TypeObject} */ (typObj);
|
|
781
|
-
let arr = /** @type {string[]} */ (array);
|
|
782
|
-
if (typeObj.regexEndings) {
|
|
783
|
-
arr.push(...typeObj.regexEndings);
|
|
784
|
-
arr = [...new Set(arr)];
|
|
785
|
-
}
|
|
786
|
-
return arr;
|
|
787
|
-
}, []);
|
|
788
|
-
const endings = '|' + /** @type {string[]} */ (
|
|
789
|
-
reduced
|
|
790
|
-
).map((str) => escapeRegex(str)).join('|');
|
|
791
|
-
|
|
792
|
-
/**
|
|
793
|
-
* @type {RegExpMatchArray|boolean|null}
|
|
794
|
-
*/
|
|
795
|
-
let match = null;
|
|
796
|
-
let found = allowedTypeObjs.find(([/* _type */, typObj]) => {
|
|
797
|
-
const typeObj = /** @type {TypeObject} */ (typObj);
|
|
798
|
-
let {stringRegex} = typeObj;
|
|
799
|
-
if (typeof typeObj.stringRegex === 'function') {
|
|
800
|
-
stringRegex = typeObj.stringRegex(true);
|
|
801
|
-
}
|
|
802
|
-
stringRegex = stringRegex
|
|
803
|
-
// Strip off terminal (dollar sign) when matching substrings
|
|
804
|
-
? new RegExp(
|
|
805
|
-
/** @type {RegExp} */ (
|
|
806
|
-
stringRegex
|
|
807
|
-
).source.slice(0, -1) + '(?=$' + endings + ')',
|
|
808
|
-
'u'
|
|
809
|
-
)
|
|
810
|
-
: stringRegex;
|
|
811
|
-
match = Boolean(stringRegex && s) && s.match(
|
|
812
|
-
/** @type {RegExp} */ (stringRegex)
|
|
813
|
-
);
|
|
814
|
-
return match;
|
|
815
|
-
});
|
|
816
|
-
|
|
817
|
-
let beginOnly = false;
|
|
818
|
-
if (found === undefined) {
|
|
819
|
-
found = allowedTypeObjs.find(([/* _type */, typObj]) => {
|
|
820
|
-
const typeObj = /** @type {TypeObject} */ (typObj);
|
|
821
|
-
const {stringRegexBegin} = typeObj;
|
|
822
|
-
match = Boolean(stringRegexBegin && s) && s.match(
|
|
823
|
-
/** @type {RegExp} */ (stringRegexBegin)
|
|
824
|
-
);
|
|
825
|
-
if (match) {
|
|
826
|
-
beginOnly = true;
|
|
827
|
-
endMatchTypeObjs.push(typeObj);
|
|
828
|
-
}
|
|
829
|
-
return match;
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
let assign = true;
|
|
833
|
-
if (found !== undefined) {
|
|
834
|
-
// The `found` is evaluated again, so sets `match` to non-null
|
|
835
|
-
const mtch = /** @type {RegExpMatchArray} */ (
|
|
836
|
-
/** @type {unknown} */ (match)
|
|
837
|
-
);
|
|
838
|
-
const [content, innerContent] = mtch;
|
|
839
|
-
let remnant = s.slice(content.length);
|
|
840
|
-
s = s.slice(0, content.length);
|
|
841
|
-
// console.log('s0', s, '::', remnant, match);
|
|
842
|
-
let valObj;
|
|
843
|
-
try {
|
|
844
|
-
valObj = /** @type {TypeObject} */ (found[1]).toValue(
|
|
845
|
-
mtch.groups?.innerContent || innerContent || s, {
|
|
846
|
-
format,
|
|
847
|
-
match,
|
|
848
|
-
endMatchTypeObjs,
|
|
849
|
-
remnant,
|
|
850
|
-
rootHolder,
|
|
851
|
-
parent,
|
|
852
|
-
parentPath
|
|
853
|
-
}
|
|
854
|
-
);
|
|
855
|
-
/* istanbul ignore next -- Good regexes should prevent */
|
|
856
|
-
} catch (e) {
|
|
857
|
-
/* istanbul ignore next -- Good regexes should prevent */
|
|
858
|
-
console.log('eee', e);
|
|
859
|
-
/* istanbul ignore next -- Good regexes should prevent */
|
|
860
|
-
throw e;
|
|
861
|
-
}
|
|
862
|
-
if (valObj.assign === false) {
|
|
863
|
-
assign = false;
|
|
864
|
-
}
|
|
865
|
-
const {value} = valObj;
|
|
866
|
-
if (valObj.remnant !== undefined) {
|
|
867
|
-
({remnant} = valObj);
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
if (beginOnly && endMatchTypeObjs.length) {
|
|
871
|
-
const endMatch = remnant.match(
|
|
872
|
-
/** @type {RegExp} */ (/** @type {TypeObject} */ (
|
|
873
|
-
endMatchTypeObjs.at(-1)
|
|
874
|
-
).stringRegexEnd)
|
|
875
|
-
);
|
|
876
|
-
if (endMatch) {
|
|
877
|
-
endMatchTypeObjs.pop(); // Safe now to extract
|
|
878
|
-
remnant = remnant.slice(endMatch[0].length);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
if (firstRun) {
|
|
882
|
-
const typeson = new Typeson().register(
|
|
883
|
-
structuredCloningThrowing
|
|
884
|
-
);
|
|
885
|
-
try {
|
|
886
|
-
const topRoot = typeson.revive(value);
|
|
887
|
-
rootHolder.forEach(([type, parent, parentPath, path]) => {
|
|
888
|
-
const typeObject = Types.availableTypes[
|
|
889
|
-
/** @type {AvailableType} */ (type + 'Reference')
|
|
890
|
-
];
|
|
891
|
-
|
|
892
|
-
// @ts-expect-error Reference method exists
|
|
893
|
-
const val = typeObject.resolveReference(path, topRoot);
|
|
894
|
-
const basicType = getJSONType(val);
|
|
895
|
-
// eslint-disable-next-line @stylistic/max-len -- Long
|
|
896
|
-
/* istanbul ignore else -- Successful reference always an object/array? */
|
|
897
|
-
if (
|
|
898
|
-
['array', 'object'].includes(type) && basicType === type
|
|
899
|
-
) {
|
|
900
|
-
/** @type {{[key: string]: any}} */ (
|
|
901
|
-
parent
|
|
902
|
-
)[/** @type {string|number} */ (parentPath)] = val;
|
|
903
|
-
}
|
|
904
|
-
});
|
|
905
|
-
return [topRoot, remnant, beginOnly, assign];
|
|
906
|
-
} catch (err) {
|
|
907
|
-
console.log('failed Typeson revival', err);
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
return [value, remnant, beginOnly, assign];
|
|
911
|
-
}
|
|
912
|
-
throw new Error('Bad parsing data');
|
|
913
|
-
};
|
|
914
|
-
|
|
915
967
|
export default Types;
|