@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/dist/index.js CHANGED
@@ -2680,6 +2680,8 @@ dialogs.setLocale();
2680
2680
  * requireObject?: boolean,
2681
2681
  * objectHasValue?: boolean,
2682
2682
  * topRoot?: import('./types.js').RootElement,
2683
+ * formats?: import('./formats.js').default
2684
+ * types?: import('./types.js').default
2683
2685
  * schema?: string,
2684
2686
  * schemaContent?: object,
2685
2687
  * }} cfg
@@ -2706,13 +2708,15 @@ const buildTypeChoices = ({
2706
2708
  requireObject,
2707
2709
  objectHasValue,
2708
2710
  topRoot,
2711
+ formats = new Formats(),
2712
+ types = new Types(),
2709
2713
  schema,
2710
2714
  schemaContent
2711
2715
  }) => {
2712
2716
  // console.log('format', format, 'state', state, 'path', typeNamespace);
2713
2717
  const typeOptions = requireObject
2714
- ? [Types$1.getOptionForType('object')]
2715
- : Types$1.getTypeOptionsForFormatAndState(format, state);
2718
+ ? [types.getOptionForType('object')]
2719
+ : types.getTypeOptionsForFormatAndState(format, state);
2716
2720
 
2717
2721
  let editUI;
2718
2722
 
@@ -2721,11 +2725,13 @@ const buildTypeChoices = ({
2721
2725
  const root = /** @type {HTMLDivElement} */ (
2722
2726
  $e(typeContainer, 'div[data-type]')
2723
2727
  );
2724
- return Types$1.getValueForRoot(
2728
+ return types.getValueForRoot(
2725
2729
  root,
2726
2730
  /** @type {import('./types.js').StateObject} */ ({
2727
2731
  typeNamespace,
2732
+ formats,
2728
2733
  format,
2734
+ types,
2729
2735
  ...stateObj
2730
2736
  }),
2731
2737
  currentPath
@@ -2788,7 +2794,7 @@ const buildTypeChoices = ({
2788
2794
  // (needed by arrayNonindexKeys for setting an array
2789
2795
  // length and avoiding errors); could set all
2790
2796
  // values through here?
2791
- editUI = Types$1.getUIForModeAndType({
2797
+ editUI = types.getUIForModeAndType({
2792
2798
  readonly: false,
2793
2799
  typeNamespace,
2794
2800
  type,
@@ -2803,7 +2809,7 @@ const buildTypeChoices = ({
2803
2809
  this.$validate();
2804
2810
  topRoot = this.$getTopRoot(); // May be existing now
2805
2811
  // Needed; Array/object ref somewhere could now be valid or invalid
2806
- Types$1.validateAllReferences({topRoot});
2812
+ types.validateAllReferences({topRoot});
2807
2813
  },
2808
2814
 
2809
2815
  /** @type {AddTypeAndEditUI} */
@@ -2835,7 +2841,7 @@ const buildTypeChoices = ({
2835
2841
  return false;
2836
2842
  }
2837
2843
  const editUI = container.firstElementChild;
2838
- return Types$1.validate({
2844
+ return types.validate({
2839
2845
  type, root: editUI, topRoot: this.$getTopRoot()
2840
2846
  });
2841
2847
  }
@@ -2872,17 +2878,21 @@ const buildTypeChoices = ({
2872
2878
  value = {};
2873
2879
  }
2874
2880
  try {
2875
- const rootEditUI = await Formats.availableFormats[format].iterate(
2881
+ const rootEditUI = await formats.getControlsForFormatAndValue(
2882
+ types,
2883
+ format,
2876
2884
  value,
2877
2885
  {
2878
2886
  readonly: false,
2879
2887
  typeNamespace,
2880
2888
  schema,
2881
- schemaContent
2889
+ schemaContent,
2890
+ formats,
2891
+ types
2882
2892
  }
2883
2893
  );
2884
2894
  const type =
2885
- Types$1.getTypeForRoot(/** @type {HTMLDivElement} */ (
2895
+ Types.getTypeForRoot(/** @type {HTMLDivElement} */ (
2886
2896
  rootEditUI
2887
2897
  ));
2888
2898
  // eslint-disable-next-line @stylistic/max-len -- Long
@@ -2919,7 +2929,7 @@ const buildTypeChoices = ({
2919
2929
  const root = /** @type {HTMLDivElement} */ (
2920
2930
  $e(typeContainer, 'div[data-type]')
2921
2931
  );
2922
- return Types$1.getTypeForRoot(root);
2932
+ return Types.getTypeForRoot(root);
2923
2933
  },
2924
2934
 
2925
2935
  /** @type {ValidValuesSet} */
@@ -2928,17 +2938,18 @@ const buildTypeChoices = ({
2928
2938
  $e(typeContainer, 'div[data-type]')
2929
2939
  );
2930
2940
  const form = /** @type {HTMLFormElement} */ (root.closest('form'));
2931
- return Types$1.validValuesSet({form, typeNamespace});
2941
+ return Types.validValuesSet({form, typeNamespace});
2932
2942
  },
2933
2943
 
2934
2944
  /** @type {SetValue} */
2935
2945
  async setValue (value, stateObj) {
2936
2946
  const rootEditUI = /** @type {HTMLDivElement} */ (
2937
- await getControlsForFormatAndValue(
2947
+ await formats.getControlsForFormatAndValue(
2948
+ types,
2938
2949
  format, value, stateObj
2939
2950
  )
2940
2951
  );
2941
- const type = Types$1.getTypeForRoot(rootEditUI);
2952
+ const type = Types.getTypeForRoot(rootEditUI);
2942
2953
  /** @type {HTMLSelectElement & {$addTypeAndEditUI: AddTypeAndEditUI}} */ (
2943
2954
  sel
2944
2955
  ).$addTypeAndEditUI({type, editUI: rootEditUI});
@@ -3061,7 +3072,7 @@ const json = {
3061
3072
  return undefined; // Todo: Fix?
3062
3073
  }
3063
3074
  },
3064
- getTypesForState (state) {
3075
+ getTypesForState (types, state) {
3065
3076
  /* istanbul ignore else -- No other states apparently */
3066
3077
  if (!state || ['array', 'object'].includes(state)) {
3067
3078
  return this.types();
@@ -3094,6 +3105,8 @@ const json = {
3094
3105
  const encapsulateObserver = (stateObj) => {
3095
3106
  const {
3096
3107
  typeNamespace, readonly, format: frmt, schemaContent,
3108
+ formats,
3109
+ types,
3097
3110
  getPossibleSchemasForPathAndType
3098
3111
  } = stateObj;
3099
3112
 
@@ -3166,11 +3179,13 @@ const encapsulateObserver = (stateObj) => {
3166
3179
  newValue = typesonPathToJSONPointer(cyclicKeypath);
3167
3180
  newType = type === 'array' ? 'arrayReference' : 'objectReference';
3168
3181
  newType = canonicalToAvailableType(
3169
- format, state, newType, value
3182
+ types, formats, format, state, newType, value
3170
3183
  ); // Todo (low): Add accurate state for second argument
3171
3184
  } else {
3172
3185
  try {
3173
3186
  newType = canonicalToAvailableType(
3187
+ types,
3188
+ formats,
3174
3189
  format,
3175
3190
  state,
3176
3191
  /**
@@ -3207,7 +3222,7 @@ const encapsulateObserver = (stateObj) => {
3207
3222
 
3208
3223
  if (!stateObj.rootUI) {
3209
3224
  // console.log('vvvv0', newType, newValue);
3210
- stateObj.rootUI = Types$1.getUIForModeAndType({
3225
+ stateObj.rootUI = types.getUIForModeAndType({
3211
3226
  readonly,
3212
3227
  typeNamespace,
3213
3228
  type: newType,
@@ -3305,13 +3320,13 @@ const encapsulateObserver = (stateObj) => {
3305
3320
  }
3306
3321
 
3307
3322
  if (!readonly) {
3308
- Types$1.setValue({
3323
+ types.setValue({
3309
3324
  type: newType,
3310
3325
  // eslint-disable-next-line object-shorthand -- TS
3311
3326
  root: /** @type {HTMLDivElement} */ (root),
3312
3327
  value: newValue
3313
3328
  });
3314
- Types$1.validate({
3329
+ types.validate({
3315
3330
  type: newType,
3316
3331
  // eslint-disable-next-line object-shorthand -- TS
3317
3332
  root: /** @type {HTMLDivElement} */ (root),
@@ -3355,6 +3370,8 @@ const replaceTypes = (originTypes, replacements) => {
3355
3370
  };
3356
3371
 
3357
3372
  /**
3373
+ * @param {import('../types.js').default} types
3374
+ * @param {import('../formats.js').default} formats
3358
3375
  * @param {import('../formats.js').AvailableFormat} format
3359
3376
  * @param {string} state
3360
3377
  * @param {import('../types.js').AvailableType} valType
@@ -3362,10 +3379,12 @@ const replaceTypes = (originTypes, replacements) => {
3362
3379
  * @throws {Error}
3363
3380
  * @returns {import('../types.js').AvailableType}
3364
3381
  */
3365
- const canonicalToAvailableType = (format, state, valType, v) => {
3366
- const frmt = Formats.availableFormats[format];
3382
+ const canonicalToAvailableType = (
3383
+ types, formats, format, state, valType, v
3384
+ ) => {
3385
+ const frmt = formats.getAvailableFormat(format);
3367
3386
  const {getTypesForState, convertFromTypeson, testInvalid} = frmt;
3368
- const allowableTypes = getTypesForState.call(frmt, state);
3387
+ const allowableTypes = getTypesForState.call(frmt, types, state);
3369
3388
  /* istanbul ignore if -- Guard */
3370
3389
  if (!allowableTypes) {
3371
3390
  throw new Error('Unexpected undefined type for state');
@@ -3402,7 +3421,7 @@ const canonicalToAvailableType = (format, state, valType, v) => {
3402
3421
  allowableTypes.some((allowableType) => {
3403
3422
  // eslint-disable-next-line @stylistic/max-len -- Long
3404
3423
  const typeObj = /** @type {import('../types.js').TypeObject & {childTypes: string[]}} */ (
3405
- Types$1.availableTypes[allowableType]
3424
+ types.getTypeObject(allowableType)
3406
3425
  );
3407
3426
  const {
3408
3427
  valueMatch, superType, childTypes
@@ -3469,12 +3488,13 @@ const structuredCloning = {
3469
3488
  }
3470
3489
  });
3471
3490
  },
3472
- getTypesForState (state) {
3473
- if (state && Types$1.contexts.structuredCloning[state]) {
3474
- const typesForFormat = this.getTypesForState() ||
3491
+ getTypesForState (types, state) {
3492
+ if (state && types.getContextInfo('structuredCloning', state)) {
3493
+ const typesForFormat = this.getTypesForState(types) ||
3475
3494
  /* istanbul ignore next -- types should be an array */
3476
3495
  [];
3477
- Types$1.contexts.structuredCloning[state].forEach(({type, after}) => {
3496
+ const contextInfo = types.getContextInfo('structuredCloning', state);
3497
+ contextInfo.forEach(({type, after}) => {
3478
3498
  const precedingIdx = typesForFormat.indexOf(after);
3479
3499
  typesForFormat.splice(precedingIdx + 1, 0, type);
3480
3500
  });
@@ -3588,7 +3608,7 @@ const indexedDBKey = {
3588
3608
  stateObj.format = 'indexedDBKey';
3589
3609
  return structuredCloning.iterate(records, stateObj);
3590
3610
  },
3591
- getTypesForState (state) {
3611
+ getTypesForState (types, state) {
3592
3612
  if (!state || ['array'].includes(state)) {
3593
3613
  return this.types();
3594
3614
  }
@@ -3617,6 +3637,14 @@ const typesonToIndexedDBKey = new Map([
3617
3637
  * @typedef {any} StructuredCloneValue
3618
3638
  */
3619
3639
 
3640
+ /**
3641
+ * @callback GetTypesForFormatAndState
3642
+ * @param {import('./types.js').default} types
3643
+ * @param {AvailableFormat} format
3644
+ * @param {string} [state]
3645
+ * @returns {import('./types.js').AvailableType[]|undefined}
3646
+ */
3647
+
3620
3648
  /* schema:
3621
3649
  export const getTypeForFormatStateAndValue = ({format, state, value}) => {
3622
3650
  const valType = new Typeson().register(
@@ -3640,39 +3668,92 @@ export const getTypeForFormatStateAndValue = ({format, state, value}) => {
3640
3668
  * typesonType: import('./types.js').AvailableType
3641
3669
  * ) => import('./types.js').AvailableType|undefined,
3642
3670
  * iterate: import('./formats/structuredCloning.js').FormatIterator,
3643
- * getTypesForState: (state?: string) => undefined|
3671
+ * getTypesForState: (
3672
+ * types: import('./types.js').default,
3673
+ * state?: string
3674
+ * ) => undefined|
3644
3675
  * (import('./types.js').AvailableType)[]
3645
3676
  * }} Format
3646
3677
  */
3647
3678
 
3648
- // Using methods ensure we have fresh copies
3649
- const Formats = {
3650
- availableFormats: /** @type {{[key: string]: Format}} */ ({
3651
- indexedDBKey,
3652
- json,
3653
- // Todo (readme): these too? getTypesForState(state)
3654
- /* schema:
3655
- schemaAndArbitrary,
3656
- schemaOnly,
3657
- */
3658
- structuredCloning
3659
- })
3660
- };
3661
-
3662
3679
  /**
3663
- * @param {AvailableFormat} format
3664
- * @param {StructuredCloneValue} record
3665
- * @param {import('./types.js').StateObject} stateObj
3666
- * @returns {Promise<Element>}
3680
+ * Class for processing multiple formats.
3667
3681
  */
3668
- async function getControlsForFormatAndValue (format, record, stateObj) {
3669
- return await Formats.availableFormats[format].
3670
- iterate(record, {
3671
- ...stateObj,
3672
- // This had been before `stateObj` but should apparently have precedence
3673
- // or just avoid passing `format` to this function
3674
- format
3682
+ class Formats {
3683
+ /**
3684
+ *
3685
+ */
3686
+ constructor () {
3687
+ // Can enable later (and add tests)
3688
+ // if (formats) {
3689
+ // this.availableFormats = {};
3690
+ // formats.forEach((format) => {
3691
+ // let formatValue;
3692
+ // switch (format) {
3693
+ // case 'indexedDBKey':
3694
+ // formatValue = indexedDBKey;
3695
+ // break;
3696
+ // case 'json':
3697
+ // formatValue = json;
3698
+ // break;
3699
+ // case 'structuredCloning':
3700
+ // formatValue = structuredCloning;
3701
+ // break;
3702
+ // default:
3703
+ // throw new Error('Unknown format');
3704
+ // }
3705
+ // this.availableFormats[format] = formatValue;
3706
+ // });
3707
+ // return;
3708
+ // }
3709
+ // Using methods ensure we have fresh copies
3710
+ this.availableFormats = /** @type {{[key: string]: Format}} */ ({
3711
+ indexedDBKey,
3712
+ json,
3713
+ // Todo (readme): these too? getTypesForState(types, state)
3714
+ /* schema:
3715
+ schemaAndArbitrary,
3716
+ schemaOnly,
3717
+ */
3718
+ structuredCloning
3675
3719
  });
3720
+ }
3721
+
3722
+ /**
3723
+ * @param {import('./types.js').default} types
3724
+ * @param {AvailableFormat} format
3725
+ * @param {StructuredCloneValue} record
3726
+ * @param {import('./types.js').StateObject} stateObj
3727
+ * @returns {Promise<Element>}
3728
+ */
3729
+ async getControlsForFormatAndValue (types, format, record, stateObj) {
3730
+ return await this.availableFormats[format].
3731
+ iterate(record, {
3732
+ ...stateObj,
3733
+ types,
3734
+ formats: this,
3735
+ // This had been before `stateObj` but should apparently have precedence
3736
+ // or just avoid passing `format` to this function
3737
+ format
3738
+ });
3739
+ }
3740
+
3741
+ /**
3742
+ * @type {GetTypesForFormatAndState}
3743
+ */
3744
+ getTypesForFormatAndState (
3745
+ types, format, state
3746
+ ) {
3747
+ return this.availableFormats[format].getTypesForState(types, state);
3748
+ }
3749
+
3750
+ /**
3751
+ * @param {AvailableFormat} format
3752
+ * @returns {Format}
3753
+ */
3754
+ getAvailableFormat (format) {
3755
+ return this.availableFormats[format];
3756
+ }
3676
3757
  }
3677
3758
 
3678
3759
  /**
@@ -4191,7 +4272,7 @@ const arrayReferenceType = {
4191
4272
  'arrayNonindexKeys'
4192
4273
  // 'sparseArrays'
4193
4274
  ].includes(
4194
- Types$1.getTypeForRoot(referent)
4275
+ Types.getTypeForRoot(referent)
4195
4276
  );
4196
4277
  };
4197
4278
  let message;
@@ -4248,7 +4329,7 @@ const arrayReferenceType = {
4248
4329
 
4249
4330
  // Condition to confirm that referenced element is an array or
4250
4331
  // object (or reference) with the designated `type`
4251
- const referentType = Types$1.getTypeForRoot(referent);
4332
+ const referentType = Types.getTypeForRoot(referent);
4252
4333
  const referentBaseType = /** @type {(string|null|undefined)[]} */ ([
4253
4334
  'array',
4254
4335
  'arrayNonindexKeys'
@@ -4270,12 +4351,12 @@ const arrayReferenceType = {
4270
4351
  valid: true
4271
4352
  };
4272
4353
  },
4273
- validateAll ({topRoot}) {
4354
+ validateAll ({types, topRoot}) {
4274
4355
  const type = /** @type {import('../types.js').AvailableType} */ (
4275
4356
  `${this.type}Reference`
4276
4357
  );
4277
4358
  $$e(topRoot, `div[data-type="${type}"]`).forEach((root) => {
4278
- Types$1.validate({
4359
+ types.validate({
4279
4360
  type,
4280
4361
  // eslint-disable-next-line object-shorthand -- TS
4281
4362
  root: /** @type {HTMLDivElement} */ (root),
@@ -4481,7 +4562,10 @@ const arrayType = {
4481
4562
  } = /** @type {import('../types.js').RootInfo} */ (info);
4482
4563
  // eslint-disable-next-line prefer-destructuring -- TS
4483
4564
  const format = /** @type {import('../types.js').RootInfo} */ (info).format;
4484
-
4565
+ // eslint-disable-next-line prefer-destructuring -- TS
4566
+ const types = /** @type {import('../types.js').default} */ (
4567
+ /** @type {import('../types.js').RootInfo} */ (info).types
4568
+ );
4485
4569
  const {sparse} = this;
4486
4570
  const state = sparse
4487
4571
  ? 'arrayNonindexKeys'
@@ -4532,7 +4616,7 @@ const arrayType = {
4532
4616
  }
4533
4617
  let v, beginOnly, assign;
4534
4618
  try {
4535
- [v, stringVal, beginOnly, assign] = Types$1.getValueForString(
4619
+ [v, stringVal, beginOnly, assign] = types.getValueForString(
4536
4620
  stringVal,
4537
4621
  {
4538
4622
  firstRun: false,
@@ -4589,7 +4673,7 @@ const arrayType = {
4589
4673
  stringVal = stringVal.slice(propAndColon[0].length);
4590
4674
  let v, beginOnly, assign;
4591
4675
  try {
4592
- [v, stringVal, beginOnly, assign] = Types$1.getValueForString(
4676
+ [v, stringVal, beginOnly, assign] = types.getValueForString(
4593
4677
  stringVal,
4594
4678
  {
4595
4679
  firstRun: false, format, state,
@@ -4618,7 +4702,13 @@ const arrayType = {
4618
4702
  parse();
4619
4703
  return {value: retObj, remnant: stringVal};
4620
4704
  },
4621
- getValue ({root, stateObj = {}, currentPath = ''}) {
4705
+ getValue ({root, stateObj, currentPath = ''}) {
4706
+ /* istanbul ignore if */
4707
+ if (!stateObj) {
4708
+ throw new Error('TS guard'); // TS guard
4709
+ }
4710
+ // eslint-disable-next-line prefer-destructuring -- TS
4711
+ const types = /** @type {import('../types.js').default} */ (stateObj.types);
4622
4712
  const top = currentPath === '';
4623
4713
  const arrayItems =
4624
4714
  /**
@@ -4651,10 +4741,10 @@ const arrayType = {
4651
4741
  * @returns {[boolean, any?]}
4652
4742
  */
4653
4743
  const getValOrRef = (root, currentPathPart) => {
4654
- const value = Types$1.getValueForRoot(
4744
+ const value = types.getValueForRoot(
4655
4745
  root, stateObj, currentPath + '/' + currentPathPart
4656
4746
  );
4657
- if (stateObj.handlingReference) {
4747
+ if ('handlingReference' in stateObj && stateObj.handlingReference) {
4658
4748
  // We deal with references later once object is fully constructed
4659
4749
  stateObj.handlingReference = false;
4660
4750
  return [false];
@@ -4730,7 +4820,7 @@ const arrayType = {
4730
4820
  },
4731
4821
 
4732
4822
  // Try to keep in sync with basic structure of `editUI`
4733
- viewUI ({typeNamespace, type, value, topRoot, resultType, format}) {
4823
+ viewUI ({typeNamespace, type, types, value, topRoot, resultType, format}) {
4734
4824
  // const {sparse} = this;
4735
4825
  let itemIndex = -1;
4736
4826
 
@@ -4787,7 +4877,7 @@ const arrayType = {
4787
4877
  schemaContent, schemaState
4788
4878
  }) {
4789
4879
  if (parentType === 'map') {
4790
- const root = Types$1.getUIForModeAndType({
4880
+ const root = types.getUIForModeAndType({
4791
4881
  resultType,
4792
4882
  readonly: true,
4793
4883
  typeNamespace, type, topRoot,
@@ -4818,7 +4908,7 @@ const arrayType = {
4818
4908
  }
4819
4909
 
4820
4910
  const fieldset = this.$addArrayElement({propName});
4821
- const root = Types$1.getUIForModeAndType({
4911
+ const root = types.getUIForModeAndType({
4822
4912
  resultType,
4823
4913
  readonly: true,
4824
4914
  typeNamespace, type, topRoot,
@@ -4928,6 +5018,7 @@ const arrayType = {
4928
5018
  // population of the array in the callback
4929
5019
  editUI ({
4930
5020
  typeNamespace, buildTypeChoices, format, // resultType,
5021
+ formats, types,
4931
5022
  type, arrayState, topRoot, value, bringIntoFocus = true
4932
5023
  }) {
4933
5024
  const {sparse} = this;
@@ -5018,7 +5109,7 @@ const arrayType = {
5018
5109
  } else {
5019
5110
  group.before(swapGroup);
5020
5111
  }
5021
- Types$1.validateAllReferences({
5112
+ types.validateAllReferences({
5022
5113
  // eslint-disable-next-line object-shorthand -- TS
5023
5114
  topRoot: /** @type {HTMLDivElement} */ (topRoot)
5024
5115
  }); // Needed
@@ -5201,7 +5292,7 @@ const arrayType = {
5201
5292
  (this).$validateMapKey();
5202
5293
 
5203
5294
  // Needed?
5204
- Types$1.validateAllReferences({
5295
+ types.validateAllReferences({
5205
5296
  // eslint-disable-next-line object-shorthand -- TS
5206
5297
  topRoot: /** @type {HTMLDivElement} */ (topRoot)
5207
5298
  });
@@ -5286,7 +5377,7 @@ const arrayType = {
5286
5377
  /* await */ this.$validateLegend();
5287
5378
  // Todo (low): We could make this more efficient by waiting
5288
5379
  // until all added (when pre-populating)
5289
- Types$1.validateAllReferences({
5380
+ types.validateAllReferences({
5290
5381
  // eslint-disable-next-line object-shorthand -- TS
5291
5382
  topRoot: /** @type {HTMLDivElement} */ (topRoot)
5292
5383
  }); // Needed
@@ -5541,7 +5632,17 @@ const arrayType = {
5541
5632
  parentTypeObject.getValue.call({
5542
5633
  ...parentTypeObject,
5543
5634
  set: false
5544
- }, {root})
5635
+ }, {
5636
+ root,
5637
+ stateObj: {
5638
+ types,
5639
+ // eslint-disable-next-line object-shorthand -- TS
5640
+ formats:
5641
+ /** @type {import('../formats.js').default} */ (
5642
+ formats
5643
+ )
5644
+ }
5645
+ })
5545
5646
  );
5546
5647
  // console.log('values', values);
5547
5648
 
@@ -5563,7 +5664,7 @@ const arrayType = {
5563
5664
  if (!root) {
5564
5665
  return null;
5565
5666
  }
5566
- return Types$1.getFormControlForRoot(root);
5667
+ return types.getFormControlForRoot(root);
5567
5668
  });
5568
5669
 
5569
5670
  const control = controls[dupeIndex];
@@ -5665,7 +5766,7 @@ const arrayType = {
5665
5766
  }
5666
5767
  // Maybe not needed as addition (without renumbering)
5667
5768
  // wouldn't yet add type
5668
- Types$1.validateAllReferences({
5769
+ types.validateAllReferences({
5669
5770
  // eslint-disable-next-line object-shorthand -- TS
5670
5771
  topRoot: /** @type {HTMLDivElement} */ (topRoot)
5671
5772
  });
@@ -5691,7 +5792,7 @@ const arrayType = {
5691
5792
  });
5692
5793
  }
5693
5794
  // Maybe not needed as removal would remove circular
5694
- Types$1.validateAllReferences({
5795
+ types.validateAllReferences({
5695
5796
  // eslint-disable-next-line object-shorthand -- TS
5696
5797
  topRoot: /** @type {HTMLDivElement} */ (topRoot)
5697
5798
  });
@@ -5803,7 +5904,7 @@ const arrayType = {
5803
5904
  (this).$addArrayElement({alwaysFocus: true});
5804
5905
  // Maybe not needed as addition (without renumbering)
5805
5906
  // wouldn't yet add type
5806
- Types$1.validateAllReferences({
5907
+ types.validateAllReferences({
5807
5908
  // eslint-disable-next-line object-shorthand -- TS
5808
5909
  topRoot: /** @type {HTMLDivElement} */ (topRoot)
5809
5910
  });
@@ -5855,7 +5956,7 @@ const arrayType = {
5855
5956
  fieldset.remove();
5856
5957
  });
5857
5958
  // Maybe not needed as removal would remove circular
5858
- Types$1.validateAllReferences({
5959
+ types.validateAllReferences({
5859
5960
  // eslint-disable-next-line object-shorthand -- TS
5860
5961
  topRoot: /** @type {HTMLDivElement} */ (topRoot)
5861
5962
  });
@@ -5949,7 +6050,7 @@ const arrayType = {
5949
6050
  ).$redrawMoveArrows();
5950
6051
  }
5951
6052
  // Maybe not needed as removal would remove circular
5952
- Types$1.validateAllReferences({
6053
+ types.validateAllReferences({
5953
6054
  // eslint-disable-next-line object-shorthand -- TS
5954
6055
  topRoot: /** @type {HTMLDivElement} */ (topRoot)
5955
6056
  });
@@ -5971,7 +6072,7 @@ const arrayType = {
5971
6072
  itemIndex = -1;
5972
6073
  }
5973
6074
  // Maybe not needed as removal would remove circular
5974
- Types$1.validateAllReferences({
6075
+ types.validateAllReferences({
5975
6076
  // eslint-disable-next-line object-shorthand -- TS
5976
6077
  topRoot: /** @type {HTMLDivElement} */ (topRoot)
5977
6078
  });
@@ -6026,13 +6127,13 @@ const arrayType = {
6026
6127
  // from `errorsSpecialType` (and `filelistType`)
6027
6128
  if (setAValue) {
6028
6129
  const typeObj = /** @type {import('../types.js').TypeObject} */ (
6029
- Types$1.availableTypes[type]
6130
+ types.getTypeObject(type)
6030
6131
  );
6031
6132
  if (typeObj.setValue) {
6032
6133
  typeObj.setValue({root, value});
6033
6134
  }
6034
6135
  }
6035
- Types$1.validate({type, root, topRoot});
6136
+ types.validate({type, root, topRoot});
6036
6137
  return root;
6037
6138
  },
6038
6139
 
@@ -6255,7 +6356,7 @@ const dateType = {
6255
6356
  },
6256
6357
  // Change to default to `new Date()` when can't be `NaN`
6257
6358
  // value (keys)?
6258
- editUI ({typeNamespace, value = ''}) {
6359
+ editUI ({typeNamespace, types, value = ''}) {
6259
6360
  const notANum = this.isValueInvalid(value);
6260
6361
  const invalid = this.valid
6261
6362
  ? ''
@@ -6285,7 +6386,7 @@ const dateType = {
6285
6386
  const root = /** @type {HTMLDivElement} */ (
6286
6387
  label.parentElement
6287
6388
  );
6288
- Types$1.validate({type: 'date', root});
6389
+ types.validate({type: 'date', root});
6289
6390
  }
6290
6391
  },
6291
6392
  $on: {
@@ -6473,7 +6574,7 @@ const regexpType = {
6473
6574
  viewUI ({value}) {
6474
6575
  return ['i', {dataset: {type: 'regexp'}}, [String(value)]];
6475
6576
  },
6476
- editUI ({typeNamespace, value = {source: '', flags: ''}}) {
6577
+ editUI ({typeNamespace, types, value = {source: '', flags: ''}}) {
6477
6578
  // Todo (low): Add RegExp syntax highlighter
6478
6579
  const select = /** @type {HTMLSelectElement} */ (jml(
6479
6580
  'select',
@@ -6515,7 +6616,7 @@ const regexpType = {
6515
6616
  // Could be disallowed flags; we might instead try in
6516
6617
  // advance which will work
6517
6618
  select.addEventListener('change', () => {
6518
- Types$1.validate({type: 'regexp', root});
6619
+ types.validate({type: 'regexp', root});
6519
6620
  });
6520
6621
  return [root];
6521
6622
  }
@@ -6948,7 +7049,7 @@ const errorType = {
6948
7049
  return errObj;
6949
7050
  },
6950
7051
  viewUI (
6951
- {value: o, format}
7052
+ {value: o, types, format}
6952
7053
  ) {
6953
7054
  return /** @type {import('jamilih').JamilihArray} */ ([
6954
7055
  'div', {dataset: {type: 'error'}}, [
@@ -6987,7 +7088,8 @@ const errorType = {
6987
7088
  o.cause
6988
7089
  ? this.viewUI({
6989
7090
  format,
6990
- value: o.cause
7091
+ value: o.cause,
7092
+ types
6991
7093
  })
6992
7094
  : ''
6993
7095
  ]]
@@ -6995,7 +7097,7 @@ const errorType = {
6995
7097
  ]
6996
7098
  ]);
6997
7099
  },
6998
- editUI ({typeNamespace, value = {
7100
+ editUI ({typeNamespace, types, value = {
6999
7101
  message: '',
7000
7102
  name: '',
7001
7103
  fileName: '',
@@ -7161,6 +7263,7 @@ const errorType = {
7161
7263
  if (!causeContents?.children.length) {
7162
7264
  const editui = component.editUI({
7163
7265
  typeNamespace,
7266
+ types,
7164
7267
  value: value.cause
7165
7268
  });
7166
7269
  jml(...editui, causeContents);
@@ -7360,7 +7463,7 @@ const errorsSpecialType = {
7360
7463
  }
7361
7464
  });
7362
7465
  },
7363
- getValue ({root}) {
7466
+ getValue ({root, stateObj}) {
7364
7467
  const UserErrorType = specialErrorsMap.get(
7365
7468
  /** @type {HTMLSelectElement} */ (
7366
7469
  $e(root, '.errorType')
@@ -7397,6 +7500,7 @@ const errorsSpecialType = {
7397
7500
  )).firstElementChild;
7398
7501
 
7399
7502
  const errors = arrayType.getValue({
7503
+ stateObj,
7400
7504
  root: /** @type {HTMLDivElement} */ (errorRoot)
7401
7505
  });
7402
7506
  errObj = new UET(errors, message);
@@ -7412,6 +7516,7 @@ const errorsSpecialType = {
7412
7516
  root, '.aggregateErrorsContents'
7413
7517
  )).firstElementChild;
7414
7518
  const errors = arrayType.getValue({
7519
+ stateObj,
7415
7520
  root: /** @type {HTMLDivElement} */ (errorRoot)
7416
7521
  });
7417
7522
  errObj = new UET(errors);
@@ -7469,6 +7574,7 @@ const errorsSpecialType = {
7469
7574
  );
7470
7575
  if (causeContents.children.length) {
7471
7576
  errObj.cause = this.getValue({
7577
+ stateObj,
7472
7578
  root: causeContents
7473
7579
  });
7474
7580
  }
@@ -7476,7 +7582,7 @@ const errorsSpecialType = {
7476
7582
  return errObj;
7477
7583
  },
7478
7584
  viewUI (
7479
- {value: o, format, typeNamespace, resultType}
7585
+ {value: o, types, format, typeNamespace, resultType}
7480
7586
  ) {
7481
7587
  const constructor = getConstructor(o.constructor);
7482
7588
  return /** @type {import('jamilih').JamilihArray} */ ([
@@ -7518,7 +7624,8 @@ const errorsSpecialType = {
7518
7624
  o.cause
7519
7625
  ? this.viewUI({
7520
7626
  format,
7521
- value: o.cause
7627
+ value: o.cause,
7628
+ types
7522
7629
  })
7523
7630
  : ''
7524
7631
  ]]
@@ -7545,7 +7652,8 @@ const errorsSpecialType = {
7545
7652
  const [div] = arrayType.viewUI({
7546
7653
  typeNamespace, resultType, format,
7547
7654
  type: 'arrayNonindexKeys',
7548
- value: []
7655
+ value: [],
7656
+ types
7549
7657
  });
7550
7658
 
7551
7659
  /** @type {Error[]} */
@@ -7580,6 +7688,7 @@ const errorsSpecialType = {
7580
7688
  },
7581
7689
  editUI ({
7582
7690
  typeNamespace,
7691
+ types,
7583
7692
 
7584
7693
  // Pass these to `errors` array
7585
7694
  buildTypeChoices,
@@ -7808,6 +7917,7 @@ const errorsSpecialType = {
7808
7917
  if (!causeContents?.children.length) {
7809
7918
  const editui = component.editUI({
7810
7919
  typeNamespace,
7920
+ types,
7811
7921
  value: value.cause
7812
7922
  });
7813
7923
  jml(...editui, causeContents);
@@ -7874,7 +7984,7 @@ const errorsSpecialType = {
7874
7984
  );
7875
7985
  if (!aggregateErrorsContents?.children.length) {
7876
7986
  const [editui] = arrayType.editUI({
7877
- typeNamespace, buildTypeChoices, format,
7987
+ typeNamespace, types, buildTypeChoices, format,
7878
7988
  type: 'array',
7879
7989
  topRoot, bringIntoFocus,
7880
7990
  arrayState: 'errorsArray',
@@ -10889,6 +10999,11 @@ const getPropertyValueFromLegend = (legend) => {
10889
10999
  return String(Number.parseInt(propElem.textContent) - 1);
10890
11000
  };
10891
11001
 
11002
+ /**
11003
+ * Any other possibilities than `div`?
11004
+ * @typedef {HTMLDivElement} RootElement
11005
+ */
11006
+
10892
11007
  /**
10893
11008
  * Utility to retrieve the type out of a type root element.
10894
11009
  * @callback GetTypeForRoot
@@ -10901,7 +11016,7 @@ const getPropertyValueFromLegend = (legend) => {
10901
11016
  * state and path.
10902
11017
  * @callback GetValueForRoot
10903
11018
  * @param {RootElement} root
10904
- * @param {StateObject} [stateObj]
11019
+ * @param {StateObject} stateObj
10905
11020
  * @param {string} [currentPath]
10906
11021
  * @returns {StructuredCloneValue}
10907
11022
  */
@@ -10918,7 +11033,7 @@ const getPropertyValueFromLegend = (legend) => {
10918
11033
  * Utility to get the value for a root using its ancestor and state.
10919
11034
  * @callback GetValueFromRootAncestor
10920
11035
  * @param {string|HTMLElement} selOrEl
10921
- * @param {StateObject} [stateObj]
11036
+ * @param {StateObject} stateObj
10922
11037
  * @returns {StructuredCloneValue}
10923
11038
  */
10924
11039
 
@@ -10929,13 +11044,6 @@ const getPropertyValueFromLegend = (legend) => {
10929
11044
  * HTMLButtonElement}
10930
11045
  */
10931
11046
 
10932
- /**
10933
- * @callback GetTypesForFormatAndState
10934
- * @param {string} format
10935
- * @param {string} [state]
10936
- * @returns {AvailableType[]|undefined}
10937
- */
10938
-
10939
11047
  /**
10940
11048
  * @callback GetOptionForType
10941
11049
  * @param {AvailableType} type
@@ -10944,11 +11052,40 @@ const getPropertyValueFromLegend = (legend) => {
10944
11052
 
10945
11053
  /**
10946
11054
  * @callback GetTypeOptionsForFormatAndState
10947
- * @param {string} format
11055
+ * @param {import('./formats.js').AvailableFormat} format
10948
11056
  * @param {string} [parserState]
10949
11057
  * @returns {[string, {value: AvailableType, title?: string}][]}
10950
11058
  */
10951
11059
 
11060
+ /**
11061
+ * @typedef {(
11062
+ * keypath: string,
11063
+ * parentPath: string,
11064
+ * arrayOrObjectPropertyName: string,
11065
+ * valueType: string
11066
+ * ) => StateObject} GetPossibleSchemasForPathAndType
11067
+ */
11068
+
11069
+ /**
11070
+ * @typedef {{
11071
+ * typeNamespace?: string,
11072
+ * "readonly"?: boolean,
11073
+ * format?: import('./formats.js').AvailableFormat,
11074
+ * formats: import('./formats.js').default,
11075
+ * types: Types,
11076
+ * error?: Error,
11077
+ * rootUI?: Element,
11078
+ * schema?: string,
11079
+ * schemaContent?: object,
11080
+ * getPossibleSchemasForPathAndType?: GetPossibleSchemasForPathAndType,
11081
+ * paths?: {[currentPath: string]: {
11082
+ * referentPath: string,
11083
+ * expectArrayReferent: boolean
11084
+ * }},
11085
+ * handlingReference?: boolean
11086
+ * }} StateObject
11087
+ */
11088
+
10952
11089
  /* eslint-disable jsdoc/valid-types -- readonly reserved */
10953
11090
  /**
10954
11091
  * @typedef {(cfg: {
@@ -11027,35 +11164,6 @@ const getPropertyValueFromLegend = (legend) => {
11027
11164
  * ) => void} CustomValidateAllReferences
11028
11165
  */
11029
11166
 
11030
- /**
11031
- * @type {{
11032
- * availableTypes: {
11033
- * [key in AvailableType]: Partial<TypeObject>|string[]
11034
- * },
11035
- * getTypeForRoot: GetTypeForRoot,
11036
- * getValueForRoot: GetValueForRoot,
11037
- * getFormControlForRoot: GetFormControlForRoot,
11038
- * getValueFromRootAncestor: GetValueFromRootAncestor,
11039
- * getFormControlFromRootAncestor: GetFormControlFromRootAncestor,
11040
- * getTypesForFormatAndState: GetTypesForFormatAndState,
11041
- * getOptionForType: GetOptionForType,
11042
- * getTypeOptionsForFormatAndState: GetTypeOptionsForFormatAndState,
11043
- * getUIForModeAndType: GetUIForModeAndType,
11044
- * contexts: {
11045
- * [key: string]: {
11046
- * [key: string]: {type: AvailableType, after: AvailableType}[]
11047
- * }
11048
- * },
11049
- * validValuesSet: ValidValuesSet,
11050
- * validateAllReferences: ValidateAllReferences,
11051
- * validate: Validate,
11052
- * setValue: SetValue,
11053
- * getValueForString: GetValueForString,
11054
- * customValidateAllReferences?: CustomValidateAllReferences
11055
- * }}
11056
- */
11057
- const Types = {};
11058
-
11059
11167
  /**
11060
11168
  * @typedef {import('./formats.js').StructuredCloneValue} StructuredCloneValue
11061
11169
  */
@@ -11077,7 +11185,8 @@ const Types = {};
11077
11185
  * path: string
11078
11186
  * ][],
11079
11187
  * parent?: object,
11080
- * parentPath?: string|number
11188
+ * parentPath?: string|number,
11189
+ * types?: Types
11081
11190
  * }} RootInfo
11082
11191
  */
11083
11192
 
@@ -11142,13 +11251,16 @@ const Types = {};
11142
11251
  * type?: AvailableType,
11143
11252
  * topRoot?: HTMLDivElement,
11144
11253
  * resultType?: "keys"|"values"|"both",
11145
- * format: import('./formats.js').AvailableFormat
11254
+ * format: import('./formats.js').AvailableFormat,
11255
+ * types: Types
11146
11256
  * }) => JamilihArray} viewUI
11147
11257
  * @property {(info: {
11148
11258
  * value?: StructuredCloneValue,
11149
11259
  * typeNamespace?: string,
11150
11260
  * bringIntoFocus?: boolean,
11151
11261
  * format?: import('./formats.js').AvailableFormat,
11262
+ * formats?: import('./formats.js').default,
11263
+ * types: Types,
11152
11264
  * resultType?: "keys"|"values"|"both",
11153
11265
  * type?: AvailableType,
11154
11266
  * arrayState?: string,
@@ -11168,7 +11280,10 @@ const Types = {};
11168
11280
  * message?: string,
11169
11281
  * valid: boolean
11170
11282
  * }} [validate] Message will be used if validity is false.
11171
- * @property {(info: {topRoot: HTMLDivElement}) => void} [validateAll] For
11283
+ * @property {(info: {
11284
+ * topRoot: HTMLDivElement,
11285
+ * types: Types
11286
+ * }) => void} [validateAll] For
11172
11287
  * validation of array and object references only.
11173
11288
  * @property {{
11174
11289
  * structuredCloning: {
@@ -11179,308 +11294,566 @@ const Types = {};
11179
11294
  * context types
11180
11295
  */
11181
11296
 
11182
- Types.availableTypes = {
11183
- null: nullType,
11184
- true: trueType,
11185
- false: falseType,
11186
- number: numberType,
11187
- bigint: bigintType,
11188
- string: stringType,
11189
- arrayReference: arrayReferenceType,
11190
- objectReference: objectReferenceType,
11191
- array: arrayType,
11192
- // Note: We don't do for BooleanObject/NumberObject/StringObject, date,
11193
- // regexp, as added properties on them are not being cloned (in Chrome
11194
- // at least)
11195
- object: objectType,
11196
- date: dateType,
11297
+ /**
11298
+ * @typedef {"null"|"true"|"false"|"number"|"bigint"|"string"|"arrayReference"|
11299
+ * "objectReference"|"array"|"object"|"date"|"userObject"|"undef"|
11300
+ * "SpecialRealNumber"|"SpecialNumber"|"regexp"|"BooleanObject"|
11301
+ * "NumberObject"|"StringObject"|"map"|"set"|"file"|"filelist"|"blobHTML"|
11302
+ * "arraybuffer"|"arraybufferview"|"dataview"|"imagedata"|"imagebitmap"|
11303
+ * "int8array"|"uint8array"|"uint8clampedarray"|"int16array"|"uint16array"|
11304
+ * "int32array"|"uint32array"|"float32array"|"float64array"|"ValidDate"|
11305
+ * "arrayNonindexKeys"|"error"|"errors"|"blob"|"domexception"|"domrect"|
11306
+ * "dompoint"|"dommatrix"} AvailableType
11307
+ */
11197
11308
 
11198
- // This type is only for throwing upon cloning errors:
11199
- // 'checkDataCloneException'
11200
- // This type might be supported by evaluable JS or config
11201
- // passed in:
11202
- userObject: ['User objects'],
11203
- undef: undefinedType,
11204
- SpecialRealNumber: SpecialRealNumberSuperType,
11205
- SpecialNumber: SpecialNumberSuperType,
11309
+ /**
11310
+ *
11311
+ */
11312
+ class Types {
11313
+ /**
11314
+ *
11315
+ */
11316
+ constructor () {
11317
+ this.formats = new Formats(); // Todo: Make customizable and test
11206
11318
 
11207
- error: errorType,
11208
- errors: errorsSpecialType,
11319
+ /** @type {CustomValidateAllReferences|undefined} */
11320
+ this.customValidateAllReferences = undefined;
11321
+ /**
11322
+ * @type {{
11323
+ * [key in AvailableType]: Partial<TypeObject>|string[]
11324
+ * }}
11325
+ */
11326
+ this.availableTypes = {
11327
+ null: nullType,
11328
+ true: trueType,
11329
+ false: falseType,
11330
+ number: numberType,
11331
+ bigint: bigintType,
11332
+ string: stringType,
11333
+ arrayReference: arrayReferenceType,
11334
+ objectReference: objectReferenceType,
11335
+ array: arrayType,
11336
+ // Note: We don't do for BooleanObject/NumberObject/StringObject, date,
11337
+ // regexp, as added properties on them are not being cloned (in Chrome
11338
+ // at least)
11339
+ object: objectType,
11340
+ date: dateType,
11209
11341
 
11210
- regexp: regexpType,
11211
- BooleanObject: BooleanObjectType,
11212
- NumberObject: NumberObjectType,
11213
- StringObject: StringObjectType,
11342
+ // This type is only for throwing upon cloning errors:
11343
+ // 'checkDataCloneException'
11344
+ // This type might be supported by evaluable JS or config
11345
+ // passed in:
11346
+ userObject: ['User objects'],
11347
+ undef: undefinedType,
11348
+ SpecialRealNumber: SpecialRealNumberSuperType,
11349
+ SpecialNumber: SpecialNumberSuperType,
11350
+
11351
+ error: errorType,
11352
+ errors: errorsSpecialType,
11353
+
11354
+ regexp: regexpType,
11355
+ BooleanObject: BooleanObjectType,
11356
+ NumberObject: NumberObjectType,
11357
+ StringObject: StringObjectType,
11358
+
11359
+ map: mapType,
11360
+ set: setType,
11361
+
11362
+ file: fileType,
11363
+ filelist: filelistType,
11364
+ blob: blobType,
11365
+ blobHTML: blobHTMLType,
11366
+
11367
+ domexception: domexceptionType,
11368
+ domrect: domrectType,
11369
+ dompoint: dompointType,
11370
+ dommatrix: dommatrixType,
11371
+
11372
+ arraybuffer: {
11373
+ option: ['ArrayBuffer']
11374
+ },
11375
+ arraybufferview: {
11376
+ option: ['ArrayBufferView']
11377
+ },
11378
+ dataview: {
11379
+ option: ['DataView']
11380
+ },
11381
+ imagedata: {
11382
+ option: ['ImageData']
11383
+ },
11384
+ imagebitmap: {
11385
+ option: ['ImageBitmap']
11386
+ },
11214
11387
 
11215
- map: mapType,
11216
- set: setType,
11388
+ // Typed Arrays
11389
+ int8array: {
11390
+ option: ['Int8Array']
11391
+ },
11392
+ uint8array: {
11393
+ option: ['Uint8Array']
11394
+ },
11395
+ uint8clampedarray: {
11396
+ option: ['Uint8ClampedArray']
11397
+ },
11398
+ int16array: {
11399
+ option: ['Int16Array']
11400
+ },
11401
+ uint16array: {
11402
+ option: ['Uint16Array']
11403
+ },
11404
+ int32array: {
11405
+ option: ['Int32Array']
11406
+ },
11407
+ uint32array: {
11408
+ option: ['Uint32Array']
11409
+ },
11410
+ float32array: {
11411
+ option: ['Float32Array']
11412
+ },
11413
+ float64array: {
11414
+ option: ['Float64Array']
11415
+ },
11217
11416
 
11218
- file: fileType,
11219
- filelist: filelistType,
11220
- blob: blobType,
11221
- blobHTML: blobHTMLType,
11417
+ // We're catching this instead of using this
11418
+ // sparseUndefined: sparseUndefinedType,
11222
11419
 
11223
- domexception: domexceptionType,
11224
- domrect: domrectType,
11225
- dompoint: dompointType,
11226
- dommatrix: dommatrixType,
11420
+ ValidDate: {
11421
+ valid: true
11422
+ },
11423
+ /*
11424
+ sparseArrays: {
11425
+ sparse: true
11426
+ },
11427
+ */
11428
+ arrayNonindexKeys: {
11429
+ sparse: true
11430
+ }
11431
+ };
11227
11432
 
11228
- arraybuffer: {
11229
- option: ['ArrayBuffer']
11230
- },
11231
- arraybufferview: {
11232
- option: ['ArrayBufferView']
11233
- },
11234
- dataview: {
11235
- option: ['DataView']
11236
- },
11237
- imagedata: {
11238
- option: ['ImageData']
11239
- },
11240
- imagebitmap: {
11241
- option: ['ImageBitmap']
11242
- },
11433
+ /**
11434
+ * @param {[
11435
+ * copyFrom: AvailableType, copyTo: AvailableType
11436
+ * ][]} replacements
11437
+ * @returns {void}
11438
+ */
11439
+ const copyTypeObjs = (replacements) => {
11440
+ replacements.forEach(([copyFrom, copyTo]) => {
11441
+ Object.assign(this.availableTypes[
11442
+ /** @type {AvailableType} */
11443
+ (copyTo)
11444
+ ], this.availableTypes[
11445
+ /** @type {AvailableType} */
11446
+ (copyFrom)
11447
+ ]);
11448
+ });
11449
+ };
11243
11450
 
11244
- // Typed Arrays
11245
- int8array: {
11246
- option: ['Int8Array']
11247
- },
11248
- uint8array: {
11249
- option: ['Uint8Array']
11250
- },
11251
- uint8clampedarray: {
11252
- option: ['Uint8ClampedArray']
11253
- },
11254
- int16array: {
11255
- option: ['Int16Array']
11256
- },
11257
- uint16array: {
11258
- option: ['Uint16Array']
11259
- },
11260
- int32array: {
11261
- option: ['Int32Array']
11262
- },
11263
- uint32array: {
11264
- option: ['Uint32Array']
11265
- },
11266
- float32array: {
11267
- option: ['Float32Array']
11268
- },
11269
- float64array: {
11270
- option: ['Float64Array']
11271
- },
11451
+ copyTypeObjs(
11452
+ [
11453
+ /** @type {[AvailableType, AvailableType]} */ (
11454
+ ['date', 'ValidDate']
11455
+ ),
11456
+ // 'sparseArrays'
11457
+ /** @type {[AvailableType, AvailableType]} */ (
11458
+ ['array', 'arrayNonindexKeys']
11459
+ )
11460
+ ]
11461
+ );
11272
11462
 
11273
- // We're catching this instead of using this
11274
- // sparseUndefined: sparseUndefinedType,
11463
+ /**
11464
+ * @type {{
11465
+ * [key: string]: {
11466
+ * [key: string]: {type: AvailableType, after: AvailableType}[]
11467
+ * }
11468
+ * }}
11469
+ */
11470
+ this.contexts = {};
11471
+ Object.entries(this.availableTypes).forEach(([typ, typeObj]) => {
11472
+ const type = /** @type {AvailableType} */ (typ);
11473
+ const {stateDependent} = /** @type {TypeObject} */ (typeObj);
11474
+ if (stateDependent) {
11475
+ Object.entries(stateDependent).forEach(([
11476
+ format, formatStateDependent
11477
+ ]) => {
11478
+ if (!this.contexts[format]) {
11479
+ this.contexts[format] = {};
11480
+ }
11481
+ const {contexts, after} = formatStateDependent;
11482
+ contexts.forEach((context) => {
11483
+ if (!this.contexts[format][context]) {
11484
+ this.contexts[format][context] = [];
11485
+ }
11486
+ this.contexts[format][context].push({type, after});
11487
+ });
11488
+ });
11489
+ }
11490
+ });
11491
+ }
11275
11492
 
11276
- ValidDate: {
11277
- valid: true
11278
- },
11279
- /*
11280
- sparseArrays: {
11281
- sparse: true
11282
- },
11283
- */
11284
- arrayNonindexKeys: {
11285
- sparse: true
11493
+ /**
11494
+ * @param {import('./formats.js').AvailableFormat} format
11495
+ * @param {StructuredCloneValue} record
11496
+ * @param {StateObject} stateObj
11497
+ * @returns {Promise<Element>}
11498
+ */
11499
+ async getControlsForFormatAndValue (
11500
+ format, record, stateObj
11501
+ ) {
11502
+ return await this.formats.getControlsForFormatAndValue(
11503
+ this, format, record, stateObj
11504
+ );
11286
11505
  }
11287
- };
11288
11506
 
11289
- /**
11290
- * @typedef {"null"|"true"|"false"|"number"|"bigint"|"string"|"arrayReference"|
11291
- * "objectReference"|"array"|"object"|"date"|"userObject"|"undef"|
11292
- * "SpecialRealNumber"|"SpecialNumber"|"regexp"|"BooleanObject"|
11293
- * "NumberObject"|"StringObject"|"map"|"set"|"file"|"filelist"|"blobHTML"|
11294
- * "arraybuffer"|"arraybufferview"|"dataview"|"imagedata"|"imagebitmap"|
11295
- * "int8array"|"uint8array"|"uint8clampedarray"|"int16array"|"uint16array"|
11296
- * "int32array"|"uint32array"|"float32array"|"float64array"|"ValidDate"|
11297
- * "arrayNonindexKeys"|"error"|"errors"|"blob"|"domexception"|"domrect"|
11298
- * "dompoint"|"dommatrix"} AvailableType
11299
- */
11507
+ /**
11508
+ * @param {import('./formats.js').AvailableFormat} format
11509
+ * @param {string} context
11510
+ * @returns {{
11511
+ * type: AvailableType
11512
+ * after: AvailableType
11513
+ * }[]}
11514
+ */
11515
+ getContextInfo (format, context) {
11516
+ return this.contexts[format][context];
11517
+ }
11300
11518
 
11301
- /**
11302
- * @param {[
11303
- * copyFrom: AvailableType, copyTo: AvailableType
11304
- * ][]} replacements
11305
- * @returns {void}
11306
- */
11307
- const copyTypeObjs = (replacements) => {
11308
- replacements.forEach(([copyFrom, copyTo]) => {
11309
- Object.assign(Types.availableTypes[
11310
- /** @type {AvailableType} */
11311
- (copyTo)
11312
- ], Types.availableTypes[
11519
+ /** @type {GetValueForRoot} */
11520
+ getValueForRoot (root, stateObj, currentPath) {
11521
+ const typeObject = /** @type {TypeObject} */ (
11522
+ this.availableTypes[
11523
+ /** @type {AvailableType} */
11524
+ (Types.getTypeForRoot(root))
11525
+ ]
11526
+ );
11527
+ return typeObject.getValue({
11528
+ root, stateObj, currentPath
11529
+ });
11530
+ }
11531
+
11532
+ /** @type {GetFormControlForRoot} */
11533
+ getFormControlForRoot (root) {
11534
+ const typeObj = /** @type {TypeObject} */ (this.availableTypes[
11313
11535
  /** @type {AvailableType} */
11314
- (copyFrom)
11536
+ (Types.getTypeForRoot(root))
11315
11537
  ]);
11316
- });
11317
- };
11538
+ /* istanbul ignore if -- All have except aliases */
11539
+ if (!typeObj.getInput) {
11540
+ return null;
11541
+ }
11542
+ return typeObj.getInput({root});
11543
+ }
11318
11544
 
11319
- copyTypeObjs(
11320
- [
11321
- /** @type {[AvailableType, AvailableType]} */ (
11322
- ['date', 'ValidDate']
11323
- ),
11324
- // 'sparseArrays'
11325
- /** @type {[AvailableType, AvailableType]} */ (
11326
- ['array', 'arrayNonindexKeys']
11327
- )
11328
- ]
11329
- );
11545
+ /** @type {GetValueFromRootAncestor} */
11546
+ getValueFromRootAncestor (selOrEl, stateObj) {
11547
+ return this.getValueForRoot(
11548
+ /** @type {RootElement} */
11549
+ ($e(selOrEl, 'div[data-type]')),
11550
+ {
11551
+ ...stateObj,
11552
+ formats: this.formats
11553
+ }
11554
+ );
11555
+ }
11330
11556
 
11331
- Types.getTypeForRoot = (root) => {
11332
- return String(root ? root.dataset.type : root);
11333
- };
11557
+ /** @type {GetFormControlFromRootAncestor} */
11558
+ getFormControlFromRootAncestor (selOrEl) {
11559
+ const root = /** @type {RootElement} */ ($e(selOrEl, 'div[data-type]'));
11560
+ if (!root) {
11561
+ return null;
11562
+ }
11563
+ return this.getFormControlForRoot(root);
11564
+ }
11334
11565
 
11335
- /**
11336
- * @typedef {(
11337
- * keypath: string,
11338
- * parentPath: string,
11339
- * arrayOrObjectPropertyName: string,
11340
- * valueType: string
11341
- * ) => StateObject} GetPossibleSchemasForPathAndType
11342
- */
11566
+ /** @type {GetOptionForType} */
11567
+ getOptionForType (type) {
11568
+ /** @type {[string, {value?: AvailableType, title?: string}?]} */
11569
+ const optInfo = [
11570
+ ...(/** @type {TypeObject} */ (
11571
+ this.availableTypes[type]
11572
+ )).option
11573
+ ];
11574
+ optInfo[1] = {value: type, ...(optInfo[1])};
11575
+ return /** @type {[string, {value: AvailableType, title?: string}]} */ (
11576
+ optInfo
11577
+ );
11578
+ }
11343
11579
 
11344
- /**
11345
- * @typedef {{
11346
- * typeNamespace?: string,
11347
- * "readonly"?: boolean,
11348
- * format?: import('./formats.js').AvailableFormat,
11349
- * error?: Error,
11350
- * rootUI?: Element,
11351
- * schema?: string,
11352
- * schemaContent?: object,
11353
- * getPossibleSchemasForPathAndType?: GetPossibleSchemasForPathAndType,
11354
- * paths?: {[currentPath: string]: {
11355
- * referentPath: string,
11356
- * expectArrayReferent: boolean
11357
- * }},
11358
- * handlingReference?: boolean
11359
- * }} StateObject
11360
- */
11361
-
11362
- Types.getValueForRoot = (root, stateObj, currentPath) => {
11363
- const typeObject = /** @type {TypeObject} */ (
11364
- Types.availableTypes[
11365
- /** @type {AvailableType} */
11366
- (Types.getTypeForRoot(root))
11367
- ]
11368
- );
11369
- return typeObject.getValue({
11370
- root, stateObj, currentPath
11371
- });
11372
- };
11580
+ /** @type {GetTypeOptionsForFormatAndState} */
11581
+ getTypeOptionsForFormatAndState (format, parserState) {
11582
+ const typesForFormatAndState = this.formats.getTypesForFormatAndState(
11583
+ this, format, parserState
11584
+ );
11585
+ if (!typesForFormatAndState) {
11586
+ throw new Error('Unexpected type for format and state');
11587
+ }
11588
+ return typesForFormatAndState.map((type) => {
11589
+ return this.getOptionForType(type);
11590
+ });
11591
+ }
11373
11592
 
11374
- Types.getFormControlForRoot = (root) => {
11375
- const typeObj = /** @type {TypeObject} */ (Types.availableTypes[
11376
- /** @type {AvailableType} */
11377
- (Types.getTypeForRoot(root))
11378
- ]);
11379
- /* istanbul ignore if -- All have except aliases */
11380
- if (!typeObj.getInput) {
11381
- return null;
11593
+ /** @type {GetUIForModeAndType} */
11594
+ getUIForModeAndType ({
11595
+ readonly, resultType, typeNamespace, type, topRoot, bringIntoFocus,
11596
+ buildTypeChoices, format, schemaContent, schemaState, value, hasValue,
11597
+ replaced
11598
+ }) {
11599
+ const typeObj = /** @type {TypeObject} */ (this.availableTypes[type]);
11600
+ const arg = hasValue
11601
+ ? {
11602
+ typeNamespace, type, buildTypeChoices,
11603
+ format, schemaContent, schemaState,
11604
+ resultType, topRoot, bringIntoFocus, value,
11605
+ replaced,
11606
+ types: this
11607
+ }
11608
+ : {
11609
+ typeNamespace, type, buildTypeChoices,
11610
+ format, schemaContent, schemaState,
11611
+ resultType, topRoot, bringIntoFocus,
11612
+ replaced,
11613
+ types: this
11614
+ };
11615
+ const root = /** @type {HTMLDivElement} */ (jml(
11616
+ ...(readonly
11617
+ ? typeObj.viewUI(arg)
11618
+ : typeObj.editUI(arg))
11619
+ ));
11620
+ if (!readonly && typeObj.validate) {
11621
+ const formControl = typeObj.getInput({root});
11622
+ formControl.addEventListener('input', () => {
11623
+ this.validate({type, root, topRoot});
11624
+ });
11625
+ }
11626
+ return root;
11382
11627
  }
11383
- return typeObj.getInput({root});
11384
- };
11385
11628
 
11386
- Types.getValueFromRootAncestor = (selOrEl, stateObj) => {
11387
- return Types.getValueForRoot(
11388
- /** @type {RootElement} */
11389
- ($e(selOrEl, 'div[data-type]')),
11390
- stateObj
11391
- );
11392
- };
11629
+ /** @type {ValidateAllReferences} */
11630
+ validateAllReferences ({topRoot}) {
11631
+ /* istanbul ignore if -- Unreachable? */
11632
+ if (!topRoot) {
11633
+ console.log('No references present');
11634
+ return;
11635
+ }
11393
11636
 
11394
- Types.getFormControlFromRootAncestor = (selOrEl) => {
11395
- const root = /** @type {RootElement} */ ($e(selOrEl, 'div[data-type]'));
11396
- if (!root) {
11397
- return null;
11637
+ // Could just hard-code arrayReference and objectReference,
11638
+ // but we'll try to avoid depending on specific types
11639
+ Object.values(this.availableTypes).forEach((typeObj) => {
11640
+ const typeObject = /** @type {TypeObject} */ (typeObj);
11641
+ if (typeObject.validateAll) {
11642
+ typeObject.validateAll({topRoot, types: this});
11643
+ }
11644
+ });
11645
+
11646
+ if (this.customValidateAllReferences) {
11647
+ this.customValidateAllReferences({topRoot});
11648
+ }
11398
11649
  }
11399
- return Types.getFormControlForRoot(root);
11400
- };
11401
11650
 
11402
- Types.getTypesForFormatAndState = (
11403
- format, state
11404
- ) => Formats.availableFormats[format].getTypesForState(state);
11405
-
11406
- Types.getOptionForType = (type) => {
11407
- /** @type {[string, {value?: AvailableType, title?: string}?]} */
11408
- const optInfo = [
11409
- ...(/** @type {TypeObject} */ (
11410
- Types.availableTypes[type]
11411
- )).option
11412
- ];
11413
- optInfo[1] = {value: type, ...(optInfo[1])};
11414
- return /** @type {[string, {value: AvailableType, title?: string}]} */ (
11415
- optInfo
11416
- );
11417
- };
11651
+ /** @type {Validate} */
11652
+ validate ({type, root, topRoot}) {
11653
+ const typeObj = /** @type {TypeObject} */ (this.availableTypes[type]);
11654
+ // Todo (low): We limit for now to input boxes which have `validate`
11655
+ if (typeObj.validate) {
11656
+ const {valid, message} = typeObj.validate({root, topRoot});
11657
+ const formControl = typeObj.getInput({root});
11658
+ formControl.setCustomValidity(
11659
+ valid
11660
+ ? ''
11661
+ /* istanbul ignore next -- Should always have a message */
11662
+ : message || 'Invalid'
11663
+ );
11664
+ formControl.reportValidity();
11665
+ return valid;
11666
+ }
11667
+ return true;
11668
+ }
11418
11669
 
11419
- Types.getTypeOptionsForFormatAndState = (format, parserState) => {
11420
- const typesForFormatAndState = Types.getTypesForFormatAndState(
11421
- format, parserState
11422
- );
11423
- if (!typesForFormatAndState) {
11424
- throw new Error('Unexpected type for format and state');
11670
+ /** @type {SetValue} */
11671
+ setValue ({type, root, value}) {
11672
+ const typeObj = /** @type {TypeObject} */ (this.availableTypes[type]);
11673
+ if (typeObj.setValue) {
11674
+ typeObj.setValue({root, value});
11675
+ }
11425
11676
  }
11426
- return typesForFormatAndState.map((type) => {
11427
- return Types.getOptionForType(type);
11428
- });
11429
- };
11430
11677
 
11431
- Types.getUIForModeAndType = ({
11432
- readonly, resultType, typeNamespace, type, topRoot, bringIntoFocus,
11433
- buildTypeChoices, format, schemaContent, schemaState, value, hasValue,
11434
- replaced
11435
- }) => {
11436
- const typeObj = /** @type {TypeObject} */ (Types.availableTypes[type]);
11437
- const arg = hasValue
11438
- ? {
11439
- typeNamespace, type, buildTypeChoices,
11440
- format, schemaContent, schemaState,
11441
- resultType, topRoot, bringIntoFocus, value,
11442
- replaced
11678
+ // Todo (low): Should really add real parser
11679
+ // Todo (low): Implement `getStringForValue` (e.g., to expose feature for
11680
+ // bookmarking object value currently in view); would not be
11681
+ // enough to iterate DOM to get string URL as we'd also like
11682
+ // the ability to have arbitrary JSON/structuredCloning sent to this
11683
+ // URL from other sites/programs (can currently pass in JSON
11684
+ // format to the URL, but that is still expecting our Router
11685
+ // string syntax)
11686
+
11687
+ /** @type {GetValueForString} */
11688
+ getValueForString (s, {
11689
+ format, state, endMatchTypeObjs = [], firstRun = true,
11690
+ rootHolder = [], parent, parentPath
11691
+ }) {
11692
+ const allowedTypes = this.formats.getTypesForFormatAndState(
11693
+ this, format, state
11694
+ );
11695
+ if (!allowedTypes) {
11696
+ throw new Error('Could not get types for format and state');
11443
11697
  }
11444
- : {
11445
- typeNamespace, type, buildTypeChoices,
11446
- format, schemaContent, schemaState,
11447
- resultType, topRoot, bringIntoFocus,
11448
- replaced
11449
- };
11450
- const root = /** @type {HTMLDivElement} */ (jml(
11451
- ...(readonly
11452
- ? typeObj.viewUI(arg)
11453
- : typeObj.editUI(arg))
11454
- ));
11455
- if (!readonly && typeObj.validate) {
11456
- const formControl = typeObj.getInput({root});
11457
- formControl.addEventListener('input', () => {
11458
- Types.validate({type, root, topRoot});
11698
+ const allowedTypeObjs = Object.entries(
11699
+ this.availableTypes
11700
+ ).filter(([type]) => {
11701
+ return allowedTypes.includes(/** @type {AvailableType} */ (type));
11459
11702
  });
11460
- }
11461
- return root;
11462
- };
11703
+ const allowedTypeObjsVals = allowedTypeObjs.map(([, arr]) => arr);
11463
11704
 
11464
- Types.contexts = {};
11465
- Object.entries(Types.availableTypes).forEach(([typ, typeObj]) => {
11466
- const type = /** @type {AvailableType} */ (typ);
11467
- const {stateDependent} = /** @type {TypeObject} */ (typeObj);
11468
- if (stateDependent) {
11469
- Object.entries(stateDependent).forEach(([format, formatStateDependent]) => {
11470
- if (!Types.contexts[format]) {
11471
- Types.contexts[format] = {};
11705
+ const reduced = allowedTypeObjsVals.reduce((array, typObj) => {
11706
+ const typeObj = /** @type {TypeObject} */ (typObj);
11707
+ let arr = /** @type {string[]} */ (array);
11708
+ if (typeObj.regexEndings) {
11709
+ arr.push(...typeObj.regexEndings);
11710
+ arr = [...new Set(arr)];
11472
11711
  }
11473
- const {contexts, after} = formatStateDependent;
11474
- contexts.forEach((context) => {
11475
- if (!Types.contexts[format][context]) {
11476
- Types.contexts[format][context] = [];
11712
+ return arr;
11713
+ }, []);
11714
+ const endings = '|' + /** @type {string[]} */ (
11715
+ reduced
11716
+ ).map((str) => escapeRegex(str)).join('|');
11717
+
11718
+ /**
11719
+ * @type {RegExpMatchArray|boolean|null}
11720
+ */
11721
+ let match = null;
11722
+ let found = allowedTypeObjs.find(([/* _type */, typObj]) => {
11723
+ const typeObj = /** @type {TypeObject} */ (typObj);
11724
+ let {stringRegex} = typeObj;
11725
+ if (typeof typeObj.stringRegex === 'function') {
11726
+ stringRegex = typeObj.stringRegex(true);
11727
+ }
11728
+ stringRegex = stringRegex
11729
+ // Strip off terminal (dollar sign) when matching substrings
11730
+ ? new RegExp(
11731
+ /** @type {RegExp} */ (
11732
+ stringRegex
11733
+ ).source.slice(0, -1) + '(?=$' + endings + ')',
11734
+ 'u'
11735
+ )
11736
+ : stringRegex;
11737
+ match = Boolean(stringRegex && s) && s.match(
11738
+ /** @type {RegExp} */ (stringRegex)
11739
+ );
11740
+ return match;
11741
+ });
11742
+
11743
+ let beginOnly = false;
11744
+ if (found === undefined) {
11745
+ found = allowedTypeObjs.find(([/* _type */, typObj]) => {
11746
+ const typeObj = /** @type {TypeObject} */ (typObj);
11747
+ const {stringRegexBegin} = typeObj;
11748
+ match = Boolean(stringRegexBegin && s) && s.match(
11749
+ /** @type {RegExp} */ (stringRegexBegin)
11750
+ );
11751
+ if (match) {
11752
+ beginOnly = true;
11753
+ endMatchTypeObjs.push(typeObj);
11477
11754
  }
11478
- Types.contexts[format][context].push({type, after});
11755
+ return match;
11479
11756
  });
11480
- });
11757
+ }
11758
+ let assign = true;
11759
+ if (found !== undefined) {
11760
+ // The `found` is evaluated again, so sets `match` to non-null
11761
+ const mtch = /** @type {RegExpMatchArray} */ (
11762
+ /** @type {unknown} */ (match)
11763
+ );
11764
+ const [content, innerContent] = mtch;
11765
+ let remnant = s.slice(content.length);
11766
+ s = s.slice(0, content.length);
11767
+ // console.log('s0', s, '::', remnant, match);
11768
+ let valObj;
11769
+ try {
11770
+ valObj = /** @type {TypeObject} */ (found[1]).toValue(
11771
+ mtch.groups?.innerContent || innerContent || s, {
11772
+ types: this,
11773
+ format,
11774
+ match,
11775
+ endMatchTypeObjs,
11776
+ remnant,
11777
+ rootHolder,
11778
+ parent,
11779
+ parentPath
11780
+ }
11781
+ );
11782
+ /* istanbul ignore next -- Good regexes should prevent */
11783
+ } catch (e) {
11784
+ /* istanbul ignore next -- Good regexes should prevent */
11785
+ console.log('eee', e);
11786
+ /* istanbul ignore next -- Good regexes should prevent */
11787
+ throw e;
11788
+ }
11789
+ if (valObj.assign === false) {
11790
+ assign = false;
11791
+ }
11792
+ const {value} = valObj;
11793
+ if (valObj.remnant !== undefined) {
11794
+ ({remnant} = valObj);
11795
+ }
11796
+
11797
+ if (beginOnly && endMatchTypeObjs.length) {
11798
+ const endMatch = remnant.match(
11799
+ /** @type {RegExp} */ (/** @type {TypeObject} */ (
11800
+ endMatchTypeObjs.at(-1)
11801
+ ).stringRegexEnd)
11802
+ );
11803
+ if (endMatch) {
11804
+ endMatchTypeObjs.pop(); // Safe now to extract
11805
+ remnant = remnant.slice(endMatch[0].length);
11806
+ }
11807
+ }
11808
+ if (firstRun) {
11809
+ const typeson = new c().register(
11810
+ ae
11811
+ );
11812
+ try {
11813
+ const topRoot = typeson.revive(value);
11814
+ rootHolder.forEach(([type, parent, parentPath, path]) => {
11815
+ const typeObject = this.availableTypes[
11816
+ /** @type {AvailableType} */ (type + 'Reference')
11817
+ ];
11818
+
11819
+ // @ts-expect-error Reference method exists
11820
+ const val = typeObject.resolveReference(path, topRoot);
11821
+ const basicType = getJSONType(val);
11822
+ // eslint-disable-next-line @stylistic/max-len -- Long
11823
+ /* istanbul ignore else -- Successful reference always an object/array? */
11824
+ if (
11825
+ ['array', 'object'].includes(type) && basicType === type
11826
+ ) {
11827
+ /** @type {{[key: string]: any}} */ (
11828
+ parent
11829
+ )[/** @type {string|number} */ (parentPath)] = val;
11830
+ }
11831
+ });
11832
+ return [topRoot, remnant, beginOnly, assign];
11833
+ } catch (err) {
11834
+ console.log('failed Typeson revival', err);
11835
+ }
11836
+ }
11837
+ return [value, remnant, beginOnly, assign];
11838
+ }
11839
+ throw new Error('Bad parsing data');
11481
11840
  }
11482
- });
11483
11841
 
11842
+ /**
11843
+ * @param {AvailableType} type
11844
+ * @returns {Partial<TypeObject>|string[]}
11845
+ */
11846
+ getTypeObject (type) {
11847
+ return this.availableTypes[type];
11848
+ }
11849
+ }
11850
+
11851
+ /** @type {GetTypeForRoot} */
11852
+ Types.getTypeForRoot = (root) => {
11853
+ return String(root ? root.dataset.type : root);
11854
+ };
11855
+
11856
+ /** @type {ValidValuesSet} */
11484
11857
  Types.validValuesSet = ({form, typeNamespace, keySelectClass}) => {
11485
11858
  // If form is hidden, don't list errors by default
11486
11859
  if (!form.offsetParent ||
@@ -11514,57 +11887,6 @@ Types.validValuesSet = ({form, typeNamespace, keySelectClass}) => {
11514
11887
  );
11515
11888
  };
11516
11889
 
11517
- /**
11518
- * Any other possibilities than `div`?
11519
- * @typedef {HTMLDivElement} RootElement
11520
- */
11521
-
11522
- Types.validateAllReferences = ({topRoot}) => {
11523
- /* istanbul ignore if -- Unreachable? */
11524
- if (!topRoot) {
11525
- console.log('No references present');
11526
- return;
11527
- }
11528
-
11529
- // Could just hard-code arrayReference and objectReference,
11530
- // but we'll try to avoid depending on specific types
11531
- Object.values(Types.availableTypes).forEach((typeObj) => {
11532
- const typeObject = /** @type {TypeObject} */ (typeObj);
11533
- if (typeObject.validateAll) {
11534
- typeObject.validateAll({topRoot});
11535
- }
11536
- });
11537
-
11538
- if (Types.customValidateAllReferences) {
11539
- Types.customValidateAllReferences({topRoot});
11540
- }
11541
- };
11542
-
11543
- Types.validate = ({type, root, topRoot}) => {
11544
- const typeObj = /** @type {TypeObject} */ (Types.availableTypes[type]);
11545
- // Todo (low): We limit for now to input boxes which have `validate`
11546
- if (typeObj.validate) {
11547
- const {valid, message} = typeObj.validate({root, topRoot});
11548
- const formControl = typeObj.getInput({root});
11549
- formControl.setCustomValidity(
11550
- valid
11551
- ? ''
11552
- /* istanbul ignore next -- Should always have a message */
11553
- : message || 'Invalid'
11554
- );
11555
- formControl.reportValidity();
11556
- return valid;
11557
- }
11558
- return true;
11559
- };
11560
-
11561
- Types.setValue = ({type, root, value}) => {
11562
- const typeObj = /** @type {TypeObject} */ (Types.availableTypes[type]);
11563
- if (typeObj.setValue) {
11564
- typeObj.setValue({root, value});
11565
- }
11566
- };
11567
-
11568
11890
  /**
11569
11891
  *
11570
11892
  * @param {string} str
@@ -11575,168 +11897,6 @@ function escapeRegex (str) {
11575
11897
  replaceAll(/[.\\+*?^[\]$(){}=!<>|:-]/gu, '\\$&');
11576
11898
  }
11577
11899
 
11578
- // Todo (low): Should really add real parser
11579
- // Todo (low): Implement `getStringForValue` (e.g., to expose feature for
11580
- // bookmarking object value currently in view); would not be
11581
- // enough to iterate DOM to get string URL as we'd also like
11582
- // the ability to have arbitrary JSON/structuredCloning sent to this
11583
- // URL from other sites/programs (can currently pass in JSON
11584
- // format to the URL, but that is still expecting our Router
11585
- // string syntax)
11586
-
11587
- Types.getValueForString = (s, {
11588
- format, state, endMatchTypeObjs = [], firstRun = true,
11589
- rootHolder = [], parent, parentPath
11590
- }) => {
11591
- const allowedTypes = Types.getTypesForFormatAndState(format, state);
11592
- if (!allowedTypes) {
11593
- throw new Error('Could not get types for format and state');
11594
- }
11595
- const allowedTypeObjs = Object.entries(
11596
- Types.availableTypes
11597
- ).filter(([type]) => {
11598
- return allowedTypes.includes(/** @type {AvailableType} */ (type));
11599
- });
11600
- const allowedTypeObjsVals = allowedTypeObjs.map(([, arr]) => arr);
11601
-
11602
- const reduced = allowedTypeObjsVals.reduce((array, typObj) => {
11603
- const typeObj = /** @type {TypeObject} */ (typObj);
11604
- let arr = /** @type {string[]} */ (array);
11605
- if (typeObj.regexEndings) {
11606
- arr.push(...typeObj.regexEndings);
11607
- arr = [...new Set(arr)];
11608
- }
11609
- return arr;
11610
- }, []);
11611
- const endings = '|' + /** @type {string[]} */ (
11612
- reduced
11613
- ).map((str) => escapeRegex(str)).join('|');
11614
-
11615
- /**
11616
- * @type {RegExpMatchArray|boolean|null}
11617
- */
11618
- let match = null;
11619
- let found = allowedTypeObjs.find(([/* _type */, typObj]) => {
11620
- const typeObj = /** @type {TypeObject} */ (typObj);
11621
- let {stringRegex} = typeObj;
11622
- if (typeof typeObj.stringRegex === 'function') {
11623
- stringRegex = typeObj.stringRegex(true);
11624
- }
11625
- stringRegex = stringRegex
11626
- // Strip off terminal (dollar sign) when matching substrings
11627
- ? new RegExp(
11628
- /** @type {RegExp} */ (
11629
- stringRegex
11630
- ).source.slice(0, -1) + '(?=$' + endings + ')',
11631
- 'u'
11632
- )
11633
- : stringRegex;
11634
- match = Boolean(stringRegex && s) && s.match(
11635
- /** @type {RegExp} */ (stringRegex)
11636
- );
11637
- return match;
11638
- });
11639
-
11640
- let beginOnly = false;
11641
- if (found === undefined) {
11642
- found = allowedTypeObjs.find(([/* _type */, typObj]) => {
11643
- const typeObj = /** @type {TypeObject} */ (typObj);
11644
- const {stringRegexBegin} = typeObj;
11645
- match = Boolean(stringRegexBegin && s) && s.match(
11646
- /** @type {RegExp} */ (stringRegexBegin)
11647
- );
11648
- if (match) {
11649
- beginOnly = true;
11650
- endMatchTypeObjs.push(typeObj);
11651
- }
11652
- return match;
11653
- });
11654
- }
11655
- let assign = true;
11656
- if (found !== undefined) {
11657
- // The `found` is evaluated again, so sets `match` to non-null
11658
- const mtch = /** @type {RegExpMatchArray} */ (
11659
- /** @type {unknown} */ (match)
11660
- );
11661
- const [content, innerContent] = mtch;
11662
- let remnant = s.slice(content.length);
11663
- s = s.slice(0, content.length);
11664
- // console.log('s0', s, '::', remnant, match);
11665
- let valObj;
11666
- try {
11667
- valObj = /** @type {TypeObject} */ (found[1]).toValue(
11668
- mtch.groups?.innerContent || innerContent || s, {
11669
- format,
11670
- match,
11671
- endMatchTypeObjs,
11672
- remnant,
11673
- rootHolder,
11674
- parent,
11675
- parentPath
11676
- }
11677
- );
11678
- /* istanbul ignore next -- Good regexes should prevent */
11679
- } catch (e) {
11680
- /* istanbul ignore next -- Good regexes should prevent */
11681
- console.log('eee', e);
11682
- /* istanbul ignore next -- Good regexes should prevent */
11683
- throw e;
11684
- }
11685
- if (valObj.assign === false) {
11686
- assign = false;
11687
- }
11688
- const {value} = valObj;
11689
- if (valObj.remnant !== undefined) {
11690
- ({remnant} = valObj);
11691
- }
11692
-
11693
- if (beginOnly && endMatchTypeObjs.length) {
11694
- const endMatch = remnant.match(
11695
- /** @type {RegExp} */ (/** @type {TypeObject} */ (
11696
- endMatchTypeObjs.at(-1)
11697
- ).stringRegexEnd)
11698
- );
11699
- if (endMatch) {
11700
- endMatchTypeObjs.pop(); // Safe now to extract
11701
- remnant = remnant.slice(endMatch[0].length);
11702
- }
11703
- }
11704
- if (firstRun) {
11705
- const typeson = new c().register(
11706
- ae
11707
- );
11708
- try {
11709
- const topRoot = typeson.revive(value);
11710
- rootHolder.forEach(([type, parent, parentPath, path]) => {
11711
- const typeObject = Types.availableTypes[
11712
- /** @type {AvailableType} */ (type + 'Reference')
11713
- ];
11714
-
11715
- // @ts-expect-error Reference method exists
11716
- const val = typeObject.resolveReference(path, topRoot);
11717
- const basicType = getJSONType(val);
11718
- // eslint-disable-next-line @stylistic/max-len -- Long
11719
- /* istanbul ignore else -- Successful reference always an object/array? */
11720
- if (
11721
- ['array', 'object'].includes(type) && basicType === type
11722
- ) {
11723
- /** @type {{[key: string]: any}} */ (
11724
- parent
11725
- )[/** @type {string|number} */ (parentPath)] = val;
11726
- }
11727
- });
11728
- return [topRoot, remnant, beginOnly, assign];
11729
- } catch (err) {
11730
- console.log('failed Typeson revival', err);
11731
- }
11732
- }
11733
- return [value, remnant, beginOnly, assign];
11734
- }
11735
- throw new Error('Bad parsing data');
11736
- };
11737
-
11738
- var Types$1 = Types;
11739
-
11740
11900
  /**
11741
11901
  * @callback TypeRootGetter
11742
11902
  * @returns {HTMLDivElement|null}
@@ -11823,6 +11983,8 @@ const getFormatAndSchemaChoices = ({schema, hasKeyPath} = {}) => {
11823
11983
  * plain object).
11824
11984
  * @param {string} [cfg.typeNamespace] Used to prevent conflicts with other
11825
11985
  * instances of typeChoices on the page
11986
+ * @param {import('./formats.js').default} cfg.formats
11987
+ * @param {import('./types.js').default} [cfg.types]
11826
11988
  * @returns {{
11827
11989
  * formatChoices: FormatChoices,
11828
11990
  * typesHolder: TypesHolder,
@@ -11831,7 +11993,9 @@ const getFormatAndSchemaChoices = ({schema, hasKeyPath} = {}) => {
11831
11993
  * currentPath: string) => import('./formats.js').StructuredCloneValue,
11832
11994
  * getType: () => string,
11833
11995
  * validValuesSet: () => boolean,
11834
- * setValue: SetValue
11996
+ * setValue: SetValue,
11997
+ * formats: import('./formats.js').default,
11998
+ * types: import('./types.js').default
11835
11999
  * }} The selector for types and the container for them. Both should be
11836
12000
  * added to the page.
11837
12001
  */
@@ -11841,7 +12005,9 @@ function formatAndTypeChoices ({
11841
12005
  hasValue,
11842
12006
  singleValue,
11843
12007
  hasKeyPath,
11844
- typeNamespace
12008
+ typeNamespace,
12009
+ formats = new Formats(),
12010
+ types = new Types()
11845
12011
  }) {
11846
12012
  const format = 'structuredCloning';
11847
12013
  const formatChoices = /** @type {FormatChoices} */ (jml('select', {
@@ -11888,6 +12054,8 @@ function formatAndTypeChoices ({
11888
12054
  ),
11889
12055
  // resultType: 'both',
11890
12056
  format: this.value,
12057
+ formats,
12058
+ types,
11891
12059
  typeNamespace,
11892
12060
  requireObject: hasKeyPath,
11893
12061
  objectHasValue: hasValue,
@@ -11939,6 +12107,8 @@ function formatAndTypeChoices ({
11939
12107
  // resultType: 'both',
11940
12108
  topRoot: /** @type {HTMLDivElement} */ ($e(typesHolder, 'div[data-type]')),
11941
12109
  format,
12110
+ formats,
12111
+ types,
11942
12112
  typeNamespace,
11943
12113
  requireObject: hasKeyPath,
11944
12114
  objectHasValue: hasValue,
@@ -11947,6 +12117,8 @@ function formatAndTypeChoices ({
11947
12117
  }).domArray}, typesHolder);
11948
12118
 
11949
12119
  return {
12120
+ formats,
12121
+ types,
11950
12122
  formatChoices,
11951
12123
  typesHolder,
11952
12124
  // Easier for Jamilih
@@ -11965,11 +12137,13 @@ function formatAndTypeChoices ({
11965
12137
  const root = /** @type {HTMLDivElement & {$getTypeRoot: TypeRootGetter}} */ (
11966
12138
  typesHolder
11967
12139
  ).$getTypeRoot();
11968
- return Types$1.getValueForRoot(/** @type {HTMLDivElement} */ (root), {
12140
+ return types.getValueForRoot(/** @type {HTMLDivElement} */ (root), {
11969
12141
  typeNamespace,
11970
12142
  format: /** @type {import('./formats.js').AvailableFormat} */ (
11971
12143
  formatChoices.value
11972
12144
  ),
12145
+ formats,
12146
+ types,
11973
12147
  ...stateObj
11974
12148
  }, currentPath);
11975
12149
  },
@@ -11982,7 +12156,7 @@ function formatAndTypeChoices ({
11982
12156
  const root = /** @type {HTMLDivElement & {$getTypeRoot: TypeRootGetter}} */ (
11983
12157
  typesHolder
11984
12158
  ).$getTypeRoot();
11985
- return Types$1.getTypeForRoot(/** @type {HTMLDivElement} */ (root));
12159
+ return Types.getTypeForRoot(/** @type {HTMLDivElement} */ (root));
11986
12160
  },
11987
12161
 
11988
12162
  /**
@@ -11996,13 +12170,14 @@ function formatAndTypeChoices ({
11996
12170
  const form = /** @type {HTMLFormElement} */ (
11997
12171
  /** @type {Element} */ (root).closest('form')
11998
12172
  );
11999
- return Types$1.validValuesSet({form, typeNamespace});
12173
+ return Types.validValuesSet({form, typeNamespace});
12000
12174
  },
12001
12175
 
12002
12176
  /** @type {SetValue} */
12003
12177
  async setValue (value, stateObj) {
12004
12178
  const rootEditUI = /** @type {HTMLDivElement} */ (
12005
- await getControlsForFormatAndValue(
12179
+ await formats.getControlsForFormatAndValue(
12180
+ types,
12006
12181
  /** @type {import('./formats.js').AvailableFormat} */ (
12007
12182
  formatChoices.value
12008
12183
  ),
@@ -12010,7 +12185,7 @@ function formatAndTypeChoices ({
12010
12185
  stateObj
12011
12186
  )
12012
12187
  );
12013
- const type = Types$1.getTypeForRoot(rootEditUI);
12188
+ const type = Types.getTypeForRoot(rootEditUI);
12014
12189
  // eslint-disable-next-line @stylistic/max-len -- Long
12015
12190
  const sel = /** @type {HTMLDivElement & {$getTypeSelect: TypeSelectGetter}} */ (
12016
12191
  typesHolder
@@ -12023,4 +12198,4 @@ function formatAndTypeChoices ({
12023
12198
  };
12024
12199
  }
12025
12200
 
12026
- export { Formats, Types$1 as Types, formatAndTypeChoices, getControlsForFormatAndValue, getFormatAndSchemaChoices, buildTypeChoices as typeChoices };
12201
+ export { Formats, Types, formatAndTypeChoices, getFormatAndSchemaChoices, buildTypeChoices as typeChoices };