@es-joy/jsoe 0.5.1 → 0.6.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 (80) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.cjs +2 -0
  3. package/.nyc_output/out.json +121296 -0
  4. package/CHANGES.md +10 -0
  5. package/README.md +47 -21
  6. package/cypress.config.ts +13 -0
  7. package/demo/index-instrumented.html +29 -0
  8. package/demo/index.html +10 -1
  9. package/demo/index.js +482 -126
  10. package/dist/formatAndTypeChoices.d.ts +2 -2
  11. package/dist/formatAndTypeChoices.d.ts.map +1 -1
  12. package/dist/formats/json.d.ts.map +1 -1
  13. package/dist/formats/structuredCloning.d.ts +2 -1
  14. package/dist/formats/structuredCloning.d.ts.map +1 -1
  15. package/dist/fundamentalTypes/arrayType.d.ts +6 -37
  16. package/dist/fundamentalTypes/arrayType.d.ts.map +1 -1
  17. package/dist/fundamentalTypes/dateType.d.ts.map +1 -1
  18. package/dist/fundamentalTypes/errorType.d.ts +6 -0
  19. package/dist/fundamentalTypes/errorType.d.ts.map +1 -0
  20. package/dist/fundamentalTypes/mapType.d.ts +6 -0
  21. package/dist/fundamentalTypes/mapType.d.ts.map +1 -0
  22. package/dist/fundamentalTypes/nullType.d.ts.map +1 -1
  23. package/dist/fundamentalTypes/objectReferenceType.d.ts.map +1 -1
  24. package/dist/fundamentalTypes/setType.d.ts +6 -0
  25. package/dist/fundamentalTypes/setType.d.ts.map +1 -0
  26. package/dist/fundamentalTypes/sparseUndefinedType.d.ts.map +1 -1
  27. package/dist/fundamentalTypes/undefinedType.d.ts.map +1 -1
  28. package/dist/subTypes/blobHTMLType.d.ts +12 -6
  29. package/dist/subTypes/blobHTMLType.d.ts.map +1 -1
  30. package/dist/superTypes/SpecialNumberType.d.ts +14 -0
  31. package/dist/superTypes/SpecialNumberType.d.ts.map +1 -0
  32. package/dist/superTypes/SpecialRealNumberType.d.ts +6 -0
  33. package/dist/superTypes/SpecialRealNumberType.d.ts.map +1 -0
  34. package/dist/superTypes/errorsSpecialType.d.ts +8 -0
  35. package/dist/superTypes/errorsSpecialType.d.ts.map +1 -0
  36. package/dist/typeChoices.d.ts +4 -2
  37. package/dist/typeChoices.d.ts.map +1 -1
  38. package/dist/types.d.ts +83 -231
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/utils/dialogs.d.ts +22 -22
  41. package/dist/utils/dialogs.d.ts.map +1 -1
  42. package/dist/utils/templateUtils.d.ts +6 -6
  43. package/dist/utils/templateUtils.d.ts.map +1 -1
  44. package/dist/vendor-imports.d.ts +1 -1
  45. package/package.json +39 -34
  46. package/rollup.config.js +14 -2
  47. package/server.js +1 -1
  48. package/src/formatAndTypeChoices.js +9 -9
  49. package/src/formats/indexedDBKey.js +2 -2
  50. package/src/formats/json.js +1 -2
  51. package/src/formats/structuredCloning.js +101 -29
  52. package/src/formats.js +2 -2
  53. package/src/fundamentalTypes/arrayReferenceType.js +3 -3
  54. package/src/fundamentalTypes/arrayType.js +390 -87
  55. package/src/fundamentalTypes/dateType.js +4 -6
  56. package/src/fundamentalTypes/errorType.js +416 -0
  57. package/src/fundamentalTypes/mapType.js +34 -0
  58. package/src/fundamentalTypes/nullType.js +1 -2
  59. package/src/fundamentalTypes/objectReferenceType.js +2 -1
  60. package/src/fundamentalTypes/regexpType.js +2 -2
  61. package/src/fundamentalTypes/setType.js +34 -0
  62. package/src/fundamentalTypes/sparseUndefinedType.js +2 -3
  63. package/src/fundamentalTypes/undefinedType.js +2 -3
  64. package/src/jsoe.css +3 -0
  65. package/src/subTypes/blobHTMLType.js +37 -33
  66. package/src/subTypes/falseType.js +2 -2
  67. package/src/subTypes/trueType.js +2 -2
  68. package/src/superTypes/{SpecialNumberSuperType.js → SpecialNumberType.js} +1 -1
  69. package/src/superTypes/{SpecialRealNumberSuperType.js → SpecialRealNumberType.js} +3 -2
  70. package/src/superTypes/errorsSpecialType.js +768 -0
  71. package/src/typeChoices.js +27 -20
  72. package/src/types.js +71 -39
  73. package/src/utils/dialogs.js +11 -12
  74. package/src/utils/jsonPointer.js +3 -3
  75. package/src/utils/templateUtils.js +4 -4
  76. package/src/vendor-imports.js +2 -2
  77. package/tsconfig.json +1 -1
  78. package/typings/CypressPlugins.d.ts +10 -0
  79. package/vendor/jamilih/dist/jml-es.js +126 -106
  80. package/vendor/typeson-registry/dist/index.js +188 -44
