@es-joy/jsoe 0.15.0 → 0.17.0

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 (207) hide show
  1. package/.ncurc.cjs +4 -5
  2. package/CHANGES.md +9 -0
  3. package/README.md +28 -4
  4. package/badges/licenses-badge.svg +1 -0
  5. package/dist/formatAndTypeChoices.d.ts +39 -19
  6. package/dist/formatAndTypeChoices.d.ts.map +1 -1
  7. package/dist/formats/indexedDBKey.d.ts +1 -1
  8. package/dist/formats/indexedDBKey.d.ts.map +1 -1
  9. package/dist/formats/json.d.ts +1 -1
  10. package/dist/formats/json.d.ts.map +1 -1
  11. package/dist/formats/schema.d.ts +11 -0
  12. package/dist/formats/schema.d.ts.map +1 -0
  13. package/dist/formats/structuredCloning.d.ts +11 -13
  14. package/dist/formats/structuredCloning.d.ts.map +1 -1
  15. package/dist/formats.d.ts +86 -25
  16. package/dist/formats.d.ts.map +1 -1
  17. package/dist/fundamentalTypes/BooleanObjectType.d.ts +1 -1
  18. package/dist/fundamentalTypes/BooleanObjectType.d.ts.map +1 -1
  19. package/dist/fundamentalTypes/NumberObjectType.d.ts +1 -1
  20. package/dist/fundamentalTypes/NumberObjectType.d.ts.map +1 -1
  21. package/dist/fundamentalTypes/StringObjectType.d.ts +1 -1
  22. package/dist/fundamentalTypes/StringObjectType.d.ts.map +1 -1
  23. package/dist/fundamentalTypes/arrayReferenceType.d.ts +1 -1
  24. package/dist/fundamentalTypes/arrayType.d.ts +3 -3
  25. package/dist/fundamentalTypes/arrayType.d.ts.map +1 -1
  26. package/dist/fundamentalTypes/bigintObjectType.d.ts +6 -0
  27. package/dist/fundamentalTypes/bigintObjectType.d.ts.map +1 -0
  28. package/dist/fundamentalTypes/bigintType.d.ts +1 -1
  29. package/dist/fundamentalTypes/bigintType.d.ts.map +1 -1
  30. package/dist/fundamentalTypes/blobType.d.ts +1 -1
  31. package/dist/fundamentalTypes/blobType.d.ts.map +1 -1
  32. package/dist/fundamentalTypes/booleanType.d.ts +8 -0
  33. package/dist/fundamentalTypes/booleanType.d.ts.map +1 -0
  34. package/dist/fundamentalTypes/catchType.d.ts +6 -0
  35. package/dist/fundamentalTypes/catchType.d.ts.map +1 -0
  36. package/dist/fundamentalTypes/dateType.d.ts +4 -4
  37. package/dist/fundamentalTypes/dateType.d.ts.map +1 -1
  38. package/dist/fundamentalTypes/domexceptionType.d.ts +1 -1
  39. package/dist/fundamentalTypes/domexceptionType.d.ts.map +1 -1
  40. package/dist/fundamentalTypes/enumType.d.ts +6 -0
  41. package/dist/fundamentalTypes/enumType.d.ts.map +1 -0
  42. package/dist/fundamentalTypes/errorType.d.ts +1 -1
  43. package/dist/fundamentalTypes/errorType.d.ts.map +1 -1
  44. package/dist/fundamentalTypes/fileType.d.ts +1 -1
  45. package/dist/fundamentalTypes/fileType.d.ts.map +1 -1
  46. package/dist/fundamentalTypes/filelistType.d.ts +1 -1
  47. package/dist/fundamentalTypes/filelistType.d.ts.map +1 -1
  48. package/dist/fundamentalTypes/functionType.d.ts +6 -0
  49. package/dist/fundamentalTypes/functionType.d.ts.map +1 -0
  50. package/dist/fundamentalTypes/literalType.d.ts +6 -0
  51. package/dist/fundamentalTypes/literalType.d.ts.map +1 -0
  52. package/dist/fundamentalTypes/mapType.d.ts +1 -1
  53. package/dist/fundamentalTypes/mapType.d.ts.map +1 -1
  54. package/dist/fundamentalTypes/nanType.d.ts +6 -0
  55. package/dist/fundamentalTypes/nanType.d.ts.map +1 -0
  56. package/dist/fundamentalTypes/nativeEnumType.d.ts +6 -0
  57. package/dist/fundamentalTypes/nativeEnumType.d.ts.map +1 -0
  58. package/dist/fundamentalTypes/neverType.d.ts +6 -0
  59. package/dist/fundamentalTypes/neverType.d.ts.map +1 -0
  60. package/dist/fundamentalTypes/noneditableType.d.ts +1 -1
  61. package/dist/fundamentalTypes/noneditableType.d.ts.map +1 -1
  62. package/dist/fundamentalTypes/nullType.d.ts +1 -1
  63. package/dist/fundamentalTypes/nullType.d.ts.map +1 -1
  64. package/dist/fundamentalTypes/numberType.d.ts +1 -1
  65. package/dist/fundamentalTypes/numberType.d.ts.map +1 -1
  66. package/dist/fundamentalTypes/objectReferenceType.d.ts +1 -1
  67. package/dist/fundamentalTypes/objectType.d.ts +1 -1
  68. package/dist/fundamentalTypes/objectType.d.ts.map +1 -1
  69. package/dist/fundamentalTypes/promiseType.d.ts +6 -0
  70. package/dist/fundamentalTypes/promiseType.d.ts.map +1 -0
  71. package/dist/fundamentalTypes/recordType.d.ts +6 -0
  72. package/dist/fundamentalTypes/recordType.d.ts.map +1 -0
  73. package/dist/fundamentalTypes/regexpType.d.ts +1 -1
  74. package/dist/fundamentalTypes/regexpType.d.ts.map +1 -1
  75. package/dist/fundamentalTypes/setType.d.ts +1 -1
  76. package/dist/fundamentalTypes/setType.d.ts.map +1 -1
  77. package/dist/fundamentalTypes/sparseUndefinedType.d.ts +1 -1
  78. package/dist/fundamentalTypes/sparseUndefinedType.d.ts.map +1 -1
  79. package/dist/fundamentalTypes/stringType.d.ts +1 -1
  80. package/dist/fundamentalTypes/stringType.d.ts.map +1 -1
  81. package/dist/fundamentalTypes/symbolType.d.ts +8 -0
  82. package/dist/fundamentalTypes/symbolType.d.ts.map +1 -0
  83. package/dist/fundamentalTypes/tupleType.d.ts +6 -0
  84. package/dist/fundamentalTypes/tupleType.d.ts.map +1 -0
  85. package/dist/fundamentalTypes/undefinedType.d.ts +1 -1
  86. package/dist/fundamentalTypes/undefinedType.d.ts.map +1 -1
  87. package/dist/fundamentalTypes/voidType.d.ts +6 -0
  88. package/dist/fundamentalTypes/voidType.d.ts.map +1 -0
  89. package/dist/index.d.ts +2 -2
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +9661 -1587
  92. package/dist/subTypes/blobHTMLType.d.ts.map +1 -1
  93. package/dist/subTypes/falseType.d.ts +1 -1
  94. package/dist/subTypes/trueType.d.ts +1 -1
  95. package/dist/superTypes/SpecialNumberType.d.ts +1 -1
  96. package/dist/superTypes/SpecialRealNumberType.d.ts +1 -1
  97. package/dist/superTypes/SpecialRealNumberType.d.ts.map +1 -1
  98. package/dist/superTypes/buffersourceType.d.ts +1 -1
  99. package/dist/superTypes/buffersourceType.d.ts.map +1 -1
  100. package/dist/superTypes/dommatrixType.d.ts +1 -1
  101. package/dist/superTypes/dommatrixType.d.ts.map +1 -1
  102. package/dist/superTypes/dompointType.d.ts +1 -1
  103. package/dist/superTypes/dompointType.d.ts.map +1 -1
  104. package/dist/superTypes/domrectType.d.ts +1 -1
  105. package/dist/superTypes/domrectType.d.ts.map +1 -1
  106. package/dist/superTypes/errorsSpecialType.d.ts +1 -1
  107. package/dist/superTypes/errorsSpecialType.d.ts.map +1 -1
  108. package/dist/typeChoices.d.ts +33 -16
  109. package/dist/typeChoices.d.ts.map +1 -1
  110. package/dist/types.d.ts +161 -157
  111. package/dist/types.d.ts.map +1 -1
  112. package/dist/utils/dialogs.d.ts +3 -3
  113. package/dist/utils/dialogs.d.ts.map +1 -1
  114. package/dist/utils/jsonPointer.d.ts.map +1 -1
  115. package/dist/utils/media.d.ts.map +1 -1
  116. package/dist/utils/objects.d.ts +16 -0
  117. package/dist/utils/objects.d.ts.map +1 -0
  118. package/dist/utils/types.d.ts +1 -1
  119. package/dist/utils/types.d.ts.map +1 -1
  120. package/dist/vendor-imports.d.ts +1 -1
  121. package/eslint.config.js +74 -0
  122. package/licenseInfo.json +4 -0
  123. package/package.json +59 -69
  124. package/rollup.config.js +16 -1
  125. package/src/formatAndTypeChoices.js +68 -54
  126. package/src/formats/indexedDBKey.js +9 -4
  127. package/src/formats/json.js +7 -4
  128. package/src/formats/schema.js +772 -0
  129. package/src/formats/structuredCloning.js +187 -86
  130. package/src/formats.js +61 -26
  131. package/src/fundamentalTypes/BooleanObjectType.js +18 -6
  132. package/src/fundamentalTypes/NumberObjectType.js +18 -5
  133. package/src/fundamentalTypes/StringObjectType.js +20 -8
  134. package/src/fundamentalTypes/arrayType.js +1241 -533
  135. package/src/fundamentalTypes/bigintObjectType.js +64 -0
  136. package/src/fundamentalTypes/bigintType.js +31 -5
  137. package/src/fundamentalTypes/blobType.js +201 -190
  138. package/src/fundamentalTypes/booleanType.js +63 -0
  139. package/src/fundamentalTypes/catchType.js +140 -0
  140. package/src/fundamentalTypes/dateType.js +57 -19
  141. package/src/fundamentalTypes/domexceptionType.js +61 -63
  142. package/src/fundamentalTypes/enumType.js +52 -0
  143. package/src/fundamentalTypes/errorType.js +20 -7
  144. package/src/fundamentalTypes/fileType.js +195 -189
  145. package/src/fundamentalTypes/filelistType.js +4 -0
  146. package/src/fundamentalTypes/functionType.js +150 -0
  147. package/src/fundamentalTypes/literalType.js +81 -0
  148. package/src/fundamentalTypes/mapType.js +4 -0
  149. package/src/fundamentalTypes/nanType.js +41 -0
  150. package/src/fundamentalTypes/nativeEnumType.js +155 -0
  151. package/src/fundamentalTypes/neverType.js +44 -0
  152. package/src/fundamentalTypes/noneditableType.js +19 -5
  153. package/src/fundamentalTypes/nullType.js +16 -6
  154. package/src/fundamentalTypes/numberType.js +49 -6
  155. package/src/fundamentalTypes/objectType.js +10 -2
  156. package/src/fundamentalTypes/promiseType.js +60 -0
  157. package/src/fundamentalTypes/recordType.js +42 -0
  158. package/src/fundamentalTypes/regexpType.js +52 -34
  159. package/src/fundamentalTypes/setType.js +4 -0
  160. package/src/fundamentalTypes/sparseUndefinedType.js +3 -0
  161. package/src/fundamentalTypes/stringType.js +254 -6
  162. package/src/fundamentalTypes/symbolType.js +81 -0
  163. package/src/fundamentalTypes/tupleType.js +39 -0
  164. package/src/fundamentalTypes/undefinedType.js +14 -4
  165. package/src/fundamentalTypes/voidType.js +48 -0
  166. package/src/jsoe.css +37 -3
  167. package/src/subTypes/blobHTMLType.js +33 -24
  168. package/src/superTypes/SpecialRealNumberType.js +22 -6
  169. package/src/superTypes/buffersourceType.js +86 -30
  170. package/src/superTypes/dommatrixType.js +24 -7
  171. package/src/superTypes/dompointType.js +27 -7
  172. package/src/superTypes/domrectType.js +33 -7
  173. package/src/superTypes/errorsSpecialType.js +30 -14
  174. package/src/typeChoices.js +115 -44
  175. package/src/types.js +129 -45
  176. package/src/utils/FileList.js +3 -3
  177. package/src/utils/dialogs.js +6 -8
  178. package/src/utils/objects.js +22 -0
  179. package/src/vendor-imports.js +1 -1
  180. package/tsconfig.json +2 -2
  181. package/vendor/jamilih/dist/jml-es.js +23 -6
  182. package/vendor/typeson-registry/dist/index.js +77 -47
  183. package/vendor/zod/lib/index.mjs +4236 -0
  184. package/vendor/zodex/dist/esm/dezerialize.d.ts +26 -0
  185. package/vendor/zodex/dist/esm/dezerialize.js +393 -0
  186. package/vendor/zodex/dist/esm/index.d.ts +9 -0
  187. package/vendor/zodex/dist/esm/index.js +4 -0
  188. package/vendor/zodex/dist/esm/infer.d.ts +29 -0
  189. package/vendor/zodex/dist/esm/infer.js +1 -0
  190. package/vendor/zodex/dist/esm/package.json +1 -0
  191. package/vendor/zodex/dist/esm/types.d.ts +201 -0
  192. package/vendor/zodex/dist/esm/types.js +13 -0
  193. package/vendor/zodex/dist/esm/ui.d.ts +23 -0
  194. package/vendor/zodex/dist/esm/ui.js +10 -0
  195. package/vendor/zodex/dist/esm/zerialize.d.ts +50 -0
  196. package/vendor/zodex/dist/esm/zerialize.js +417 -0
  197. package/vendor/zodex/dist/esm/zod-types.d.ts +11 -0
  198. package/vendor/zodex/dist/esm/zod-types.js +1 -0
  199. package/.eslintignore +0 -13
  200. package/.eslintrc.cjs +0 -145
  201. package/.nyc_output/out.json +0 -114984
  202. package/cypress.config.ts +0 -15
  203. package/demo/index-instrumented.html +0 -29
  204. package/demo/index.html +0 -29
  205. package/demo/index.js +0 -644
  206. package/src/formats/schemaAndArbitrary.js +0 -20
  207. package/src/formats/schemaOnly.js +0 -539
