@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.
Files changed (37) hide show
  1. package/.nyc_output/out.json +11754 -11508
  2. package/CHANGES.md +8 -0
  3. package/demo/index.js +38 -29
  4. package/dist/formatAndTypeChoices.d.ts +11 -2
  5. package/dist/formatAndTypeChoices.d.ts.map +1 -1
  6. package/dist/formats/structuredCloning.d.ts.map +1 -1
  7. package/dist/formats.d.ts +52 -10
  8. package/dist/formats.d.ts.map +1 -1
  9. package/dist/fundamentalTypes/arrayType.d.ts.map +1 -1
  10. package/dist/fundamentalTypes/dateType.d.ts.map +1 -1
  11. package/dist/fundamentalTypes/errorType.d.ts.map +1 -1
  12. package/dist/fundamentalTypes/regexpType.d.ts.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +795 -620
  16. package/dist/superTypes/errorsSpecialType.d.ts.map +1 -1
  17. package/dist/typeChoices.d.ts +4 -0
  18. package/dist/typeChoices.d.ts.map +1 -1
  19. package/dist/types.d.ts +308 -74
  20. package/dist/types.d.ts.map +1 -1
  21. package/package.json +1 -1
  22. package/src/formatAndTypeChoices.js +20 -5
  23. package/src/formats/indexedDBKey.js +1 -1
  24. package/src/formats/json.js +1 -1
  25. package/src/formats/schemaAndArbitrary.js +1 -1
  26. package/src/formats/schemaOnly.js +1 -1
  27. package/src/formats/structuredCloning.js +22 -13
  28. package/src/formats.js +87 -26
  29. package/src/fundamentalTypes/arrayReferenceType.js +2 -2
  30. package/src/fundamentalTypes/arrayType.js +43 -23
  31. package/src/fundamentalTypes/dateType.js +2 -3
  32. package/src/fundamentalTypes/errorType.js +5 -3
  33. package/src/fundamentalTypes/regexpType.js +2 -3
  34. package/src/index.js +1 -1
  35. package/src/superTypes/errorsSpecialType.js +12 -5
  36. package/src/typeChoices.js +21 -10
  37. 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} [stateObj]
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} [stateObj]
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 {string} format
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: {topRoot: HTMLDivElement}) => void} [validateAll] For
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
- * @param {[
480
- * copyFrom: AvailableType, copyTo: AvailableType
481
- * ][]} replacements
482
- * @returns {void}
377
+ *
483
378
  */
484
- const copyTypeObjs = (replacements) => {
485
- replacements.forEach(([copyFrom, copyTo]) => {
486
- Object.assign(Types.availableTypes[
487
- /** @type {AvailableType} */
488
- (copyTo)
489
- ], Types.availableTypes[
490
- /** @type {AvailableType} */
491
- (copyFrom)
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
- copyTypeObjs(
497
- [
498
- /** @type {[AvailableType, AvailableType]} */ (
499
- ['date', 'ValidDate']
500
- ),
501
- // 'sparseArrays'
502
- /** @type {[AvailableType, AvailableType]} */ (
503
- ['array', 'arrayNonindexKeys']
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
- Types.getTypeForRoot = (root) => {
509
- return String(root ? root.dataset.type : root);
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
- * @typedef {(
514
- * keypath: string,
515
- * parentPath: string,
516
- * arrayOrObjectPropertyName: string,
517
- * valueType: string
518
- * ) => StateObject} GetPossibleSchemasForPathAndType
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
- * @typedef {{
523
- * typeNamespace?: string,
524
- * "readonly"?: boolean,
525
- * format?: import('./formats.js').AvailableFormat,
526
- * error?: Error,
527
- * rootUI?: Element,
528
- * schema?: string,
529
- * schemaContent?: object,
530
- * getPossibleSchemasForPathAndType?: GetPossibleSchemasForPathAndType,
531
- * paths?: {[currentPath: string]: {
532
- * referentPath: string,
533
- * expectArrayReferent: boolean
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
- Types.getValueForRoot = (root, stateObj, currentPath) => {
540
- const typeObject = /** @type {TypeObject} */ (
541
- Types.availableTypes[
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
- return typeObject.getValue({
547
- root, stateObj, currentPath
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
- Types.getFormControlForRoot = (root) => {
552
- const typeObj = /** @type {TypeObject} */ (Types.availableTypes[
553
- /** @type {AvailableType} */
554
- (Types.getTypeForRoot(root))
555
- ]);
556
- /* istanbul ignore if -- All have except aliases */
557
- if (!typeObj.getInput) {
558
- return null;
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
- Types.getValueFromRootAncestor = (selOrEl, stateObj) => {
564
- return Types.getValueForRoot(
565
- /** @type {RootElement} */
566
- ($e(selOrEl, 'div[data-type]')),
567
- stateObj
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
- Types.getFormControlFromRootAncestor = (selOrEl) => {
572
- const root = /** @type {RootElement} */ ($e(selOrEl, 'div[data-type]'));
573
- if (!root) {
574
- return null;
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
- Types.getTypesForFormatAndState = (
580
- format, state
581
- ) => Formats.availableFormats[format].getTypesForState(state);
582
-
583
- Types.getOptionForType = (type) => {
584
- /** @type {[string, {value?: AvailableType, title?: string}?]} */
585
- const optInfo = [
586
- ...(/** @type {TypeObject} */ (
587
- Types.availableTypes[type]
588
- )).option
589
- ];
590
- optInfo[1] = {value: type, ...(optInfo[1])};
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
- Types.getTypeOptionsForFormatAndState = (format, parserState) => {
597
- const typesForFormatAndState = Types.getTypesForFormatAndState(
598
- format, parserState
599
- );
600
- if (!typesForFormatAndState) {
601
- throw new Error('Unexpected type for format and state');
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
- Types.getUIForModeAndType = ({
609
- readonly, resultType, typeNamespace, type, topRoot, bringIntoFocus,
610
- buildTypeChoices, format, schemaContent, schemaState, value, hasValue,
611
- replaced
612
- }) => {
613
- const typeObj = /** @type {TypeObject} */ (Types.availableTypes[type]);
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
- typeNamespace, type, buildTypeChoices,
623
- format, schemaContent, schemaState,
624
- resultType, topRoot, bringIntoFocus,
625
- replaced
626
- };
627
- const root = /** @type {HTMLDivElement} */ (jml(
628
- ...(readonly
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
- Types.contexts = {};
642
- Object.entries(Types.availableTypes).forEach(([typ, typeObj]) => {
643
- const type = /** @type {AvailableType} */ (typ);
644
- const {stateDependent} = /** @type {TypeObject} */ (typeObj);
645
- if (stateDependent) {
646
- Object.entries(stateDependent).forEach(([format, formatStateDependent]) => {
647
- if (!Types.contexts[format]) {
648
- Types.contexts[format] = {};
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
- const {contexts, after} = formatStateDependent;
651
- contexts.forEach((context) => {
652
- if (!Types.contexts[format][context]) {
653
- Types.contexts[format][context] = [];
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
- Types.contexts[format][context].push({type, after});
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;