@es-joy/jsoe 0.3.0 → 0.4.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 (189) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc.cjs +1 -0
  3. package/.ncurc.cjs +9 -0
  4. package/CHANGES.md +4 -0
  5. package/demo/index.html +8 -0
  6. package/demo/index.js +1 -1
  7. package/dist/formatAndTypeChoices.d.ts +56 -0
  8. package/dist/formatAndTypeChoices.d.ts.map +1 -0
  9. package/dist/formats/indexedDBKey.d.ts +4 -0
  10. package/dist/formats/indexedDBKey.d.ts.map +1 -0
  11. package/dist/formats/json.d.ts +4 -0
  12. package/dist/formats/json.d.ts.map +1 -0
  13. package/dist/formats/schemaAndArbitrary.d.ts +6 -0
  14. package/dist/formats/schemaAndArbitrary.d.ts.map +1 -0
  15. package/dist/formats/schemaOnly.d.ts +6 -0
  16. package/dist/formats/schemaOnly.d.ts.map +1 -0
  17. package/dist/formats/structuredCloning.d.ts +20 -0
  18. package/dist/formats/structuredCloning.d.ts.map +1 -0
  19. package/dist/formats.d.ts +26 -0
  20. package/dist/formats.d.ts.map +1 -0
  21. package/dist/fundamentalTypes/BooleanObjectType.d.ts +8 -0
  22. package/dist/fundamentalTypes/BooleanObjectType.d.ts.map +1 -0
  23. package/dist/fundamentalTypes/NumberObjectType.d.ts +6 -0
  24. package/dist/fundamentalTypes/NumberObjectType.d.ts.map +1 -0
  25. package/dist/fundamentalTypes/StringObjectType.d.ts +6 -0
  26. package/dist/fundamentalTypes/StringObjectType.d.ts.map +1 -0
  27. package/dist/fundamentalTypes/arrayReferenceType.d.ts +8 -0
  28. package/dist/fundamentalTypes/arrayReferenceType.d.ts.map +1 -0
  29. package/dist/fundamentalTypes/arrayType.d.ts +60 -0
  30. package/dist/fundamentalTypes/arrayType.d.ts.map +1 -0
  31. package/dist/fundamentalTypes/bigintType.d.ts +6 -0
  32. package/dist/fundamentalTypes/bigintType.d.ts.map +1 -0
  33. package/dist/fundamentalTypes/dateType.d.ts +26 -0
  34. package/dist/fundamentalTypes/dateType.d.ts.map +1 -0
  35. package/dist/fundamentalTypes/nullType.d.ts +6 -0
  36. package/dist/fundamentalTypes/nullType.d.ts.map +1 -0
  37. package/dist/fundamentalTypes/numberType.d.ts +6 -0
  38. package/dist/fundamentalTypes/numberType.d.ts.map +1 -0
  39. package/dist/fundamentalTypes/objectReferenceType.d.ts +8 -0
  40. package/dist/fundamentalTypes/objectReferenceType.d.ts.map +1 -0
  41. package/dist/fundamentalTypes/objectType.d.ts +6 -0
  42. package/dist/fundamentalTypes/objectType.d.ts.map +1 -0
  43. package/dist/fundamentalTypes/regexpType.d.ts +8 -0
  44. package/dist/fundamentalTypes/regexpType.d.ts.map +1 -0
  45. package/dist/fundamentalTypes/sparseUndefinedType.d.ts +6 -0
  46. package/dist/fundamentalTypes/sparseUndefinedType.d.ts.map +1 -0
  47. package/dist/fundamentalTypes/stringType.d.ts +6 -0
  48. package/dist/fundamentalTypes/stringType.d.ts.map +1 -0
  49. package/dist/fundamentalTypes/undefinedType.d.ts +6 -0
  50. package/dist/fundamentalTypes/undefinedType.d.ts.map +1 -0
  51. package/dist/index.d.ts +5 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/src/deepEqual.d.ts +3 -0
  54. package/dist/src/deepEqual.d.ts.map +1 -0
  55. package/dist/src/formatAndTypeChoices.d.ts +47 -0
  56. package/dist/src/formatAndTypeChoices.d.ts.map +1 -0
  57. package/dist/src/formats/indexedDBKey.d.ts +9 -0
  58. package/dist/src/formats/indexedDBKey.d.ts.map +1 -0
  59. package/dist/src/formats/json.d.ts +9 -0
  60. package/dist/src/formats/json.d.ts.map +1 -0
  61. package/dist/src/formats/schemaAndArbitrary.d.ts +6 -0
  62. package/dist/src/formats/schemaAndArbitrary.d.ts.map +1 -0
  63. package/dist/src/formats/schemaOnly.d.ts +6 -0
  64. package/dist/src/formats/schemaOnly.d.ts.map +1 -0
  65. package/dist/src/formats/structuredCloning.d.ts +15 -0
  66. package/dist/src/formats/structuredCloning.d.ts.map +1 -0
  67. package/dist/src/formats.d.ts +18 -0
  68. package/dist/src/formats.d.ts.map +1 -0
  69. package/dist/src/fundamentalTypes/BooleanObjectType.d.ts +6 -0
  70. package/dist/src/fundamentalTypes/BooleanObjectType.d.ts.map +1 -0
  71. package/dist/src/fundamentalTypes/NumberObjectType.d.ts +6 -0
  72. package/dist/src/fundamentalTypes/NumberObjectType.d.ts.map +1 -0
  73. package/dist/src/fundamentalTypes/StringObjectType.d.ts +6 -0
  74. package/dist/src/fundamentalTypes/StringObjectType.d.ts.map +1 -0
  75. package/dist/src/fundamentalTypes/arrayReferenceType.d.ts +6 -0
  76. package/dist/src/fundamentalTypes/arrayReferenceType.d.ts.map +1 -0
  77. package/dist/src/fundamentalTypes/arrayType.d.ts +6 -0
  78. package/dist/src/fundamentalTypes/arrayType.d.ts.map +1 -0
  79. package/dist/src/fundamentalTypes/bigintType.d.ts +6 -0
  80. package/dist/src/fundamentalTypes/bigintType.d.ts.map +1 -0
  81. package/dist/src/fundamentalTypes/dateType.d.ts +6 -0
  82. package/dist/src/fundamentalTypes/dateType.d.ts.map +1 -0
  83. package/dist/src/fundamentalTypes/nullType.d.ts +6 -0
  84. package/dist/src/fundamentalTypes/nullType.d.ts.map +1 -0
  85. package/dist/src/fundamentalTypes/numberType.d.ts +6 -0
  86. package/dist/src/fundamentalTypes/numberType.d.ts.map +1 -0
  87. package/dist/src/fundamentalTypes/objectReferenceType.d.ts +6 -0
  88. package/dist/src/fundamentalTypes/objectReferenceType.d.ts.map +1 -0
  89. package/dist/src/fundamentalTypes/objectType.d.ts +6 -0
  90. package/dist/src/fundamentalTypes/objectType.d.ts.map +1 -0
  91. package/dist/src/fundamentalTypes/regexpType.d.ts +6 -0
  92. package/dist/src/fundamentalTypes/regexpType.d.ts.map +1 -0
  93. package/dist/src/fundamentalTypes/sparseUndefinedType.d.ts +6 -0
  94. package/dist/src/fundamentalTypes/sparseUndefinedType.d.ts.map +1 -0
  95. package/dist/src/fundamentalTypes/stringType.d.ts +6 -0
  96. package/dist/src/fundamentalTypes/stringType.d.ts.map +1 -0
  97. package/dist/src/fundamentalTypes/undefinedType.d.ts +6 -0
  98. package/dist/src/fundamentalTypes/undefinedType.d.ts.map +1 -0
  99. package/dist/src/index.d.ts +5 -0
  100. package/dist/src/index.d.ts.map +1 -0
  101. package/dist/src/subTypes/blobHTMLType.d.ts +26 -0
  102. package/dist/src/subTypes/blobHTMLType.d.ts.map +1 -0
  103. package/dist/src/subTypes/falseType.d.ts +6 -0
  104. package/dist/src/subTypes/falseType.d.ts.map +1 -0
  105. package/dist/src/subTypes/trueType.d.ts +6 -0
  106. package/dist/src/subTypes/trueType.d.ts.map +1 -0
  107. package/dist/src/superTypes/SpecialNumberSuperType.d.ts +11 -0
  108. package/dist/src/superTypes/SpecialNumberSuperType.d.ts.map +1 -0
  109. package/dist/src/superTypes/SpecialRealNumberSuperType.d.ts +6 -0
  110. package/dist/src/superTypes/SpecialRealNumberSuperType.d.ts.map +1 -0
  111. package/dist/src/typeChoices.d.ts +30 -0
  112. package/dist/src/typeChoices.d.ts.map +1 -0
  113. package/dist/src/types.d.ts +333 -0
  114. package/dist/src/types.d.ts.map +1 -0
  115. package/dist/src/utils/dialogs.d.ts +29 -0
  116. package/dist/src/utils/dialogs.d.ts.map +1 -0
  117. package/dist/src/utils/jsonPointer.d.ts +42 -0
  118. package/dist/src/utils/jsonPointer.d.ts.map +1 -0
  119. package/dist/src/utils/templateUtils.d.ts +59 -0
  120. package/dist/src/utils/templateUtils.d.ts.map +1 -0
  121. package/dist/src/utils/types.d.ts +2 -0
  122. package/dist/src/utils/types.d.ts.map +1 -0
  123. package/dist/subTypes/blobHTMLType.d.ts +14 -0
  124. package/dist/subTypes/blobHTMLType.d.ts.map +1 -0
  125. package/dist/subTypes/falseType.d.ts +8 -0
  126. package/dist/subTypes/falseType.d.ts.map +1 -0
  127. package/dist/subTypes/trueType.d.ts +8 -0
  128. package/dist/subTypes/trueType.d.ts.map +1 -0
  129. package/dist/superTypes/SpecialNumberSuperType.d.ts +14 -0
  130. package/dist/superTypes/SpecialNumberSuperType.d.ts.map +1 -0
  131. package/dist/superTypes/SpecialRealNumberSuperType.d.ts +6 -0
  132. package/dist/superTypes/SpecialRealNumberSuperType.d.ts.map +1 -0
  133. package/dist/typeChoices.d.ts +103 -0
  134. package/dist/typeChoices.d.ts.map +1 -0
  135. package/dist/types.d.ts +523 -0
  136. package/dist/types.d.ts.map +1 -0
  137. package/dist/utils/dialogs.d.ts +109 -0
  138. package/dist/utils/dialogs.d.ts.map +1 -0
  139. package/dist/utils/jsonPointer.d.ts +46 -0
  140. package/dist/utils/jsonPointer.d.ts.map +1 -0
  141. package/dist/utils/templateUtils.d.ts +59 -0
  142. package/dist/utils/templateUtils.d.ts.map +1 -0
  143. package/dist/utils/types.d.ts +2 -0
  144. package/dist/utils/types.d.ts.map +1 -0
  145. package/dist/vendor/jamilih/dist/jml-es.d.ts +436 -0
  146. package/dist/vendor/jamilih/dist/jml-es.d.ts.map +1 -0
  147. package/dist/vendor/typeson-registry/dist/index.d.ts +400 -0
  148. package/dist/vendor/typeson-registry/dist/index.d.ts.map +1 -0
  149. package/dist/vendor-imports.d.ts +3 -0
  150. package/dist/vendor-imports.d.ts.map +1 -0
  151. package/package.json +16 -11
  152. package/postorder-traversal.js +58 -0
  153. package/src/formatAndTypeChoices.js +95 -50
  154. package/src/formats/indexedDBKey.js +49 -57
  155. package/src/formats/json.js +47 -56
  156. package/src/formats/schemaOnly.js +12 -2
  157. package/src/formats/structuredCloning.js +162 -105
  158. package/src/formats.js +38 -10
  159. package/src/fundamentalTypes/BooleanObjectType.js +6 -4
  160. package/src/fundamentalTypes/NumberObjectType.js +5 -3
  161. package/src/fundamentalTypes/StringObjectType.js +1 -1
  162. package/src/fundamentalTypes/arrayReferenceType.js +29 -14
  163. package/src/fundamentalTypes/arrayType.js +789 -278
  164. package/src/fundamentalTypes/bigintType.js +2 -2
  165. package/src/fundamentalTypes/dateType.js +42 -14
  166. package/src/fundamentalTypes/nullType.js +1 -1
  167. package/src/fundamentalTypes/numberType.js +2 -2
  168. package/src/fundamentalTypes/objectReferenceType.js +7 -5
  169. package/src/fundamentalTypes/regexpType.js +45 -24
  170. package/src/fundamentalTypes/sparseUndefinedType.js +7 -1
  171. package/src/fundamentalTypes/stringType.js +1 -1
  172. package/src/fundamentalTypes/undefinedType.js +2 -2
  173. package/src/subTypes/blobHTMLType.js +42 -22
  174. package/src/subTypes/falseType.js +2 -2
  175. package/src/subTypes/trueType.js +2 -2
  176. package/src/superTypes/SpecialNumberSuperType.js +13 -6
  177. package/src/superTypes/SpecialRealNumberSuperType.js +11 -5
  178. package/src/typeChoices.js +137 -55
  179. package/src/types.js +309 -146
  180. package/src/utils/dialogs.js +98 -29
  181. package/src/utils/jsonPointer.js +4 -0
  182. package/src/utils/templateUtils.js +27 -12
  183. package/src/vendor-imports.js +8 -0
  184. package/tsconfig.json +18 -0
  185. package/typings/sceditor.d.ts +20 -0
  186. package/vendor/jamilih/dist/jml-es.js +1019 -389
  187. package/vendor/jamilih/dist/jml.d.ts +436 -0
  188. package/vendor/jamilih/package.json +136 -0
  189. package/vendor/typeson-registry/dist/index.js +365 -83
