@json-layout/vocabulary 0.9.1 → 0.11.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.
- package/package.json +1 -1
- package/src/index.js +1 -1
- package/src/normalize.js +62 -19
- package/src/normalized-layout/schema.js +58 -0
- package/src/normalized-layout/schema.json +33 -1
- package/src/normalized-layout/types.ts +9 -0
- package/src/normalized-layout/validate.js +1 -1
- package/src/types.ts +1 -0
- package/types/index.d.ts +1 -1
- package/types/index.d.ts.map +1 -1
- package/types/normalize.d.ts +6 -1
- package/types/normalize.d.ts.map +1 -1
- package/types/normalized-layout/schema.d.ts +36 -0
- package/types/normalized-layout/types.d.ts +9 -0
- package/types/normalized-layout/types.d.ts.map +1 -1
- package/types/normalized-layout/validate.d.ts.map +1 -1
- package/types/types.d.ts +1 -0
- package/types/types.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@json-layout/vocabulary",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Main JSON Layout vocabulary as JSON schemas and Typescript types. Also contains some small utility functions to validate and normalize annotations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** @typedef {import("./types.js").SchemaFragment} SchemaFragment */
|
|
2
2
|
export * from './layout-keyword/index.js'
|
|
3
3
|
export * from './normalized-layout/index.js'
|
|
4
|
-
export { normalizeLayoutFragment } from './normalize.js'
|
|
4
|
+
export { normalizeLayoutFragment, getSchemaFragmentType } from './normalize.js'
|
package/src/normalize.js
CHANGED
|
@@ -18,7 +18,7 @@ import { validateNormalizedLayout, compositeCompNames } from './normalized-layou
|
|
|
18
18
|
* @returns {Children}
|
|
19
19
|
*/
|
|
20
20
|
function getDefaultChildren (schemaFragment) {
|
|
21
|
-
const { type } =
|
|
21
|
+
const { type } = getSchemaFragmentType(schemaFragment)
|
|
22
22
|
/** @type {Children} */
|
|
23
23
|
const children = []
|
|
24
24
|
if (type === 'object') {
|
|
@@ -87,8 +87,8 @@ function getChildren (defaultChildren, partialChildren) {
|
|
|
87
87
|
* @returns {import('./index.js').ComponentName}
|
|
88
88
|
*/
|
|
89
89
|
function getDefaultComp (partial, schemaFragment, arrayChild) {
|
|
90
|
-
const { type } =
|
|
91
|
-
const hasSimpleType = ['string', 'integer', 'number'].includes(type)
|
|
90
|
+
const { type } = getSchemaFragmentType(schemaFragment)
|
|
91
|
+
const hasSimpleType = type && ['string', 'integer', 'number'].includes(type)
|
|
92
92
|
if (arrayChild === 'oneOf') return 'one-of-select'
|
|
93
93
|
if (hasSimpleType && schemaFragment.enum) return schemaFragment.enum.length > 20 ? 'autocomplete' : 'select'
|
|
94
94
|
if (hasSimpleType && schemaFragment.oneOf) return schemaFragment.oneOf.length > 20 ? 'autocomplete' : 'select'
|
|
@@ -162,8 +162,8 @@ function normalizeExpression (expression, defaultType = 'js-eval') {
|
|
|
162
162
|
*/
|
|
163
163
|
function getItemsFromSchema (schemaFragment) {
|
|
164
164
|
if (!schemaFragment) return null
|
|
165
|
-
const { type } =
|
|
166
|
-
const hasSimpleType = ['string', 'integer', 'number'].includes(type)
|
|
165
|
+
const { type } = getSchemaFragmentType(schemaFragment)
|
|
166
|
+
const hasSimpleType = type && ['string', 'integer', 'number'].includes(type)
|
|
167
167
|
if (schemaFragment.enum && hasSimpleType) {
|
|
168
168
|
return schemaFragment.enum.map((/** @type {string} */ value) => ({ key: value + '', title: value + '', value }))
|
|
169
169
|
}
|
|
@@ -194,13 +194,36 @@ function getItemsFromSchema (schemaFragment) {
|
|
|
194
194
|
|
|
195
195
|
/**
|
|
196
196
|
* @param {SchemaFragment} schemaFragment
|
|
197
|
-
* @returns {{type: string, nullable: boolean}}
|
|
197
|
+
* @returns {{type: string | undefined, nullable: boolean}}
|
|
198
198
|
*/
|
|
199
|
-
const
|
|
199
|
+
export const getSchemaFragmentType = (schemaFragment) => {
|
|
200
200
|
if (Array.isArray(schemaFragment.type) && schemaFragment.type.length === 2 && schemaFragment.type.includes('null')) {
|
|
201
201
|
const type = schemaFragment.type.find(t => t !== 'null')
|
|
202
202
|
return { type, nullable: true }
|
|
203
203
|
}
|
|
204
|
+
|
|
205
|
+
if (!schemaFragment.type && schemaFragment.properties) {
|
|
206
|
+
return { type: 'object', nullable: false }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// case where type is not defined but can be deduced from children oneOf/allOf/anyOf
|
|
210
|
+
/** @type {any[]} */
|
|
211
|
+
if (!schemaFragment.type) {
|
|
212
|
+
/** @type {string[]} */
|
|
213
|
+
const combinationTypes = []
|
|
214
|
+
for (const combinationKey of ['allOf', 'anyOf', 'oneOf']) {
|
|
215
|
+
// @ts-ignore
|
|
216
|
+
if (schemaFragment[combinationKey]) {
|
|
217
|
+
// @ts-ignore
|
|
218
|
+
for (const subSchema of schemaFragment[combinationKey]) {
|
|
219
|
+
const { type: subType } = getSchemaFragmentType(subSchema)
|
|
220
|
+
if (subType && !combinationTypes.includes(subType)) combinationTypes.push(subType)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (combinationTypes.length === 1) return { type: combinationTypes[0], nullable: false }
|
|
225
|
+
}
|
|
226
|
+
|
|
204
227
|
return { type: schemaFragment.type, nullable: false }
|
|
205
228
|
}
|
|
206
229
|
|
|
@@ -209,15 +232,16 @@ const getType = (schemaFragment) => {
|
|
|
209
232
|
* @param {SchemaFragment} schemaFragment
|
|
210
233
|
* @param {string} schemaPath
|
|
211
234
|
* @param {(text: string) => string} markdown
|
|
235
|
+
* @param {string[]} optionsKeys
|
|
212
236
|
* @param {'oneOf'} [arrayChild]
|
|
213
237
|
* @returns {{normalized: CompObject, errors: string[]}}
|
|
214
238
|
*/
|
|
215
|
-
function getCompObject (layoutKeyword, schemaFragment, schemaPath, markdown, arrayChild) {
|
|
239
|
+
function getCompObject (layoutKeyword, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild) {
|
|
216
240
|
/** @type {string[]} */
|
|
217
241
|
const errors = []
|
|
218
242
|
const key = schemaPath.slice(schemaPath.lastIndexOf('/') + 1)
|
|
219
243
|
|
|
220
|
-
const { type, nullable } =
|
|
244
|
+
const { type, nullable } = getSchemaFragmentType(schemaFragment)
|
|
221
245
|
|
|
222
246
|
if ('const' in schemaFragment) return { normalized: { comp: 'none' }, errors }
|
|
223
247
|
if (!type) return { normalized: { comp: 'none' }, errors }
|
|
@@ -243,14 +267,14 @@ function getCompObject (layoutKeyword, schemaFragment, schemaPath, markdown, arr
|
|
|
243
267
|
|
|
244
268
|
// @ts-ignore
|
|
245
269
|
if (compositeCompNames.includes(partial.comp)) {
|
|
246
|
-
|
|
270
|
+
if (!('title' in partial)) partial.title = schemaFragment.title ?? null
|
|
247
271
|
partial.children = getChildren(getDefaultChildren(schemaFragment), partial.children)
|
|
248
272
|
} else if (partial.comp === 'list') {
|
|
249
|
-
|
|
273
|
+
if (!('title' in partial)) partial.title = schemaFragment.title ?? key
|
|
250
274
|
partial.listEditMode = partial.listEditMode ?? (schemaFragment.items.type === 'object' ? 'inline-single' : 'inline')
|
|
251
275
|
partial.listActions = partial.listActions ?? ['add', 'edit', 'delete', 'duplicate', 'sort']
|
|
252
276
|
} else {
|
|
253
|
-
|
|
277
|
+
if (!('label' in partial)) partial.label = schemaFragment.title ?? key
|
|
254
278
|
}
|
|
255
279
|
|
|
256
280
|
if (['select', 'autocomplete', 'combobox'].includes(partial.comp) && !partial.items) {
|
|
@@ -298,6 +322,20 @@ function getCompObject (layoutKeyword, schemaFragment, schemaPath, markdown, arr
|
|
|
298
322
|
partial.defaultData = ''
|
|
299
323
|
}
|
|
300
324
|
|
|
325
|
+
// options can be set directly in layout and normalized in layout.options
|
|
326
|
+
// TODO: fetch this list from the schema ?
|
|
327
|
+
for (const optionKey of optionsKeys) {
|
|
328
|
+
if (optionKey in partial) {
|
|
329
|
+
partial.options = partial.options ?? {}
|
|
330
|
+
partial.options[optionKey] = partial[optionKey]
|
|
331
|
+
delete partial[optionKey]
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (schemaFragment.readOnly) {
|
|
335
|
+
partial.options = partial.options ?? {}
|
|
336
|
+
if (!('readOnly' in partial.options)) partial.options.readOnly = true
|
|
337
|
+
}
|
|
338
|
+
|
|
301
339
|
if (partial.getOptions !== undefined) partial.getOptions = normalizeExpression(partial.getOptions)
|
|
302
340
|
if (partial.getDefaultData !== undefined) partial.getDefaultData = normalizeExpression(partial.getDefaultData)
|
|
303
341
|
if (partial.getConstData !== undefined) partial.getConstData = normalizeExpression(partial.getConstData)
|
|
@@ -371,10 +409,11 @@ function getCompObject (layoutKeyword, schemaFragment, schemaPath, markdown, arr
|
|
|
371
409
|
* @param {SchemaFragment} schemaFragment
|
|
372
410
|
* @param {string} schemaPath
|
|
373
411
|
* @param {(text: string) => string} markdown
|
|
412
|
+
* @param {string[]} optionsKeys
|
|
374
413
|
* @param {'oneOf'} [arrayChild]
|
|
375
414
|
* @returns {{normalized: NormalizedLayout, errors: string[]}}
|
|
376
415
|
*/
|
|
377
|
-
function getNormalizedLayout (layoutKeyword, schemaFragment, schemaPath, markdown, arrayChild) {
|
|
416
|
+
function getNormalizedLayout (layoutKeyword, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild) {
|
|
378
417
|
if (isPartialSwitch(layoutKeyword)) {
|
|
379
418
|
/** @type {CompObject[]} */
|
|
380
419
|
const normalizedSwitchCases = []
|
|
@@ -385,13 +424,13 @@ function getNormalizedLayout (layoutKeyword, schemaFragment, schemaPath, markdow
|
|
|
385
424
|
}
|
|
386
425
|
for (let i = 0; i < switchCases.length; i++) {
|
|
387
426
|
const switchCase = switchCases[i]
|
|
388
|
-
const compObjectResult = getCompObject(switchCase, schemaFragment, schemaPath, markdown, arrayChild)
|
|
427
|
+
const compObjectResult = getCompObject(switchCase, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild)
|
|
389
428
|
normalizedSwitchCases.push(compObjectResult.normalized)
|
|
390
429
|
for (const error of compObjectResult.errors) errors.push(`switch ${i} - ${error}`)
|
|
391
430
|
}
|
|
392
431
|
return { normalized: { switch: normalizedSwitchCases }, errors: [] }
|
|
393
432
|
} else {
|
|
394
|
-
return getCompObject(layoutKeyword, schemaFragment, schemaPath, markdown, arrayChild)
|
|
433
|
+
return getCompObject(layoutKeyword, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild)
|
|
395
434
|
}
|
|
396
435
|
}
|
|
397
436
|
|
|
@@ -423,14 +462,18 @@ function lighterValidationErrors (errors) {
|
|
|
423
462
|
return errors.map(e => e.message ?? e.keyword)
|
|
424
463
|
}
|
|
425
464
|
|
|
465
|
+
const defaultOptionsKeys = ['readOnly', 'summary', 'titleDepth', 'density', 'removeAdditional', 'validateOn', 'initialValidation', 'defaultOn', 'readOnlyPropertiesMode']
|
|
466
|
+
|
|
426
467
|
/**
|
|
427
468
|
* @param {SchemaFragment} schemaFragment
|
|
428
469
|
* @param {string} schemaPath
|
|
429
470
|
* @param {(text: string) => string} markdown
|
|
471
|
+
* @param {string[]} [optionsKeys]
|
|
430
472
|
* @param {'oneOf'} [arrayChild]
|
|
431
473
|
* @returns {{layout: NormalizedLayout, errors: string[]}}
|
|
432
474
|
*/
|
|
433
|
-
export function normalizeLayoutFragment (schemaFragment, schemaPath, markdown = (src) => src, arrayChild) {
|
|
475
|
+
export function normalizeLayoutFragment (schemaFragment, schemaPath, markdown = (src) => src, optionsKeys, arrayChild) {
|
|
476
|
+
optionsKeys = optionsKeys ? optionsKeys.concat(defaultOptionsKeys) : defaultOptionsKeys
|
|
434
477
|
let layoutKeyword
|
|
435
478
|
if (arrayChild === 'oneOf') {
|
|
436
479
|
layoutKeyword = schemaFragment.oneOfLayout ?? {}
|
|
@@ -440,15 +483,15 @@ export function normalizeLayoutFragment (schemaFragment, schemaPath, markdown =
|
|
|
440
483
|
if (!validateLayoutKeyword(layoutKeyword)) {
|
|
441
484
|
console.error(`layout keyword validation errors at path ${schemaPath}`, layoutKeyword, validateLayoutKeyword.errors)
|
|
442
485
|
return {
|
|
443
|
-
layout: getNormalizedLayout({}, schemaFragment, schemaPath, markdown, arrayChild).normalized,
|
|
486
|
+
layout: getNormalizedLayout({}, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild).normalized,
|
|
444
487
|
errors: lighterValidationErrors(validateLayoutKeyword.errors)
|
|
445
488
|
}
|
|
446
489
|
}
|
|
447
|
-
const normalizedLayout = getNormalizedLayout(layoutKeyword, schemaFragment, schemaPath, markdown, arrayChild)
|
|
490
|
+
const normalizedLayout = getNormalizedLayout(layoutKeyword, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild)
|
|
448
491
|
if (!validateNormalizedLayout(normalizedLayout.normalized)) {
|
|
449
492
|
console.error(`normalized layout validation errors at path ${schemaPath}`, normalizedLayout, validateNormalizedLayout.errors)
|
|
450
493
|
return {
|
|
451
|
-
layout: getNormalizedLayout({}, schemaFragment, schemaPath, markdown, arrayChild).normalized,
|
|
494
|
+
layout: getNormalizedLayout({}, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild).normalized,
|
|
452
495
|
errors: lighterValidationErrors(validateNormalizedLayout.errors)
|
|
453
496
|
}
|
|
454
497
|
// throw new Error(`invalid layout at path ${schemaPath}`, { cause: validateNormalizedLayout.errors })
|
|
@@ -1033,7 +1033,65 @@ export default {
|
|
|
1033
1033
|
"default",
|
|
1034
1034
|
"comfortable",
|
|
1035
1035
|
"compact"
|
|
1036
|
+
],
|
|
1037
|
+
"default": "default"
|
|
1038
|
+
},
|
|
1039
|
+
"removeAdditional": {
|
|
1040
|
+
"description": "true is the same as 'unknown', false is the same as 'none'",
|
|
1041
|
+
"default": "error",
|
|
1042
|
+
"oneOf": [
|
|
1043
|
+
{
|
|
1044
|
+
"type": "boolean"
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
"type": "string",
|
|
1048
|
+
"enum": [
|
|
1049
|
+
"unknown",
|
|
1050
|
+
"error",
|
|
1051
|
+
"none"
|
|
1052
|
+
]
|
|
1053
|
+
}
|
|
1036
1054
|
]
|
|
1055
|
+
},
|
|
1056
|
+
"validateOn": {
|
|
1057
|
+
"type": "string",
|
|
1058
|
+
"enum": [
|
|
1059
|
+
"input",
|
|
1060
|
+
"blur",
|
|
1061
|
+
"submit"
|
|
1062
|
+
],
|
|
1063
|
+
"default": "input"
|
|
1064
|
+
},
|
|
1065
|
+
"initialValidation": {
|
|
1066
|
+
"type": "string",
|
|
1067
|
+
"enum": [
|
|
1068
|
+
"never",
|
|
1069
|
+
"always",
|
|
1070
|
+
"withData"
|
|
1071
|
+
],
|
|
1072
|
+
"default": "withData"
|
|
1073
|
+
},
|
|
1074
|
+
"defaultOn": {
|
|
1075
|
+
"type": "string",
|
|
1076
|
+
"enum": [
|
|
1077
|
+
"missing",
|
|
1078
|
+
"empty",
|
|
1079
|
+
"never"
|
|
1080
|
+
],
|
|
1081
|
+
"default": "empty"
|
|
1082
|
+
},
|
|
1083
|
+
"autofocus": {
|
|
1084
|
+
"type": "boolean",
|
|
1085
|
+
"default": false
|
|
1086
|
+
},
|
|
1087
|
+
"readOnlyPropertiesMode": {
|
|
1088
|
+
"type": "string",
|
|
1089
|
+
"enum": [
|
|
1090
|
+
"remove",
|
|
1091
|
+
"hide",
|
|
1092
|
+
"show"
|
|
1093
|
+
],
|
|
1094
|
+
"default": "show"
|
|
1037
1095
|
}
|
|
1038
1096
|
}
|
|
1039
1097
|
}
|
|
@@ -487,7 +487,39 @@
|
|
|
487
487
|
"readOnly": {"type": "boolean", "default": false},
|
|
488
488
|
"summary": {"type": "boolean", "default": false},
|
|
489
489
|
"titleDepth": {"type": "integer", "minimum": 1, "maximum": 6, "default": 2},
|
|
490
|
-
"density": {"type": "string", "enum": ["default", "comfortable", "compact"]}
|
|
490
|
+
"density": {"type": "string", "enum": ["default", "comfortable", "compact"], "default": "default"},
|
|
491
|
+
"removeAdditional": {
|
|
492
|
+
"description": "true is the same as 'unknown', false is the same as 'none'",
|
|
493
|
+
"default": "error",
|
|
494
|
+
"oneOf": [
|
|
495
|
+
{"type": "boolean"},
|
|
496
|
+
{
|
|
497
|
+
"type": "string",
|
|
498
|
+
"enum": ["unknown", "error", "none"]
|
|
499
|
+
}
|
|
500
|
+
]
|
|
501
|
+
},
|
|
502
|
+
"validateOn": {
|
|
503
|
+
"type": "string",
|
|
504
|
+
"enum": ["input", "blur", "submit"],
|
|
505
|
+
"default": "input"
|
|
506
|
+
},
|
|
507
|
+
"initialValidation": {
|
|
508
|
+
"type": "string",
|
|
509
|
+
"enum": ["never", "always", "withData"],
|
|
510
|
+
"default": "withData"
|
|
511
|
+
},
|
|
512
|
+
"defaultOn": {
|
|
513
|
+
"type": "string",
|
|
514
|
+
"enum": ["missing", "empty", "never"],
|
|
515
|
+
"default": "empty"
|
|
516
|
+
},
|
|
517
|
+
"autofocus": {"type": "boolean", "default": false},
|
|
518
|
+
"readOnlyPropertiesMode": {
|
|
519
|
+
"type": "string",
|
|
520
|
+
"enum": ["remove", "hide", "show"],
|
|
521
|
+
"default": "show"
|
|
522
|
+
}
|
|
491
523
|
}
|
|
492
524
|
}
|
|
493
525
|
]
|
|
@@ -51,6 +51,15 @@ export type StateNodeOptionsBase = StateNodeOptionsBaseLib & {
|
|
|
51
51
|
summary?: boolean;
|
|
52
52
|
titleDepth?: number;
|
|
53
53
|
density?: "default" | "comfortable" | "compact";
|
|
54
|
+
/**
|
|
55
|
+
* true is the same as 'unknown', false is the same as 'none'
|
|
56
|
+
*/
|
|
57
|
+
removeAdditional?: boolean | ("unknown" | "error" | "none");
|
|
58
|
+
validateOn?: "input" | "blur" | "submit";
|
|
59
|
+
initialValidation?: "never" | "always" | "withData";
|
|
60
|
+
defaultOn?: "missing" | "empty" | "never";
|
|
61
|
+
autofocus?: boolean;
|
|
62
|
+
readOnlyPropertiesMode?: "remove" | "hide" | "show";
|
|
54
63
|
[k: string]: unknown;
|
|
55
64
|
};
|
|
56
65
|
export type Cols = number;
|