@es-joy/jsoe 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +15 -0
- package/.eslintignore +7 -0
- package/.eslintrc.cjs +120 -0
- package/CHANGES.md +5 -0
- package/LICENSE-MIT.txt +22 -0
- package/README.md +82 -0
- package/package.json +101 -0
- package/rollup.config.js +29 -0
- package/server.js +25 -0
- package/src/deepEqual.js +72 -0
- package/src/formats/indexedDBKey.js +66 -0
- package/src/formats/json.js +64 -0
- package/src/formats/schemaAndArbitrary.js +20 -0
- package/src/formats/schemaOnly.js +529 -0
- package/src/formats/structuredCloning.js +372 -0
- package/src/formats.js +229 -0
- package/src/fundamentalTypes/BooleanObjectType.js +55 -0
- package/src/fundamentalTypes/NumberObjectType.js +41 -0
- package/src/fundamentalTypes/StringObjectType.js +37 -0
- package/src/fundamentalTypes/arrayReferenceType.js +203 -0
- package/src/fundamentalTypes/arrayType.js +898 -0
- package/src/fundamentalTypes/bigintType.js +53 -0
- package/src/fundamentalTypes/dateType.js +129 -0
- package/src/fundamentalTypes/nullType.js +33 -0
- package/src/fundamentalTypes/numberType.js +56 -0
- package/src/fundamentalTypes/objectReferenceType.js +51 -0
- package/src/fundamentalTypes/objectType.js +30 -0
- package/src/fundamentalTypes/regexpType.js +95 -0
- package/src/fundamentalTypes/sparseUndefinedType.js +43 -0
- package/src/fundamentalTypes/stringType.js +31 -0
- package/src/fundamentalTypes/undefinedType.js +37 -0
- package/src/index.js +7 -0
- package/src/subTypes/blobHTMLType.js +160 -0
- package/src/subTypes/falseType.js +42 -0
- package/src/subTypes/trueType.js +42 -0
- package/src/superTypes/InfinitiesSuperType.js +49 -0
- package/src/superTypes/SpecialNumberSuperType.js +62 -0
- package/src/typeChoices.js +125 -0
- package/src/types.js +665 -0
- package/src/utils/dialogs.js +115 -0
- package/src/utils/jsonPointer.js +89 -0
- package/src/utils/templateUtils.js +106 -0
- package/src/utils/types.js +6 -0
package/src/types.js
ADDED
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
import {jml} from '../node_modules/jamilih/dist/jml-es.js';
|
|
2
|
+
import {
|
|
3
|
+
Typeson, getJSONType, structuredCloningThrowing
|
|
4
|
+
} from '../node_modules/typeson-registry/dist/index.js';
|
|
5
|
+
|
|
6
|
+
import Formats from './formats.js';
|
|
7
|
+
|
|
8
|
+
import {$e, $$e} from './utils/templateUtils.js';
|
|
9
|
+
|
|
10
|
+
import nullType from './fundamentalTypes/nullType.js';
|
|
11
|
+
import trueType from './subTypes/trueType.js';
|
|
12
|
+
import falseType from './subTypes/falseType.js';
|
|
13
|
+
import blobHTMLType from './subTypes/blobHTMLType.js';
|
|
14
|
+
import numberType from './fundamentalTypes/numberType.js';
|
|
15
|
+
import bigintType from './fundamentalTypes/bigintType.js';
|
|
16
|
+
import stringType from './fundamentalTypes/stringType.js';
|
|
17
|
+
import arrayReferenceType from './fundamentalTypes/arrayReferenceType.js';
|
|
18
|
+
import objectReferenceType from './fundamentalTypes/objectReferenceType.js';
|
|
19
|
+
import arrayType from './fundamentalTypes/arrayType.js';
|
|
20
|
+
import objectType from './fundamentalTypes/objectType.js';
|
|
21
|
+
import dateType from './fundamentalTypes/dateType.js';
|
|
22
|
+
import undefinedType from './fundamentalTypes/undefinedType.js';
|
|
23
|
+
import regexpType from './fundamentalTypes/regexpType.js';
|
|
24
|
+
import BooleanObjectType from './fundamentalTypes/BooleanObjectType.js';
|
|
25
|
+
import NumberObjectType from './fundamentalTypes/NumberObjectType.js';
|
|
26
|
+
import StringObjectType from './fundamentalTypes/StringObjectType.js';
|
|
27
|
+
import sparseUndefinedType from './fundamentalTypes/sparseUndefinedType.js';
|
|
28
|
+
import InfinitiesSuperType from './superTypes/InfinitiesSuperType.js';
|
|
29
|
+
import SpecialNumberSuperType from './superTypes/SpecialNumberSuperType.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {HTMLLegendElement} legend
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
export const getPropertyValueFromLegend = (legend) => {
|
|
36
|
+
const propElem = $e(legend, '*[data-prop="true"]');
|
|
37
|
+
return propElem.nodeName.toLowerCase() === 'input'
|
|
38
|
+
? propElem.value
|
|
39
|
+
: String(Number.parseInt(propElem.textContent) - 1); // 1-based to 0-based
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const Types = {};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {object} TypeObject
|
|
46
|
+
* @property {JamilihArray} option Creates the option HTML. May set an option
|
|
47
|
+
* `title` or `value`
|
|
48
|
+
* @property {boolean} [array] Private context variable. Whether or not
|
|
49
|
+
* it is an array. Do not use in other types.
|
|
50
|
+
* @property {string[]} [regexEndings] Used for string parsing.
|
|
51
|
+
* @property {RegExp} [stringRegex] Used for string parsing. If not
|
|
52
|
+
* present, use `stringRegexBegin` and `stringRegexEnd`
|
|
53
|
+
* @property {RegExp} [stringRegexBegin] Used for string parsing. If not
|
|
54
|
+
* present, use `stringRegex`
|
|
55
|
+
* @property {RegExp} [stringRegexEnd] Used for string parsing. If not
|
|
56
|
+
* present, use `stringRegex`
|
|
57
|
+
* @property {
|
|
58
|
+
* (ArbitraryValue) => boolean
|
|
59
|
+
* } [valueMatch] Function to check whether this subtype matches
|
|
60
|
+
* @property {string} [superType] The greater fundamental type to which
|
|
61
|
+
* the type belongs
|
|
62
|
+
* @property {(s: string) => ArbitraryValue} toValue Converts from
|
|
63
|
+
* string to value. May use `stringRegex` to find components.
|
|
64
|
+
* @property {
|
|
65
|
+
* (info: {root?: HTMLDivElement}) => ArbitraryValue
|
|
66
|
+
* } getValue Gets the value for the type
|
|
67
|
+
* @property {
|
|
68
|
+
* (info: {root?: HTMLDivElement}) => void
|
|
69
|
+
* } [setValue] Should set the value of the form's `getInput` element
|
|
70
|
+
* @property {
|
|
71
|
+
* (info: {
|
|
72
|
+
* value?: ArbitraryValue,
|
|
73
|
+
* typeNamespace?: string,
|
|
74
|
+
* type?: string,
|
|
75
|
+
* topRoot?: HTMLDivElement,
|
|
76
|
+
* resultType?: "keys"|"values"|"both",
|
|
77
|
+
* format?: string
|
|
78
|
+
* }) => JamilihArray
|
|
79
|
+
* } viewUI
|
|
80
|
+
* @property {
|
|
81
|
+
* (info: {
|
|
82
|
+
* value?: ArbitraryValue,
|
|
83
|
+
* typeNamespace?: string,
|
|
84
|
+
* }) => JamilihArray
|
|
85
|
+
* } editUI
|
|
86
|
+
* @property {
|
|
87
|
+
* (info: {root: HTMLDivElement}) =>
|
|
88
|
+
* HTMLInputElement|HTMLTextareaElement|HTMLSelectElement
|
|
89
|
+
* } getInput Gets the form control (with `value`)
|
|
90
|
+
* @property {
|
|
91
|
+
* (path: string, value: ArbitraryValue) => ArbitraryValue
|
|
92
|
+
* } [resolveReference] Gets the reference. For array and object
|
|
93
|
+
* references types only
|
|
94
|
+
* @property {(info: {root: HTMLDivElement, topRoot?: HTMLDivElement}) => {
|
|
95
|
+
* message: string,
|
|
96
|
+
* valid: boolean
|
|
97
|
+
* }} [validate] Message will be used if validity is false.
|
|
98
|
+
* @property {(info: {topRoot: HTMLDivElement}) => void} [validateAll] For
|
|
99
|
+
* validation of array and object references only.
|
|
100
|
+
* @property {{
|
|
101
|
+
* structuredCloning: {
|
|
102
|
+
* after: string,
|
|
103
|
+
* contexts: string[]
|
|
104
|
+
* }
|
|
105
|
+
* }} [stateDependent] The type after which it should be placed and its
|
|
106
|
+
* context types
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
Types.availableTypes = {
|
|
110
|
+
null: nullType,
|
|
111
|
+
true: trueType,
|
|
112
|
+
false: falseType,
|
|
113
|
+
number: numberType,
|
|
114
|
+
bigint: bigintType,
|
|
115
|
+
string: stringType,
|
|
116
|
+
arrayReference: arrayReferenceType,
|
|
117
|
+
objectReference: objectReferenceType,
|
|
118
|
+
array: arrayType,
|
|
119
|
+
// Note: We don't do for BooleanObject/NumberObject/StringObject, date,
|
|
120
|
+
// regexp, as added properties on them are not being cloned (in Chrome
|
|
121
|
+
// at least)
|
|
122
|
+
object: objectType,
|
|
123
|
+
date: dateType,
|
|
124
|
+
|
|
125
|
+
// This type is only for throwing upon cloning errors:
|
|
126
|
+
// 'checkDataCloneException'
|
|
127
|
+
// This type might be supported by evaluable JS or config
|
|
128
|
+
// passed in:
|
|
129
|
+
userObject: ['User objects'],
|
|
130
|
+
undef: undefinedType,
|
|
131
|
+
Infinities: InfinitiesSuperType,
|
|
132
|
+
SpecialNumber: SpecialNumberSuperType,
|
|
133
|
+
|
|
134
|
+
regexp: regexpType,
|
|
135
|
+
BooleanObject: BooleanObjectType,
|
|
136
|
+
NumberObject: NumberObjectType,
|
|
137
|
+
StringObject: StringObjectType,
|
|
138
|
+
|
|
139
|
+
map: {
|
|
140
|
+
option: ['Map']
|
|
141
|
+
},
|
|
142
|
+
set: {
|
|
143
|
+
option: ['Set']
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
file: {
|
|
147
|
+
option: ['File']
|
|
148
|
+
},
|
|
149
|
+
filelist: {
|
|
150
|
+
option: ['FileList']
|
|
151
|
+
},
|
|
152
|
+
blobHTML: blobHTMLType,
|
|
153
|
+
arraybuffer: {
|
|
154
|
+
option: ['ArrayBuffer']
|
|
155
|
+
},
|
|
156
|
+
arraybufferview: {
|
|
157
|
+
option: ['ArrayBufferView']
|
|
158
|
+
},
|
|
159
|
+
dataview: {
|
|
160
|
+
option: ['DataView']
|
|
161
|
+
},
|
|
162
|
+
imagedata: {
|
|
163
|
+
option: ['ImageData']
|
|
164
|
+
},
|
|
165
|
+
imagebitmap: {
|
|
166
|
+
option: ['ImageBitmap']
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
// Typed Arrays
|
|
170
|
+
int8array: {
|
|
171
|
+
option: ['Int8Array']
|
|
172
|
+
},
|
|
173
|
+
uint8array: {
|
|
174
|
+
option: ['Uint8Array']
|
|
175
|
+
},
|
|
176
|
+
uint8clampedarray: {
|
|
177
|
+
option: ['Uint8ClampedArray']
|
|
178
|
+
},
|
|
179
|
+
int16array: {
|
|
180
|
+
option: ['Int16Array']
|
|
181
|
+
},
|
|
182
|
+
uint16array: {
|
|
183
|
+
option: ['Uint16Array']
|
|
184
|
+
},
|
|
185
|
+
int32array: {
|
|
186
|
+
option: ['Int32Array']
|
|
187
|
+
},
|
|
188
|
+
uint32array: {
|
|
189
|
+
option: ['Uint32Array']
|
|
190
|
+
},
|
|
191
|
+
float32array: {
|
|
192
|
+
option: ['Float32Array']
|
|
193
|
+
},
|
|
194
|
+
float64array: {
|
|
195
|
+
option: ['Float64Array']
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
// Intl (imperfect)
|
|
199
|
+
IntlCollator: {
|
|
200
|
+
option: ['Intl.Collator']
|
|
201
|
+
},
|
|
202
|
+
IntlDateTimeFormat: {
|
|
203
|
+
option: ['Intl.DateTimeFormat']
|
|
204
|
+
},
|
|
205
|
+
IntlNumberFormat: {
|
|
206
|
+
option: ['Intl.NumberFormat']
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
// We're catching this instead of using this
|
|
210
|
+
sparseUndefined: sparseUndefinedType
|
|
211
|
+
};
|
|
212
|
+
Types.availableTypes.ValidDate = {
|
|
213
|
+
valid: true
|
|
214
|
+
};
|
|
215
|
+
/*
|
|
216
|
+
Types.availableTypes.sparseArrays = {
|
|
217
|
+
sparse: true
|
|
218
|
+
};
|
|
219
|
+
*/
|
|
220
|
+
Types.availableTypes.arrayNonindexKeys = {
|
|
221
|
+
sparse: true
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @param {[copyTo: string, copyFrom: string]} replacements
|
|
226
|
+
* @returns {void}
|
|
227
|
+
*/
|
|
228
|
+
const copyTypeObjs = (replacements) => {
|
|
229
|
+
replacements.forEach(([copyFrom, copyTo]) => {
|
|
230
|
+
Object.assign(Types.availableTypes[copyTo], Types.availableTypes[copyFrom]);
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
copyTypeObjs([
|
|
234
|
+
['date', 'ValidDate'],
|
|
235
|
+
[
|
|
236
|
+
'array',
|
|
237
|
+
'arrayNonindexKeys'
|
|
238
|
+
// 'sparseArrays'
|
|
239
|
+
]
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @public
|
|
244
|
+
* @param {RootElement} root
|
|
245
|
+
* @returns {string|boolean} Why would it not exist?
|
|
246
|
+
*/
|
|
247
|
+
Types.getTypeForRoot = (root) => {
|
|
248
|
+
return root && root.dataset.type;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @public
|
|
253
|
+
* @param {RootElement} root
|
|
254
|
+
* @param {StateObject} stateObj
|
|
255
|
+
* @param {string} currentPath
|
|
256
|
+
* @returns {StructuredCloneValue}
|
|
257
|
+
*/
|
|
258
|
+
Types.getValueForRoot = (root, stateObj, currentPath) => {
|
|
259
|
+
return Types.availableTypes[Types.getTypeForRoot(root)].getValue({
|
|
260
|
+
root, stateObj, currentPath
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @public
|
|
266
|
+
* @param {RootElement} root
|
|
267
|
+
* @returns {null|HTMLInputElement}
|
|
268
|
+
*/
|
|
269
|
+
Types.getFormControlForRoot = (root) => {
|
|
270
|
+
const typeObj = Types.availableTypes[Types.getTypeForRoot(root)];
|
|
271
|
+
/* istanbul ignore if -- All have except aliases */
|
|
272
|
+
if (!typeObj.getInput) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
return typeObj.getInput({root});
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @public
|
|
280
|
+
* @param {string|Element} selOrEl
|
|
281
|
+
* @param {StateObject} stateObj
|
|
282
|
+
* @returns {StructuredCloneValue}
|
|
283
|
+
*/
|
|
284
|
+
Types.getValueFromRootAncestor = (selOrEl, stateObj) => {
|
|
285
|
+
return Types.getValueForRoot($e(selOrEl, 'div[data-type]'), stateObj);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @public
|
|
290
|
+
* @param {string|Element} selOrEl
|
|
291
|
+
* @returns {null|HTMLInputElement}
|
|
292
|
+
*/
|
|
293
|
+
Types.getFormControlFromRootAncestor = (selOrEl) => {
|
|
294
|
+
const root = $e(selOrEl, 'div[data-type]');
|
|
295
|
+
if (!root) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
return Types.getFormControlForRoot(root);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* @param {string} format
|
|
303
|
+
* @param {string} state
|
|
304
|
+
* @returns {string[]}
|
|
305
|
+
*/
|
|
306
|
+
Types.getTypesForFormatAndState = (
|
|
307
|
+
format, state
|
|
308
|
+
) => Formats.availableFormats[format].getTypesForState(state);
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* @public
|
|
312
|
+
* @param {string} type
|
|
313
|
+
* @returns {JamilihArray}
|
|
314
|
+
*/
|
|
315
|
+
Types.getOptionForType = (type) => {
|
|
316
|
+
const optInfo = [...Types.availableTypes[type].option];
|
|
317
|
+
optInfo[1] = {value: type, ...optInfo[1]};
|
|
318
|
+
return optInfo;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* @public
|
|
323
|
+
* @param {string} format
|
|
324
|
+
* @param {string} parserState
|
|
325
|
+
* @returns {JamilihArray[]}
|
|
326
|
+
*/
|
|
327
|
+
Types.getTypeOptionsForFormatAndState = (format, parserState) => {
|
|
328
|
+
const typesForFormatAndState = Types.getTypesForFormatAndState(
|
|
329
|
+
format, parserState
|
|
330
|
+
);
|
|
331
|
+
return typesForFormatAndState.map((type) => {
|
|
332
|
+
return Types.getOptionForType(type);
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* @public
|
|
338
|
+
* @param {object} cfg
|
|
339
|
+
* @param {boolean} cfg."readonly"
|
|
340
|
+
* @param {"both"|"keys"|"values"} cfg.resultType
|
|
341
|
+
* @param {string} cfg.typeNamespace
|
|
342
|
+
* @param {string} cfg.type
|
|
343
|
+
* @param {RootElement} cfg.topRoot
|
|
344
|
+
* @param {boolean} cfg.bringIntoFocus
|
|
345
|
+
* @param {BuildTypeChoices} cfg.buildTypeChoices
|
|
346
|
+
* @param {string} cfg.format
|
|
347
|
+
* @param {string} cfg.schemaContent
|
|
348
|
+
* @param {StateObject} cfg.schemaState Not currently in use and may
|
|
349
|
+
* need to change the type
|
|
350
|
+
* @param {StructuredCloneValue} cfg.value
|
|
351
|
+
* @param {boolean} cfg.hasValue
|
|
352
|
+
* @returns {Element}
|
|
353
|
+
*/
|
|
354
|
+
Types.getUIForModeAndType = ({
|
|
355
|
+
readonly, resultType, typeNamespace, type, topRoot, bringIntoFocus,
|
|
356
|
+
buildTypeChoices, format, schemaContent, schemaState, value, hasValue
|
|
357
|
+
}) => {
|
|
358
|
+
const typeObj = Types.availableTypes[type];
|
|
359
|
+
const root = jml(
|
|
360
|
+
...typeObj[readonly ? 'viewUI' : 'editUI'](
|
|
361
|
+
hasValue
|
|
362
|
+
? {
|
|
363
|
+
typeNamespace, type, buildTypeChoices,
|
|
364
|
+
format, schemaContent, schemaState,
|
|
365
|
+
resultType, topRoot, bringIntoFocus, value
|
|
366
|
+
}
|
|
367
|
+
: {
|
|
368
|
+
typeNamespace, type, buildTypeChoices,
|
|
369
|
+
format, schemaContent, schemaState,
|
|
370
|
+
resultType, topRoot, bringIntoFocus
|
|
371
|
+
}
|
|
372
|
+
)
|
|
373
|
+
);
|
|
374
|
+
if (!readonly && typeObj.validate) {
|
|
375
|
+
const formControl = typeObj.getInput({root});
|
|
376
|
+
formControl.addEventListener('input', () => {
|
|
377
|
+
Types.validate({type, root, topRoot});
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return root;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
Types.contexts = {};
|
|
384
|
+
Object.entries(Types.availableTypes).forEach(([type, {stateDependent}]) => {
|
|
385
|
+
if (stateDependent) {
|
|
386
|
+
Object.entries(stateDependent).forEach(([format, formatStateDependent]) => {
|
|
387
|
+
if (!Types.contexts[format]) {
|
|
388
|
+
Types.contexts[format] = {};
|
|
389
|
+
}
|
|
390
|
+
const {contexts, after} = formatStateDependent;
|
|
391
|
+
contexts.forEach((context) => {
|
|
392
|
+
if (!Types.contexts[format][context]) {
|
|
393
|
+
Types.contexts[format][context] = [];
|
|
394
|
+
}
|
|
395
|
+
Types.contexts[format][context].push({type, after});
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* @public
|
|
403
|
+
* @param {object} cfg
|
|
404
|
+
* @param {HTMLFormElement} cfg.form
|
|
405
|
+
* @param {string} cfg.typeNamespace
|
|
406
|
+
* @param {string} cfg.keySelectClass
|
|
407
|
+
* @returns {boolean}
|
|
408
|
+
*/
|
|
409
|
+
Types.validValuesSet = ({form, typeNamespace, keySelectClass}) => {
|
|
410
|
+
// If form is hidden, don't list errors by default
|
|
411
|
+
if (!form.offsetParent ||
|
|
412
|
+
// Not an invalid form (bad key or value)
|
|
413
|
+
// May be redundant as re-validating below
|
|
414
|
+
!form.checkValidity()
|
|
415
|
+
) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const typeChoices = $$e(
|
|
420
|
+
form,
|
|
421
|
+
keySelectClass ? `.${keySelectClass}` : `.typeChoices-${typeNamespace}`
|
|
422
|
+
);
|
|
423
|
+
return (
|
|
424
|
+
// Specific value type set if present (any descendant, not
|
|
425
|
+
// only the first) chosen
|
|
426
|
+
typeChoices.every((sel) => {
|
|
427
|
+
// console.log('sel', sel.value !== '' && sel.$validate());
|
|
428
|
+
// Hidden are ok
|
|
429
|
+
return !sel.offsetParent ||
|
|
430
|
+
// If present, must be valid
|
|
431
|
+
(sel.value !== '' && sel.$validate());
|
|
432
|
+
})
|
|
433
|
+
// Container of a specific type added (should always be present
|
|
434
|
+
// if typeChoices non-empty)
|
|
435
|
+
// $e(form, '.typeContainer')
|
|
436
|
+
);
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Any other possibilities than `div`?
|
|
441
|
+
* @typedef {HTMLDivElement} RootElement
|
|
442
|
+
*/
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* @public
|
|
446
|
+
* @param {object} cfg
|
|
447
|
+
* @param {RootElement} cfg.topRoot
|
|
448
|
+
* @returns {void}
|
|
449
|
+
*/
|
|
450
|
+
Types.validateAllReferences = ({topRoot}) => {
|
|
451
|
+
/* istanbul ignore if -- Unreachable? */
|
|
452
|
+
if (!topRoot) {
|
|
453
|
+
console.log('No references present');
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Could just hard-code arrayReference and objectReference,
|
|
458
|
+
// but we'll try to avoid depending on specific types
|
|
459
|
+
Object.values(Types.availableTypes).forEach((typeObject) => {
|
|
460
|
+
if (typeObject.validateAll) {
|
|
461
|
+
typeObject.validateAll({topRoot});
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
if (Types.customValidateAllReferences) {
|
|
466
|
+
Types.customValidateAllReferences({topRoot});
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @public
|
|
472
|
+
* @param {object} cfg
|
|
473
|
+
* @param {string} cfg.type
|
|
474
|
+
* @param {RootElement} cfg.root
|
|
475
|
+
* @param {RootElement} cfg.topRoot
|
|
476
|
+
* @returns {boolean}
|
|
477
|
+
*/
|
|
478
|
+
Types.validate = ({type, root, topRoot}) => {
|
|
479
|
+
const typeObj = Types.availableTypes[type];
|
|
480
|
+
// Todo (low): We limit for now to input boxes which have `validate`
|
|
481
|
+
if (typeObj.validate) {
|
|
482
|
+
const {valid, message} = typeObj.validate({root, topRoot});
|
|
483
|
+
const formControl = typeObj.getInput({root});
|
|
484
|
+
formControl.setCustomValidity(
|
|
485
|
+
valid
|
|
486
|
+
? ''
|
|
487
|
+
/* istanbul ignore next -- Should always have a message */
|
|
488
|
+
: message || 'Invalid'
|
|
489
|
+
);
|
|
490
|
+
formControl.reportValidity();
|
|
491
|
+
return valid;
|
|
492
|
+
}
|
|
493
|
+
return true;
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* @param {object} cfg
|
|
498
|
+
* @param {string} cfg.type
|
|
499
|
+
* @param {RootElement} cfg.root
|
|
500
|
+
* @param {StructuredCloneValue} cfg.value
|
|
501
|
+
* @returns {void}
|
|
502
|
+
*/
|
|
503
|
+
Types.setValue = ({type, root, value}) => {
|
|
504
|
+
const typeObj = Types.availableTypes[type];
|
|
505
|
+
if (typeObj.setValue) {
|
|
506
|
+
typeObj.setValue({root, value});
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
*
|
|
512
|
+
* @param {string} str
|
|
513
|
+
* @returns {string}
|
|
514
|
+
*/
|
|
515
|
+
function escapeRegex (str) {
|
|
516
|
+
return String(str)
|
|
517
|
+
.replace(/[.\\+*?^[\]$(){}=!<>|:-]/gu, '\\$&');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Todo (low): Should really add real parser
|
|
521
|
+
// Todo (low): Implement `getStringForValue` (e.g., to expose feature for
|
|
522
|
+
// bookmarking object value currently in view); would not be
|
|
523
|
+
// enough to iterate DOM to get string URL as we'd also like
|
|
524
|
+
// the ability to have arbitrary JSON/structuredCloning sent to this
|
|
525
|
+
// URL from other sites/programs (can currently pass in JSON
|
|
526
|
+
// format to the URL, but that is still expecting our Router
|
|
527
|
+
// string syntax)
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* @public
|
|
531
|
+
* @param {string} s
|
|
532
|
+
* @param {object} cfg
|
|
533
|
+
* @param {string} cfg.format
|
|
534
|
+
* @param {string} cfg.state
|
|
535
|
+
* @param {TypeObject[]} [cfg.endMatchTypeObjs=[]]
|
|
536
|
+
* @param {boolean} [cfg.firstRun=true]
|
|
537
|
+
* @param {
|
|
538
|
+
* [
|
|
539
|
+
* type: string,
|
|
540
|
+
* parent: array|object,
|
|
541
|
+
* parentPath: string,
|
|
542
|
+
* path: string
|
|
543
|
+
* ]
|
|
544
|
+
* } [cfg.rootHolder=[]]
|
|
545
|
+
* @param {ArbitraryArray|ArbitraryObject} cfg.parent
|
|
546
|
+
* @param {string} cfg.parentPath
|
|
547
|
+
* @returns {{
|
|
548
|
+
* value: ArbitraryValue,
|
|
549
|
+
* remnant: string,
|
|
550
|
+
* beginOnly: boolean,
|
|
551
|
+
* assign: boolean
|
|
552
|
+
* }}
|
|
553
|
+
*/
|
|
554
|
+
Types.getValueForString = (s, {
|
|
555
|
+
format, state, endMatchTypeObjs = [], firstRun = true,
|
|
556
|
+
rootHolder = [], parent, parentPath
|
|
557
|
+
}) => {
|
|
558
|
+
let assign = true;
|
|
559
|
+
let match;
|
|
560
|
+
const allowedTypes = Types.getTypesForFormatAndState(format, state);
|
|
561
|
+
const allowedTypeObjs = Object.entries(
|
|
562
|
+
Types.availableTypes
|
|
563
|
+
).filter(([type]) => allowedTypes.includes(type));
|
|
564
|
+
const allowedTypeObjsVals = allowedTypeObjs.map(([, arr]) => arr);
|
|
565
|
+
|
|
566
|
+
const endings = '|' + allowedTypeObjsVals.reduce((arr, typeObj) => {
|
|
567
|
+
if (typeObj.regexEndings) {
|
|
568
|
+
arr.push(...typeObj.regexEndings);
|
|
569
|
+
arr = [...new Set(arr)];
|
|
570
|
+
}
|
|
571
|
+
return arr;
|
|
572
|
+
}, []).map((str) => escapeRegex(str)).join('|');
|
|
573
|
+
|
|
574
|
+
let found = allowedTypeObjs.find(([_type, typeObj]) => {
|
|
575
|
+
let {stringRegex} = typeObj;
|
|
576
|
+
if (typeof stringRegex === 'function') {
|
|
577
|
+
stringRegex = typeObj.stringRegex(true);
|
|
578
|
+
}
|
|
579
|
+
stringRegex = stringRegex
|
|
580
|
+
// Strip off terminal (dollar sign) when matching substrings
|
|
581
|
+
? new RegExp(
|
|
582
|
+
stringRegex.source.slice(0, -1) + '(?=$' + endings + ')',
|
|
583
|
+
'u'
|
|
584
|
+
)
|
|
585
|
+
: stringRegex;
|
|
586
|
+
match = stringRegex && s && s.match(stringRegex);
|
|
587
|
+
return match;
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
let beginOnly;
|
|
591
|
+
if (found === undefined) {
|
|
592
|
+
found = allowedTypeObjs.find(([_type, typeObj]) => {
|
|
593
|
+
const {stringRegexBegin} = typeObj;
|
|
594
|
+
match = stringRegexBegin && s && s.match(stringRegexBegin);
|
|
595
|
+
if (match) {
|
|
596
|
+
beginOnly = true;
|
|
597
|
+
endMatchTypeObjs.push(typeObj);
|
|
598
|
+
}
|
|
599
|
+
return match;
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
if (found !== undefined) {
|
|
603
|
+
let remnant = s.slice(match[0].length);
|
|
604
|
+
s = s.slice(0, match[0].length);
|
|
605
|
+
// console.log('s0', s, '::', remnant, match);
|
|
606
|
+
let valObj;
|
|
607
|
+
try {
|
|
608
|
+
valObj = found[1].toValue(match[1] || s, {
|
|
609
|
+
format,
|
|
610
|
+
endMatchTypeObjs,
|
|
611
|
+
remnant,
|
|
612
|
+
rootHolder,
|
|
613
|
+
parent,
|
|
614
|
+
parentPath
|
|
615
|
+
});
|
|
616
|
+
} catch (e) {
|
|
617
|
+
console.log('eee', e);
|
|
618
|
+
throw e;
|
|
619
|
+
}
|
|
620
|
+
if (valObj.assign === false) {
|
|
621
|
+
assign = false;
|
|
622
|
+
}
|
|
623
|
+
const {value} = valObj;
|
|
624
|
+
if (valObj.remnant !== undefined) {
|
|
625
|
+
({remnant} = valObj);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (beginOnly && endMatchTypeObjs.length) {
|
|
629
|
+
const endMatch = remnant.match(
|
|
630
|
+
endMatchTypeObjs.slice(-1)[0].stringRegexEnd
|
|
631
|
+
);
|
|
632
|
+
if (endMatch) {
|
|
633
|
+
endMatchTypeObjs.pop(); // Safe now to extract
|
|
634
|
+
remnant = remnant.slice(endMatch[0].length);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (firstRun) {
|
|
638
|
+
const typeson = new Typeson().register(
|
|
639
|
+
structuredCloningThrowing
|
|
640
|
+
);
|
|
641
|
+
try {
|
|
642
|
+
const topRoot = typeson.revive(value);
|
|
643
|
+
rootHolder.forEach(([type, parent, parentPath, path]) => {
|
|
644
|
+
const val = Types.availableTypes[type + 'Reference']
|
|
645
|
+
.resolveReference(path, topRoot);
|
|
646
|
+
const basicType = getJSONType(val);
|
|
647
|
+
// eslint-disable-next-line max-len -- Long
|
|
648
|
+
/* istanbul ignore else -- Successful reference always an object/array? */
|
|
649
|
+
if (
|
|
650
|
+
['array', 'object'].includes(type) && basicType === type
|
|
651
|
+
) {
|
|
652
|
+
parent[parentPath] = val;
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
return [topRoot, remnant, beginOnly, assign];
|
|
656
|
+
} catch (err) {
|
|
657
|
+
console.log('failed Typeson revival', err);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return [value, remnant, beginOnly, assign];
|
|
661
|
+
}
|
|
662
|
+
throw new Error('Bad parsing data');
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
export default Types;
|