@@ -1,13 +1,15 @@
1
1
  import {jml, nbsp} from '../vendor-imports.js';
2
2
 
3
3
  import {getPropertyValueFromLegend} from '../types.js';
4
- import {$e, U, DOM} from '../utils/templateUtils.js';
4
+ import {$e, $$e, U, DOM} from '../utils/templateUtils.js';
5
5
  import dialogs from '../utils/dialogs.js';
6
6
  import {
7
7
  resolveJSONPointer, getJSONPointerParts, reduceJSONPointerParts
8
8
  } from '../utils/jsonPointer.js';
9
9
  import FileList from '../utils/FileList.js';
10
10
 
11
+ let optionalPropertyId = 0;
12
+
11
13
  /**
12
14
  * @typedef {number} Integer
13
15
  */
@@ -88,12 +90,16 @@ const arrayType = {
88
90
  regexEndings: [',', ']'],
89
91
  stringRegexBegin: /^\[/u,
90
92
  stringRegexEnd: /^\]/u,
93
+ valueMatch (x) {
94
+ return Array.isArray(x);
95
+ },
91
96
  toValue (s, info) {
92
97
  const {
93
98
  /* istanbul ignore next -- Just a guard */
94
99
  endMatchTypeObjs = [],
95
100
  remnant: innerContents,
96
- rootHolder
101
+ rootHolder,
102
+ schemaObject
97
103
  } = /** @type {import('../types.js').RootInfo} */ (info);
98
104
  // eslint-disable-next-line prefer-destructuring -- TS
99
105
  const format = /** @type {import('../types.js').RootInfo} */ (info).format;
@@ -157,6 +163,7 @@ const arrayType = {
157
163
  firstRun: false,
158
164
  format,
159
165
  state,
166
+ schemaObject,
160
167
  endMatchTypeObjs, rootHolder, parent: retObj, parentPath: idx
161
168
  }
162
169
  );
@@ -211,11 +218,11 @@ const arrayType = {
211
218
  [v, stringVal, beginOnly, assign] = types.getValueForString(
212
219
  stringVal,
213
220
  {
214
- firstRun: false, format, state,
221
+ firstRun: false, format, state, schemaObject,
215
222
  endMatchTypeObjs, rootHolder, parent: retObj, parentPath: pr
216
223
  }
217
224
  );
218
- } catch (err) {
225
+ } catch {
219
226
  // console.log(
220
227
  // 'errrrr', stringVal,
221
228
  // JSON.stringify(endMatchTypeObjs)
@@ -299,6 +306,14 @@ const arrayType = {
299
306
  '.typeContainer',
300
307
  'div[data-type]'
301
308
  ]
309
+ )[0] || DOM.filterChildElements(
310
+ /** @type {HTMLFieldSetElement} */
311
+ (fieldset),
312
+ [
313
+ '[class^=optionalProperties-placeholder]',
314
+ '.typeContainer',
315
+ 'div[data-type]'
316
+ ]
302
317
  )[0]
303
318
  );
304
319
  /* istanbul ignore if -- Should err first? */
@@ -351,11 +366,26 @@ const arrayType = {
351
366
  const key = select.$getValue();
352
367
  return [key, /** @type {any[]} */ (ret)[idx]];
353
368
  }))
354
- : ret;
369
+ : this.record
370
+ ? arrayItems.$getMapKeySelects().reduce((obj, select, idx) => {
371
+ const key = select.$getValue();
372
+ const val = /** @type {any[]} */ (ret)[idx];
373
+ obj[key] = val;
374
+ // Todo: Reenable for native enums
375
+ // The nature of native enums
376
+ // if (typeof val === 'number') {
377
+ // obj[val] = key;
378
+ // }
379
+ return obj;
380
+ }, /** @type {{[key: string]: any}} */ ({}))
381
+ : ret;
355
382
  },
356
383
 
357
384
  // Try to keep in sync with basic structure of `editUI`
