@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
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import Formats from '../formats.js';
|
|
2
|
+
import Types from '../types.js';
|
|
3
|
+
import {
|
|
4
|
+
Typeson, unescapeKeyPathComponent, structuredCloningThrowing
|
|
5
|
+
} from '../../node_modules/typeson-registry/dist/index.js';
|
|
6
|
+
import {
|
|
7
|
+
typesonPathToJSONPointer
|
|
8
|
+
} from '../utils/jsonPointer.js';
|
|
9
|
+
|
|
10
|
+
import * as json from './json.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @callback EncapsulateObserver
|
|
14
|
+
* @param {TypesonObserver} observerObj
|
|
15
|
+
* @returns {void}
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {{
|
|
20
|
+
* typeNamespace: string,
|
|
21
|
+
* "readonly": boolean,
|
|
22
|
+
* format: string,
|
|
23
|
+
* schemaContent: string,
|
|
24
|
+
* getPossibleSchemasForPathAndType: (
|
|
25
|
+
* keypath: string,
|
|
26
|
+
* parentPath: string,
|
|
27
|
+
* arrayOrObjectPropertyName: string,
|
|
28
|
+
* valueType: string
|
|
29
|
+
* ) => StateObject
|
|
30
|
+
* }} StateObject
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {StateObject} stateObj
|
|
35
|
+
* @returns {EncapsulateObserver}
|
|
36
|
+
*/
|
|
37
|
+
const encapsulateObserver = (stateObj) => {
|
|
38
|
+
const {
|
|
39
|
+
typeNamespace, readonly, format, schemaContent,
|
|
40
|
+
getPossibleSchemasForPathAndType
|
|
41
|
+
} = stateObj;
|
|
42
|
+
const parents = {};
|
|
43
|
+
return (observerObj) => {
|
|
44
|
+
const {
|
|
45
|
+
type,
|
|
46
|
+
cyclic,
|
|
47
|
+
keypath,
|
|
48
|
+
value,
|
|
49
|
+
replaced,
|
|
50
|
+
cyclicKeypath,
|
|
51
|
+
endIterateIn,
|
|
52
|
+
endIterateOwn,
|
|
53
|
+
endIterateUnsetNumeric,
|
|
54
|
+
clone
|
|
55
|
+
} = observerObj;
|
|
56
|
+
// console.log('observerObj', observerObj);
|
|
57
|
+
if ('replaced' in observerObj) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (cyclic === 'readonly' && !Array.isArray(observerObj.value)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (endIterateIn || endIterateOwn) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (endIterateUnsetNumeric || (
|
|
67
|
+
clone === undefined && cyclicKeypath === undefined && Array.isArray(value)
|
|
68
|
+
)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* istanbul ignore if -- Not part of format */
|
|
73
|
+
if (type === 'sparseUndefined') { // We'll handle otherwise
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// console.log('observerObj', observerObj);
|
|
78
|
+
|
|
79
|
+
let newType;
|
|
80
|
+
let newValue = value;
|
|
81
|
+
|
|
82
|
+
/* schema: || format.startsWith('schema-') */
|
|
83
|
+
const state = format === 'structuredCloning'
|
|
84
|
+
? 'arrayNonindexKeys'
|
|
85
|
+
// ? 'sparseArrays'
|
|
86
|
+
: 'array';
|
|
87
|
+
if (typeof cyclicKeypath === 'string') {
|
|
88
|
+
newValue = typesonPathToJSONPointer(cyclicKeypath);
|
|
89
|
+
newType = type === 'array' ? 'arrayReference' : 'objectReference';
|
|
90
|
+
newType = canonicalToAvailableType(
|
|
91
|
+
format, state, newType, value
|
|
92
|
+
); // Todo (low): Add accurate state for second argument
|
|
93
|
+
} else {
|
|
94
|
+
try {
|
|
95
|
+
newType = canonicalToAvailableType(
|
|
96
|
+
format, state, type, value
|
|
97
|
+
); // Todo (low): Add state for second argument
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.log('err', type, err);
|
|
100
|
+
stateObj.error = err;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const li = keypath.lastIndexOf('.');
|
|
106
|
+
const arrayOrObjectPropertyName =
|
|
107
|
+
unescapeKeyPathComponent(keypath.slice(li + 1));
|
|
108
|
+
const parentPath = li === -1 ? '' : keypath.slice(0, li);
|
|
109
|
+
|
|
110
|
+
const hasChildren = [
|
|
111
|
+
'array', 'object',
|
|
112
|
+
// 'sparseArrays',
|
|
113
|
+
'arrayNonindexKeys'
|
|
114
|
+
].includes(newType);
|
|
115
|
+
|
|
116
|
+
if (!stateObj.rootUI) {
|
|
117
|
+
stateObj.rootUI = Types.getUIForModeAndType({
|
|
118
|
+
readonly,
|
|
119
|
+
typeNamespace,
|
|
120
|
+
type: newType,
|
|
121
|
+
bringIntoFocus: false,
|
|
122
|
+
buildTypeChoices: Formats.buildTypeChoices,
|
|
123
|
+
format,
|
|
124
|
+
schemaContent,
|
|
125
|
+
schemaState: getPossibleSchemasForPathAndType,
|
|
126
|
+
/* schema:
|
|
127
|
+
&&
|
|
128
|
+
getPossibleSchemasForPathAndType({
|
|
129
|
+
keypath,
|
|
130
|
+
parentPath: '',
|
|
131
|
+
valueType: newType
|
|
132
|
+
}),
|
|
133
|
+
*/
|
|
134
|
+
value: newValue,
|
|
135
|
+
hasValue: true,
|
|
136
|
+
// Not currently in use but may be convenient for a
|
|
137
|
+
// type wanting the serialized data
|
|
138
|
+
replaced
|
|
139
|
+
});
|
|
140
|
+
parents[''] = stateObj.rootUI;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Todo (low): If could be async, use async encapsulate method
|
|
145
|
+
// Todo (low): Handle `awaitingTypesonPromise` with place-holder
|
|
146
|
+
// Todo (low): Handle `resolvingTypesonPromise` to replace place-holder
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
const ui = parents[parentPath];
|
|
149
|
+
// These errors occur, e.g., if `replacing` not first added and then
|
|
150
|
+
// a converted object gets treated as the root UI (e.g., for `regexp`
|
|
151
|
+
// or `blobHTML` at root)
|
|
152
|
+
// If there isn't a problem in Typeson with transmitting the `readonly`
|
|
153
|
+
// status recursively down the object (should be no need to check
|
|
154
|
+
// for circulars there?), could change Typeson to report `readonly`
|
|
155
|
+
// for the nested items, in which case, we could block out `readonly`
|
|
156
|
+
// instead of doing this here
|
|
157
|
+
if (!ui || !ui.$addAndSetArrayElement) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const root = ui.$addAndSetArrayElement({
|
|
161
|
+
propName: arrayOrObjectPropertyName,
|
|
162
|
+
type: newType,
|
|
163
|
+
value: newValue,
|
|
164
|
+
bringIntoFocus: false,
|
|
165
|
+
schemaContent,
|
|
166
|
+
schemaState: getPossibleSchemasForPathAndType
|
|
167
|
+
/* schema:
|
|
168
|
+
&& getPossibleSchemasForPathAndType({
|
|
169
|
+
keypath,
|
|
170
|
+
parentPath,
|
|
171
|
+
arrayOrObjectPropertyName,
|
|
172
|
+
valueType: newType
|
|
173
|
+
})
|
|
174
|
+
*/
|
|
175
|
+
});
|
|
176
|
+
if (!readonly) {
|
|
177
|
+
Types.setValue({type: newType, root, value: newValue});
|
|
178
|
+
Types.validate({type: newType, root, topRoot: stateObj.rootUI});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (hasChildren) {
|
|
182
|
+
parents[keypath] = root;
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @param {string[]} originTypes
|
|
190
|
+
* @param {[originType: string, replacementType: string][]} replacements
|
|
191
|
+
* @returns {void}
|
|
192
|
+
*/
|
|
193
|
+
const replaceTypes = (originTypes, replacements) => {
|
|
194
|
+
replacements.forEach(([originType, replacementType]) => {
|
|
195
|
+
originTypes.splice(originTypes.indexOf(originType), 1, replacementType);
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @param {string} format
|
|
201
|
+
* @param {string} state
|
|
202
|
+
* @param {string} valType
|
|
203
|
+
* @param {ArbitraryValue} v
|
|
204
|
+
* @throws {Error}
|
|
205
|
+
* @returns {string}
|
|
206
|
+
*/
|
|
207
|
+
const canonicalToAvailableType = (format, state, valType, v) => {
|
|
208
|
+
const frmt = Formats.availableFormats[format];
|
|
209
|
+
const {getTypesForState, convertFromTypeson, testInvalid} = frmt;
|
|
210
|
+
const allowableTypes = getTypesForState.call(frmt, state);
|
|
211
|
+
let ret;
|
|
212
|
+
console.log('format, state, valType, v', format, state, valType, v);
|
|
213
|
+
const isInvalid = (newValType) => {
|
|
214
|
+
console.log('newValType', newValType);
|
|
215
|
+
const err = new Error('Invalid');
|
|
216
|
+
err.newValType = newValType;
|
|
217
|
+
throw err;
|
|
218
|
+
};
|
|
219
|
+
if (convertFromTypeson) {
|
|
220
|
+
const newValType = convertFromTypeson(valType);
|
|
221
|
+
if (typeof newValType === 'string') {
|
|
222
|
+
if (testInvalid && testInvalid(newValType, v)) {
|
|
223
|
+
return isInvalid(newValType);
|
|
224
|
+
}
|
|
225
|
+
valType = newValType;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (allowableTypes.some((allowableType) => {
|
|
229
|
+
if (allowableType === valType) {
|
|
230
|
+
ret = allowableType;
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
})) {
|
|
235
|
+
return ret;
|
|
236
|
+
}
|
|
237
|
+
console.log('ret', ret);
|
|
238
|
+
allowableTypes.some((allowableType) => {
|
|
239
|
+
const {
|
|
240
|
+
valueMatch, superType, childTypes
|
|
241
|
+
} = Types.availableTypes[allowableType];
|
|
242
|
+
if (
|
|
243
|
+
(superType && valueMatch &&
|
|
244
|
+
// Currently using for `true` and `false`
|
|
245
|
+
superType === valType && valueMatch(v)) ||
|
|
246
|
+
(childTypes && childTypes.includes(valType))
|
|
247
|
+
) {
|
|
248
|
+
ret = allowableType;
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
return false;
|
|
252
|
+
});
|
|
253
|
+
console.log('ret2', ret);
|
|
254
|
+
if (ret === undefined) {
|
|
255
|
+
return isInvalid(valType);
|
|
256
|
+
}
|
|
257
|
+
return ret;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* @callback FormatIterator
|
|
262
|
+
* @param {StructuredCloneValue} records
|
|
263
|
+
* @param {{
|
|
264
|
+
* format: string
|
|
265
|
+
* error: Error
|
|
266
|
+
* rootUI: Element
|
|
267
|
+
* }} stateObj
|
|
268
|
+
* @returns {Promise<Element>}
|
|
269
|
+
*/
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @type {FormatIterator}
|
|
273
|
+
*/
|
|
274
|
+
export const iterate = (records, stateObj) => {
|
|
275
|
+
console.log('records', records);
|
|
276
|
+
if (!stateObj.format) {
|
|
277
|
+
stateObj.format = 'structuredCloning';
|
|
278
|
+
}
|
|
279
|
+
// Todo: Replace this with async typeson?
|
|
280
|
+
// eslint-disable-next-line promise/avoid-new
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
const typeson = new Typeson({
|
|
283
|
+
encapsulateObserver: encapsulateObserver(stateObj)
|
|
284
|
+
}).register(structuredCloningThrowing);
|
|
285
|
+
typeson.encapsulate(records);
|
|
286
|
+
// Todo (low): We might want to run async encapsulate for
|
|
287
|
+
// async types (and put this after Promise resolves)
|
|
288
|
+
if (stateObj.error) {
|
|
289
|
+
reject(stateObj.error);
|
|
290
|
+
} else {
|
|
291
|
+
resolve(stateObj.rootUI);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @param {string} state
|
|
298
|
+
* @returns {string[]}
|
|
299
|
+
*/
|
|
300
|
+
export const getTypesForState = function (state) {
|
|
301
|
+
if (state && Types.contexts.structuredCloning[state]) {
|
|
302
|
+
const typesForFormat = this.getTypesForState() ||
|
|
303
|
+
/* istanbul ignore next -- types should be an array */
|
|
304
|
+
[];
|
|
305
|
+
Types.contexts.structuredCloning[state].forEach(({type, after}) => {
|
|
306
|
+
const precedingIdx = typesForFormat.indexOf(after);
|
|
307
|
+
typesForFormat.splice(precedingIdx + 1, 0, type);
|
|
308
|
+
});
|
|
309
|
+
return typesForFormat;
|
|
310
|
+
}
|
|
311
|
+
return this.types();
|
|
312
|
+
/*
|
|
313
|
+
// Todo (low): These need to specify their own inner contexts
|
|
314
|
+
if (['map', 'set'].includes(state)) {return;}
|
|
315
|
+
if ('int8array', 'uint8array', 'uint8clampedarray',
|
|
316
|
+
'int16array', 'uint16array', 'int32array',
|
|
317
|
+
'uint32array', 'float32array', 'float64array'
|
|
318
|
+
).includes(state)) {return;}
|
|
319
|
+
*/
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* @returns {string[]}
|
|
324
|
+
*/
|
|
325
|
+
export const types = () => {
|
|
326
|
+
const jsonTypes = json.types();
|
|
327
|
+
replaceTypes(jsonTypes, [
|
|
328
|
+
[
|
|
329
|
+
'array',
|
|
330
|
+
// 'sparseArrays',
|
|
331
|
+
'arrayNonindexKeys'
|
|
332
|
+
]
|
|
333
|
+
]);
|
|
334
|
+
return [
|
|
335
|
+
// This type is only for throwing upon cloning errors:
|
|
336
|
+
// 'checkDataCloneException'
|
|
337
|
+
// This type might be supported by evaluable JS or config passed in:
|
|
338
|
+
// 'userObject'
|
|
339
|
+
...jsonTypes,
|
|
340
|
+
'undef', // Explicit undefined only
|
|
341
|
+
'bigint',
|
|
342
|
+
'SpecialNumber', // '`NaN`, `Infinity`, `-Infinity`'},
|
|
343
|
+
'date',
|
|
344
|
+
'regexp',
|
|
345
|
+
'BooleanObject',
|
|
346
|
+
'NumberObject',
|
|
347
|
+
'StringObject',
|
|
348
|
+
'blobHTML'
|
|
349
|
+
// Ok, but will need some work
|
|
350
|
+
// 'map', 'set',
|
|
351
|
+
// 'blob', 'file', 'filelist'
|
|
352
|
+
// 'arraybuffer', 'arraybufferview'
|
|
353
|
+
// 'dataview', 'imagedata', 'imagebitmap',
|
|
354
|
+
/*
|
|
355
|
+
// Typed Arrays
|
|
356
|
+
'int8array',
|
|
357
|
+
'uint8array',
|
|
358
|
+
'uint8clampedarray',
|
|
359
|
+
'int16array',
|
|
360
|
+
'uint16array',
|
|
361
|
+
'int32array',
|
|
362
|
+
'uint32array',
|
|
363
|
+
'float32array',
|
|
364
|
+
'float64array',
|
|
365
|
+
|
|
366
|
+
// Intl (imperfect)
|
|
367
|
+
'IntlCollator',
|
|
368
|
+
'IntlDateTimeFormat',
|
|
369
|
+
'IntlNumberFormat'
|
|
370
|
+
*/
|
|
371
|
+
];
|
|
372
|
+
};
|
package/src/formats.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import Types from './types.js';
|
|
2
|
+
import {jml} from '../node_modules/jamilih/dist/jml-es.js';
|
|
3
|
+
|
|
4
|
+
import {$e, DOM} from './utils/templateUtils.js';
|
|
5
|
+
import dialogs from './utils/dialogs.js';
|
|
6
|
+
|
|
7
|
+
import * as indexedDBKey from './formats/indexedDBKey.js';
|
|
8
|
+
import * as json from './formats/json.js';
|
|
9
|
+
import * as structuredCloning from './formats/structuredCloning.js';
|
|
10
|
+
|
|
11
|
+
// Using methods ensure we have fresh copies
|
|
12
|
+
const Formats = {
|
|
13
|
+
availableFormats: {
|
|
14
|
+
indexedDBKey,
|
|
15
|
+
json,
|
|
16
|
+
// Todo (readme): these too? getTypesForState(state)
|
|
17
|
+
/* schema:
|
|
18
|
+
schemaAndArbitrary,
|
|
19
|
+
schemaOnly,
|
|
20
|
+
*/
|
|
21
|
+
structuredCloning
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* An arbitrary Structured Clone, JSON, etc. value.
|
|
27
|
+
* @typedef {any} StructuredCloneValue
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @callback BuildTypeChoices
|
|
32
|
+
* @param {object} cfg
|
|
33
|
+
* @param {string} cfg.format
|
|
34
|
+
* @param {string} cfg.typeNamespace
|
|
35
|
+
* @param {StructuredCloneValue} cfg.value
|
|
36
|
+
* @param {boolean} [cfg.setValue=false]
|
|
37
|
+
* @param {string} cfg.state
|
|
38
|
+
* @param {string} cfg.keySelectClass
|
|
39
|
+
* @param {boolean} cfg.requireObject
|
|
40
|
+
* @param {boolean} cfg.objectHasValue
|
|
41
|
+
* @param {RootElement} cfg.topRoot Always a `div` element?
|
|
42
|
+
* @param {string} cfg.schema Schema name
|
|
43
|
+
* @param {string} cfg.schemaContent Schema contents
|
|
44
|
+
* @returns {JamilihArray}
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @type {BuildTypeChoices}
|
|
49
|
+
*/
|
|
50
|
+
const buildTypeChoices = Formats.buildTypeChoices = ({
|
|
51
|
+
format,
|
|
52
|
+
typeNamespace,
|
|
53
|
+
value,
|
|
54
|
+
setValue = false,
|
|
55
|
+
state,
|
|
56
|
+
// itemIndex = 0,
|
|
57
|
+
keySelectClass,
|
|
58
|
+
requireObject,
|
|
59
|
+
objectHasValue,
|
|
60
|
+
topRoot,
|
|
61
|
+
schema,
|
|
62
|
+
schemaContent
|
|
63
|
+
}) => {
|
|
64
|
+
// console.log('format', format, 'state', state, 'path', typeNamespace);
|
|
65
|
+
const typeOptions = requireObject
|
|
66
|
+
? [Types.getOptionForType('object')]
|
|
67
|
+
: Types.getTypeOptionsForFormatAndState(format, state);
|
|
68
|
+
|
|
69
|
+
let editUI;
|
|
70
|
+
const sel = jml('select', {
|
|
71
|
+
hidden: requireObject,
|
|
72
|
+
class: `typeChoices-${typeNamespace}${keySelectClass
|
|
73
|
+
? ' ' + keySelectClass
|
|
74
|
+
: ''
|
|
75
|
+
}`,
|
|
76
|
+
// is: 'type-choices',
|
|
77
|
+
$custom: {
|
|
78
|
+
$setType ({type, baseValue, bringIntoFocus}) {
|
|
79
|
+
this.value = type;
|
|
80
|
+
this.$setStyles();
|
|
81
|
+
this.$addAndValidateEditUI({baseValue, bringIntoFocus});
|
|
82
|
+
},
|
|
83
|
+
$setTypeNoEditUI ({type}) {
|
|
84
|
+
this.value = type;
|
|
85
|
+
this.$setStyles();
|
|
86
|
+
},
|
|
87
|
+
$setStyles () {
|
|
88
|
+
const {value: type} = this;
|
|
89
|
+
this.dataset.type = type; // Used for styling
|
|
90
|
+
const parEl = this.parentElement;
|
|
91
|
+
if (parEl.nodeName.toLowerCase() === 'fieldset') {
|
|
92
|
+
parEl.dataset.type = type;
|
|
93
|
+
DOM.filterChildElements(parEl, 'legend').forEach((legend) => {
|
|
94
|
+
legend.dataset.type = type;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
$getTypeRoot () {
|
|
99
|
+
const container = this.$getContainer();
|
|
100
|
+
/* istanbul ignore if -- How to replicate? */
|
|
101
|
+
if (!container) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return $e(container, 'div[data-type]');
|
|
105
|
+
},
|
|
106
|
+
$addAndValidateEditUI ({baseValue, bringIntoFocus} = {}) {
|
|
107
|
+
const {value: type} = this;
|
|
108
|
+
|
|
109
|
+
const container = this.$getContainer();
|
|
110
|
+
DOM.removeChildren(container);
|
|
111
|
+
|
|
112
|
+
if (!type) { return; }
|
|
113
|
+
let topRoot = this.$getTopRoot();
|
|
114
|
+
|
|
115
|
+
// Todo (low): Try to avoid need for `baseValue`
|
|
116
|
+
// (needed by arrayNonindexKeys for setting an array
|
|
117
|
+
// length and avoiding errors); could set all
|
|
118
|
+
// values through here?
|
|
119
|
+
editUI = Types.getUIForModeAndType({
|
|
120
|
+
readonly: false,
|
|
121
|
+
typeNamespace,
|
|
122
|
+
type,
|
|
123
|
+
bringIntoFocus,
|
|
124
|
+
hasValue: type === 'arrayNonindexKeys' && baseValue,
|
|
125
|
+
value: baseValue,
|
|
126
|
+
buildTypeChoices,
|
|
127
|
+
format,
|
|
128
|
+
topRoot
|
|
129
|
+
});
|
|
130
|
+
this.$addEditUI({editUI});
|
|
131
|
+
this.$validate();
|
|
132
|
+
topRoot = this.$getTopRoot(); // May be existing now
|
|
133
|
+
// Needed; Array/object ref somewhere could now be valid or invalid
|
|
134
|
+
Types.validateAllReferences({topRoot});
|
|
135
|
+
},
|
|
136
|
+
$addTypeAndEditUI ({type, editUI}) {
|
|
137
|
+
this.$setTypeNoEditUI({type});
|
|
138
|
+
this.$addEditUI({editUI});
|
|
139
|
+
},
|
|
140
|
+
$addEditUI ({editUI}) {
|
|
141
|
+
const container = this.$getContainer();
|
|
142
|
+
jml(editUI, container);
|
|
143
|
+
},
|
|
144
|
+
$getContainer () {
|
|
145
|
+
return this.nextElementSibling;
|
|
146
|
+
},
|
|
147
|
+
$getTopRoot () {
|
|
148
|
+
return topRoot || this.$getTypeRoot();
|
|
149
|
+
},
|
|
150
|
+
$validate () {
|
|
151
|
+
const {value: type} = this;
|
|
152
|
+
const container = this.$getContainer();
|
|
153
|
+
if (!container.firstElementChild) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const editUI = container.firstElementChild;
|
|
157
|
+
return Types.validate({
|
|
158
|
+
type, root: editUI, topRoot: this.$getTopRoot()
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
$on: {change (e) {
|
|
163
|
+
// We don't want form `onchange` to run `$checkForKeyDuplicates`
|
|
164
|
+
// again (through `addAndValidateEditUI`->`validateAllReferences`)
|
|
165
|
+
e.stopPropagation();
|
|
166
|
+
this.$addAndValidateEditUI();
|
|
167
|
+
this.$setStyles();
|
|
168
|
+
}}
|
|
169
|
+
}, [
|
|
170
|
+
['option', {value: ''}, [
|
|
171
|
+
'(Choose a type)'
|
|
172
|
+
]],
|
|
173
|
+
...typeOptions.map(
|
|
174
|
+
([optText, optAtts]) => [
|
|
175
|
+
'option',
|
|
176
|
+
optAtts ||
|
|
177
|
+
/* istanbul ignore next -- Should always have atts */
|
|
178
|
+
{},
|
|
179
|
+
[optText]
|
|
180
|
+
]
|
|
181
|
+
)
|
|
182
|
+
]);
|
|
183
|
+
if (setValue || (requireObject && !objectHasValue)) {
|
|
184
|
+
setTimeout(async () => {
|
|
185
|
+
if (!setValue) { // if (requireObject && !objectHasValue) {
|
|
186
|
+
// Todo (low): We could auto-populate keypath if has
|
|
187
|
+
// keypath (and we probably also only want if
|
|
188
|
+
// not autoincrement)
|
|
189
|
+
value = {};
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
const rootEditUI = await Formats.availableFormats[format].iterate(
|
|
193
|
+
value,
|
|
194
|
+
{
|
|
195
|
+
readonly: false,
|
|
196
|
+
typeNamespace,
|
|
197
|
+
schema,
|
|
198
|
+
schemaContent
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
const type = Types.getTypeForRoot(rootEditUI);
|
|
202
|
+
sel.$addTypeAndEditUI({type, editUI: rootEditUI});
|
|
203
|
+
} catch (err) {
|
|
204
|
+
/* istanbul ignore next -- At least some errors handled earlier */
|
|
205
|
+
dialogs.alert({
|
|
206
|
+
message: 'The object to be added had types not supported ' +
|
|
207
|
+
'by the current format.'
|
|
208
|
+
});
|
|
209
|
+
/* istanbul ignore next -- How to trigger? */
|
|
210
|
+
console.log('err', err);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return [
|
|
215
|
+
sel,
|
|
216
|
+
['div', {class: 'typeContainer'}]
|
|
217
|
+
];
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/* schema:
|
|
221
|
+
Formats.getTypeForFormatStateAndValue = ({format, state, value}) => {
|
|
222
|
+
const valType = new Typeson().register(
|
|
223
|
+
structuredCloningThrowing
|
|
224
|
+
).rootTypeName(value);
|
|
225
|
+
return canonicalToAvailableType(format, state, valType, value);
|
|
226
|
+
};
|
|
227
|
+
*/
|
|
228
|
+
|
|
229
|
+
export default Formats;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {$e, $$e} from '../utils/templateUtils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @type {TypeObject}
|
|
5
|
+
*/
|
|
6
|
+
const BooleanObjectType = {
|
|
7
|
+
option: ['BooleanObject'],
|
|
8
|
+
stringRegex: /^Boolean\((.*)\)$/u,
|
|
9
|
+
toValue (s) {
|
|
10
|
+
return {
|
|
11
|
+
// eslint-disable-next-line no-new-wrappers, unicorn/new-for-builtins
|
|
12
|
+
value: new Boolean(s === 'true')
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
getInput ({root}) {
|
|
16
|
+
return $e(root, 'input');
|
|
17
|
+
},
|
|
18
|
+
getValue ({root}) {
|
|
19
|
+
return this.toValue(String(this.getInput({root}).checked)).value;
|
|
20
|
+
},
|
|
21
|
+
setValue ({root, value}) {
|
|
22
|
+
const inputs = $$e(root, 'input');
|
|
23
|
+
const input = inputs[value.valueOf() ? 0 : 1];
|
|
24
|
+
input.checked = true;
|
|
25
|
+
},
|
|
26
|
+
viewUI ({value}) {
|
|
27
|
+
return ['i', {dataset: {type: 'BooleanObject'}}, [`Boolean(${value})`]];
|
|
28
|
+
},
|
|
29
|
+
ct: 0,
|
|
30
|
+
editUI ({
|
|
31
|
+
// eslint-disable-next-line no-new-wrappers, unicorn/new-for-builtins
|
|
32
|
+
typeNamespace, value = new Boolean(true)
|
|
33
|
+
}) {
|
|
34
|
+
this.ct++;
|
|
35
|
+
return ['div', {dataset: {type: 'BooleanObject'}}, [
|
|
36
|
+
['label', [
|
|
37
|
+
'True',
|
|
38
|
+
['input', {
|
|
39
|
+
type: 'radio', name: `${typeNamespace}-BooleanObject${this.ct}`,
|
|
40
|
+
value: 'true', checked: value.valueOf()
|
|
41
|
+
}]
|
|
42
|
+
]],
|
|
43
|
+
['label', [
|
|
44
|
+
'False',
|
|
45
|
+
['input', {
|
|
46
|
+
type: 'radio',
|
|
47
|
+
name: `${typeNamespace}-BooleanObject${this.ct}`,
|
|
48
|
+
value: 'false', checked: !value.valueOf()
|
|
49
|
+
}]
|
|
50
|
+
]]
|
|
51
|
+
]];
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default BooleanObjectType;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {$e} from '../utils/templateUtils.js';
|
|
2
|
+
import Types from '../types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @type {TypeObject}
|
|
6
|
+
*/
|
|
7
|
+
const NumberObjectType = {
|
|
8
|
+
option: ['NumberObject'],
|
|
9
|
+
stringRegex: /^Number\((.*)\)$/u,
|
|
10
|
+
toValue (s) {
|
|
11
|
+
// eslint-disable-next-line no-new-wrappers, unicorn/new-for-builtins
|
|
12
|
+
return {value: new Number(s)};
|
|
13
|
+
},
|
|
14
|
+
validate ({root}) {
|
|
15
|
+
return Types.availableTypes.number.validate({root});
|
|
16
|
+
},
|
|
17
|
+
getInput ({root}) {
|
|
18
|
+
return $e(root, 'input');
|
|
19
|
+
},
|
|
20
|
+
getValue ({root}) {
|
|
21
|
+
return this.toValue(this.getInput({root}).value).value;
|
|
22
|
+
},
|
|
23
|
+
setValue ({root, value}) {
|
|
24
|
+
this.getInput({root}).value = String(value);
|
|
25
|
+
},
|
|
26
|
+
viewUI ({value}) {
|
|
27
|
+
return ['i', {dataset: {type: 'NumberObject'}}, [`Number(${value})`]];
|
|
28
|
+
},
|
|
29
|
+
editUI ({typeNamespace, value}) {
|
|
30
|
+
return ['div', {dataset: {type: 'NumberObject'}}, [
|
|
31
|
+
['input', {
|
|
32
|
+
name: `${typeNamespace}-NumberObject`,
|
|
33
|
+
type: 'number',
|
|
34
|
+
value,
|
|
35
|
+
step: 'any'
|
|
36
|
+
}]
|
|
37
|
+
]];
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default NumberObjectType;
|