@@ -1,4 +1,4 @@
1
- import {jml, nbsp} from '../../vendor/jamilih/dist/jml-es.js';
1
+ import {jml, nbsp} from '../vendor-imports.js';
2
2
 
3
3
  import Types, {getPropertyValueFromLegend} from '../types.js';
4
4
  import {$e, U, DOM} from '../utils/templateUtils.js';
@@ -8,35 +8,86 @@ import {
8
8
  } from '../utils/jsonPointer.js';
9
9
 
10
10
  /**
11
- * @type {import('../types.js').TypeObject}
11
+ * @typedef {number} Integer
12
+ */
13
+ /**
14
+ * @callback ParseInt
15
+ * @this {HTMLInputElement}
16
+ * @returns {false|Integer}
17
+ */
18
+ /**
19
+ * @typedef {() => HTMLInputElement[]} InputsExceedingLength
20
+ */
21
+ /**
22
+ * @callback GetPropertyInputs
23
+ * @returns {HTMLInputElement[]}
24
+ */
25
+
26
+ /**
27
+ * @callback RedrawMoveArrows
28
+ * @returns {void}
29
+ */
30
+ /**
31
+ * @typedef {() => HTMLDivElement & {
32
+ * $inputsExceedingLength: InputsExceedingLength,
33
+ * $getPropertyInputs: GetPropertyInputs,
34
+ * $redrawMoveArrows: RedrawMoveArrows
35
+ * }} GetArrayItems
36
+ */
37
+ /**
38
+ * @typedef {() => Promise<void>} Validate
39
+ */
40
+
41
+ /**
42
+ * @callback Resort
43
+ * @param {{alwaysFocus?: true}} cfg
44
+ * @returns {void}
45
+ */
46
+
47
+ /**
48
+ * @typedef {() => HTMLInputElement|undefined} GetPropertyInput
49
+ */
50
+
51
+ /**
52
+ * @type {import('../types.js').TypeObject & {sparse?: boolean|undefined}}
12
53
  */