358
- viewUI ({typeNamespace, type, types, value, topRoot, resultType, format}) {
385
+ viewUI ({
386
+ typeNamespace, type, types, value, topRoot, resultType, format,
387
+ specificSchemaObject
388
+ }) {
359
389
  // const {sparse} = this;
360
390
  let itemIndex = -1;
361
391
 
@@ -373,175 +403,238 @@ const arrayType = {
373
403
  /* className, type, arrayItems, */
374
404
  itemIndex, typeNamespace, propName
375
405
  }) => {
406
+ const tupleItem = /** @type {import('zodex').SzTuple} */ (
407
+ specificSchemaObject
408
+ )?.items?.[itemIndex];
409
+ const restItem = /** @type {import('zodex').SzTuple} */ (
410
+ specificSchemaObject
411
+ )?.rest;
376
412
  return ['legend', [
377
- this.array
378
- ? 'Item'
379
- : 'Property',
380
- ':',
413
+ this.array && type !== 'record'
414
+ ? /** @type {import('zodex').SzArray} */ (
415
+ specificSchemaObject
416
+ )?.element?.description ??
417
+ ((type === 'set' && /** @type {import('zodex').SzSet} */ (
418
+ specificSchemaObject
419
+ )?.value?.description)
420
+ ? /** @type {import('zodex').SzSet} */ (
421
+ specificSchemaObject
422
+ )?.value?.description
423
+ : (
424
+ tupleItem?.description ?? restItem?.description
425
+ )) ?? 'Item'
426
+ : specificSchemaObject ? '' : 'Property',
427
+ specificSchemaObject ? '' : ':',
381
428
  nbsp.repeat(2),
382
429
  ['span', {
383
- class: `propertyName-${typeNamespace}`
430
+ class: `propertyName-${typeNamespace}`,
431
+ title: type === 'record' && /** @type {import('zodex').SzRecord} */ (
432
+ specificSchemaObject
433
+ )?.key?.description
434
+ ? /** @type {import('zodex').SzRecord} */ (
435
+ specificSchemaObject
436
+ )?.key?.description
437
+ : specificSchemaObject ? propName : undefined
384
438
  }, [
385
439
  propName !== undefined
386
- ? propName
440
+ ? /** @type {import('zodex').SzObject} */ (
441
+ specificSchemaObject
442
+ )?.properties?.[propName]?.description ?? propName
387
443
  // eslint-disable-next-line @stylistic/max-len -- Long
388
444
  /* istanbul ignore next -- Won't reach here as typeson will always give keypath? */
389
445
  : itemIndex
390
446
  ]]
391
447
  ]];
392
448
  };
393
- const div = /** @type {HTMLDivElement} */ (
394
- jml('div', /** @type {import('jamilih').JamilihAttributes} */ ({
395
- class: 'arrayHolder',
396
- dataset: {type},
397
- $custom: {
398
- /**
399
- * @param {{
400
- * propName: string,
401
- * type: import('../types.js').AvailableType,
402
- * value: import('../formats.js').StructuredCloneValue,
403
- * bringIntoFocus: boolean,
404
- * schemaContent: object,
405
- * schemaState:
406
- * import('../types.js').GetPossibleSchemasForPathAndType
407
- * }} cfg
408
- * @returns {Element}
409
- */
410
- $addAndSetArrayElement ({
411
- propName, type, value, bringIntoFocus,
412
- schemaContent, schemaState
413
- }) {
414
- if (parentType === 'map') {
415
- const root = types.getUIForModeAndType({
416
- resultType,
417
- readonly: true,
418
- typeNamespace, type, topRoot,
419
- bringIntoFocus,
420
- format, schemaContent, schemaState,
421
- value,
422
- hasValue: true // type === 'sparseArrays' && value
423
- });
424
- if (propName === '0') {
425
- const fieldset = this.$addMapElement();
426
- this._lastFieldset = fieldset;
427
- const keyFieldset = /** @type {HTMLFieldSetElement} */ (jml(
428
- 'fieldset', [
429
- ['legend', ['Key']]
430
- ], fieldset
431
- ));
432
- jml(root, keyFieldset);
433
- } else { // propName === '1'
434
- const valueFieldset = /** @type {HTMLFieldSetElement} */ (jml(
435
- 'fieldset', [
436
- ['legend', ['Value']]
437
- ], this._lastFieldset
438
- ));
439
- this._lastFieldset = null;
440
- jml(root, valueFieldset);
441
- }
442
- return root;
443
- }
444
-
445
- const fieldset = this.$addArrayElement({propName});
449
+ const div = jml('div', /** @type {import('jamilih').JamilihAttributes} */ ({
450
+ class: 'arrayHolder',
451
+ dataset: {type},
452
+ $custom: {
453
+ /**
454
+ * @param {{
455
+ * propName: string,
456
+ * type: import('../types.js').AvailableType,
457
+ * value: import('../formats.js').StructuredCloneValue,
458
+ * bringIntoFocus: boolean,
459
+ * schemaContent: import('../formats/schema.js').ZodexSchema,
460
+ * }} cfg
461
+ * @returns {Element}
462
+ */
463
+ $addAndSetArrayElement ({
464
+ propName, type, value, bringIntoFocus,
465
+ schemaContent
466
+ }) {
467
+ if (parentType === 'map') {
446
468
  const root = types.getUIForModeAndType({
447
469
  resultType,
448
470
  readonly: true,
449
471
  typeNamespace, type, topRoot,
450
472
  bringIntoFocus,
451
- format, schemaContent, schemaState,
473
+ format, schemaContent,
452
474
  value,
453
475
  hasValue: true // type === 'sparseArrays' && value
454
476
  });
455
- jml(root, fieldset);
477
+ if (propName === '0') {
478
+ const fieldset = this.$addMapElement();
479
+ this._lastFieldset = fieldset;
480
+ const keyFieldset = jml(
481
+ 'fieldset', [
482
+ ['legend', {
483
+ title: /** @type {import('zodex').SzMap<any, any>} */ (
484
+ specificSchemaObject
485
+ )?.key?.description
486
+ ? '(map key)'
487
+ : undefined
488
+ }, [
489
+ /** @type {import('zodex').SzMap<any, any>} */ (
490
+ specificSchemaObject
491
+ )?.key?.description ?? 'Key'
492
+ ]]
493
+ ], fieldset
494
+ );
495
+ jml(root, keyFieldset);
496
+ } else { // propName === '1'
497
+ const valueFieldset = jml(
498
+ 'fieldset', [
499
+ ['legend', {
500
+ title: /** @type {import('zodex').SzMap<any, any>} */ (
501
+ specificSchemaObject
502
+ )?.value?.description
503
+ ? '(map value)'
504
+ : undefined
505
+ }, [
506
+ /** @type {import('zodex').SzMap<any, any>} */ (
507
+ specificSchemaObject
508
+ )?.value?.description ??
509
+ 'Value'
510
+ ]]
511
+ ], this._lastFieldset
512
+ );
513
+ this._lastFieldset = null;
514
+ jml(root, valueFieldset);
515
+ }
456
516
  return root;
457
- },
458
- /**
459
- * @returns {HTMLFieldSetElement}
460
- */
461
- $addMapElement () {
462
- itemIndex++;
463
- const arrayItems = this.$getArrayItems();
464
- // const className = `${type}Item`;
465
- const fieldset = /** @type {HTMLFieldSetElement} */ (
466
- jml('fieldset', [
467
- buildLegend({
468
- // className,
469
- // type,
470
- // arrayItems,
471
- itemIndex,
472
- typeNamespace,
473
- propName: undefined
474
- })
475
- ], arrayItems)
476
- );
477
- return fieldset;
478
- },
479
- /**
480
- * @param {{propName: string}} cfg
481
- * @returns {HTMLFieldSetElement}
482
- */
483
- $addArrayElement ({propName}) {
484
- itemIndex++;
485
- const arrayItems = this.$getArrayItems();
486
- // const className = `${type}Item`;
487
- const fieldset = /** @type {HTMLFieldSetElement} */ (
488
- jml('fieldset', [
489
- buildLegend({
490
- // className,
491
- // type,
492
- // arrayItems,
493
- itemIndex,
494
- typeNamespace,
495
- propName
496
- })
497
- ], arrayItems)
498
- );
499
- return fieldset;
500
- },
501
- $getArrayItems () {
502
- return this.lastElementChild.lastElementChild;
503
517
  }
518
+
519
+ const fieldset = this.$addArrayElement({propName});
520
+ const root = types.getUIForModeAndType({
521
+ resultType,
522
+ readonly: true,
523
+ typeNamespace, type, topRoot,
524
+ bringIntoFocus,
525
+ format,
526
+ schemaContent,
527
+ specificSchemaObject: schemaContent,
528
+ value,
529
+ hasValue: true // type === 'sparseArrays' && value
530
+ });
531
+ jml(root, fieldset);
532
+ return root;
533
+ },
534
+ /**
535
+ * @returns {HTMLFieldSetElement}
536
+ */
537
+ $addMapElement () {
538
+ itemIndex++;
539
+ const arrayItems = this.$getArrayItems();
540
+ // const className = `${type}Item`;
541
+ const fieldset = jml('fieldset', [
542
+ buildLegend({
543
+ // className,
544
+ // type,
545
+ // arrayItems,
546
+ itemIndex,
547
+ typeNamespace,
548
+ propName: undefined
549
+ })
550
+ ], arrayItems);
551
+ return fieldset;
552
+ },
553
+ /**
554
+ * @param {{propName: string}} cfg
555
+ * @returns {HTMLFieldSetElement}
556
+ */
557
+ $addArrayElement ({propName}) {
558
+ itemIndex++;
559
+ const arrayItems = this.$getArrayItems();
560
+ // const className = `${type}Item`;
561
+ const fieldset = jml('fieldset', [
562
+ buildLegend({
563
+ // className,
564
+ // type,
565
+ // arrayItems,
566
+ itemIndex,
567
+ typeNamespace,
568
+ propName
569
+ })
570
+ ], arrayItems);
571
+ return fieldset;
572
+ },
573
+ $getArrayItems () {
574
+ return this.lastElementChild.lastElementChild;
504
575
  }
505
- }), [
506
- DOM.initialCaps(/** @type {import('../types.js').AvailableType} */ (
507
- type
508
- )).replace(/s$/u, ''), nbsp.repeat(2),
509
- ['button', {$on: {click (/** @type {Event} */ e) {
510
- e.preventDefault();
511
- const {target} = e;
512
- const arrayContents = /** @type {HTMLDivElement} */ ($e(
513
- /** @type {HTMLElement} */
514
- (/** @type {HTMLElement} */ (target).closest('.arrayHolder')),
515
- '.arrayContents'
516
- ));
517
- arrayContents.hidden = !arrayContents.hidden;
518
- /** @type {HTMLElement} */ (
519
- target
520
- ).textContent = arrayContents.hidden ? '+' : '-';
521
- }}}, ['-']],
522
- ['div', {class: 'arrayContents'}, [
523
- this.array
524
- ? ['div', [
525
- type === 'filelist'
526
- ? 'FileList length: '
576
+ }
577
+ }), [
578
+ ['span', {
579
+ title: specificSchemaObject?.description
580
+ }, [
581
+ specificSchemaObject
582
+ ? '—'
583
+ : DOM.initialCaps(/** @type {import('../types.js').AvailableType} */ (
584
+ type
585
+ )).replace(/s$/u, '')
586
+ ]],
587
+ nbsp.repeat(2),
588
+ ['button', {$on: {click (/** @type {Event} */ e) {
589
+ e.preventDefault();
590
+ const {target} = e;
591
+ const arrayContents = /** @type {HTMLDivElement} */ ($e(
592
+ /** @type {HTMLElement} */
593
+ (/** @type {HTMLElement} */ (target).closest('.arrayHolder')),
594
+ '.arrayContents'
595
+ ));
596
+ arrayContents.hidden = !arrayContents.hidden;
597
+ /** @type {HTMLElement} */ (
598
+ target
599
+ ).textContent = arrayContents.hidden ? '+' : '-';
600
+ }}}, ['-']],
601
+ ['div', {class: 'arrayContents'}, [
602
+ this.array && type !== 'record'
603
+ ? ['div', {
604
+ title: specificSchemaObject
605
+ ? (type === 'filelist'
606
+ ? '(a FileList)'
527
607
  : type === 'set'
528
- ? 'Set size: '
608
+ ? '(a Set)'
529
609
  : type === 'map'
530
- ? 'Map size: '
531
- : 'Array length: ',
532
- ['span', [
533
- (value && (type === 'set' || type === 'map')
534
- ? value.size
535
- : value.length) || 0
536
- ]]
610
+ ? '(a Map)'
611
+ : type === 'tuple'
612
+ ? '(a tuple)'
613
+ : '(an Array)')
614
+ : undefined
615
+ }, [
616
+ type === 'filelist'
617
+ ? (specificSchemaObject?.description ?? 'FileList') + ' length: '
618
+ : type === 'set'
619
+ ? (specificSchemaObject?.description ?? 'Set') + ' size: '
620
+ : type === 'map'
621
+ ? (specificSchemaObject?.description ?? 'Map') + ' size: '
622
+ : (specificSchemaObject?.description ?? 'Array') +
623
+ ' length: ',
624
+ ['span', [
625
+ (value && (type === 'set' || type === 'map')
626
+ ? value.size
627
+ : value.length) || 0
537
628
  ]]
538
- : '',
539
- ['div', {
540
- class: 'arrayItems'
541
- }]
542
- ]]
543
- ])
544
- );
629
+ ]]
630
+ : (type === 'record'
631
+ ? specificSchemaObject?.description ?? 'Record'
632
+ : ''),
633
+ ['div', {
634
+ class: 'arrayItems'
635
+ }]
636
+ ]]
637
+ ]);
545
638
  return [div];
546
639
  },
547
640
  getInput ({root}) {
@@ -553,17 +646,26 @@ const arrayType = {
553
646
  // population of the array in the callback
554
647
  editUI ({
555
648
  typeNamespace, buildTypeChoices, format, // resultType,
556
- formats, types,
557
- type, arrayState, topRoot, value, bringIntoFocus = true
649
+ formats, types, specificSchemaObject, schemaFallingBack, schemaContent,
650
+ type, forcedState, topRoot, value: objectValue, bringIntoFocus = true
558
651
  }) {
559
652
  const {sparse} = this;
560
- // eslint-disable-next-line consistent-this
653
+ // eslint-disable-next-line consistent-this -- Clearer
561
654
  const parentTypeObject = this;
562
655
  const itemAdjust = type === 'object' ? 1 : 0;
563
656
  let itemIndex = itemAdjust - 1;
564
657
  const editableProperties = type !== 'array' &&
565
- type !== 'set' && type !== 'map' && type !== 'filelist';
566
- const mapProperties = type === 'map';
658
+ type !== 'set' && type !== 'map' && type !== 'filelist' &&
659
+ type !== 'tuple' && type !== 'record'; // arrayNonindexKeys and object?
660
+ const mapProperties = type === 'map' || type === 'record';
661
+
662
+ const elementDesc = /** @type {import('zodex').SzArray} */ (
663
+ specificSchemaObject
664
+ )?.element?.description ?? (type === 'set'
665
+ ? /** @type {import('zodex').SzSet} */ (
666
+ specificSchemaObject
667
+ )?.value?.description
668
+ : undefined);
567
669
 
568
670
  /**
569
671
  * @param {HTMLInputElement} input
@@ -597,7 +699,7 @@ const arrayType = {
597
699
  if (!swapGroup || swapGroup.nodeName.toLowerCase() !== 'fieldset') {
598
700
  return;
599
701
  }
600
- if (!sparse) {
702
+ if (!sparse && (!specificSchemaObject || parentTypeObject.array)) {
601
703
  const swapCountElem = DOM.filterChildElements(
602
704
  swapGroup, ['legend', `.${type}Item`]
603
705
  )[0];
@@ -659,6 +761,9 @@ const arrayType = {
659
761
  * @this {HTMLDivElement & {$swapGroup: SwapGroup}}
660
762
  */
661
763
  const $redrawMoveArrows = function () {
764
+ if (type === 'tuple') { // Don't want to move non-rest items at least
765
+ return;
766
+ }
662
767
  DOM.filterChildElements(this, [
663
768
  'fieldset', `.${type}Item-arrowHolder-${typeNamespace}`
664
769
  ]).forEach((holder, j, arr) => {
@@ -752,13 +857,15 @@ const arrayType = {
752
857
  * itemIndex: number,
753
858
  * typeNamespace: string|undefined,
754
859
  * arrayItems: ArrayItems,
755
- * propName: string|undefined
860
+ * propName: string|undefined,
861
+ * required?: true,
862
+ * schema?: import('zodex').SzType
756
863
  * }} cfg
757
864
  * @returns {import('jamilih').JamilihArray}
758
865
  */
759
866
  const buildLegend = ({
760
867
  className, splice, itemIndex, /* type, */ typeNamespace,
761
- arrayItems, propName
868
+ arrayItems, propName, required, schema
762
869
  }) => {
763
870
  /**
764
871
  * @callback ValidateLength
@@ -774,40 +881,81 @@ const arrayType = {
774
881
  */
775
882
  const $validateLength = async function (avoidDialog) {
776
883
  if (!sparse) {
777
- return undefined;
884
+ return;
778
885
  }
779
886
  const inputsExceedingLength = arrayItems.$inputsExceedingLength();
780
887
  const exceedsLength = inputsExceedingLength.length;
781
888
  if (avoidDialog || !exceedsLength || !(/^\d+$/u).test(this.value)) {
782
- return undefined;
889
+ return;
783
890
  }
784
891
  await dialogs.confirm({
785
892
  message: 'You are attempting to add an (integer-based) ' +
786
893
  'array item beyond the length. Click "Ok" to allow to ' +
787
894
  'permit and extend the array length or "Cancel" otherwise.'
788
895
  });
789
- const arrLengthInput = /** @type {HTMLInputElement} */ (
790
- $e(
791
- /** @type {HTMLElement} */ (arrayItems.previousElementSibling),
792
- 'input'
793
- )
794
- );
896
+ const arrLengthInput =
897
+ /** @type {HTMLInputElement & {$oldvalue: string}} */ (
898
+ $e(
899
+ /** @type {HTMLElement} */ (arrayItems.previousElementSibling),
900
+ 'input'
901
+ )
902
+ );
795
903
  const highest = /** @type {number} */ (inputsExceedingLength.map(
796
904
  (i) => Number.parseInt(i.value)
797
905
  ).sort().at(-1));
798
906
  arrLengthInput.value = String(highest + 1);
799
- return this.$validateLength(true);
907
+ arrLengthInput.$oldvalue = String(highest + 1);
908
+ // Does have potential side effects calling `$inputsExceedingLength`
909
+ this.$validateLength(true);
800
910
  };
801
911
  if (mapProperties) {
802
912
  const keyTypeSelection =
803
913
  /** @type {import('../typeChoices.js').BuildTypeChoices} */ (
804
914
  buildTypeChoices
805
915
  )({
806
- format: 'structuredCloning',
916
+ value: propName !== undefined && type === 'record'
917
+ ? propName
918
+ : undefined,
919
+ setValue: propName !== undefined && type === 'record',
920
+ // Needed as false when map value supplied
921
+ autoTrigger: propName === undefined || type === 'record',
922
+ // eslint-disable-next-line object-shorthand -- TS
923
+ format: /** @type {import('../formats.js').AvailableFormat} */ (
924
+ format
925
+ ),
926
+ schemaOriginal: schemaContent,
927
+ // Can also be a `Record`
928
+ schemaContent: /** @type {import('zodex').SzMap<any, any>} */ (
929
+ specificSchemaObject
930
+ )?.key,
807
931
  typeNamespace: 'key-type-choices-only'
808
932
  });
809
933
  return ['legend', [
810
- 'Key ',
934
+ ['span', {
935
+ class: 'mapKey',
936
+ title: (
937
+ type === 'map' &&
938
+ /** @type {import('zodex').SzMap<any, any>} */ (
939
+ specificSchemaObject
940
+ )?.key?.description
941
+ )
942
+ ? '(map key)'
943
+ : type === 'record' &&
944
+ /** @type {import('zodex').SzRecord} */ (
945
+ specificSchemaObject
946
+ )?.key?.description
947
+ ? '(record key)'
948
+ : undefined
949
+ }, [
950
+ type === 'map'
951
+ ? /** @type {import('zodex').SzMap<any, any>} */ (
952
+ specificSchemaObject
953
+ )?.key?.description ?? 'Map key'
954
+ : /** @type {import('zodex').SzRecord} */ (
955
+ specificSchemaObject
956
+ )?.key?.description ?? 'Record key',
957
+ ' '
958
+ ]],
811
959
  ['span', {
812
960
  dataset: {prop: 'true'},
813
961
  className
@@ -816,7 +964,7 @@ const arrayType = {
816
964
  nbsp.repeat(2),
817
965
 
818
966
  ['span', {
819
- class: 'mapKey',
967
+ class: 'mapKeyHolder',
820
968
  $on: {
821
969
  change: [function () {
822
970
  /**
@@ -858,10 +1006,18 @@ const arrayType = {
858
1006
  return;
859
1007
  }
860
1008
 
861
- select.setCustomValidity(
862
- 'Duplicate Map key value'
1009
+ const control = select.hidden
1010
+ ? /** @type {HTMLInputElement|HTMLTextAreaElement} */ ($e(
1011
+ /** @type {HTMLDivElement} */ (
1012
+ select.nextElementSibling
1013
+ ), 'input,textarea'
1014
+ ))
1015
+ : select;
1016
+
1017
+ control.setCustomValidity(
1018
+ `Duplicate ${type === 'map' ? 'Map' : 'Record'} key value`
863
1019
  );
864
- select.reportValidity();
1020
+ control.reportValidity();
865
1021
  });
866
1022
  }
867
1023
  }
@@ -869,24 +1025,85 @@ const arrayType = {
869
1025
  ]];
870
1026
  }
871
1027
  if (editableProperties) {
1028
+ // console.log('PROPNAME', propName, schema, specificSchemaObject);
1029
+ const description = /** @type {import('zodex').SzObject} */ (
1030
+ specificSchemaObject
1031
+ )?.properties?.[/** @type {string} */ (propName)]?.description;
1032
+ const optionalProperties = Object.entries(
1033
+ /** @type {import('zodex').SzObject} */ (
1034
+ specificSchemaObject
1035
+ )?.properties ?? {}
1036
+ ).map(([prop, val]) => {
1037
+ if (!val.isOptional) {
1038
+ return null;
1039
+ }
1040
+ return prop;
1041
+ }).filter(Boolean);
1042
+ optionalPropertyId++;
1043
+ const initialValue = sparse
1044
+ ? (
1045
+ splice === 'append'
1046
+ ? ''
1047
+ : (propName !== undefined ? propName : itemIndex)
1048
+ )
1049
+ : propName || '';
872
1050
  return /** @type {import('jamilih').JamilihArray} */ (['legend', [
873
1051
  sparse
874
- ? 'Item'
875
- : {'#': [
876
- 'Property ',
877
- ['span', {className}, [String(itemIndex)]],
878
- ':'
879
- ]},
1052
+ ? elementDesc ?? 'Item'
1053
+ : {'#': required
1054
+ ? [
1055
+ ['b', {className, title: description ? propName : undefined}, [
1056
+ elementDesc ?? description ?? propName ?? ''
1057
+ ]]
1058
+ ]
1059
+ : [
1060
+ ['span', {
1061
+ className: `${className}_propertyHolder${optionalPropertyId}`
1062
+ }, [
1063
+ ...(specificSchemaObject && propName) // Optional but has name
1064
+ ? [
1065
+ ['b', [
1066
+ /** @type {import('zodex').SzObject} */
1067
+ (specificSchemaObject)?.properties?.[
1068
+ propName
1069
+ ]
1070
+ ? /** @type {import('zodex').SzObject} */ (
1071
+ specificSchemaObject
1072
+ )?.properties?.[
1073
+ propName
1074
+ ]?.description ?? propName
1075
+ : /** @type {import('zodex').SzObject} */ (
1076
+ specificSchemaObject
1077
+ )?.catchall?.description ?? propName
1078
+ ]]
1079
+ ]
1080
+ : [
1081
+ 'Property ',
1082
+ ['span', {className}, [String(itemIndex)]],
1083
+ ':'
1084
+ ]
1085
+ ]]
1086
+ ]},
880
1087
  nbsp.repeat(2),
1088
+ specificSchemaObject && !required
1089
+ ? ['datalist', {
1090
+ id: `optionalProperties_${optionalPropertyId}`
1091
+ }, optionalProperties.map((optionalProperty) => {
1092
+ return ['option', {value: optionalProperty}];
1093
+ })]
1094
+ : '',
881
1095
  ['input', {
882
- value: sparse
883
- ? (
884
- splice === 'append'
885
- ? ''
886
- : (propName !== undefined ? propName : itemIndex)
887
- )
888
- : propName || '',
889
- dataset: {prop: true, object: true},
1096
+ list: specificSchemaObject && !required
1097
+ ? `optionalProperties_${optionalPropertyId}`
1098
+ : undefined,
1099
+ style: {
1100
+ display: required && type !== 'arrayNonindexKeys'
1101
+ ? 'none'
1102
+ : 'block'
1103
+ },
1104
+ disabled: required && type === 'arrayNonindexKeys',
1105
+ value: initialValue,
1106
+ dataset: {prop: true, object: true, optionalPropertyId},
890
1107
  /*
891
1108
  // Works but we do want to let the user input non-integer
892
1109
  type: sparse ? 'number' : 'text',
@@ -905,7 +1122,7 @@ const arrayType = {
905
1122
  try {
906
1123
  try {
907
1124
  await this.$validateLength();
908
- } catch (err) {
1125
+ } catch {
909
1126
  // Give chance for other validations if cancelled
910
1127
  }
911
1128
  // Don't give chance to validate positively if failed
@@ -916,7 +1133,7 @@ const arrayType = {
916
1133
  // eslint-disable-next-line object-shorthand -- TS
917
1134
  topRoot: /** @type {HTMLDivElement} */ (topRoot)
918
1135
  }); // Needed
919
- } catch (err) {}
1136
+ } catch {}
920
1137
  },
921
1138
 
922
1139
  /**
@@ -943,12 +1160,12 @@ const arrayType = {
943
1160
  * })[]}
944
1161
  */
945
1162
  (/**
946
- * @type {HTMLInputElement & {
947
- * $arrayItems: HTMLDivElement & {
948
- * $getPropertyInputs: GetPropertyInputs
949
- * }
950
- * }}
951
- */ (
1163
+ * @type {HTMLInputElement & {
1164
+ * $arrayItems: HTMLDivElement & {
1165
+ * $getPropertyInputs: GetPropertyInputs
1166
+ * }
1167
+ * }}
1168
+ */ (
952
1169
  this
953
1170
  ).$arrayItems.$getPropertyInputs());
954
1171
  if (inputs.length === 1) {
@@ -1051,6 +1268,34 @@ const arrayType = {
1051
1268
  }
1052
1269
  },
1053
1270
  $on: {
1271
+ /**
1272
+ * @this {HTMLInputElement}
1273
+ */
1274
+ input () {
1275
+ if (!specificSchemaObject && !schema) {
1276
+ return;
1277
+ }
1278
+ const propHolder = /** @type {HTMLElement} */ (
1279
+ $e(arrayItems, `.${className}_propertyHolder${
1280
+ this.dataset.optionalPropertyId
1281
+ }`)
1282
+ );
1283
+ DOM.removeChildren(propHolder);
1284
+ jml('b', [
1285
+ /** @type {import('zodex').SzObject} */
1286
+ (specificSchemaObject)?.properties?.[
1287
+ this.value
1288
+ ]
1289
+ ? /** @type {import('zodex').SzObject} */ (
1290
+ specificSchemaObject
1291
+ )?.properties?.[
1292
+ this.value
1293
+ ]?.description ?? this.value
1294
+ : /** @type {import('zodex').SzObject} */ (
1295
+ specificSchemaObject
1296
+ )?.catchall?.description ?? this.value
1297
+ ], propHolder);
1298
+ },
1054
1299
  /**
1055
1300
  * @param {Event} e
1056
1301
  * @this {HTMLInputElement & {
@@ -1059,7 +1304,86 @@ const arrayType = {
1059
1304
  * }}
1060
1305
  */
1061
1306
  change (e) {
1062
- this.$validate();
1307
+ const neverProperty = /** @type {import('zodex').SzObject} */ (
1308
+ specificSchemaObject
1309
+ )?.properties?.[
1310
+ /** @type {string} */ (this.value)
1311
+ ]?.type === 'never';
1312
+
1313
+ let invalid = false;
1314
+ if (neverProperty) {
1315
+ this.setCustomValidity('Never value');
1316
+ this.reportValidity();
1317
+ this.style.backgroundColor = 'pink';
1318
+
1319
+ invalid = true;
1320
+ } else if (this.list && !parentTypeObject.array) {
1321
+ const dataListValues = [
1322
+ ...this.list.options
1323
+ ].map(({value}) => value);
1324
+
1325
+ if (
1326
+ !dataListValues.includes(this.value) &&
1327
+ /** @type {import('zodex').SzObject} */ (
1328
+ specificSchemaObject
1329
+ ).unknownKeys === 'strict'
1330
+ ) {
1331
+ this.setCustomValidity('Bad value');
1332
+ this.reportValidity();
1333
+ this.style.backgroundColor = 'pink';
1334
+ invalid = true;
1335
+ } else {
1336
+ this.style.backgroundColor = 'revert-layer';
1337
+ this.setCustomValidity('');
1338
+ this.reportValidity();
1339
+ const {optionalPropertyId} = this.dataset;
1340
+ let placeholder = /** @type {HTMLElement} */ (
1341
+ $e(
1342
+ arrayItems,
1343
+ `.optionalProperties-placeholder${optionalPropertyId}`
1344
+ )
1345
+ );
1346
+ if (!placeholder) {
1347
+ placeholder = /** @type {HTMLElement} */ ($e(
1348
+ /** @type {HTMLElement} */
1349
+ (this.parentElement?.parentElement),
1350
+ `.property-placeholder`
1351
+ )?.previousElementSibling);
1352
+ // Remove extra select element too
1353
+ placeholder.previousElementSibling?.remove();
1354
+ }
1355
+ placeholder.replaceWith(
1356
+ jml('div', {
1357
+ className:
1358
+ `optionalProperties-placeholder${optionalPropertyId}`
1359
+ }, [
1360
+ ...buildTypeChoicesForProperty({
1361
+ propName: this.value,
1362
+ schema:
1363
+ /** @type {import('zodex').SzObject} */
1364
+ (specificSchemaObject)?.properties?.[this.value] ??
1365
+ /** @type {import('zodex').SzObject} */ (
1366
+ specificSchemaObject
1367
+ )?.catchall ??
1368
+ {type: 'any'}
1369
+ })
1370
+ ])
1371
+ );
1372
+ }
1373
+ }
1374
+
1375
+ if (invalid) {
1376
+ const placeholder = /** @type {HTMLElement} */ (
1377
+ $e(
1378
+ arrayItems,
1379
+ `.optionalProperties-placeholder${optionalPropertyId}`
1380
+ )
1381
+ );
1382
+ DOM.removeChildren(placeholder);
1383
+ } else {
1384
+ // Should this be awaited or awaited after stopPropagation?
1385
+ this.$validate();
1386
+ }
1063
1387
  // We don't want form `onchange` to run
1064
1388
  // `$checkForKeyDuplicates` again
1065
1389
  e.stopPropagation();
@@ -1070,13 +1394,34 @@ const arrayType = {
1070
1394
  }]
1071
1395
  ]]);
1072
1396
  }
1397
+
1398
+ const fileDesc = /** @type {import('zodex').SzEffect} */ (
1399
+ specificSchemaObject
1400
+ )?.inner?.description;
1401
+
1073
1402
  return /** @type {import('jamilih').JamilihArray} */ (['legend', [
1074
- 'Item ',
1403
+ elementDesc
1404
+ ? ''
1405
+ : fileDesc
1406
+ ? `${fileDesc} `
1407
+ : schema?.description
1408
+ ? `${schema?.description} `
1409
+ : type === 'tuple' && /** @type {import('zodex').SzTuple} */ (
1410
+ specificSchemaObject
1411
+ )?.rest?.description
1412
+ ? `${/** @type {import('zodex').SzTuple} */ (
1413
+ specificSchemaObject
1414
+ )?.rest?.description} `
1415
+ : 'Item ',
1075
1416
  ['span', {
1076
1417
  dataset: {prop: true, array: true},
1077
1418
  className
1078
1419
  }, [
1079
- propName !== undefined ? propName : String(itemIndex)
1420
+ elementDesc
1421
+ ? `${elementDesc} ${itemIndex + 1}`
1422
+ : propName !== undefined
1423
+ ? propName
1424
+ : String(itemIndex)
1080
1425
  ]]
1081
1426
  ]]);
1082
1427
  };
@@ -1104,26 +1449,155 @@ const arrayType = {
1104
1449
  }
1105
1450
  };
1106
1451
 
1452
+ /**
1453
+ * @param {{
1454
+ * propName: string|undefined,
1455
+ * schema?: import('zodex').SzType,
1456
+ * schemaIdx?: number,
1457
+ * autoTrigger?: boolean
1458
+ * }} cfg
1459
+ */
1460
+ const buildTypeChoicesForProperty = ({
1461
+ propName, schema, schemaIdx, autoTrigger
1462
+ }) => {
1463
+ return /** @type {import('../typeChoices.js').BuildTypeChoices} */ (
1464
+ buildTypeChoices
1465
+ )({
1466
+ autoTrigger,
1467
+ // resultType,
1468
+ // eslint-disable-next-line object-shorthand -- TS
1469
+ topRoot: /** @type {HTMLDivElement} */ (topRoot),
1470
+ // eslint-disable-next-line object-shorthand -- TS
1471
+ format: /** @type {import('../formats.js').AvailableFormat} */ (format),
1472
+ schemaOriginal: schemaContent,
1473
+ schemaIdx,
1474
+ schemaContent: schema || type === 'tuple'
1475
+ ? (schema ?? /** @type {import('zodex').SzTuple} */ (
1476
+ specificSchemaObject
1477
+ ).rest)
1478
+ : mapProperties
1479
+ // Can also be a `Record`
1480
+ ? /** @type {import('zodex').SzMap<any, any>} */ (
1481
+ specificSchemaObject
1482
+ )?.value
1483
+ : type === 'set'
1484
+ ? /** @type {import('zodex').SzSet} */ (
1485
+ specificSchemaObject
1486
+ )?.value
1487
+ : type === 'filelist'
1488
+ ? /** @type {import('zodex').SzEffect} */ (
1489
+ specificSchemaObject
1490
+ )?.inner
1491
+ : type === 'array' || type === 'arrayNonindexKeys'
1492
+ ? /** @type {import('zodex').SzArray} */ (
1493
+ specificSchemaObject
1494
+ )?.element
1495
+ : /** @type {import('zodex').SzObject} */ (
1496
+ specificSchemaObject
1497
+ )?.properties?.[/** @type {string} */ (propName)],
1498
+ state: parentTypeObject.filelist
1499
+ ? 'filelistArray'
1500
+ : forcedState ?? type,
1501
+ // itemIndex,
1502
+ typeNamespace
1503
+ }).domArray;
1504
+ };
1505
+
1506
+ /**
1507
+ * @param {Integer} [offset]
1508
+ * @returns {boolean}
1509
+ */
1510
+ function preventAdding (offset = 0) {
1511
+ switch (type) {
1512
+ case 'tuple':
1513
+ if (/** @type {import('zodex').SzTuple} */ (
1514
+ specificSchemaObject
1515
+ )?.rest?.type === 'never') {
1516
+ dialogs.alert(
1517
+ 'Tuple has rest type "never", so one cannot add to it.'
1518
+ );
1519
+ return true;
1520
+ }
1521
+ if (
1522
+ /** @type {import('zodex').SzTuple} */ (
1523
+ specificSchemaObject
1524
+ )?.items?.[0]?.type === 'never'
1525
+ ) {
1526
+ dialogs.alert(
1527
+ 'Tuple has items type "never", so one cannot add to it.'
1528
+ );
1529
+ return true;
1530
+ }
1531
+ break;
1532
+ case 'set': {
1533
+ if (/** @type {import('zodex').SzSet} */ (
1534
+ specificSchemaObject
1535
+ )?.value?.type === 'never') {
1536
+ dialogs.alert('Set has type "never", so one cannot add to it.');
1537
+ return true;
1538
+ }
1539
+
1540
+ const {maxSize} = /** @type {import('zodex').SzSet} */ (
1541
+ specificSchemaObject
1542
+ ) ?? {};
1543
+ if (maxSize !== undefined &&
1544
+ [...arrayItems.children].length + offset > maxSize
1545
+ ) {
1546
+ dialogs.alert(`You cannot add beyond the \`maxSize\` of the Set`);
1547
+ return true;
1548
+ }
1549
+ break;
1550
+ } case 'array': case 'arrayNonindexKeys': {
1551
+ if (/** @type {import('zodex').SzArray} */ (
1552
+ specificSchemaObject
1553
+ )?.element?.type === 'never') {
1554
+ dialogs.alert('Array has type "never", so one cannot add to it.');
1555
+ return true;
1556
+ }
1557
+
1558
+ const {maxLength} = /** @type {import('zodex').SzArray} */ (
1559
+ specificSchemaObject
1560
+ ) ?? {};
1561
+
1562
+ if (maxLength !== undefined &&
1563
+ arrayItems.$getArrayLength() + offset > maxLength
1564
+ ) {
1565
+ dialogs.alert(`You cannot add beyond the \`maxLength\` of the array`);
1566
+ return true;
1567
+ }
1568
+ break;
1569
+ } default:
1570
+ break;
1571
+ }
1572
+
1573
+ return false;
1574
+ }
1575
+
1107
1576
  /**
1108
1577
  * @callback AddArrayElement
1109
1578
  * @param {{
1110
1579
  * propName?: string,
1111
1580
  * splice?: "append"|number,
1112
1581
  * alwaysFocus?: true
1582
+ * required?: true
1583
+ * autoTrigger?: boolean,
1584
+ * schema?: import('zodex').SzType,
1585
+ * schemaIdx?: number
1113
1586
  * }} cfg
1114
1587
  * @returns {void}
1115
1588
  */
1116
1589
 
1117
1590
  /**
1118
1591
  * @type {AddArrayElement}
1119
- * @this {HTMLButtonElement & {
1592
+ * @this {HTMLElement & {
1120
1593
  * $getArrayItems: GetArrayItems,
1121
1594
  * $addArrayElement: AddArrayElement,
1122
1595
  * $getMapKeySelects?: GetMapKeySelects
1123
1596
  * }}
1124
1597
  */
1125
- const $addArrayElement = function ({propName, splice, alwaysFocus}) {
1126
- // console.log('propName', propName);
1598
+ const $addArrayElement = function ({
1599
+ propName, splice, alwaysFocus, required, schema, schemaIdx, autoTrigger
1600
+ }) {
1127
1601
  const arrayItems = this.$getArrayItems();
1128
1602
  if (sparse) {
1129
1603
  if (propName) {
@@ -1150,180 +1624,230 @@ const arrayType = {
1150
1624
  } else {
1151
1625
  itemIndex++;
1152
1626
  }
1153
- const thisButton = this; // eslint-disable-line consistent-this
1627
+ const thisButton = this; // eslint-disable-line consistent-this -- Clarity
1154
1628
  const className = `${type}Item`;
1155
- const fieldset =
1156
- /** @type {HTMLFieldSetElement} */ (
1157
- jml('fieldset',
1158
- {
1159
- $on: {
1160
- change: [() => {
1161
- if (type !== 'set') {
1162
- return;
1163
- }
1164
- setTimeout(() => {
1165
- const root = div;
1166
- const values = /** @type {any[]} */ (
1167
- parentTypeObject.getValue.call({
1168
- ...parentTypeObject,
1169
- set: false
1170
- }, {
1171
- root,
1172
- stateObj: {
1173
- types,
1174
- // eslint-disable-next-line object-shorthand -- TS
1175
- formats:
1176
- /** @type {import('../formats.js').default} */ (
1177
- formats
1178
- )
1179
- }
1180
- })
1181
- );
1182
- // console.log('values', values);
1183
-
1184
- const dupeIndex = values.findLastIndex((value, idx) => {
1185
- return values.some((val, index) => {
1186
- return idx !== index && value === val;
1187
- });
1188
- });
1189
-
1190
- if (dupeIndex === -1) {
1191
- return;
1192
- }
1193
- const fieldsets = arrayItems.children;
1194
- const controls = [...fieldsets].map((fieldset) => {
1195
- const root = /** @type {HTMLDivElement} */ (
1196
- $e(fieldset, 'div[data-type]')
1197
- );
1198
- /* istanbul ignore if -- Should err first? */
1199
- if (!root) {
1200
- return null;
1201
- }
1202
- return types.getFormControlForRoot(root);
1203
- });
1204
-
1205
- const control = controls[dupeIndex];
1206
- /* istanbul ignore if -- Should exist */
1207
- if (!control) {
1208
- return;
1629
+ const fieldset = jml('fieldset',
1630
+ {
1631
+ dataset: required ? {required: 'required'} : {},
1632
+ $on: {
1633
+ change: [() => {
1634
+ if (type !== 'set') {
1635
+ return;
1636
+ }
1637
+ setTimeout(() => {
1638
+ const root = div;
1639
+ const values = /** @type {any[]} */ (
1640
+ parentTypeObject.getValue.call({
1641
+ ...parentTypeObject,
1642
+ set: false
1643
+ }, {
1644
+ root,
1645
+ stateObj: {
1646
+ types,
1647
+ // eslint-disable-next-line object-shorthand -- TS
1648
+ formats:
1649
+ /** @type {import('../formats.js').default} */ (
1650
+ formats
1651
+ )
1209
1652
  }
1653
+ })
1654
+ );
1655
+ // console.log('values', values);
1210
1656
 
1211
- control.setCustomValidity(
1212
- 'Duplicate Set value'
1213
- );
1214
- control.reportValidity();
1657
+ const dupeIndex = values.findLastIndex((value, idx) => {
1658
+ return values.some((val, index) => {
1659
+ return idx !== index && value === val;
1215
1660
  });
1216
- }, true]
1217
- },
1218
- $custom: {
1219
- /** @type {GetPropertyInput} */
1220
- $getPropertyInput () {
1221
- return /** @type {HTMLInputElement} */ (
1222
- DOM.filterChildElements(
1223
- /** @type {HTMLFieldSetElement} */ (this),
1224
- ['legend', `input.propertyName-${typeNamespace}`]
1225
- ).pop()
1661
+ });
1662
+
1663
+ if (dupeIndex === -1) {
1664
+ return;
1665
+ }
1666
+ const fieldsets = arrayItems.children;
1667
+ const controls = [...fieldsets].map((fieldset) => {
1668
+ const root = /** @type {HTMLDivElement} */ (
1669
+ $e(fieldset, 'div[data-type]')
1226
1670
  );
1671
+ /* istanbul ignore if -- Should err first? */
1672
+ if (!root) {
1673
+ return null;
1674
+ }
1675
+ return types.getFormControlForRoot(root);
1676
+ });
1677
+
1678
+ const control = controls[dupeIndex];
1679
+ /* istanbul ignore if -- Should exist */
1680
+ if (!control) {
1681
+ return;
1227
1682
  }
1228
- }
1229
- },
1230
- [
1231
- buildLegend({
1232
- className,
1233
- splice,
1234
- itemIndex,
1235
- // type,
1236
- typeNamespace,
1237
- arrayItems,
1238
- propName
1239
- })
1240
- ],
1241
- arrayItems)
1242
- );
1683
+
1684
+ control.setCustomValidity(
1685
+ 'Duplicate Set value'
1686
+ );
1687
+ control.reportValidity();
1688
+ });
1689
+ }, true]
1690
+ },
1691
+ $custom: {
1692
+ /** @type {GetPropertyInput} */
1693
+ $getPropertyInput () {
1694
+ return /** @type {HTMLInputElement} */ (
1695
+ DOM.filterChildElements(
1696
+ /**
1697
+ * @type {HTMLFieldSetElement & {
1698
+ * $getPropertyInput: GetPropertyInput
1699
+ * }}
1700
+ */ (this),
1701
+ ['legend', `input.propertyName-${typeNamespace}`]
1702
+ ).pop()
1703
+ );
1704
+ }
1705
+ }
1706
+ },
1707
+ [
1708
+ buildLegend({
1709
+ className,
1710
+ splice,
1711
+ itemIndex,
1712
+ // type,
1713
+ typeNamespace,
1714
+ arrayItems,
1715
+ propName,
1716
+ required,
1717
+ schema
1718
+ })
1719
+ ],
1720
+ arrayItems);
1721
+
1243
1722
  // We must ensure fieldset is built before passing it
1244
1723
  jml({'#': [
1245
- ...(/** @type {import('../typeChoices.js').BuildTypeChoices} */ (
1246
- buildTypeChoices
1247
- )({
1248
- // resultType,
1249
- // eslint-disable-next-line object-shorthand -- TS
1250
- topRoot: /** @type {HTMLDivElement} */ (topRoot),
1251
- // eslint-disable-next-line object-shorthand, @stylistic/max-len -- TS
1252
- format: /** @type {import('../formats.js').AvailableFormat} */ (format),
1253
- state: parentTypeObject.filelist
1254
- ? 'filelistArray'
1255
- : arrayState ?? type,
1256
- // itemIndex,
1257
- typeNamespace
1258
- }).domArray),
1724
+ ['span', {
1725
+ class: 'mapValue',
1726
+ title: type === 'map' &&
1727
+ /** @type {import('zodex').SzMap<any, any>} */ (
1728
+ specificSchemaObject
1729
+ )?.value?.description
1730
+ ? '(map value)'
1731
+ : type === 'record' &&
1732
+ /** @type {import('zodex').SzRecord} */ (
1733
+ specificSchemaObject
1734
+ )?.value?.description
1735
+ ? '(record value)'
1736
+ : undefined
1737
+ }, [
1738
+ type === 'map'
1739
+ ? /** @type {import('zodex').SzMap<any, any>} */ (
1740
+ specificSchemaObject
1741
+ )?.value?.description ?? 'Map value'
1742
+ : type === 'record'
1743
+ ? /** @type {import('zodex').SzRecord} */ (
1744
+ specificSchemaObject
1745
+ )?.value?.description ?? 'Record value'
1746
+ : '',
1747
+ ' '
1748
+ ]],
1749
+ ...(specificSchemaObject && !propName && !parentTypeObject.array
1750
+ ? [jml('span', {
1751
+ className: `optionalProperties-placeholder${optionalPropertyId}`
1752
+ })]
1753
+ : buildTypeChoicesForProperty({
1754
+ propName, schema, schemaIdx, autoTrigger
1755
+ })),
1756
+ ['span', {className: 'property-placeholder'}],
1259
1757
  nbsp.repeat(2),
1260
- ['button', {$on: {click (/** @type {Event} */ e) {
1261
- e.preventDefault();
1262
- // e.stopPropagation();
1263
- let splice;
1264
- if (sparse) {
1265
- const prevInputVal =
1266
- /**
1267
- * @type {HTMLInputElement & {
1268
- * $parseInt: ParseInt
1269
- * }}
1270
- */ (
1758
+ ['button', {
1759
+ disabled: type === 'tuple' && required &&
1760
+ // The last item of a tuple can have content after it, but earlier
1761
+ // items cannot
1762
+ /** @type {import('zodex').SzTuple} */
1763
+ (specificSchemaObject)?.items?.length - 1 > itemIndex,
1764
+ $on: {click (/** @type {Event} */ e) {
1765
+ e.preventDefault();
1766
+ // e.stopPropagation();
1767
+
1768
+ if (preventAdding(1)) {
1769
+ return;
1770
+ }
1771
+
1772
+ /** @type {number | "append" | undefined} */
1773
+ let splice;
1774
+ if (sparse) {
1775
+ const prevInputVal =
1271
1776
  /**
1272
- * @type {HTMLFieldSetElement & {
1273
- * $getPropertyInput: GetPropertyInput
1777
+ * @type {HTMLInputElement & {
1778
+ * $parseInt: ParseInt
1274
1779
  * }}
1275
1780
  */ (
1276
- fieldset
1277
- ).$getPropertyInput()).$parseInt();
1278
- splice = prevInputVal === false ? 'append' : prevInputVal;
1279
- }
1280
- thisButton.$addArrayElement({
1281
- splice, alwaysFocus: true
1282
- });
1283
- const newArrayFieldset =
1284
- /** @type {Element & {$getPropertyInput: GetPropertyInput}} */ (
1285
- arrayItems.lastElementChild
1286
- );
1287
- fieldset.after(newArrayFieldset);
1288
- if (sparse) {
1289
- const newPrevInput = /** @type {HTMLInputElement} */ (
1290
- newArrayFieldset.$getPropertyInput()
1291
- );
1292
- bringFocus(newPrevInput, true);
1293
- } else {
1294
- DOM.filterChildElements(
1295
- arrayItems, [
1296
- 'fieldset', 'legend', '.' + className
1297
- ]
1298
- ).forEach((span, i) => {
1299
- span.textContent = String(i + itemAdjust);
1781
+ /**
1782
+ * @type {HTMLFieldSetElement & {
1783
+ * $getPropertyInput: GetPropertyInput
1784
+ * }}
1785
+ */ (
1786
+ fieldset
1787
+ ).$getPropertyInput()).$parseInt();
1788
+ splice = prevInputVal === false ? 'append' : prevInputVal;
1789
+ }
1790
+ thisButton.$addArrayElement({
1791
+ splice, alwaysFocus: true, schema
1300
1792
  });
1301
- }
1302
- // Maybe not needed as addition (without renumbering)
1303
- // wouldn't yet add type
1304
- types.validateAllReferences({
1305
- // eslint-disable-next-line object-shorthand -- TS
1306
- topRoot: /** @type {HTMLDivElement} */ (topRoot)
1307
- });
1308
- arrayItems.$redrawMoveArrows();
1309
- }}}, ['+']],
1793
+ const newArrayFieldset =
1794
+ /** @type {Element & {$getPropertyInput: GetPropertyInput}} */ (
1795
+ arrayItems.lastElementChild
1796
+ );
1797
+ fieldset.after(newArrayFieldset);
1798
+ if (sparse || (
1799
+ // Because schemas (with descriptions?) don't use className for
1800
+ // property count (and the else block will rewrite the
1801
+ // property name)
1802
+ (specificSchemaObject || schema) &&
1803
+ !parentTypeObject.array
1804
+ )) {
1805
+ const newPrevInput = /** @type {HTMLInputElement} */ (
1806
+ newArrayFieldset.$getPropertyInput()
1807
+ );
1808
+ bringFocus(newPrevInput, true);
1809
+ } else {
1810
+ DOM.filterChildElements(
1811
+ arrayItems, [
1812
+ 'fieldset', 'legend', '.' + className
1813
+ ]
1814
+ ).forEach((span, i) => {
1815
+ span.textContent = elementDesc
1816
+ ? `${elementDesc} ${i + itemAdjust + 1}`
1817
+ : String(i + itemAdjust);
1818
+ });
1819
+ }
1820
+ // Maybe not needed as addition (without renumbering)
1821
+ // wouldn't yet add type
1822
+ types.validateAllReferences({
1823
+ // eslint-disable-next-line object-shorthand -- TS
1824
+ topRoot: /** @type {HTMLDivElement} */ (topRoot)
1825
+ });
1826
+ arrayItems.$redrawMoveArrows();
1827
+ }}
1828
+ }, ['+']],
1310
1829
  /**
1311
1830
  * @type {import('jamilih').JamilihArray}
1312
1831
  */
1313
1832
  ([
1314
1833
  'button',
1315
1834
  {
1835
+ disabled: required,
1316
1836
  $on: {
1317
1837
  click (/** @type {Event} */ e) {
1318
1838
  e.preventDefault();
1319
1839
  // e.stopPropagation();
1320
1840
  fieldset.remove();
1321
1841
  decrementItemIndex(arrayItems);
1322
- if (!sparse) {
1842
+ if (!sparse &&
1843
+ (!specificSchemaObject || parentTypeObject.array)
1844
+ ) {
1323
1845
  DOM.filterChildElements(arrayItems, [
1324
1846
  'fieldset', 'legend', '.' + className
1325
1847
  ]).forEach((span, i) => {
1326
- span.textContent = String(i + itemAdjust);
1848
+ span.textContent = elementDesc
1849
+ ? `${elementDesc} ${i + itemAdjust + 1}`
1850
+ : String(i + itemAdjust);
1327
1851
  });
1328
1852
  }
1329
1853
  // Maybe not needed as removal would remove circular
@@ -1346,6 +1870,7 @@ const arrayType = {
1346
1870
  class: `${type}Item-arrowHolder-${typeNamespace}`
1347
1871
  }, []]
1348
1872
  ]}, fieldset);
1873
+
1349
1874
  // Need to validate if adding more than one property (in
1350
1875
  // case two have empty string)
1351
1876
  if (editableProperties) {
@@ -1430,6 +1955,12 @@ const arrayType = {
1430
1955
  }
1431
1956
  },
1432
1957
  $on: {click () {
1958
+ if (preventAdding(1)) {
1959
+ return;
1960
+ }
1961
+
1962
+ // Todo: Should really check if all object properties have been used,
1963
+ // and stop and warn if so
1433
1964
  /**
1434
1965
  * @type {HTMLButtonElement & {
1435
1966
  * $addArrayElement: AddArrayElement,
@@ -1453,10 +1984,11 @@ const arrayType = {
1453
1984
  'Array length: ',
1454
1985
  ['input', {
1455
1986
  type: 'number',
1456
- value: (value && value.length) || 0,
1987
+ value: (objectValue && objectValue.length) || 0,
1457
1988
  step: 1,
1458
1989
  size: 4,
1459
- pattern: '\\d',
1990
+ class: 'arrayLength',
1991
+ pattern: String.raw`\d`,
1460
1992
  $on: {
1461
1993
  /**
1462
1994
  * @this {HTMLInputElement & {
@@ -1465,6 +1997,11 @@ const arrayType = {
1465
1997
  * @returns {Promise<void>}
1466
1998
  */
1467
1999
  async change () {
2000
+ if (preventAdding()) {
2001
+ this.value = this.$oldvalue ?? this.defaultValue;
2002
+ return;
2003
+ }
2004
+
1468
2005
  const arrayItems = $e(
1469
2006
  /** @type {HTMLElement} */ (
1470
2007
  this.closest('.arrayContents')
@@ -1488,16 +2025,54 @@ const arrayType = {
1488
2025
  const fieldset = /** @type {HTMLFieldSetElement} */ (
1489
2026
  input.closest('fieldset')
1490
2027
  );
1491
- fieldset.remove();
2028
+ if (!fieldset.matches('[data-required]')) {
2029
+ fieldset.remove();
2030
+ } else {
2031
+ // If not truncating the array, put the length back
2032
+ const arrayLengthInput =
2033
+ /**
2034
+ * @type {HTMLInputElement & {
2035
+ * $oldvalue: string
2036
+ * }}
2037
+ */ (
2038
+ $e(div, '.arrayLength')
2039
+ );
2040
+ arrayLengthInput.value = String(Number.parseInt(
2041
+ arrayLengthInput.value
2042
+ ) + 1);
2043
+ }
1492
2044
  });
1493
2045
  // Maybe not needed as removal would remove circular
1494
2046
  types.validateAllReferences({
1495
2047
  // eslint-disable-next-line object-shorthand -- TS
1496
2048
  topRoot: /** @type {HTMLDivElement} */ (topRoot)
1497
2049
  });
2050
+ } else {
2051
+ const element = /** @type {import('zodex').SzArray} */ (
2052
+ specificSchemaObject
2053
+ )?.element;
2054
+ if (!['void', 'undefined'].includes(
2055
+ element?.type
2056
+ ) && (
2057
+ element?.type !== 'union' ||
2058
+ /** @type {import('zodex').SzUnion} */
2059
+ (element)?.options?.every((option) => {
2060
+ return !['void', 'undefined'].includes(option.type);
2061
+ })
2062
+ )) {
2063
+ const diff = Number.parseInt(this.value) -
2064
+ Number.parseInt(this.$oldvalue ?? this.defaultValue);
2065
+ for (let i = 0; i < diff; i++) {
2066
+ // Timeout needed by Cypress at least or will get
2067
+ // validation triggered which prevents moving forward
2068
+ setTimeout(() => {
2069
+ div.$addArrayElement({});
2070
+ });
2071
+ }
2072
+ }
1498
2073
  }
1499
2074
  this.$oldvalue = this.value;
1500
- } catch (err) {
2075
+ } catch {
1501
2076
  this.value = this.$oldvalue; // Revert
1502
2077
  }
1503
2078
  }
@@ -1507,47 +2082,50 @@ const arrayType = {
1507
2082
  ]]
1508
2083
  : '';
1509
2084
 
1510
- const arrayItems = ['div', {
1511
- class: 'arrayItems',
1512
- $custom: {
1513
- $swapGroup, $redrawMoveArrows, $getArrayLength,
1514
- $inputsExceedingLength,
2085
+ const arrayItems =
2086
+ /** @type {HTMLDivElement & {$getArrayLength: GetArrayLength}} */ (
2087
+ jml('div', {
2088
+ class: 'arrayItems',
2089
+ $custom: {
2090
+ $swapGroup, $redrawMoveArrows, $getArrayLength,
2091
+ $inputsExceedingLength,
1515
2092
 
1516
- /**
1517
- * Only relevant for maps.
1518
- * @type {GetMapKeySelects}
1519
- * @this {HTMLDivElement}
1520
- */
1521
- $getMapKeySelects () {
1522
- const selects =
1523
2093
  /**
1524
- * @type {(HTMLSelectElement & {
1525
- * $getValue: import('../typeChoices.js').GetValue}
1526
- * )[]}
1527
- */ (DOM.filterChildElements(
1528
- this,
1529
- [
1530
- 'fieldset',
1531
- 'legend:first-child',
1532
- 'span.mapKey',
1533
- 'select.typeChoices-key-type-choices-only'
1534
- ]
1535
- ));
1536
- return selects;
1537
- },
2094
+ * Only relevant for maps.
2095
+ * @type {GetMapKeySelects}
2096
+ * @this {HTMLDivElement}
2097
+ */
2098
+ $getMapKeySelects () {
2099
+ const selects =
2100
+ /**
2101
+ * @type {(HTMLSelectElement & {
2102
+ * $getValue: import('../typeChoices.js').GetValue}
2103
+ * )[]}
2104
+ */ (DOM.filterChildElements(
2105
+ this,
2106
+ [
2107
+ 'fieldset',
2108
+ 'legend:first-child',
2109
+ 'span.mapKeyHolder',
2110
+ 'select.typeChoices-key-type-choices-only'
2111
+ ]
2112
+ ));
2113
+ return selects;
2114
+ },
1538
2115
 
1539
- /**
1540
- * @type {GetPropertyInputs}
1541
- * @this {HTMLDivElement}
1542
- */
1543
- $getPropertyInputs () {
1544
- return /** @type {HTMLInputElement[]} */ (DOM.filterChildElements(
1545
- this,
1546
- ['fieldset', 'legend', `input.propertyName-${typeNamespace}`]
1547
- ));
1548
- }
1549
- }
1550
- }];
2116
+ /**
2117
+ * @type {GetPropertyInputs}
2118
+ * @this {HTMLDivElement}
2119
+ */
2120
+ $getPropertyInputs () {
2121
+ return /** @type {HTMLInputElement[]} */ (DOM.filterChildElements(
2122
+ this,
2123
+ ['fieldset', 'legend', `input.propertyName-${typeNamespace}`]
2124
+ ));
2125
+ }
2126
+ }
2127
+ })
2128
+ );
1551
2129
 
1552
2130
  const minusButton = ['button', {$on: {click (/** @type {Event} */ e) {
1553
2131
  e.preventDefault();
@@ -1576,7 +2154,7 @@ const arrayType = {
1576
2154
  $e(/** @type {HTMLElement} */ (arrayContents), '.arrayItems')
1577
2155
  );
1578
2156
  const lastElement = arrayItems.lastElementChild;
1579
- if (lastElement) {
2157
+ if (lastElement && !lastElement.matches('[data-required]')) {
1580
2158
  lastElement.remove();
1581
2159
  decrementItemIndex(arrayItems);
1582
2160
  // eslint-disable-next-line @stylistic/max-len -- Long
@@ -1592,15 +2170,25 @@ const arrayType = {
1592
2170
  }}}, ['- Last item']],
1593
2171
  // We could only add this when there was more than one
1594
2172
  ['button', {$on: {click () {
2173
+ const arrayContents = /** @type {HTMLElement} */ (
2174
+ /** @type {HTMLElement} */ (this).closest('.arrayContents')
2175
+ );
1595
2176
  const arrayItems =
1596
2177
  // eslint-disable-next-line @stylistic/max-len -- Long
1597
2178
  /** @type {HTMLDivElement & {$getPropertyInputs: GetPropertyInputs}} */ ($e(
1598
- /** @type {HTMLElement} */ (
1599
- /** @type {HTMLElement} */ (this).closest('.arrayContents')
1600
- ),
2179
+ arrayContents,
1601
2180
  '.arrayItems'
1602
2181
  ));
1603
- DOM.removeChildren(arrayItems);
2182
+
2183
+ const optionalFieldsetItems = $$e(
2184
+ arrayContents,
2185
+ '.arrayItems > fieldset:not([data-required])'
2186
+ );
2187
+
2188
+ for (const optionalFieldsetItem of optionalFieldsetItems) {
2189
+ optionalFieldsetItem.remove();
2190
+ }
2191
+
1604
2192
  if (sparse) {
1605
2193
  decrementItemIndex(arrayItems);
1606
2194
  } else {
@@ -1617,159 +2205,279 @@ const arrayType = {
1617
2205
 
1618
2206
  const parentType = type;
1619
2207
 
1620
- // eslint-disable-next-line @stylistic/max-len -- Long
1621
- const div = /** @type {HTMLDivElement & {$addAndSetArrayElement: import('../formats/structuredCloning.js').AddAndSetArrayElement}} */ (
1622
- jml('div', /** @type {import('jamilih').JamilihAttributes} */ ({
1623
- dataset: {type},
1624
- // is: 'array-or-object-editor',
1625
- $custom: {
1626
- // eslint-disable-next-line @stylistic/max-len -- Long
1627
- /** @type {import('../formats/structuredCloning.js').AddAndSetArrayElement} */
1628
- $addAndSetArrayElement ({
1629
- propName, type, value, bringIntoFocus, setAValue
1630
- // , schemaContent, schemaState
1631
- }) {
1632
- if (parentType === 'map') {
1633
- if (propName === '0') {
1634
- this.$addArrayElement({propName});
1635
- const arrayItems = this.$getArrayItems();
1636
- const keyTypeChoices = /**
1637
- * @type {HTMLSelectElement & {
1638
- * $setType: import('../typeChoices.js').SetType,
1639
- * $getTypeRoot: import('../formatAndTypeChoices.js').
1640
- * TypeRootGetter
1641
- * }}
1642
- */ (DOM.filterChildElements(
1643
- arrayItems,
1644
- ['fieldset:last-of-type', 'legend', '.mapKey', 'select']
1645
- )[0]);
1646
- keyTypeChoices.$setType({
1647
- type, baseValue: value, bringIntoFocus
1648
- });
2208
+ const div =
2209
+ /**
2210
+ * @type {HTMLDivElement & {
2211
+ * $addAndSetArrayElement:
2212
+ * import('../formats/structuredCloning.js').AddAndSetArrayElement,
2213
+ * $addArrayElement: AddArrayElement
2214
+ * $getArrayItems: GetArrayItems,
2215
+ * $getMapKeySelects?: GetMapKeySelects
2216
+ * }}
2217
+ */ (
2218
+ jml('div', /** @type {import('jamilih').JamilihAttributes} */ ({
2219
+ dataset: {type},
2220
+ // is: 'array-or-object-editor',
2221
+ $custom: {
2222
+ // eslint-disable-next-line @stylistic/max-len -- Long
2223
+ /** @type {import('../formats/structuredCloning.js').AddAndSetArrayElement} */
2224
+ $addAndSetArrayElement ({
2225
+ propName, type, value, bringIntoFocus, setAValue,
2226
+ schemaContent: schema, schemaIdx, mustBeOptional
2227
+ }) {
2228
+ if (parentType === 'map') {
2229
+ if (propName === '0') {
2230
+ this.$addArrayElement({
2231
+ propName,
2232
+ // At least needed when value supplied
2233
+ autoTrigger: false
2234
+ });
2235
+ const arrayItems = this.$getArrayItems();
2236
+ const keyTypeChoices = /**
2237
+ * @type {HTMLSelectElement & {
2238
+ * $setType: import('../typeChoices.js').SetType,
2239
+ * $getTypeRoot: import('../formatAndTypeChoices.js').
2240
+ * TypeRootGetter
2241
+ * }}
2242
+ */ (DOM.filterChildElements(
2243
+ arrayItems,
2244
+ [
2245
+ 'fieldset:last-of-type', 'legend',
2246
+ '.mapKeyHolder', 'select'
2247
+ ]
2248
+ )[0]);
2249
+ keyTypeChoices.$setType({
2250
+ type, baseValue: value, bringIntoFocus,
2251
+ avoidReport: true
2252
+ });
1649
2253
 
1650
- // The key may itself be a map, etc.
1651
- return keyTypeChoices.$getTypeRoot();
2254
+ // The key may itself be a map, etc.
2255
+ return keyTypeChoices.$getTypeRoot();
2256
+ }
2257
+ } else if (parentType === 'record') {
2258
+ this.$addArrayElement({
2259
+ propName, autoTrigger: false,
2260
+ required: false
2261
+ });
2262
+ } else {
2263
+ // console.log('SCHEMA123', schema);
2264
+ this.$addArrayElement({
2265
+ propName, schema, autoTrigger: false,
2266
+ schemaIdx,
2267
+ required: !mustBeOptional && schema && !schema.isOptional &&
2268
+ schema.type !== 'never'
2269
+ });
1652
2270
  }
1653
- } else {
1654
- this.$addArrayElement({propName});
1655
- }
1656
- const typeChoices = this.$getTypeChoices();
1657
- typeChoices.$setType({type, baseValue: value, bringIntoFocus});
1658
- const root = typeChoices.$getTypeRoot();
1659
- // If run for all, causes problems with running `Error.cause`
1660
- // type twice and is inefficient;
1661
- // currently put behind `setAValue` as we need to set a value
1662
- // from `errorsSpecialType` (and `filelistType`)
1663
- if (setAValue) {
1664
- const typeObj = /** @type {import('../types.js').TypeObject} */ (
1665
- types.getTypeObject(type)
1666
- );
1667
- if (typeObj.setValue) {
1668
- typeObj.setValue({root, value});
2271
+ const typeChoices = this.$getTypeChoices();
2272
+ typeChoices.$setType({
2273
+ type, baseValue: value, bringIntoFocus,
2274
+ avoidReport: true
2275
+ });
2276
+ const root = typeChoices.$getTypeRoot();
2277
+ // If run for all, causes problems with running `Error.cause`
2278
+ // type twice and is inefficient;
2279
+ // currently put behind `setAValue` as we need to set a value
2280
+ // from `errorsSpecialType` (and `filelistType`)
2281
+ if (setAValue) {
2282
+ const typeObj =
2283
+ /** @type {import('../types.js').TypeObject} */ (
2284
+ types.getTypeObject(type)
2285
+ );
2286
+ if (typeObj.setValue) {
2287
+ typeObj.setValue({root, value});
2288
+ }
1669
2289
  }
1670
- }
1671
- types.validate({type, root, topRoot});
1672
- return root;
1673
- },
2290
+ types.validate({
2291
+ type, root, topRoot,
2292
+ // We don't want focus when values auto-added
2293
+ avoidReport: true
2294
+ });
2295
+ return root;
2296
+ },
1674
2297
 
1675
- /**
1676
- * @typedef {() => Element} GetAddArrayElement
1677
- */
2298
+ /**
2299
+ * @typedef {() => Element} GetAddArrayElement
2300
+ */
1678
2301
 
1679
- /** @type {GetAddArrayElement} */
1680
- $getAddArrayElement () {
1681
- const el = this.
1682
- lastElementChild.firstElementChild.nextElementSibling;
1683
- return sparse ? el.nextElementSibling : el;
1684
- },
1685
- /**
1686
- * @type {AddArrayElement}
1687
- * @this {HTMLDivElement & {
1688
- * $getAddArrayElement: GetAddArrayElement
1689
- * }}
1690
- */
1691
- // @ts-ignore TS is apparently getting wrong $addArrayElement
1692
- $addArrayElement ({propName, splice, alwaysFocus}) {
1693
- const addArrayElement = this.$getAddArrayElement();
2302
+ /** @type {GetAddArrayElement} */
2303
+ $getAddArrayElement () {
2304
+ const el = this.
2305
+ lastElementChild.firstElementChild.nextElementSibling;
2306
+ return sparse ? el.nextElementSibling : el;
2307
+ },
1694
2308
  /**
1695
- * @type {HTMLButtonElement & {
1696
- * $addArrayElement: AddArrayElement,
1697
- * $getArrayItems: GetArrayItems
2309
+ * @type {AddArrayElement}
2310
+ * @this {HTMLDivElement & {
2311
+ * $getAddArrayElement: GetAddArrayElement
1698
2312
  * }}
1699
2313
  */
1700
- (addArrayElement).$addArrayElement({propName, splice, alwaysFocus});
1701
- },
1702
- $getArrayItems () {
1703
- const addArrayElement = this.$getAddArrayElement();
1704
- return addArrayElement.previousElementSibling;
2314
+ // @ts-expect-error TS is apparently getting wrong $addArrayElement
2315
+ $addArrayElement ({
2316
+ propName, splice, alwaysFocus, required, schema, schemaIdx,
2317
+ autoTrigger
2318
+ }) {
2319
+ const addArrayElement = this.$getAddArrayElement();
2320
+ /**
2321
+ * @type {HTMLButtonElement & {
2322
+ * $addArrayElement: AddArrayElement,
2323
+ * $getArrayItems: GetArrayItems
2324
+ * }}
2325
+ */
2326
+ (addArrayElement).$addArrayElement({
2327
+ propName, splice, alwaysFocus, required, schema,
2328
+ schemaIdx,
2329
+ autoTrigger
2330
+ });
2331
+ },
2332
+ $getArrayItems () {
2333
+ const addArrayElement = this.$getAddArrayElement();
2334
+ return addArrayElement.previousElementSibling;
2335
+ },
2336
+ $getTypeChoices () {
2337
+ const arrayItems = this.$getArrayItems();
2338
+ return $e(
2339
+ arrayItems.lastElementChild,
2340
+ `fieldset > .typeChoices-${typeNamespace}` // Avoid keys
2341
+ );
2342
+ }
1705
2343
  },
1706
- $getTypeChoices () {
1707
- const arrayItems = this.$getArrayItems();
1708
- return $e(
1709
- arrayItems.lastElementChild,
1710
- `fieldset > .typeChoices-${typeNamespace}` // Avoid keys
1711
- );
1712
- }
1713
- },
1714
- $on: {
1715
- click (ev) {
1716
- const e = /** @type {Event} */ (ev);
1717
- // eslint-disable-next-line prefer-destructuring -- TS
1718
- const target = /** @type {HTMLInputElement} */ (e.target);
1719
-
1720
- // We needed to stop preventing the default for the
1721
- // invalid date checkbox; is this sufficient to prevent
1722
- // other stray clicks apparently meant for the array
1723
- // and object reference checking?
1724
- if (![
1725
- 'checkbox', 'radio', 'file',
1726
- 'datetime-local',
1727
- // For label radio clicks
1728
- undefined
1729
- ].includes(target.type)) {
1730
- e.preventDefault();
2344
+ $on: {
2345
+ click (ev) {
2346
+ const e = /** @type {Event} */ (ev);
2347
+ // eslint-disable-next-line prefer-destructuring -- TS
2348
+ const target = /** @type {HTMLInputElement} */ (e.target);
2349
+
2350
+ // We needed to stop preventing the default for the
2351
+ // invalid date checkbox; is this sufficient to prevent
2352
+ // other stray clicks apparently meant for the array
2353
+ // and object reference checking?
2354
+ if (![
2355
+ 'checkbox', 'radio', 'file',
2356
+ 'datetime-local',
2357
+ // For label radio clicks
2358
+ undefined
2359
+ ].includes(target.type)) {
2360
+ e.preventDefault();
2361
+ }
1731
2362
  }
1732
2363
  }
1733
- }
1734
- }), /** @type {import('jamilih').JamilihChildren} */ ([
1735
- DOM.initialCaps(
1736
- /** @type {import('../types.js').AvailableType} */
1737
- (type)
1738
- ).replace(/s$/u, ''), nbsp.repeat(2),
1739
- type === 'filelist'
1740
- ? ['input', {
1741
- name: typeNamespace + '-filelist',
1742
- multiple: true,
1743
- type: 'file',
1744
- $on: {
1745
- /**
1746
- * @this {HTMLInputElement}
1747
- */
1748
- change () {
1749
- /* istanbul ignore if */
1750
- if (!this.files) {
1751
- return;
1752
- }
1753
- for (let i = 0; i < this.files.length; i++) {
1754
- const file = this.files.item(i);
1755
-
1756
- div.$addAndSetArrayElement({
1757
- propName: String(i),
1758
- type: 'file',
1759
- value: file,
1760
- bringIntoFocus: false,
1761
- setAValue: true
1762
- });
2364
+ }), /** @type {import('jamilih').JamilihChildren} */ ([
2365
+ [specificSchemaObject ? 'span' : 'b', {
2366
+ title: specificSchemaObject?.description ?? (DOM.initialCaps(
2367
+ /** @type {import('../types.js').AvailableType} */
2368
+ (type)
2369
+ ).replace(/s$/u, ''))
2370
+ }, [
2371
+ specificSchemaObject
2372
+ ? ''
2373
+ : DOM.initialCaps(
2374
+ /** @type {import('../types.js').AvailableType} */
2375
+ (type)
2376
+ ).replace(/s$/u, '')
2377
+ ]],
2378
+ nbsp.repeat(2),
2379
+ type === 'filelist'
2380
+ ? ['input', {
2381
+ name: typeNamespace + '-filelist',
2382
+ multiple: true,
2383
+ type: 'file',
2384
+ $on: {
2385
+ /**
2386
+ * @this {HTMLInputElement}
2387
+ */
2388
+ change () {
2389
+ /* istanbul ignore if */
2390
+ if (!this.files) {
2391
+ return;
2392
+ }
2393
+ for (let i = 0; i < this.files.length; i++) {
2394
+ const file = this.files.item(i);
2395
+
2396
+ div.$addAndSetArrayElement({
2397
+ propName: String(i),
2398
+ type: 'file',
2399
+ value: file,
2400
+ bringIntoFocus: false,
2401
+ setAValue: true
2402
+ });
2403
+ }
1763
2404
  }
1764
2405
  }
1765
- }
1766
- }]
1767
- : '',
1768
- minusButton,
1769
- arrayContents
1770
- ]))
1771
- );
2406
+ }]
2407
+ : '',
2408
+ minusButton,
2409
+ arrayContents
2410
+ ]))
2411
+ );
1772
2412
  topRoot = topRoot || div;
2413
+
2414
+ if (!objectValue && specificSchemaObject) {
2415
+ switch (type) {
2416
+ case 'object': {
2417
+ // See comment referencing `arrayType.js` in `typeChoices.js`
2418
+ if (!schemaFallingBack) {
2419
+ for (const [prop, val] of
2420
+ Object.entries(
2421
+ /** @type {import('zodex').SzObject} */ (
2422
+ specificSchemaObject
2423
+ ).properties ?? {}
2424
+ )
2425
+ ) {
2426
+ if (!val.isOptional && val.type !== 'never') {
2427
+ div.$addArrayElement({propName: prop, required: true});
2428
+ }
2429
+ }
2430
+ }
2431
+ break;
2432
+ }
2433
+ case 'tuple': {
2434
+ // See comment referencing `arrayType.js` in `typeChoices.js`
2435
+ if (!schemaFallingBack) {
2436
+ const specificSchemaObj = /** @type {import('zodex').SzTuple} */ (
2437
+ specificSchemaObject
2438
+ );
2439
+ if (
2440
+ /** @type {import('zodex').SzTuple} */ (
2441
+ specificSchemaObject
2442
+ )?.items?.[0]?.type !== 'never'
2443
+ ) {
2444
+ for (const schema of specificSchemaObj.items) {
2445
+ div.$addArrayElement({schema, required: true});
2446
+ }
2447
+ }
2448
+ }
2449
+ break;
2450
+ } case 'array': case 'arrayNonindexKeys': {
2451
+ const {minLength = 0} = /** @type {import('zodex').SzArray} */ (
2452
+ specificSchemaObject
2453
+ ) ?? {};
2454
+ const arrayLengthInput =
2455
+ /** @type {HTMLInputElement & {$oldvalue: string}} */ (
2456
+ $e(div, '.arrayLength')
2457
+ );
2458
+ arrayLengthInput.value = String(minLength);
2459
+ arrayLengthInput.$oldvalue = String(minLength);
2460
+
2461
+ for (let i = 0; i < minLength; i++) {
2462
+ div.$addArrayElement({required: true});
2463
+ }
2464
+ break;
2465
+ } case 'set': {
2466
+ // See comment referencing `arrayType.js` in `typeChoices.js`
2467
+ if (!schemaFallingBack) {
2468
+ const {minSize = 0} = /** @type {import('zodex').SzSet} */ (
2469
+ specificSchemaObject
2470
+ ) ?? {};
2471
+ for (let i = 0; i < minSize; i++) {
2472
+ div.$addArrayElement({required: true});
2473
+ }
2474
+ }
2475
+ break;
2476
+ } default:
2477
+ break;
2478
+ }
2479
+ }
2480
+
1773
2481
  return [div];
1774
2482
  }
1775
2483
  };