@@ -18,6 +18,14 @@ import {
18
18
  /**
19
19
  * @typedef {() => HTMLInputElement[]} InputsExceedingLength
20
20
  */
21
+ /**
22
+ * @typedef {() => (HTMLSelectElement & {
23
+ * $getValue: import('../typeChoices.js').GetValue
24
+ * })[]} GetMapKeySelects
25
+ */
26
+ /**
27
+ * @typedef {() => void} ValidateMapKey
28
+ */
21
29
  /**
22
30
  * @callback GetPropertyInputs
23
31
  * @returns {HTMLInputElement[]}
@@ -31,7 +39,8 @@ import {
31
39
  * @typedef {() => HTMLDivElement & {
32
40
  * $inputsExceedingLength: InputsExceedingLength,
33
41
  * $getPropertyInputs: GetPropertyInputs,
34
- * $redrawMoveArrows: RedrawMoveArrows
42
+ * $redrawMoveArrows: RedrawMoveArrows,
43
+ * $getMapKeySelects: GetMapKeySelects
35
44
  * }} GetArrayItems
36
45
  */
37
46
  /**
@@ -48,6 +57,26 @@ import {
48
57
  * @typedef {() => HTMLInputElement|undefined} GetPropertyInput
49
58
  */
50
59
 
60
+ /**
61
+ * @typedef {any} AnyValue
62
+ */
63
+
64
+ /**
65
+ * Algorithm used for checking key identity in `Map`'s.
66
+ * @param {AnyValue} x
67
+ * @param {AnyValue} y
68
+ * @returns {boolean}
69
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#same-value-zero_equality
70
+ */
71
+ function sameValueZero (x, y) {
72
+ if (typeof x === 'number' && typeof y === 'number') {
73
+ // x and y are equal (may be -0 and 0) or they are both NaN
74
+ // eslint-disable-next-line no-self-compare -- Not pointless with -0
75
+ return x === y || (x !== x && y !== y);
76
+ }
77
+ return x === y;
78
+ }
79
+
51
80
  /**
52
81
  * @type {import('../types.js').TypeObject & {sparse?: boolean|undefined}}
53
82
  */
@@ -87,7 +116,11 @@ const arrayType = {
87
116
  const checkEnd = (beginOnly) => {
88
117
  if (beginOnly && endMatchTypeObjs.length) {
89
118
  const endMatch = stringVal.match(
90
- /** @type {RegExp} */ (endMatchTypeObjs.slice(-1)[0].stringRegexEnd)
119
+ /** @type {RegExp} */ (
120
+ /** @type {import('../types.js').TypeObject} */ (
121
+ endMatchTypeObjs.at(-1)
122
+ ).stringRegexEnd
123
+ )
91
124
  );
92
125
  if (endMatch) {
93
126
  endMatchTypeObjs.pop(); // Safe now to extract
@@ -139,7 +172,14 @@ const arrayType = {
139
172
  if (sparse && idx && idx > retObj.length - 1) {
140
173
  retObj.length = idx + 1;
141
174
  }
142
- return {value: retObj, remnant: stringVal};
175
+ return {
176
+ value: this.set && Array.isArray(retObj)
177
+ ? new Set(retObj)
178
+ : this.map
179
+ ? new Map(/** @type {Array<any>} */ (retObj))
180
+ : retObj,
181
+ remnant: stringVal
182
+ };
143
183
  }
144
184
 
145
185
  /**
@@ -178,7 +218,7 @@ const arrayType = {
178
218
  }
179
219
  // console.log('vvv', stringVal, beginOnly, v);
180
220
  if (assign) {
181
- // eslint-disable-next-line max-len -- Long
221
+ // eslint-disable-next-line @stylistic/max-len -- Long
182
222
  /** @type {{[key: string]: import('../formats.js').StructuredCloneValue}} */ (
183
223
  retObj
184
224
  )[pr] = v;
@@ -193,8 +233,13 @@ const arrayType = {
193
233
  },
194
234
  getValue ({root, stateObj = {}, currentPath = ''}) {
195
235
  const top = currentPath === '';
196
- const arrayItems = /** @type {HTMLElement} */ ($e(root, '.arrayItems'));
197
- const fieldsets = arrayItems.children;
236
+ const arrayItems =
237
+ /**
238
+ * @type {HTMLElement & {
239
+ * $getMapKeySelects: GetMapKeySelects
240
+ * }}
241
+ */ ($e(root, '.arrayItems'));
242
+ const fieldsets = [...arrayItems.children];
198
243
 
199
244
  /**
200
245
  * @type {({[key: string]: any})|any[]}
@@ -216,7 +261,7 @@ const arrayType = {
216
261
  /**
217
262
  * @param {HTMLDivElement} root
218
263
  * @param {string} currentPathPart
219
- * @returns {[boolean, boolean?]}
264
+ * @returns {[boolean, any?]}
220
265
  */
221
266
  const getValOrRef = (root, currentPathPart) => {
222
267
  const value = Types.getValueForRoot(
@@ -229,26 +274,33 @@ const arrayType = {
229
274
  }
230
275
  return [true, value];
231
276
  };
232
- [...fieldsets].forEach((fieldset) => {
233
- const legend = fieldset.firstElementChild;
234
- const propVal = getPropertyValueFromLegend(
235
- /** @type {HTMLLegendElement} */ (legend)
277
+ fieldsets.forEach((fieldset) => {
278
+ const legend = /** @type {HTMLLegendElement} */ (
279
+ fieldset.firstElementChild
236
280
  );
281
+ const propVal = getPropertyValueFromLegend(legend);
237
282
  const root = /** @type {HTMLDivElement} */ (
238
- $e(/** @type {HTMLElement} */ (fieldset), 'div[data-type]')
283
+ DOM.filterChildElements(
284
+ /** @type {HTMLFieldSetElement} */
285
+ (fieldset),
286
+ [
287
+ '.typeContainer',
288
+ 'div[data-type]'
289
+ ]
290
+ )[0]
239
291
  );
240
292
  /* istanbul ignore if -- Should err first? */
241
293
  if (!root) {
242
294
  return;
243
295
  }
244
296
 
245
- const objectProperty = $e(/** @type {HTMLElement} */ (legend), 'input');
297
+ const objectProperty = DOM.filterChildElements(legend, ['input']);
246
298
  const [isVal, value] = getValOrRef(
247
299
  root,
248
300
  propVal
249
301
  );
250
302
  if (isVal) {
251
- if (objectProperty) {
303
+ if (objectProperty.length) {
252
304
  /** @type {{[key: string]: any}} */ (ret)[propVal] = value;
253
305
  } else {
254
306
  ret.push(value);
@@ -264,11 +316,12 @@ const arrayType = {
264
316
  path: referentPath,
265
317
  obj: ret
266
318
  });
267
-
268
319
  const referencePathJsonPtr = getJSONPointerParts(referencePath);
269
320
  const referenceFinalPathPart = referencePathJsonPtr.pop();
270
321
  const referenceParentObj = referencePathJsonPtr.reduce(
271
- (obj, pathPart) => reduceJSONPointerParts(obj, pathPart),
322
+ (obj, pathPart) => {
323
+ return reduceJSONPointerParts(obj, pathPart);
324
+ },
272
325
  ret
273
326
  );
274
327
  /** @type {{[key: string]: any}} */ (referenceParentObj)[
@@ -277,7 +330,14 @@ const arrayType = {
277
330
  }
278
331
  );
279
332
  }
280
- return ret;
333
+ return this.set && Array.isArray(ret)
334
+ ? new Set(ret)
335
+ : this.map
336
+ ? new Map(arrayItems.$getMapKeySelects().map((select, idx) => {
337
+ const key = select.$getValue();
338
+ return [key, /** @type {any[]} */ (ret)[idx]];
339
+ }))
340
+ : ret;
281
341
  },
282
342
 
283
343
  // Try to keep in sync with basic structure of `editUI`
@@ -285,11 +345,13 @@ const arrayType = {
285
345
  // const {sparse} = this;
286
346
  let itemIndex = -1;
287
347
 
348
+ const parentType = type;
349
+
288
350
  /**
289
351
  * @param {{
290
352
  * itemIndex: number,
291
353
  * typeNamespace?: string,
292
- * propName: string
354
+ * propName?: string
293
355
  * }} cfg
294
356
  * @returns {import('jamilih').JamilihArray}
295
357
  */
@@ -308,7 +370,7 @@ const arrayType = {
308
370
  }, [
309
371
  propName !== undefined
310
372
  ? propName
311
- // eslint-disable-next-line max-len -- Long
373
+ // eslint-disable-next-line @stylistic/max-len -- Long
312
374
  /* istanbul ignore next -- Won't reach here as typeson will always give keypath? */
313
375
  : itemIndex
314
376
  ]]
@@ -321,7 +383,7 @@ const arrayType = {
321
383
  $custom: {
322
384
  /**
323
385
  * @param {{
324
- * propName: name,
386
+ * propName: string,
325
387
  * type: import('../types.js').AvailableType,
326
388
  * value: import('../formats.js').StructuredCloneValue,
327
389
  * bringIntoFocus: boolean,
@@ -335,6 +397,37 @@ const arrayType = {
335
397
  propName, type, value, bringIntoFocus,
336
398
  schemaContent, schemaState
337
399
  }) {
400
+ if (parentType === 'map') {
401
+ const root = Types.getUIForModeAndType({
402
+ resultType,
403
+ readonly: true,
404
+ typeNamespace, type, topRoot,
405
+ bringIntoFocus,
406
+ format, schemaContent, schemaState,
407
+ value,
408
+ hasValue: true // type === 'sparseArrays' && value
409
+ });
410
+ if (propName === '0') {
411
+ const fieldset = this.$addMapElement();
412
+ this._lastFieldset = fieldset;
413
+ const keyFieldset = /** @type {HTMLFieldSetElement} */ (jml(
414
+ 'fieldset', [
415
+ ['legend', ['Key']]
416
+ ], fieldset
417
+ ));
418
+ jml(root, keyFieldset);
419
+ } else { // propName === '1'
420
+ const valueFieldset = /** @type {HTMLFieldSetElement} */ (jml(
421
+ 'fieldset', [
422
+ ['legend', ['Value']]
423
+ ], this._lastFieldset
424
+ ));
425
+ this._lastFieldset = null;
426
+ jml(root, valueFieldset);
427
+ }
428
+ return root;
429
+ }
430
+
338
431
  const fieldset = this.$addArrayElement({propName});
339
432
  const root = Types.getUIForModeAndType({
340
433
  resultType,
@@ -348,6 +441,27 @@ const arrayType = {
348
441
  jml(root, fieldset);
349
442
  return root;
350
443
  },
444
+ /**
445
+ * @returns {HTMLFieldSetElement}
446
+ */
447
+ $addMapElement () {
448
+ itemIndex++;
449
+ const arrayItems = this.$getArrayItems();
450
+ // const className = `${type}Item`;
451
+ const fieldset = /** @type {HTMLFieldSetElement} */ (
452
+ jml('fieldset', [
453
+ buildLegend({
454
+ // className,
455
+ // type,
456
+ // arrayItems,
457
+ itemIndex,
458
+ typeNamespace,
459
+ propName: undefined
460
+ })
461
+ ], arrayItems)
462
+ );
463
+ return fieldset;
464
+ },
351
465
  /**
352
466
  * @param {{propName: string}} cfg
353
467
  * @returns {HTMLFieldSetElement}
@@ -378,9 +492,7 @@ const arrayType = {
378
492
  DOM.initialCaps(/** @type {import('../types.js').AvailableType} */ (
379
493
  type
380
494
  )).replace(/s$/u, ''), nbsp.repeat(2),
381
- // Why is this necessary
382
- /** @type {import('jamilih').JamilihArray} */
383
- (['button', {$on: {click (/** @type {Event} */ e) {
495
+ ['button', {$on: {click (/** @type {Event} */ e) {
384
496
  e.preventDefault();
385
497
  const {target} = e;
386
498
  const arrayContents = /** @type {HTMLDivElement} */ ($e(
@@ -392,13 +504,19 @@ const arrayType = {
392
504
  /** @type {HTMLElement} */ (
393
505
  target
394
506
  ).textContent = arrayContents.hidden ? '+' : '-';
395
- }}}, ['-']]),
507
+ }}}, ['-']],
396
508
  ['div', {class: 'arrayContents'}, [
397
509
  this.array
398
510
  ? ['div', [
399
- 'Array length: ',
511
+ type === 'set'
512
+ ? 'Set size: '
513
+ : type === 'map'
514
+ ? 'Map size: '
515
+ : 'Array length: ',
400
516
  ['span', [
401
- (value && value.length) || 0
517
+ (value && (type === 'set' || type === 'map')
518
+ ? value.size
519
+ : value.length) || 0
402
520
  ]]
403
521
  ]]
404
522
  : '',
@@ -418,13 +536,17 @@ const arrayType = {
418
536
  // an array (or object if called by that method) and we handle the
419
537
  // population of the array in the callback
420
538
  editUI ({
421
- typeNamespace, buildTypeChoices, format, resultType,
422
- type, topRoot, value, bringIntoFocus = true
539
+ typeNamespace, buildTypeChoices, format, // resultType,
540
+ type, arrayState, topRoot, value, bringIntoFocus = true
423
541
  }) {
424
542
  const {sparse} = this;
543
+ // eslint-disable-next-line consistent-this
544
+ const parentTypeObject = this;
425
545
  const itemAdjust = type === 'object' ? 1 : 0;
426
546
  let itemIndex = itemAdjust - 1;
427
- const editableProperties = type !== 'array';
547
+ const editableProperties = type !== 'array' &&
548
+ type !== 'set' && type !== 'map';
549
+ const mapProperties = type === 'map';
428
550
 
429
551
  /**
430
552
  * @param {HTMLInputElement} input
@@ -471,14 +593,14 @@ const arrayType = {
471
593
  swapCountElem.textContent = base;
472
594
  baseCountElem.textContent = swap;
473
595
  } else {
474
- // eslint-disable-next-line max-len -- Long
596
+ // eslint-disable-next-line @stylistic/max-len -- Long
475
597
  const swapCountElem = /** @type {HTMLInputElement & {$parseInt: ParseInt}} */
476
598
  (/**
477
599
  * @type {HTMLFieldSetElement & {$getPropertyInput: GetPropertyInput}}
478
600
  */ (
479
601
  group
480
602
  ).$getPropertyInput());
481
- // eslint-disable-next-line max-len -- Long
603
+ // eslint-disable-next-line @stylistic/max-len -- Long
482
604
  const baseCountElem = /** @type {HTMLInputElement & {$parseInt: ParseInt}} */
483
605
  (/**
484
606
  * @type {HTMLFieldSetElement & {$getPropertyInput: GetPropertyInput}}
@@ -602,6 +724,7 @@ const arrayType = {
602
724
  * $inputsExceedingLength: InputsExceedingLength,
603
725
  * $getPropertyInputs: GetPropertyInputs,
604
726
  * $redrawMoveArrows: RedrawMoveArrows
727
+ * $getMapKeySelects: GetMapKeySelects
605
728
  * }} ArrayItems
606
729
  */
607
730
 
@@ -652,12 +775,82 @@ const arrayType = {
652
775
  'input'
653
776
  )
654
777
  );
655
- const highest = inputsExceedingLength.map(
778
+ const highest = /** @type {number} */ (inputsExceedingLength.map(
656
779
  (i) => Number.parseInt(i.value)
657
- ).sort().slice(-1)[0];
780
+ ).sort().at(-1));
658
781
  arrLengthInput.value = String(highest + 1);
659
782
  return this.$validateLength(true);
660
783
  };
784
+ if (mapProperties) {
785
+ const keyTypeSelection =
786
+ /** @type {import('../typeChoices.js').BuildTypeChoices} */ (
787
+ buildTypeChoices
788
+ )({
789
+ format: 'structuredCloning',
790
+ typeNamespace: 'key-type-choices-only'
791
+ });
792
+ return ['legend', [
793
+ 'Key ',
794
+ ['span', {
795
+ dataset: {prop: 'true'},
796
+ className
797
+ }, [String(itemIndex)]],
798
+ ':',
799
+ nbsp.repeat(2),
800
+
801
+ ['span', {
802
+ class: 'mapKey',
803
+ $on: {
804
+ change: [function () {
805
+ /**
806
+ * @type {HTMLSpanElement & {
807
+ * $validateMapKey: ValidateMapKey
808
+ * }}
809
+ */
810
+ (this).$validateMapKey();
811
+
812
+ // Needed?
813
+ Types.validateAllReferences({
814
+ // eslint-disable-next-line object-shorthand -- TS
815
+ topRoot: /** @type {HTMLDivElement} */ (topRoot)
816
+ });
817
+ }, true]
818
+ },
819
+ $custom: {
820
+ /** @type {ValidateMapKey} */
821
+ $validateMapKey () {
822
+ const selects = arrayItems.$getMapKeySelects();
823
+
824
+ setTimeout(() => {
825
+ const values = selects.map((select) => {
826
+ return select.$getValue();
827
+ });
828
+
829
+ const dupeIndex = values.findLastIndex((value, idx) => {
830
+ return values.some((val, index) => {
831
+ return idx !== index && sameValueZero(value, val);
832
+ });
833
+ });
834
+
835
+ if (dupeIndex === -1) {
836
+ return;
837
+ }
838
+ const select = selects[dupeIndex];
839
+ /* istanbul ignore if -- Should exist */
840
+ if (!select) {
841
+ return;
842
+ }
843
+
844
+ select.setCustomValidity(
845
+ 'Duplicate Map key value'
846
+ );
847
+ select.reportValidity();
848
+ });
849
+ }
850
+ }
851
+ }, keyTypeSelection.domArray]
852
+ ]];
853
+ }
661
854
  if (editableProperties) {
662
855
  return /** @type {import('jamilih').JamilihArray} */ (['legend', [
663
856
  sparse
@@ -752,7 +945,7 @@ const arrayType = {
752
945
  el.closest('fieldset')
753
946
  );
754
947
  const thisFieldset = getFieldset(this);
755
- // eslint-disable-next-line max-len -- Long
948
+ // eslint-disable-next-line @stylistic/max-len -- Long
756
949
  const intVal = /** @type {HTMLInputElement & {$parseInt: ParseInt}} */ (
757
950
  this
758
951
  ).$parseInt();
@@ -825,7 +1018,7 @@ const arrayType = {
825
1018
  const method = latest ? 'after' : 'before';
826
1019
  // Ensure move *after* splice that will occur after this
827
1020
  setTimeout(() => {
828
- /* c8 ignore next 3 */
1021
+ /* istanbul ignore if */
829
1022
  if (!nearest) {
830
1023
  return;
831
1024
  }
@@ -879,7 +1072,7 @@ const arrayType = {
879
1072
  */
880
1073
  const decrementItemIndex = (arrayItems) => {
881
1074
  if (sparse) {
882
- // eslint-disable-next-line max-len -- Long
1075
+ // eslint-disable-next-line @stylistic/max-len -- Long
883
1076
  itemIndex = /** @type {(HTMLInputElement & {$parseInt: ParseInt})[]} */ (
884
1077
  arrayItems.$getPropertyInputs()
885
1078
  ).reduce(
@@ -908,7 +1101,8 @@ const arrayType = {
908
1101
  * @type {AddArrayElement}
909
1102
  * @this {HTMLButtonElement & {
910
1103
  * $getArrayItems: GetArrayItems,
911
- * $addArrayElement: AddArrayElement
1104
+ * $addArrayElement: AddArrayElement,
1105
+ * $getMapKeySelects?: GetMapKeySelects
912
1106
  * }}
913
1107
  */
914
1108
  const $addArrayElement = function ({propName, splice, alwaysFocus}) {
@@ -923,9 +1117,9 @@ const arrayType = {
923
1117
  } else if (typeof splice === 'number') {
924
1118
  itemIndex = splice + 1;
925
1119
  } else if (typeof splice !== 'string') {
926
- // eslint-disable-next-line max-len -- Long
1120
+ // eslint-disable-next-line @stylistic/max-len -- Long
927
1121
  itemIndex = /** @type {(HTMLInputElement & {$parseInt: ParseInt})[]} */ (
928
- // eslint-disable-next-line max-len -- Long
1122
+ // eslint-disable-next-line @stylistic/max-len -- Long
929
1123
  /** @type {HTMLDivElement & {$getPropertyInputs: GetPropertyInputs}} */
930
1124
  (arrayItems).$getPropertyInputs()
931
1125
  ).reduce(
@@ -941,27 +1135,84 @@ const arrayType = {
941
1135
  }
942
1136
  const thisButton = this; // eslint-disable-line consistent-this
943
1137
  const className = `${type}Item`;
944
- const fieldset = /** @type {HTMLFieldSetElement} */ (jml('fieldset', {
945
- $custom: {
946
- /** @type {GetPropertyInput} */
947
- $getPropertyInput () {
948
- return /** @type {HTMLInputElement} */ (DOM.filterChildElements(
949
- /** @type {HTMLFieldSetElement} */ (this),
950
- ['legend', `input.propertyName-${typeNamespace}`]
951
- ).pop());
952
- }
953
- }
954
- }, [
955
- buildLegend({
956
- className,
957
- splice,
958
- itemIndex,
959
- // type,
960
- typeNamespace,
961
- arrayItems,
962
- propName
963
- })
964
- ], arrayItems));
1138
+ const fieldset =
1139
+ /** @type {HTMLFieldSetElement} */ (
1140
+ jml('fieldset',
1141
+ {
1142
+ $on: {
1143
+ change: [() => {
1144
+ if (type !== 'set') {
1145
+ return;
1146
+ }
1147
+ setTimeout(() => {
1148
+ const root = div;
1149
+ const values = /** @type {any[]} */ (
1150
+ parentTypeObject.getValue.call({
1151
+ ...parentTypeObject,
1152
+ set: false
1153
+ }, {root})
1154
+ );
1155
+ // console.log('values', values);
1156
+
1157
+ const dupeIndex = values.findLastIndex((value, idx) => {
1158
+ return values.some((val, index) => {
1159
+ return idx !== index && value === val;
1160
+ });
1161
+ });
1162
+
1163
+ if (dupeIndex === -1) {
1164
+ return;
1165
+ }
1166
+ const fieldsets = arrayItems.children;
1167
+ const controls = [...fieldsets].map((fieldset) => {
1168
+ const root = /** @type {HTMLDivElement} */ (
1169
+ $e(fieldset, 'div[data-type]')
1170
+ );
1171
+ /* istanbul ignore if -- Should err first? */
1172
+ if (!root) {
1173
+ return null;
1174
+ }
1175
+ return Types.getFormControlForRoot(root);
1176
+ });
1177
+
1178
+ const control = controls[dupeIndex];
1179
+ /* istanbul ignore if -- Should exist */
1180
+ if (!control) {
1181
+ return;
1182
+ }
1183
+
1184
+ control.setCustomValidity(
1185
+ 'Duplicate Set value'
1186
+ );
1187
+ control.reportValidity();
1188
+ });
1189
+ }, true]
1190
+ },
1191
+ $custom: {
1192
+ /** @type {GetPropertyInput} */
1193
+ $getPropertyInput () {
1194
+ return /** @type {HTMLInputElement} */ (
1195
+ DOM.filterChildElements(
1196
+ /** @type {HTMLFieldSetElement} */ (this),
1197
+ ['legend', `input.propertyName-${typeNamespace}`]
1198
+ ).pop()
1199
+ );
1200
+ }
1201
+ }
1202
+ },
1203
+ [
1204
+ buildLegend({
1205
+ className,
1206
+ splice,
1207
+ itemIndex,
1208
+ // type,
1209
+ typeNamespace,
1210
+ arrayItems,
1211
+ propName
1212
+ })
1213
+ ],
1214
+ arrayItems)
1215
+ );
965
1216
  // We must ensure fieldset is built before passing it
966
1217
  jml({'#': [
967
1218
  ...(/** @type {import('../typeChoices.js').BuildTypeChoices} */ (
@@ -970,9 +1221,9 @@ const arrayType = {
970
1221
  // resultType,
971
1222
  // eslint-disable-next-line object-shorthand -- TS
972
1223
  topRoot: /** @type {HTMLDivElement} */ (topRoot),
973
- // eslint-disable-next-line object-shorthand, max-len -- TS
1224
+ // eslint-disable-next-line object-shorthand, @stylistic/max-len -- TS
974
1225
  format: /** @type {import('../formats.js').AvailableFormat} */ (format),
975
- state: type,
1226
+ state: arrayState ?? type,
976
1227
  // itemIndex,
977
1228
  typeNamespace
978
1229
  }).domArray),
@@ -1032,7 +1283,6 @@ const arrayType = {
1032
1283
  */
1033
1284
  ([
1034
1285
  'button',
1035
- // @ts-ignore Can't seem to force optional Event
1036
1286
  {
1037
1287
  $on: {
1038
1288
  click (/** @type {Event} */ e) {
@@ -1139,15 +1389,15 @@ const arrayType = {
1139
1389
  * @this {HTMLDivElement}
1140
1390
  */
1141
1391
  $getArrayItems () {
1142
- return (/**
1143
- * @type {HTMLDivElement & {
1144
- * $inputsExceedingLength: InputsExceedingLength,
1145
- * $getPropertyInputs: GetPropertyInputs,
1146
- * $redrawMoveArrows: RedrawMoveArrows
1147
- * }}
1148
- */
1149
- (this.previousElementSibling)
1150
- );
1392
+ const prevSibling = /**
1393
+ * @type {HTMLDivElement & {
1394
+ * $inputsExceedingLength: InputsExceedingLength,
1395
+ * $getPropertyInputs: GetPropertyInputs,
1396
+ * $redrawMoveArrows: RedrawMoveArrows,
1397
+ * $getMapKeySelects: GetMapKeySelects
1398
+ * }}
1399
+ */ (this.previousElementSibling);
1400
+ return prevSibling;
1151
1401
  }
1152
1402
  },
1153
1403
  $on: {click () {
@@ -1234,6 +1484,29 @@ const arrayType = {
1234
1484
  $swapGroup, $redrawMoveArrows, $getArrayLength,
1235
1485
  $inputsExceedingLength,
1236
1486
 
1487
+ /**
1488
+ * Only relevant for maps.
1489
+ * @type {GetMapKeySelects}
1490
+ * @this {HTMLDivElement}
1491
+ */
1492
+ $getMapKeySelects () {
1493
+ const selects =
1494
+ /**
1495
+ * @type {(HTMLSelectElement & {
1496
+ * $getValue: import('../typeChoices.js').GetValue}
1497
+ * )[]}
1498
+ */ (DOM.filterChildElements(
1499
+ this,
1500
+ [
1501
+ 'fieldset',
1502
+ 'legend:first-child',
1503
+ 'span.mapKey',
1504
+ 'select.typeChoices-key-type-choices-only'
1505
+ ]
1506
+ ));
1507
+ return selects;
1508
+ },
1509
+
1237
1510
  /**
1238
1511
  * @type {GetPropertyInputs}
1239
1512
  * @this {HTMLDivElement}
@@ -1266,11 +1539,10 @@ const arrayType = {
1266
1539
  addArrayElement,
1267
1540
  ['button', {$on: {click () {
1268
1541
  const arrayContents = /** @type {HTMLElement} */ (
1269
- // @ts-ignore Erring out of IDE
1270
1542
  this.closest('.arrayContents')
1271
1543
  );
1272
1544
  const arrayItems =
1273
- // eslint-disable-next-line max-len -- Long
1545
+ // eslint-disable-next-line @stylistic/max-len -- Long
1274
1546
  /** @type {HTMLDivElement & {$getPropertyInputs: GetPropertyInputs;}} */ (
1275
1547
  $e(/** @type {HTMLElement} */ (arrayContents), '.arrayItems')
1276
1548
  );
@@ -1278,7 +1550,7 @@ const arrayType = {
1278
1550
  if (lastElement) {
1279
1551
  lastElement.remove();
1280
1552
  decrementItemIndex(arrayItems);
1281
- // eslint-disable-next-line max-len -- Long
1553
+ // eslint-disable-next-line @stylistic/max-len -- Long
1282
1554
  /** @type {HTMLDivElement & {$redrawMoveArrows: RedrawMoveArrows}} */ (
1283
1555
  $e(/** @type {HTMLElement} */ (arrayContents), '.arrayItems')
1284
1556
  ).$redrawMoveArrows();
@@ -1292,7 +1564,7 @@ const arrayType = {
1292
1564
  // We could only add this when there was more than one
1293
1565
  ['button', {$on: {click () {
1294
1566
  const arrayItems =
1295
- // eslint-disable-next-line max-len -- Long
1567
+ // eslint-disable-next-line @stylistic/max-len -- Long
1296
1568
  /** @type {HTMLDivElement & {$getPropertyInputs: GetPropertyInputs}} */ ($e(
1297
1569
  /** @type {HTMLElement} */ (
1298
1570
  /** @type {HTMLElement} */ (this).closest('.arrayContents')
@@ -1314,26 +1586,57 @@ const arrayType = {
1314
1586
  ]]
1315
1587
  );
1316
1588
 
1589
+ const parentType = type;
1590
+
1317
1591
  const div = /** @type {HTMLDivElement} */ (
1318
1592
  jml('div', /** @type {import('jamilih').JamilihAttributes} */ ({
1319
1593
  dataset: {type},
1320
1594
  // is: 'array-or-object-editor',
1321
1595
  $custom: {
1322
- // eslint-disable-next-line max-len -- Long
1596
+ // eslint-disable-next-line @stylistic/max-len -- Long
1323
1597
  /** @type {import('../formats/structuredCloning.js').AddAndSetArrayElement} */
1324
1598
  $addAndSetArrayElement ({
1325
- propName, type, value, bringIntoFocus
1599
+ propName, type, value, bringIntoFocus, setAValue
1326
1600
  // , schemaContent, schemaState
1327
1601
  }) {
1328
- this.$addArrayElement({propName});
1602
+ if (parentType === 'map') {
1603
+ if (propName === '0') {
1604
+ this.$addArrayElement({propName});
1605
+ const arrayItems = this.$getArrayItems();
1606
+ const keyTypeChoices = /**
1607
+ * @type {HTMLSelectElement & {
1608
+ * $setType: import('../typeChoices.js').SetType,
1609
+ * $getTypeRoot: import('../formatAndTypeChoices.js').
1610
+ * TypeRootGetter
1611
+ * }}
1612
+ */ (DOM.filterChildElements(
1613
+ arrayItems,
1614
+ ['fieldset:last-of-type', 'legend', '.mapKey', 'select']
1615
+ )[0]);
1616
+ keyTypeChoices.$setType({
1617
+ type, baseValue: value, bringIntoFocus
1618
+ });
1619
+
1620
+ // The key may itself be a map, etc.
1621
+ return keyTypeChoices.$getTypeRoot();
1622
+ }
1623
+ } else {
1624
+ this.$addArrayElement({propName});
1625
+ }
1329
1626
  const typeChoices = this.$getTypeChoices();
1330
1627
  typeChoices.$setType({type, baseValue: value, bringIntoFocus});
1331
1628
  const root = typeChoices.$getTypeRoot();
1332
- const typeObj = /** @type {import('../types.js').TypeObject} */ (
1333
- Types.availableTypes[type]
1334
- );
1335
- if (typeObj.setValue) {
1336
- typeObj.setValue({root, value});
1629
+ // If run for all, causes problems with running `Error.cause`
1630
+ // type twice and is inefficient;
1631
+ // currently put behind `setAValue` as we need to set a value
1632
+ // from `errorsSpecialType`
1633
+ if (setAValue) {
1634
+ const typeObj = /** @type {import('../types.js').TypeObject} */ (
1635
+ Types.availableTypes[type]
1636
+ );
1637
+ if (typeObj.setValue) {
1638
+ typeObj.setValue({root, value});
1639
+ }
1337
1640
  }
1338
1641
  Types.validate({type, root, topRoot});
1339
1642
  return root;
@@ -1345,8 +1648,8 @@ const arrayType = {
1345
1648
 
1346
1649
  /** @type {GetAddArrayElement} */
1347
1650
  $getAddArrayElement () {
1348
- const el = this
1349
- .lastElementChild.firstElementChild.nextElementSibling;
1651
+ const el = this.
1652
+ lastElementChild.firstElementChild.nextElementSibling;
1350
1653
  return sparse ? el.nextElementSibling : el;
1351
1654
  },
1352
1655
  /**
@@ -1374,7 +1677,7 @@ const arrayType = {
1374
1677
  const arrayItems = this.$getArrayItems();
1375
1678
  return $e(
1376
1679
  arrayItems.lastElementChild,
1377
- `.typeChoices-${typeNamespace}`
1680
+ `fieldset > .typeChoices-${typeNamespace}` // Avoid keys
1378
1681
  );
1379
1682
  }
1380
1683
  },