13
54
  const arrayType = {
14
55
  option: ['Array'],
15
56
  array: true,
57
+ // sparse: undefined, // Don't add as will be copied
16
58
  regexEndings: [',', ']'],
17
59
  stringRegexBegin: /^\[/u,
18
60
  stringRegexEnd: /^\]/u,
19
- toValue (s, {
20
- format,
21
- /* istanbul ignore next -- Just a guard */
22
- endMatchTypeObjs = [],
23
- remnant: innerContents,
24
- rootHolder
25
- }) {
61
+ toValue (s, info) {
62
+ const {
63
+ /* istanbul ignore next -- Just a guard */
64
+ endMatchTypeObjs = [],
65
+ remnant: innerContents,
66
+ rootHolder
67
+ } = /** @type {import('../types.js').RootInfo} */ (info);
68
+ // eslint-disable-next-line prefer-destructuring -- TS
69
+ const format = /** @type {import('../types.js').RootInfo} */ (info).format;
70
+
26
71
  const {sparse} = this;
27
72
  const state = sparse
28
73
  ? 'arrayNonindexKeys'
29
74
  // ? 'sparseArrays'
30
75
  : (this.array ? 'array' : 'object');
76
+ /** @type {{[key: (string|number)]: any}} */
31
77
  const retObj = this.array ? [] : {};
32
78
  let stringVal = innerContents !== undefined
33
79
  ? innerContents
34
80
  /* istanbul ignore next -- Unreachable? */
35
81
  : s;
82
+
83
+ /**
84
+ * @param {boolean} beginOnly
85
+ * @returns {boolean}
86
+ */
36
87
  const checkEnd = (beginOnly) => {
37
88
  if (beginOnly && endMatchTypeObjs.length) {
38
89
  const endMatch = stringVal.match(
39
- endMatchTypeObjs.slice(-1)[0].stringRegexEnd
90
+ /** @type {RegExp} */ (endMatchTypeObjs.slice(-1)[0].stringRegexEnd)
40
91
  );
41
92
  if (endMatch) {
42
93
  endMatchTypeObjs.pop(); // Safe now to extract
@@ -90,6 +141,11 @@ const arrayType = {
90
141
  }
91
142
  return {value: retObj, remnant: stringVal};
92
143
  }
144
+
145
+ /**
146
+ * @param {boolean} [notFirstRun]
147
+ * @returns {void}
148
+ */
93
149
  const parse = (notFirstRun) => {
94
150
  if (!stringVal) {
95
151
  return;
@@ -122,7 +178,10 @@ const arrayType = {
122
178
  }
123
179
  // console.log('vvv', stringVal, beginOnly, v);
124
180
  if (assign) {
125
- retObj[pr] = v;
181
+ // eslint-disable-next-line max-len -- Long
182
+ /** @type {{[key: string]: import('../formats.js').StructuredCloneValue}} */ (
183
+ retObj
184
+ )[pr] = v;
126
185
  }
127
186
  if (checkEnd(beginOnly)) {
128
187
  return;
@@ -134,19 +193,31 @@ const arrayType = {
134
193
  },
135
194
  getValue ({root, stateObj = {}, currentPath = ''}) {
136
195
  const top = currentPath === '';
137
- const arrayItems = $e(root, '.arrayItems');
196
+ const arrayItems = /** @type {Element} */ ($e(root, '.arrayItems'));
138
197
  const fieldsets = arrayItems.children;
198
+
199
+ /**
200
+ * @type {({[key: string]: any})|any[]}
201
+ */
139
202
  const ret = root.dataset.type === 'object'
140
203
  ? {}
141
204
  : !this.sparse
142
205
  ? []
143
- : Array.from({length: Number.parseInt(DOM.filterChildElements(
144
- root,
145
- ['div', 'div', 'label', 'input']
146
- )[0].value)});
206
+ : Array.from({length: Number.parseInt(
207
+ /** @type {HTMLInputElement} */ (DOM.filterChildElements(
208
+ root,
209
+ ['div', 'div', 'label', 'input']
210
+ )[0]).value
211
+ )});
147
212
 
148
213
  // Todo: Should this be renamed per return arguments to
149
214
  // `getRefOrVal` or is it ok?
215
+
216
+ /**
217
+ * @param {HTMLDivElement} root
218
+ * @param {string} currentPathPart
219
+ * @returns {[boolean, boolean?]}
220
+ */
150
221
  const getValOrRef = (root, currentPathPart) => {
151
222
  const value = Types.getValueForRoot(
152
223
  root, stateObj, currentPath + '/' + currentPathPart
@@ -160,21 +231,25 @@ const arrayType = {
160
231
  };
161
232
  [...fieldsets].forEach((fieldset) => {
162
233
  const legend = fieldset.firstElementChild;
163
- const propVal = getPropertyValueFromLegend(legend);
164
- const root = $e(fieldset, 'div[data-type]');
234
+ const propVal = getPropertyValueFromLegend(
235
+ /** @type {HTMLLegendElement} */ (legend)
236
+ );
237
+ const root = /** @type {HTMLDivElement} */ (
238
+ $e(fieldset, 'div[data-type]')
239
+ );
165
240
  /* istanbul ignore if -- Should err first? */
166
241
  if (!root) {
167
242
  return;
168
243
  }
169
244
 
170
- const objectProperty = $e(legend, 'input');
245
+ const objectProperty = $e(/** @type {Element} */ (legend), 'input');
171
246
  const [isVal, value] = getValOrRef(
172
247
  root,
173
248
  propVal
174
249
  );
175
250
  if (isVal) {
176
251
  if (objectProperty) {
177
- ret[propVal] = value;
252
+ /** @type {{[key: string]: any}} */ (ret)[propVal] = value;
178
253
  } else {
179
254
  ret.push(value);
180
255
  }
@@ -190,13 +265,15 @@ const arrayType = {
190
265
  obj: ret
191
266
  });
192
267
 
193
- referencePath = getJSONPointerParts(referencePath);
194
- const referenceFinalPathPart = referencePath.pop();
195
- const referenceParentObj = referencePath.reduce(
268
+ const referencePathJsonPtr = getJSONPointerParts(referencePath);
269
+ const referenceFinalPathPart = referencePathJsonPtr.pop();
270
+ const referenceParentObj = referencePathJsonPtr.reduce(
196
271
  (obj, pathPart) => reduceJSONPointerParts(obj, pathPart),
197
272
  ret
198
273
  );
199
- referenceParentObj[referenceFinalPathPart] = referentObj;
274
+ /** @type {{[key: string]: any}} */ (referenceParentObj)[
275
+ /** @type {string} */ (referenceFinalPathPart)
276
+ ] = referentObj;
200
277
  }
201
278
  );
202
279
  }
@@ -207,6 +284,15 @@ const arrayType = {
207
284
  viewUI ({typeNamespace, type, value, topRoot, resultType, format}) {
208
285
  // const {sparse} = this;
209
286
  let itemIndex = -1;
287
+
288
+ /**
289
+ * @param {{
290
+ * itemIndex: number,
291
+ * typeNamespace?: string,
292
+ * propName: string
293
+ * }} cfg
294
+ * @returns {import('jamilih').JamilihArray}
295
+ */
210
296
  const buildLegend = ({
211
297
  /* className, type, arrayItems, */
212
298
  itemIndex, typeNamespace, propName
@@ -228,74 +314,105 @@ const arrayType = {
228
314
  ]]
229
315
  ]];
230
316
  };
231
- const div = jml('div', {
232
- class: 'arrayHolder',
233
- dataset: {type},
234
- $custom: {
235
- $addAndSetArrayElement ({
236
- propName, type, value, bringIntoFocus,
237
- schemaContent, schemaState
238
- }) {
239
- const fieldset = this.$addArrayElement({propName});
240
- const root = Types.getUIForModeAndType({
241
- resultType,
242
- readonly: true,
243
- typeNamespace, type, topRoot,
244
- bringIntoFocus,
245
- format, schemaContent, schemaState,
246
- value,
247
- hasValue: true // type === 'sparseArrays' && value
248
- });
249
- jml(root, fieldset);
250
- return root;
251
- },
252
- $addArrayElement ({propName}) {
253
- itemIndex++;
254
- const arrayItems = this.$getArrayItems();
255
- const className = `${type}Item`;
256
- const fieldset = jml('fieldset', [
257
- buildLegend({
258
- className, itemIndex, type, typeNamespace,
259
- arrayItems, propName
260
- })
261
- ], arrayItems);
262
- return fieldset;
263
- },
264
- $getArrayItems () {
265
- return this.lastElementChild.lastElementChild;
317
+ const div = /** @type {HTMLDivElement} */ (
318
+ jml('div', /** @type {import('jamilih').JamilihAttributes} */ ({
319
+ class: 'arrayHolder',
320
+ dataset: {type},
321
+ $custom: {
322
+ /**
323
+ * @param {{
324
+ * propName: name,
325
+ * type: import('../types.js').AvailableType,
326
+ * value: import('../formats.js').StructuredCloneValue,
327
+ * bringIntoFocus: boolean,
328
+ * schemaContent: object,
329
+ * schemaState:
330
+ * import('../types.js').GetPossibleSchemasForPathAndType
331
+ * }} cfg
332
+ * @returns {Element}
333
+ */
334
+ $addAndSetArrayElement ({
335
+ propName, type, value, bringIntoFocus,
336
+ schemaContent, schemaState
337
+ }) {
338
+ const fieldset = this.$addArrayElement({propName});
339
+ const root = Types.getUIForModeAndType({
340
+ resultType,
341
+ readonly: true,
342
+ typeNamespace, type, topRoot,
343
+ bringIntoFocus,
344
+ format, schemaContent, schemaState,
345
+ value,
346
+ hasValue: true // type === 'sparseArrays' && value
347
+ });
348
+ jml(root, fieldset);
349
+ return root;
350
+ },
351
+ /**
352
+ * @param {{propName: string}} cfg
353
+ * @returns {HTMLFieldSetElement}
354
+ */
355
+ $addArrayElement ({propName}) {
356
+ itemIndex++;
357
+ const arrayItems = this.$getArrayItems();
358
+ // const className = `${type}Item`;
359
+ const fieldset = /** @type {HTMLFieldSetElement} */ (
360
+ jml('fieldset', [
361
+ buildLegend({
362
+ // className,
363
+ // type,
364
+ // arrayItems,
365
+ itemIndex,
366
+ typeNamespace,
367
+ propName
368
+ })
369
+ ], arrayItems)
370
+ );
371
+ return fieldset;
372
+ },
373
+ $getArrayItems () {
374
+ return this.lastElementChild.lastElementChild;
375
+ }
266
376
  }
267
- }
268
- }, [
269
- DOM.initialCaps(type).replace(/s$/u, ''), nbsp.repeat(2),
270
- ['button', {$on: {click (e) {
271
- e.preventDefault();
272
- const {target} = e;
273
- const arrayContents = $e(
274
- target.closest('.arrayHolder'),
275
- '.arrayContents'
276
- );
277
- arrayContents.hidden = !arrayContents.hidden;
278
- target.textContent = arrayContents.hidden ? '+' : '-';
279
- }}}, ['-']],
280
- ['div', {class: 'arrayContents'}, [
281
- this.array
282
- ? ['div', [
283
- 'Array length: ',
284
- ['span', [
285
- (value && value.length) || 0
377
+ }), [
378
+ DOM.initialCaps(/** @type {import('../types.js').AvailableType} */ (
379
+ type
380
+ )).replace(/s$/u, ''), nbsp.repeat(2),
381
+ // Why is this necessary
382
+ /** @type {import('jamilih').JamilihArray} */
383
+ (['button', {$on: {click (/** @type {Event} */ e) {
384
+ e.preventDefault();
385
+ const {target} = e;
386
+ const arrayContents = /** @type {HTMLDivElement} */ ($e(
387
+ /** @type {Element} */
388
+ (/** @type {Element} */ (target).closest('.arrayHolder')),
389
+ '.arrayContents'
390
+ ));
391
+ arrayContents.hidden = !arrayContents.hidden;
392
+ /** @type {Element} */ (
393
+ target
394
+ ).textContent = arrayContents.hidden ? '+' : '-';
395
+ }}}, ['-']]),
396
+ ['div', {class: 'arrayContents'}, [
397
+ this.array
398
+ ? ['div', [
399
+ 'Array length: ',
400
+ ['span', [
401
+ (value && value.length) || 0
402
+ ]]
286
403
  ]]
287
- ]]
288
- : '',
289
- ['div', {
290
- class: 'arrayItems'
291
- }]
292
- ]]
293
- ]);
404
+ : '',
405
+ ['div', {
406
+ class: 'arrayItems'
407
+ }]
408
+ ]]
409
+ ])
410
+ );
294
411
  return [div];
295
412
  },
296
413
  getInput ({root}) {
297
414
  // One element we are guaranteed to have for adding validation
298
- return $e(root, 'button');
415
+ return /** @type {HTMLButtonElement} */ ($e(root, 'button'));
299
416
  },
300
417
  // Unlike other items, we don't use the `value`, as it wil always be
301
418
  // an array (or object if called by that method) and we handle the
@@ -308,16 +425,34 @@ const arrayType = {
308
425
  const itemAdjust = type === 'object' ? 1 : 0;
309
426
  let itemIndex = itemAdjust - 1;
310
427
  const editableProperties = type !== 'array';
428
+
429
+ /**
430
+ * @param {HTMLInputElement} input
431
+ * @param {true|undefined} alwaysFocus
432
+ * @returns {void}
433
+ */
311
434
  const bringFocus = (input, alwaysFocus) => {
312
435
  if (bringIntoFocus || alwaysFocus) {
313
436
  input.scrollIntoView();
314
437
  input.focus();
315
438
  }
316
439
  };
440
+
441
+ /**
442
+ * @callback SwapGroup
443
+ * @param {Element} holder
444
+ * @param {"up"|"down"} direction
445
+ * @returns {void}
446
+ */
447
+
448
+ /**
449
+ * @type {SwapGroup}
450
+ * @this {HTMLDivElement}
451
+ */
317
452
  const $swapGroup = function (holder, direction) {
318
- const group = holder.parentElement;
453
+ const group = /** @type {HTMLElement} */ (holder.parentElement);
319
454
  const swapGroup = group[
320
- (direction === 'up' ? 'previous' : 'next') + 'ElementSibling'
455
+ (direction === 'up' ? 'previousElementSibling' : 'nextElementSibling')
321
456
  ];
322
457
  /* istanbul ignore if -- Just a guard */
323
458
  if (!swapGroup || swapGroup.nodeName.toLowerCase() !== 'fieldset') {
@@ -336,8 +471,20 @@ const arrayType = {
336
471
  swapCountElem.textContent = base;
337
472
  baseCountElem.textContent = swap;
338
473
  } else {
339
- const swapCountElem = group.$getPropertyInput();
340
- const baseCountElem = swapGroup.$getPropertyInput();
474
+ // eslint-disable-next-line max-len -- Long
475
+ const swapCountElem = /** @type {HTMLInputElement & {$parseInt: ParseInt}} */
476
+ (/**
477
+ * @type {HTMLFieldSetElement & {$getPropertyInput: GetPropertyInput}}
478
+ */ (
479
+ group
480
+ ).$getPropertyInput());
481
+ // eslint-disable-next-line max-len -- Long
482
+ const baseCountElem = /** @type {HTMLInputElement & {$parseInt: ParseInt}} */
483
+ (/**
484
+ * @type {HTMLFieldSetElement & {$getPropertyInput: GetPropertyInput}}
485
+ */ (
486
+ swapGroup
487
+ ).$getPropertyInput());
341
488
  if (typeof swapCountElem.$parseInt() === 'number' ||
342
489
  typeof baseCountElem.$parseInt() === 'number'
343
490
  ) {
@@ -358,9 +505,20 @@ const arrayType = {
358
505
  } else {
359
506
  group.before(swapGroup);
360
507
  }
361
- Types.validateAllReferences({topRoot}); // Needed
362
- this.$redrawMoveArrows();
508
+ Types.validateAllReferences({
509
+ // eslint-disable-next-line object-shorthand -- TS
510
+ topRoot: /** @type {HTMLDivElement} */ (topRoot)
511
+ }); // Needed
512
+
513
+ /** @type {HTMLDivElement & {$redrawMoveArrows: RedrawMoveArrows}} */ (
514
+ this
515
+ ).$redrawMoveArrows();
363
516
  };
517
+
518
+ /**
519
+ * @type {RedrawMoveArrows}
520
+ * @this {HTMLDivElement & {$swapGroup: SwapGroup}}
521
+ */
364
522
  const $redrawMoveArrows = function () {
365
523
  DOM.filterChildElements(this, [
366
524
  'fieldset', `.${type}Item-arrowHolder-${typeNamespace}`
@@ -387,6 +545,19 @@ const arrayType = {
387
545
  }
388
546
  });
389
547
  };
548
+
549
+ /**
550
+ * @typedef {() => void} ValidateLegend
551
+ */
552
+
553
+ /**
554
+ * @type {ValidateLegend}
555
+ * @this {HTMLInputElement & {
556
+ * $arrayItems: HTMLDivElement & {
557
+ * $getPropertyInputs: GetPropertyInputs
558
+ * }
559
+ * }}
560
+ */
390
561
  const $validateLegend = function () {
391
562
  const propertyNameInputs = this.$arrayItems.$getPropertyInputs();
392
563
  const invalidStr = propertyNameInputs.some((input) => {
@@ -425,10 +596,42 @@ const arrayType = {
425
596
  return invalidStr; // Don't give a chance to become valid
426
597
  });
427
598
  };
599
+
600
+ /**
601
+ * @typedef {HTMLDivElement & {
602
+ * $inputsExceedingLength: InputsExceedingLength,
603
+ * $getPropertyInputs: GetPropertyInputs,
604
+ * $redrawMoveArrows: RedrawMoveArrows
605
+ * }} ArrayItems
606
+ */
607
+
608
+ /**
609
+ * @param {{
610
+ * className: string,
611
+ * splice: "append"|number|undefined,
612
+ * itemIndex: number,
613
+ * typeNamespace: string|undefined,
614
+ * arrayItems: ArrayItems,
615
+ * propName: string|undefined
616
+ * }} cfg
617
+ * @returns {import('jamilih').JamilihArray}
618
+ */
428
619
  const buildLegend = ({
429
620
  className, splice, itemIndex, /* type, */ typeNamespace,
430
621
  arrayItems, propName
431
622
  }) => {
623
+ /**
624
+ * @callback ValidateLength
625
+ * @param {boolean} [avoidDialog]
626
+ * @returns {Promise<void>}
627
+ */
628
+
629
+ /**
630
+ * @type {ValidateLength}
631
+ * @this {HTMLInputElement & {
632
+ * $validateLength: ValidateLength
633
+ * }}
634
+ */
432
635
  const $validateLength = async function (avoidDialog) {
433
636
  if (!sparse) {
434
637
  return undefined;
@@ -443,15 +646,20 @@ const arrayType = {
443
646
  'array item beyond the length. Click "Ok" to allow to ' +
444
647
  'permit and extend the array length or "Cancel" otherwise.'
445
648
  });
446
- const arrLengthInput = $e(arrayItems.previousElementSibling, 'input');
649
+ const arrLengthInput = /** @type {HTMLInputElement} */ (
650
+ $e(
651
+ /** @type {Element} */ (arrayItems.previousElementSibling),
652
+ 'input'
653
+ )
654
+ );
447
655
  const highest = inputsExceedingLength.map(
448
656
  (i) => Number.parseInt(i.value)
449
657
  ).sort().slice(-1)[0];
450
- arrLengthInput.value = highest + 1;
658
+ arrLengthInput.value = String(highest + 1);
451
659
  return this.$validateLength(true);
452
660
  };
453
- return editableProperties
454
- ? ['legend', [
661
+ if (editableProperties) {
662
+ return /** @type {import('jamilih').JamilihArray} */ (['legend', [
455
663
  sparse
456
664
  ? 'Item'
457
665
  : {'#': [
@@ -476,6 +684,13 @@ const arrayType = {
476
684
  pattern: sparse ? '\\d' : '',
477
685
  */
478
686
  $custom: {
687
+ /**
688
+ * @type {Validate}
689
+ * @this {HTMLInputElement & {
690
+ * $validateLength: ValidateLength,
691
+ * $validateLegend: ValidateLegend
692
+ * }}
693
+ */
479
694
  async $validate () {
480
695
  try {
481
696
  try {
@@ -487,9 +702,17 @@ const arrayType = {
487
702
  /* await */ this.$validateLegend();
488
703
  // Todo (low): We could make this more efficient by waiting
489
704
  // until all added (when pre-populating)
490
- Types.validateAllReferences({topRoot}); // Needed
705
+ Types.validateAllReferences({
706
+ // eslint-disable-next-line object-shorthand -- TS
707
+ topRoot: /** @type {HTMLDivElement} */ (topRoot)
708
+ }); // Needed
491
709
  } catch (err) {}
492
710
  },
711
+
712
+ /**
713
+ * @type {ParseInt}
714
+ * @this {HTMLInputElement}
715
+ */
493
716
  $parseInt () {
494
717
  if (!(/^\d+$/u).test(this.value)) {
495
718
  return false;
@@ -499,20 +722,49 @@ const arrayType = {
499
722
  $validateLegend,
500
723
  $arrayItems: arrayItems,
501
724
  $validateLength,
725
+ /**
726
+ * @type {Resort}
727
+ * @this {HTMLInputElement}
728
+ */
502
729
  $resort ({alwaysFocus}) {
503
- const inputs = this.$arrayItems.$getPropertyInputs();
730
+ const inputs = /**
731
+ * @type {(HTMLInputElement & {
732
+ * $parseInt: ParseInt
733
+ * })[]}
734
+ */
735
+ (/**
736
+ * @type {HTMLInputElement & {
737
+ * $arrayItems: HTMLDivElement & {
738
+ * $getPropertyInputs: GetPropertyInputs
739
+ * }
740
+ * }}
741
+ */ (
742
+ this
743
+ ).$arrayItems.$getPropertyInputs());
504
744
  if (inputs.length === 1) {
505
745
  return;
506
746
  }
507
- const getFieldset = (el) => el.closest('fieldset');
747
+ /**
748
+ * @param {Element} el
749
+ * @returns {Element}
750
+ */
751
+ const getFieldset = (el) => /** @type {HTMLFieldSetElement} */ (
752
+ el.closest('fieldset')
753
+ );
508
754
  const thisFieldset = getFieldset(this);
509
- const intVal = this.$parseInt();
755
+ // eslint-disable-next-line max-len -- Long
756
+ const intVal = /** @type {HTMLInputElement & {$parseInt: ParseInt}} */ (
757
+ this
758
+ ).$parseInt();
510
759
  if (intVal === false) {
511
760
  inputs.reverse().some((input) => {
512
761
  if (input === this) { // No need to search further
513
762
  return true;
514
763
  }
515
- const intValOlder = input.$parseInt();
764
+ const intValOlder =
765
+ /** @type {HTMLInputElement & {$parseInt: ParseInt}} */ (
766
+ input
767
+ ).$parseInt();
516
768
  // Not sorting non-integers
517
769
  if (typeof intValOlder !== 'number') {
518
770
  return false;
@@ -524,8 +776,17 @@ const arrayType = {
524
776
  });
525
777
  return;
526
778
  }
779
+
780
+ /**
781
+ * @param {boolean} [latest]
782
+ * @returns {boolean}
783
+ */
527
784
  const getNearest = (latest) => {
785
+ /**
786
+ * @type {HTMLInputElement|undefined}
787
+ */
528
788
  let nearest;
789
+
529
790
  inputs.some((input) => {
530
791
  if (input === this) {
531
792
  return false;
@@ -539,6 +800,12 @@ const arrayType = {
539
800
  }
540
801
  return false;
541
802
  }
803
+
804
+ /**
805
+ * @param {Integer} a
806
+ * @param {Integer} b
807
+ * @returns {boolean}
808
+ */
542
809
  const cmp = (a, b) => {
543
810
  return latest ? a > b : a < b;
544
811
  };
@@ -558,6 +825,10 @@ const arrayType = {
558
825
  const method = latest ? 'after' : 'before';
559
826
  // Ensure move *after* splice that will occur after this
560
827
  setTimeout(() => {
828
+ /* c8 ignore next 3 */
829
+ if (!nearest) {
830
+ return;
831
+ }
561
832
  getFieldset(nearest)[method](thisFieldset);
562
833
  bringFocus(this, alwaysFocus);
563
834
  arrayItems.$redrawMoveArrows();
@@ -570,6 +841,13 @@ const arrayType = {
570
841
  }
571
842
  },
572
843
  $on: {
844
+ /**
845
+ * @param {Event} e
846
+ * @this {HTMLInputElement & {
847
+ * $validate: Validate,
848
+ * $resort: Resort
849
+ * }}
850
+ */
573
851
  change (e) {
574
852
  this.$validate();
575
853
  // We don't want form `onchange` to run
@@ -580,20 +858,31 @@ const arrayType = {
580
858
  },
581
859
  class: `propertyName-${typeNamespace}`
582
860
  }]
861
+ ]]);
862
+ }
863
+ return /** @type {import('jamilih').JamilihArray} */ (['legend', [
864
+ 'Item ',
865
+ ['span', {
866
+ dataset: {prop: true, array: true},
867
+ className
868
+ }, [
869
+ propName !== undefined ? propName : String(itemIndex)
583
870
  ]]
584
- : ['legend', [
585
- 'Item ',
586
- ['span', {
587
- dataset: {prop: true, array: true},
588
- className
589
- }, [
590
- propName !== undefined ? propName : String(itemIndex)
591
- ]]
592
- ]];
871
+ ]]);
593
872
  };
873
+
874
+ /**
875
+ * @param {HTMLDivElement & {
876
+ * $getPropertyInputs: GetPropertyInputs
877
+ * }} arrayItems
878
+ * @returns {void}
879
+ */
594
880
  const decrementItemIndex = (arrayItems) => {
595
881
  if (sparse) {
596
- itemIndex = arrayItems.$getPropertyInputs().reduce(
882
+ // eslint-disable-next-line max-len -- Long
883
+ itemIndex = /** @type {(HTMLInputElement & {$parseInt: ParseInt})[]} */ (
884
+ arrayItems.$getPropertyInputs()
885
+ ).reduce(
597
886
  (highest, input) => {
598
887
  const intVal = input.$parseInt();
599
888
  return intVal !== false && intVal > highest ? intVal : highest;
@@ -604,6 +893,24 @@ const arrayType = {
604
893
  itemIndex--;
605
894
  }
606
895
  };
896
+
897
+ /**
898
+ * @callback AddArrayElement
899
+ * @param {{
900
+ * propName?: string,
901
+ * splice?: "append"|number,
902
+ * alwaysFocus?: true
903
+ * }} cfg
904
+ * @returns {void}
905
+ */
906
+
907
+ /**
908
+ * @type {AddArrayElement}
909
+ * @this {HTMLButtonElement & {
910
+ * $getArrayItems: GetArrayItems,
911
+ * $addArrayElement: AddArrayElement
912
+ * }}
913
+ */
607
914
  const $addArrayElement = function ({propName, splice, alwaysFocus}) {
608
915
  // console.log('propName', propName);
609
916
  const arrayItems = this.$getArrayItems();
@@ -616,7 +923,12 @@ const arrayType = {
616
923
  } else if (typeof splice === 'number') {
617
924
  itemIndex = splice + 1;
618
925
  } else if (typeof splice !== 'string') {
619
- itemIndex = arrayItems.$getPropertyInputs().reduce(
926
+ // eslint-disable-next-line max-len -- Long
927
+ itemIndex = /** @type {(HTMLInputElement & {$parseInt: ParseInt})[]} */ (
928
+ // eslint-disable-next-line max-len -- Long
929
+ /** @type {HTMLDivElement & {$getPropertyInputs: GetPropertyInputs}} */
930
+ (arrayItems).$getPropertyInputs()
931
+ ).reduce(
620
932
  (highest, input) => {
621
933
  const intVal = input.$parseInt();
622
934
  return intVal !== false && intVal > highest ? intVal : highest;
@@ -629,47 +941,74 @@ const arrayType = {
629
941
  }
630
942
  const thisButton = this; // eslint-disable-line consistent-this
631
943
  const className = `${type}Item`;
632
- const fieldset = jml('fieldset', {
944
+ const fieldset = /** @type {HTMLFieldSetElement} */ (jml('fieldset', {
633
945
  $custom: {
946
+ /** @type {GetPropertyInput} */
634
947
  $getPropertyInput () {
635
- return DOM.filterChildElements(
636
- this,
948
+ return /** @type {HTMLInputElement} */ (DOM.filterChildElements(
949
+ /** @type {HTMLFieldSetElement} */ (this),
637
950
  ['legend', `input.propertyName-${typeNamespace}`]
638
- ).pop();
951
+ ).pop());
639
952
  }
640
953
  }
641
954
  }, [
642
955
  buildLegend({
643
- className, splice, itemIndex, type, typeNamespace,
644
- arrayItems, propName
956
+ className,
957
+ splice,
958
+ itemIndex,
959
+ // type,
960
+ typeNamespace,
961
+ arrayItems,
962
+ propName
645
963
  })
646
- ], arrayItems);
964
+ ], arrayItems));
647
965
  // We must ensure fieldset is built before passing it
648
966
  jml({'#': [
649
- ...(buildTypeChoices({
650
- resultType,
651
- topRoot,
652
- format,
967
+ ...(/** @type {import('../typeChoices.js').BuildTypeChoices} */ (
968
+ buildTypeChoices
969
+ )({
970
+ // resultType,
971
+ // eslint-disable-next-line object-shorthand -- TS
972
+ topRoot: /** @type {HTMLDivElement} */ (topRoot),
973
+ // eslint-disable-next-line object-shorthand, max-len -- TS
974
+ format: /** @type {import('../formats.js').AvailableFormat} */ (format),
653
975
  state: type,
654
- itemIndex,
976
+ // itemIndex,
655
977
  typeNamespace
656
978
  }).domArray),
657
979
  nbsp.repeat(2),
658
- ['button', {$on: {click (e) {
980
+ ['button', {$on: {click (/** @type {Event} */ e) {
659
981
  e.preventDefault();
660
982
  // e.stopPropagation();
661
983
  let splice;
662
984
  if (sparse) {
663
- const prevInputVal = fieldset.$getPropertyInput().$parseInt();
985
+ const prevInputVal =
986
+ /**
987
+ * @type {HTMLInputElement & {
988
+ * $parseInt: ParseInt
989
+ * }}
990
+ */ (
991
+ /**
992
+ * @type {HTMLFieldSetElement & {
993
+ * $getPropertyInput: GetPropertyInput
994
+ * }}
995
+ */ (
996
+ fieldset
997
+ ).$getPropertyInput()).$parseInt();
664
998
  splice = prevInputVal === false ? 'append' : prevInputVal;
665
999
  }
666
1000
  thisButton.$addArrayElement({
667
1001
  splice, alwaysFocus: true
668
1002
  });
669
- const newArrayFieldset = arrayItems.lastElementChild;
1003
+ const newArrayFieldset =
1004
+ /** @type {Element & {$getPropertyInput: GetPropertyInput}} */ (
1005
+ arrayItems.lastElementChild
1006
+ );
670
1007
  fieldset.after(newArrayFieldset);
671
1008
  if (sparse) {
672
- const newPrevInput = newArrayFieldset.$getPropertyInput();
1009
+ const newPrevInput = /** @type {HTMLInputElement} */ (
1010
+ newArrayFieldset.$getPropertyInput()
1011
+ );
673
1012
  bringFocus(newPrevInput, true);
674
1013
  } else {
675
1014
  DOM.filterChildElements(
@@ -677,31 +1016,56 @@ const arrayType = {
677
1016
  'fieldset', 'legend', '.' + className
678
1017
  ]
679
1018
  ).forEach((span, i) => {
680
- span.textContent = Number.parseInt(i) + itemAdjust;
1019
+ span.textContent = String(i + itemAdjust);
681
1020
  });
682
1021
  }
683
1022
  // Maybe not needed as addition (without renumbering)
684
1023
  // wouldn't yet add type
685
- Types.validateAllReferences({topRoot});
1024
+ Types.validateAllReferences({
1025
+ // eslint-disable-next-line object-shorthand -- TS
1026
+ topRoot: /** @type {HTMLDivElement} */ (topRoot)
1027
+ });
686
1028
  arrayItems.$redrawMoveArrows();
687
1029
  }}}, ['+']],
688
- ['button', {$on: {click (e) {
689
- e.preventDefault();
690
- // e.stopPropagation();
691
- fieldset.remove();
692
- decrementItemIndex(arrayItems);
693
- if (!sparse) {
694
- DOM.filterChildElements(arrayItems, [
695
- 'fieldset', 'legend', '.' + className
696
- ]).forEach((span, i) => {
697
- span.textContent = Number.parseInt(i) + itemAdjust;
698
- });
699
- }
700
- // Maybe not needed as removal would remove circular
701
- Types.validateAllReferences({topRoot});
702
- arrayItems.$redrawMoveArrows();
703
- }}}, ['x']],
704
- ['span', {class: `${type}Item-arrowHolder-${typeNamespace}`}, []]
1030
+ /**
1031
+ * @type {import('jamilih').JamilihArray}
1032
+ */
1033
+ ([
1034
+ 'button',
1035
+ // @ts-ignore Can't seem to force optional Event
1036
+ {
1037
+ $on: {
1038
+ click (/** @type {Event} */ e) {
1039
+ e.preventDefault();
1040
+ // e.stopPropagation();
1041
+ fieldset.remove();
1042
+ decrementItemIndex(arrayItems);
1043
+ if (!sparse) {
1044
+ DOM.filterChildElements(arrayItems, [
1045
+ 'fieldset', 'legend', '.' + className
1046
+ ]).forEach((span, i) => {
1047
+ span.textContent = String(i + itemAdjust);
1048
+ });
1049
+ }
1050
+ // Maybe not needed as removal would remove circular
1051
+ Types.validateAllReferences({
1052
+ // eslint-disable-next-line object-shorthand -- TS
1053
+ topRoot: /** @type {HTMLDivElement} */ (topRoot)
1054
+ });
1055
+
1056
+ /**
1057
+ * @type {HTMLDivElement & {
1058
+ * $redrawMoveArrows: RedrawMoveArrows
1059
+ * }}
1060
+ */
1061
+ (arrayItems).$redrawMoveArrows();
1062
+ }
1063
+ }
1064
+ }, ['x']
1065
+ ]),
1066
+ ['span', {
1067
+ class: `${type}Item-arrowHolder-${typeNamespace}`
1068
+ }, []]
705
1069
  ]}, fieldset);
706
1070
  // Need to validate if adding more than one property (in
707
1071
  // case two have empty string)
@@ -709,7 +1073,14 @@ const arrayType = {
709
1073
  const pns = arrayItems.$getPropertyInputs();
710
1074
  /* istanbul ignore else -- Just a guard */
711
1075
  if (pns.length) {
712
- const input = pns.pop();
1076
+ const input =
1077
+ /**
1078
+ * @type {HTMLInputElement & {
1079
+ * $validate: Validate,
1080
+ * $resort: Resort
1081
+ * }}
1082
+ */
1083
+ (pns.pop());
713
1084
  input.$validate();
714
1085
  input.$resort({alwaysFocus});
715
1086
  bringFocus(input, alwaysFocus);
@@ -718,9 +1089,28 @@ const arrayType = {
718
1089
  arrayItems.$redrawMoveArrows();
719
1090
  };
720
1091
 
1092
+ /**
1093
+ * @callback GetArrayLength
1094
+ * @returns {number}
1095
+ */
1096
+
1097
+ /**
1098
+ * @type {GetArrayLength}
1099
+ * @this {HTMLDivElement}
1100
+ */
721
1101
  const $getArrayLength = function () {
722
- return $e(this.previousElementSibling, 'input').value;
1102
+ return Number(/** @type {HTMLInputElement} */ (
1103
+ $e(/** @type {Element} */ (this.previousElementSibling), 'input')
1104
+ ).value);
723
1105
  };
1106
+
1107
+ /**
1108
+ * @type {InputsExceedingLength}
1109
+ * @this {HTMLDivElement & {
1110
+ * $getArrayLength: GetArrayLength,
1111
+ * $getPropertyInputs: GetPropertyInputs
1112
+ * }}
1113
+ */
724
1114
  const $inputsExceedingLength = function () {
725
1115
  const highestExpectedIndex = this.$getArrayLength() - 1;
726
1116
  return this.$getPropertyInputs().filter(
@@ -738,147 +1128,176 @@ const arrayType = {
738
1128
  return exceedsLength;
739
1129
  });
740
1130
  };
741
- const div = jml('div', {
742
- dataset: {type},
743
- // is: 'array-or-object-editor',
744
- $custom: {
745
- $addAndSetArrayElement ({
746
- propName, type, value, bringIntoFocus
747
- // , schemaContent, schemaState
748
- }) {
749
- this.$addArrayElement({propName});
750
- const typeChoices = this.$getTypeChoices();
751
- typeChoices.$setType({type, baseValue: value, bringIntoFocus});
752
- const root = typeChoices.$getTypeRoot();
753
- const typeObj = Types.availableTypes[type];
754
- if (typeObj.setValue) typeObj.setValue({root, value});
755
- Types.validate({type, root, topRoot});
756
- return root;
757
- },
758
- $getAddArrayElement () {
759
- const el = this
760
- .lastElementChild.firstElementChild.nextElementSibling;
761
- return sparse ? el.nextElementSibling : el;
762
- },
763
- $addArrayElement ({propName, splice, alwaysFocus}) {
764
- const addArrayElement = this.$getAddArrayElement();
765
- addArrayElement.$addArrayElement({propName, splice, alwaysFocus});
766
- },
767
- $getArrayItems () {
768
- const addArrayElement = this.$getAddArrayElement();
769
- return addArrayElement.previousElementSibling;
1131
+ const addArrayElement = /** @type {import('jamilih').JamilihArray} */ (
1132
+ ['button', {
1133
+ class: 'addArrayElement',
1134
+ // is: 'add-array-element',
1135
+ $custom: {
1136
+ $addArrayElement,
1137
+ /**
1138
+ * @type {GetArrayItems}
1139
+ * @this {HTMLDivElement}
1140
+ */
1141
+ $getArrayItems () {
1142
+ return (/**
1143
+ * @type {HTMLDivElement & {
1144
+ * $inputsExceedingLength: InputsExceedingLength,
1145
+ * $getPropertyInputs: GetPropertyInputs,
1146
+ * $redrawMoveArrows: RedrawMoveArrows
1147
+ * }}
1148
+ */
1149
+ (this.previousElementSibling)
1150
+ );
1151
+ }
770
1152
  },
771
- $getTypeChoices () {
772
- const arrayItems = this.$getArrayItems();
773
- return $e(
774
- arrayItems.lastElementChild,
775
- `.typeChoices-${typeNamespace}`
776
- );
777
- }
778
- },
779
- $on: {click (e) {
780
- // We needed to stop preventing the default for the
781
- // invalid date checkbox; is this sufficient to prevent
782
- // other stray clicks apparently meant for the array
783
- // and object reference checking?
784
- if (e.target.type !== 'checkbox' && e.target.type !== 'radio') {
785
- e.preventDefault();
786
- }
787
- }}
788
- }, [
789
- DOM.initialCaps(type).replace(/s$/u, ''), nbsp.repeat(2),
790
- ['button', {$on: {click (e) {
791
- e.preventDefault();
792
- const {target} = e;
793
- const arrayContents = $e(div, '.arrayContents');
794
- arrayContents.hidden = !arrayContents.hidden;
795
- target.textContent = arrayContents.hidden ? '+' : '-';
796
- }}}, ['-']],
797
- ['div', {class: 'arrayContents'}, [
798
- sparse
799
- ? ['div', [
800
- ['label', [
801
- 'Array length: ',
802
- ['input', {
803
- type: 'number',
804
- value: (value && value.length) || 0,
805
- step: 1,
806
- size: 4,
807
- pattern: '\\d',
808
- $on: {
809
- async change () {
810
- const arrayItems = $e(
811
- this.closest('.arrayContents'), '.arrayItems'
812
- );
813
- const propInputsBeyondLength =
814
- arrayItems.$inputsExceedingLength();
815
- try {
816
- if (propInputsBeyondLength.length) {
817
- await dialogs.confirm({
818
- message: 'Your new length will truncate the ' +
819
- 'array causing items to be removed. Continue?'
820
- });
821
- propInputsBeyondLength.forEach((input) => {
822
- const fieldset = input.closest('fieldset');
823
- fieldset.remove();
824
- });
825
- // Maybe not needed as removal would remove circular
826
- Types.validateAllReferences({topRoot});
827
- }
828
- this.$oldvalue = this.value;
829
- } catch (err) {
830
- this.value = this.$oldvalue; // Revert
831
- }
1153
+ $on: {click () {
1154
+ /**
1155
+ * @type {HTMLButtonElement & {
1156
+ * $addArrayElement: AddArrayElement,
1157
+ * $getArrayItems: GetArrayItems
1158
+ * }}
1159
+ */
1160
+ (this).$addArrayElement({alwaysFocus: true});
1161
+ // Maybe not needed as addition (without renumbering)
1162
+ // wouldn't yet add type
1163
+ Types.validateAllReferences({
1164
+ // eslint-disable-next-line object-shorthand -- TS
1165
+ topRoot: /** @type {HTMLDivElement} */ (topRoot)
1166
+ });
1167
+ }}
1168
+ }, ['+ Item']]
1169
+ );
1170
+
1171
+ const arrayContentsFirstChild = sparse
1172
+ ? ['div', [
1173
+ ['label', [
1174
+ 'Array length: ',
1175
+ ['input', {
1176
+ type: 'number',
1177
+ value: (value && value.length) || 0,
1178
+ step: 1,
1179
+ size: 4,
1180
+ pattern: '\\d',
1181
+ $on: {
1182
+ /**
1183
+ * @this {HTMLInputElement & {
1184
+ * $oldvalue: string
1185
+ * }}
1186
+ * @returns {Promise<void>}
1187
+ */
1188
+ async change () {
1189
+ const arrayItems = $e(
1190
+ /** @type {Element} */ (
1191
+ this.closest('.arrayContents')
1192
+ ),
1193
+ '.arrayItems'
1194
+ );
1195
+ const propInputsBeyondLength =
1196
+ /**
1197
+ * @type {HTMLElement & {
1198
+ * $inputsExceedingLength: InputsExceedingLength
1199
+ * }}
1200
+ */
1201
+ (arrayItems).$inputsExceedingLength();
1202
+ try {
1203
+ if (propInputsBeyondLength.length) {
1204
+ await dialogs.confirm({
1205
+ message: 'Your new length will truncate the ' +
1206
+ 'array causing items to be removed. Continue?'
1207
+ });
1208
+ propInputsBeyondLength.forEach((input) => {
1209
+ const fieldset = /** @type {HTMLFieldSetElement} */ (
1210
+ input.closest('fieldset')
1211
+ );
1212
+ fieldset.remove();
1213
+ });
1214
+ // Maybe not needed as removal would remove circular
1215
+ Types.validateAllReferences({
1216
+ // eslint-disable-next-line object-shorthand -- TS
1217
+ topRoot: /** @type {HTMLDivElement} */ (topRoot)
1218
+ });
832
1219
  }
1220
+ this.$oldvalue = this.value;
1221
+ } catch (err) {
1222
+ this.value = this.$oldvalue; // Revert
833
1223
  }
834
- }]
835
- ]]
836
- ]]
837
- : '',
838
- ['div', {
839
- class: 'arrayItems',
840
- $custom: {
841
- $swapGroup, $redrawMoveArrows, $getArrayLength,
842
- $inputsExceedingLength,
843
- $getPropertyInputs () {
844
- return DOM.filterChildElements(
845
- this,
846
- ['fieldset', 'legend', `input.propertyName-${typeNamespace}`]
847
- );
1224
+ }
848
1225
  }
849
- }
850
- }],
851
- ['button', {
852
- class: 'addArrayElement',
853
- // is: 'add-array-element',
854
- $custom: {$addArrayElement, $getArrayItems () {
855
- return this.previousElementSibling;
856
- }},
857
- $on: {click () {
858
- this.$addArrayElement({alwaysFocus: true});
859
- // Maybe not needed as addition (without renumbering)
860
- // wouldn't yet add type
861
- Types.validateAllReferences({topRoot});
862
- }}
863
- }, ['+ Item']],
1226
+ }]
1227
+ ]]
1228
+ ]]
1229
+ : '';
1230
+
1231
+ const arrayItems = ['div', {
1232
+ class: 'arrayItems',
1233
+ $custom: {
1234
+ $swapGroup, $redrawMoveArrows, $getArrayLength,
1235
+ $inputsExceedingLength,
1236
+
1237
+ /**
1238
+ * @type {GetPropertyInputs}
1239
+ * @this {HTMLDivElement}
1240
+ */
1241
+ $getPropertyInputs () {
1242
+ return /** @type {HTMLInputElement[]} */ (DOM.filterChildElements(
1243
+ this,
1244
+ ['fieldset', 'legend', `input.propertyName-${typeNamespace}`]
1245
+ ));
1246
+ }
1247
+ }
1248
+ }];
1249
+
1250
+ const minusButton = ['button', {$on: {click (/** @type {Event} */ e) {
1251
+ e.preventDefault();
1252
+ const {target} = e;
1253
+ const arrayContents = /** @type {HTMLDivElement} */ (
1254
+ $e(div, '.arrayContents')
1255
+ );
1256
+ arrayContents.hidden = !arrayContents.hidden;
1257
+ /** @type {Element} */ (
1258
+ target
1259
+ ).textContent = arrayContents.hidden ? '+' : '-';
1260
+ }}}, ['-']];
1261
+
1262
+ const arrayContents = /** @type {import('jamilih').JamilihArray} */ (
1263
+ ['div', {class: 'arrayContents'}, [
1264
+ arrayContentsFirstChild,
1265
+ arrayItems,
1266
+ addArrayElement,
864
1267
  ['button', {$on: {click () {
865
- const arrayContents = this.closest('.arrayContents');
866
- const arrayItems = $e(arrayContents, '.arrayItems');
1268
+ const arrayContents = /** @type {Element} */ (
1269
+ this.closest('.arrayContents')
1270
+ );
1271
+ const arrayItems =
1272
+ // eslint-disable-next-line max-len -- Long
1273
+ /** @type {HTMLDivElement & {$getPropertyInputs: GetPropertyInputs;}} */ (
1274
+ $e(arrayContents, '.arrayItems')
1275
+ );
867
1276
  const lastElement = arrayItems.lastElementChild;
868
1277
  if (lastElement) {
869
1278
  lastElement.remove();
870
1279
  decrementItemIndex(arrayItems);
871
- $e(arrayContents, '.arrayItems').$redrawMoveArrows();
1280
+ // eslint-disable-next-line max-len -- Long
1281
+ /** @type {HTMLDivElement & {$redrawMoveArrows: RedrawMoveArrows}} */ (
1282
+ $e(arrayContents, '.arrayItems')
1283
+ ).$redrawMoveArrows();
872
1284
  }
873
1285
  // Maybe not needed as removal would remove circular
874
- Types.validateAllReferences({topRoot});
1286
+ Types.validateAllReferences({
1287
+ // eslint-disable-next-line object-shorthand -- TS
1288
+ topRoot: /** @type {HTMLDivElement} */ (topRoot)
1289
+ });
875
1290
  }}}, ['- Last item']],
876
1291
  // We could only add this when there was more than one
877
1292
  ['button', {$on: {click () {
878
- const arrayItems = $e(
879
- this.closest('.arrayContents'),
880
- '.arrayItems'
881
- );
1293
+ const arrayItems =
1294
+ // eslint-disable-next-line max-len -- Long
1295
+ /** @type {HTMLDivElement & {$getPropertyInputs: GetPropertyInputs}} */ ($e(
1296
+ /** @type {Element} */ (
1297
+ /** @type {Element} */ (this).closest('.arrayContents')
1298
+ ),
1299
+ '.arrayItems'
1300
+ ));
882
1301
  DOM.removeChildren(arrayItems);
883
1302
  if (sparse) {
884
1303
  decrementItemIndex(arrayItems);
@@ -886,10 +1305,102 @@ const arrayType = {
886
1305
  itemIndex = -1;
887
1306
  }
888
1307
  // Maybe not needed as removal would remove circular
889
- Types.validateAllReferences({topRoot});
1308
+ Types.validateAllReferences({
1309
+ // eslint-disable-next-line object-shorthand -- TS
1310
+ topRoot: /** @type {HTMLDivElement} */ (topRoot)
1311
+ });
890
1312
  }}}, ['x All']]
891
1313
  ]]
892
- ]);
1314
+ );
1315
+
1316
+ const div = /** @type {HTMLDivElement} */ (
1317
+ jml('div', /** @type {import('jamilih').JamilihAttributes} */ ({
1318
+ dataset: {type},
1319
+ // is: 'array-or-object-editor',
1320
+ $custom: {
1321
+ // eslint-disable-next-line max-len -- Long
1322
+ /** @type {import('../formats/structuredCloning.js').AddAndSetArrayElement} */
1323
+ $addAndSetArrayElement ({
1324
+ propName, type, value, bringIntoFocus
1325
+ // , schemaContent, schemaState
1326
+ }) {
1327
+ this.$addArrayElement({propName});
1328
+ const typeChoices = this.$getTypeChoices();
1329
+ typeChoices.$setType({type, baseValue: value, bringIntoFocus});
1330
+ const root = typeChoices.$getTypeRoot();
1331
+ const typeObj = /** @type {import('../types.js').TypeObject} */ (
1332
+ Types.availableTypes[type]
1333
+ );
1334
+ if (typeObj.setValue) {
1335
+ typeObj.setValue({root, value});
1336
+ }
1337
+ Types.validate({type, root, topRoot});
1338
+ return root;
1339
+ },
1340
+
1341
+ /**
1342
+ * @typedef {() => Element} GetAddArrayElement
1343
+ */
1344
+
1345
+ /** @type {GetAddArrayElement} */
1346
+ $getAddArrayElement () {
1347
+ const el = this
1348
+ .lastElementChild.firstElementChild.nextElementSibling;
1349
+ return sparse ? el.nextElementSibling : el;
1350
+ },
1351
+ /**
1352
+ * @type {AddArrayElement}
1353
+ * @this {HTMLDivElement & {
1354
+ * $getAddArrayElement: GetAddArrayElement
1355
+ * }}
1356
+ */
1357
+ // @ts-ignore TS is apparently getting wrong $addArrayElement
1358
+ $addArrayElement ({propName, splice, alwaysFocus}) {
1359
+ const addArrayElement = this.$getAddArrayElement();
1360
+ /**
1361
+ * @type {HTMLButtonElement & {
1362
+ * $addArrayElement: AddArrayElement,
1363
+ * $getArrayItems: GetArrayItems
1364
+ * }}
1365
+ */
1366
+ (addArrayElement).$addArrayElement({propName, splice, alwaysFocus});
1367
+ },
1368
+ $getArrayItems () {
1369
+ const addArrayElement = this.$getAddArrayElement();
1370
+ return addArrayElement.previousElementSibling;
1371
+ },
1372
+ $getTypeChoices () {
1373
+ const arrayItems = this.$getArrayItems();
1374
+ return $e(
1375
+ arrayItems.lastElementChild,
1376
+ `.typeChoices-${typeNamespace}`
1377
+ );
1378
+ }
1379
+ },
1380
+ $on: {
1381
+ click (ev) {
1382
+ const e = /** @type {Event} */ (ev);
1383
+ // eslint-disable-next-line prefer-destructuring -- TS
1384
+ const target = /** @type {HTMLInputElement} */ (e.target);
1385
+
1386
+ // We needed to stop preventing the default for the
1387
+ // invalid date checkbox; is this sufficient to prevent
1388
+ // other stray clicks apparently meant for the array
1389
+ // and object reference checking?
1390
+ if (target.type !== 'checkbox' && target.type !== 'radio') {
1391
+ e.preventDefault();
1392
+ }
1393
+ }
1394
+ }
1395
+ }), /** @type {import('jamilih').JamilihChildren} */ ([
1396
+ DOM.initialCaps(
1397
+ /** @type {import('../types.js').AvailableType} */
1398
+ (type)
1399
+ ).replace(/s$/u, ''), nbsp.repeat(2),
1400
+ minusButton,
1401
+ arrayContents
1402
+ ]))
1403
+ );
893
1404
  topRoot = topRoot || div;
894
1405
  return [div];
895
1406
  }