@json-layout/vocabulary 0.1.0 → 0.2.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 +4 -2
- package/src/layout-keyword/schema.js +41 -19
- package/src/layout-keyword/schema.json +27 -11
- package/src/layout-keyword/types.ts +14 -3
- package/src/layout-keyword/validate.js +1 -1
- package/src/normalize.js +218 -107
- package/src/normalized-layout/index.js +10 -1
- package/src/normalized-layout/schema.js +156 -31
- package/src/normalized-layout/schema.json +76 -17
- package/src/normalized-layout/types.ts +50 -7
- package/src/normalized-layout/validate.js +1 -1
- package/src/types.ts +2 -0
- package/types/layout-keyword/schema.d.ts +38 -20
- package/types/layout-keyword/types.d.ts +5 -3
- package/types/layout-keyword/types.d.ts.map +1 -1
- package/types/layout-keyword/validate.d.ts +3 -4
- package/types/layout-keyword/validate.d.ts.map +1 -1
- package/types/normalize.d.ts +5 -2
- package/types/normalize.d.ts.map +1 -1
- package/types/normalized-layout/index.d.ts +6 -0
- package/types/normalized-layout/index.d.ts.map +1 -1
- package/types/normalized-layout/schema.d.ts +128 -28
- package/types/normalized-layout/types.d.ts +47 -8
- package/types/normalized-layout/types.d.ts.map +1 -1
- package/types/normalized-layout/validate.d.ts +3 -3
- package/types/normalized-layout/validate.d.ts.map +1 -1
- package/types/types.d.ts +2 -0
- package/types/types.d.ts.map +1 -1
package/src/normalize.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { validateLayoutKeyword, isComponentName, isPartialCompObject, isPartialChildren, isPartialSwitch, isPartialGetItemsExpr, isPartialGetItemsObj, isPartialSlotMarkdown, isPartialGetItemsFetch } from './layout-keyword/index.js'
|
|
2
|
-
import { validateNormalizedLayout,
|
|
2
|
+
import { validateNormalizedLayout, compositeCompNames } from './normalized-layout/index.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {import('./index.js').Child} Child
|
|
@@ -13,11 +13,6 @@ import { validateNormalizedLayout, normalizedLayoutSchema, isSectionLayout, isCo
|
|
|
13
13
|
* @typedef {import("./types.js").SchemaFragment} SchemaFragment
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
// export type Markdown = (src: string) => string
|
|
17
|
-
|
|
18
|
-
// this navice implementation is sufficient here, not used very often
|
|
19
|
-
const clone = (/** @type {any} */ obj) => JSON.parse(JSON.stringify(obj))
|
|
20
|
-
|
|
21
16
|
/**
|
|
22
17
|
* @param {import('./types.js').SchemaFragment} schemaFragment
|
|
23
18
|
* @returns {Children}
|
|
@@ -85,81 +80,68 @@ function getChildren (defaultChildren, partialChildren) {
|
|
|
85
80
|
}
|
|
86
81
|
|
|
87
82
|
/**
|
|
83
|
+
* @param {PartialCompObject} partial
|
|
88
84
|
* @param {SchemaFragment} schemaFragment
|
|
89
|
-
* @param {
|
|
90
|
-
* @returns {import('./index.js').
|
|
85
|
+
* @param {'oneOf'} [arrayChild]
|
|
86
|
+
* @returns {import('./index.js').ComponentName}
|
|
91
87
|
*/
|
|
92
|
-
function
|
|
93
|
-
const
|
|
94
|
-
if ('
|
|
95
|
-
if (
|
|
96
|
-
if (schemaFragment.
|
|
97
|
-
|
|
88
|
+
function getDefaultComp (partial, schemaFragment, arrayChild) {
|
|
89
|
+
const hasSimpleType = ['string', 'integer', 'number'].includes(schemaFragment.type)
|
|
90
|
+
if (arrayChild === 'oneOf') return 'one-of-select'
|
|
91
|
+
if (hasSimpleType && schemaFragment.enum) return schemaFragment.enum.length > 20 ? 'autocomplete' : 'select'
|
|
92
|
+
if (hasSimpleType && schemaFragment.oneOf) return schemaFragment.oneOf.length > 20 ? 'autocomplete' : 'select'
|
|
93
|
+
if (hasSimpleType && schemaFragment.examples) return schemaFragment.type === 'string' ? 'combobox' : 'number-combobox'
|
|
94
|
+
if (hasSimpleType && schemaFragment.anyOf && schemaFragment.anyOf.length && Object.keys(schemaFragment.anyOf[schemaFragment.anyOf.length - 1]).length === 0) {
|
|
95
|
+
return schemaFragment.type === 'string' ? 'combobox' : 'number-combobox'
|
|
98
96
|
}
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
comp: 'list',
|
|
106
|
-
title: schemaFragment.title ?? key,
|
|
107
|
-
listEditMode: schemaFragment.items.type === 'object' ? 'inline-single' : 'inline',
|
|
108
|
-
listActions: ['add', 'edit', 'delete', 'duplicate', 'sort']
|
|
97
|
+
if (partial.items) return partial.items.length > 20 ? 'autocomplete' : 'select'
|
|
98
|
+
if (partial.getItems) {
|
|
99
|
+
if (isPartialGetItemsFetch(partial.getItems)) {
|
|
100
|
+
if (partial.getItems.qSearchParam) return 'autocomplete'
|
|
101
|
+
if (typeof partial.getItems.url === 'string' && partial.getItems.url.includes('{q}')) return 'autocomplete'
|
|
109
102
|
}
|
|
103
|
+
return 'select'
|
|
110
104
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
label: schemaFragment.title ?? key,
|
|
116
|
-
items: schemaFragment.enum.map((/** @type {string} */ value) => ({ key: value + '', title: value + '', value }))
|
|
105
|
+
if (schemaFragment.type === 'array' && schemaFragment.items) {
|
|
106
|
+
const hasSimpleTypeItems = ['string', 'integer', 'number'].includes(schemaFragment.items.type)
|
|
107
|
+
if (hasSimpleTypeItems && (schemaFragment.items.enum || schemaFragment.items.oneOf)) {
|
|
108
|
+
return (schemaFragment.items.enum || schemaFragment.items.oneOf).length > 20 ? 'autocomplete' : 'select'
|
|
117
109
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
comp: 'select',
|
|
122
|
-
label: schemaFragment.title ?? key,
|
|
123
|
-
items: schemaFragment.oneOf.map((/** @type {{ const: string; title: any; }} */ oneOfItem) => ({ key: oneOfItem.const + '', title: (oneOfItem.title ?? oneOfItem.const) + '', value: oneOfItem.const }))
|
|
110
|
+
if (hasSimpleTypeItems && schemaFragment.items.examples) {
|
|
111
|
+
return schemaFragment.items.type === 'string' ? 'combobox' : 'number-combobox'
|
|
124
112
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (schemaFragment.format === 'date') formatCompObject = { comp: 'date-picker', label: schemaFragment.title ?? key, format: 'date' }
|
|
131
|
-
if (schemaFragment.format === 'date-time') formatCompObject = { comp: 'date-time-picker', label: schemaFragment.title ?? key }
|
|
132
|
-
if (schemaFragment.format === 'time') formatCompObject = { comp: 'time-picker', label: schemaFragment.title ?? key }
|
|
133
|
-
if (formatCompObject) {
|
|
134
|
-
if ('formatMinimum' in schemaFragment) formatCompObject.min = schemaFragment.formatMinimum
|
|
135
|
-
if ('formatMaximum' in schemaFragment) formatCompObject.max = schemaFragment.formatMaximum
|
|
136
|
-
return formatCompObject
|
|
137
|
-
}
|
|
113
|
+
if (hasSimpleTypeItems && schemaFragment.items.anyOf && schemaFragment.items.anyOf.length && Object.keys(schemaFragment.items.anyOf[schemaFragment.items.anyOf.length - 1]).length === 0) {
|
|
114
|
+
return schemaFragment.items.type === 'string' ? 'combobox' : 'number-combobox'
|
|
115
|
+
}
|
|
116
|
+
if (hasSimpleTypeItems && !schemaFragment.items.layout && !['date', 'date-time', 'time'].includes(schemaFragment.items.format)) {
|
|
117
|
+
return schemaFragment.items.type === 'string' ? 'combobox' : 'number-combobox'
|
|
138
118
|
}
|
|
139
|
-
return { comp: 'text-field', label: schemaFragment.title ?? key }
|
|
140
119
|
}
|
|
141
|
-
if (schemaFragment.type === '
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if ('minimum' in schemaFragment) compObject.min = schemaFragment.minimum
|
|
146
|
-
if ('maximum' in schemaFragment) compObject.max = schemaFragment.maximum
|
|
147
|
-
return compObject
|
|
120
|
+
if (schemaFragment.type === 'object') return 'section'
|
|
121
|
+
if (schemaFragment.type === 'array') {
|
|
122
|
+
if (Array.isArray(schemaFragment.items)) return 'section'
|
|
123
|
+
else return 'list'
|
|
148
124
|
}
|
|
149
|
-
if (schemaFragment.type === '
|
|
150
|
-
|
|
151
|
-
|
|
125
|
+
if (schemaFragment.type === 'string') {
|
|
126
|
+
if (schemaFragment.format === 'date') return 'date-picker'
|
|
127
|
+
if (schemaFragment.format === 'date-time') return 'date-time-picker'
|
|
128
|
+
if (schemaFragment.format === 'time') return 'time-picker'
|
|
129
|
+
return 'text-field'
|
|
130
|
+
}
|
|
131
|
+
if (schemaFragment.type === 'integer' || schemaFragment.type === 'number') return 'number-field'
|
|
132
|
+
if (schemaFragment.type === 'boolean') return 'checkbox'
|
|
133
|
+
throw new Error('failed to calculate default component for schema fragment')
|
|
152
134
|
}
|
|
153
135
|
|
|
154
136
|
/**
|
|
155
137
|
* @param {LayoutKeyword} layoutKeyword
|
|
156
|
-
* @returns {PartialCompObject
|
|
138
|
+
* @returns {PartialCompObject}
|
|
157
139
|
*/
|
|
158
140
|
function getPartialCompObject (layoutKeyword) {
|
|
159
141
|
if (isPartialCompObject(layoutKeyword)) return { ...layoutKeyword }
|
|
160
142
|
else if (isComponentName(layoutKeyword)) return { comp: layoutKeyword }
|
|
161
143
|
else if (isPartialChildren(layoutKeyword)) return { children: layoutKeyword }
|
|
162
|
-
return
|
|
144
|
+
return {}
|
|
163
145
|
}
|
|
164
146
|
|
|
165
147
|
/**
|
|
@@ -172,16 +154,121 @@ function normalizeExpression (expression, defaultType = 'js-eval') {
|
|
|
172
154
|
else return { ...expression, type: expression.type ?? defaultType }
|
|
173
155
|
}
|
|
174
156
|
|
|
157
|
+
/**
|
|
158
|
+
* @param {SchemaFragment} schemaFragment
|
|
159
|
+
* @returns {import('./index.js').PartialSelectItem[] | null}
|
|
160
|
+
*/
|
|
161
|
+
function getItemsFromSchema (schemaFragment) {
|
|
162
|
+
if (!schemaFragment) return null
|
|
163
|
+
const hasSimpleType = ['string', 'integer', 'number'].includes(schemaFragment.type)
|
|
164
|
+
if (schemaFragment.enum && hasSimpleType) {
|
|
165
|
+
return schemaFragment.enum.map((/** @type {string} */ value) => ({ key: value + '', title: value + '', value }))
|
|
166
|
+
}
|
|
167
|
+
if (schemaFragment.examples && hasSimpleType) {
|
|
168
|
+
return schemaFragment.examples.map((/** @type {string} */ value) => ({ key: value + '', title: value + '', value }))
|
|
169
|
+
}
|
|
170
|
+
if (schemaFragment.anyOf && hasSimpleType && schemaFragment.anyOf.length && Object.keys(schemaFragment.anyOf[schemaFragment.anyOf.length - 1]).length === 0) {
|
|
171
|
+
const nonEmptyAnyOf = schemaFragment.anyOf.slice(0, -1)
|
|
172
|
+
if (!(nonEmptyAnyOf.find((/** @type {any} */ oneOfItem) => !('const' in oneOfItem)))) {
|
|
173
|
+
return nonEmptyAnyOf.map((/** @type {{ const: string; title: any; }} */ anyOfItem) => ({
|
|
174
|
+
...anyOfItem,
|
|
175
|
+
key: anyOfItem.const + '',
|
|
176
|
+
title: (anyOfItem.title ?? anyOfItem.const) + '',
|
|
177
|
+
value: anyOfItem.const
|
|
178
|
+
}))
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (schemaFragment.oneOf && hasSimpleType && !(schemaFragment.oneOf.find((/** @type {any} */ oneOfItem) => !('const' in oneOfItem)))) {
|
|
182
|
+
return schemaFragment.oneOf.map((/** @type {{ const: string; title: any; }} */ oneOfItem) => ({
|
|
183
|
+
...oneOfItem,
|
|
184
|
+
key: oneOfItem.const + '',
|
|
185
|
+
title: (oneOfItem.title ?? oneOfItem.const) + '',
|
|
186
|
+
value: oneOfItem.const
|
|
187
|
+
}))
|
|
188
|
+
}
|
|
189
|
+
return null
|
|
190
|
+
}
|
|
191
|
+
|
|
175
192
|
/**
|
|
176
193
|
* @param {LayoutKeyword} layoutKeyword
|
|
177
|
-
* @param {CompObject} defaultCompObject
|
|
178
194
|
* @param {SchemaFragment} schemaFragment
|
|
195
|
+
* @param {string} schemaPath
|
|
179
196
|
* @param {(text: string) => string} markdown
|
|
180
|
-
* @
|
|
197
|
+
* @param {'oneOf'} [arrayChild]
|
|
198
|
+
* @returns {{normalized: CompObject, errors: string[]}}
|
|
181
199
|
*/
|
|
182
|
-
function getCompObject (layoutKeyword,
|
|
200
|
+
function getCompObject (layoutKeyword, schemaFragment, schemaPath, markdown, arrayChild) {
|
|
201
|
+
/** @type {string[]} */
|
|
202
|
+
const errors = []
|
|
203
|
+
const key = schemaPath.slice(schemaPath.lastIndexOf('/') + 1)
|
|
204
|
+
|
|
205
|
+
if ('const' in schemaFragment) return { normalized: { comp: 'none' }, errors }
|
|
206
|
+
if (!schemaFragment.type) return { normalized: { comp: 'none' }, errors }
|
|
207
|
+
if (schemaFragment.type === 'array' && !schemaFragment.items) return { normalized: { comp: 'none' }, errors }
|
|
208
|
+
|
|
183
209
|
const partial = getPartialCompObject(layoutKeyword)
|
|
184
|
-
|
|
210
|
+
|
|
211
|
+
// chose the default component for a schema fragment
|
|
212
|
+
if (!partial.comp) {
|
|
213
|
+
try {
|
|
214
|
+
partial.comp = getDefaultComp(partial, schemaFragment, arrayChild)
|
|
215
|
+
} catch (/** @type {any} */err) {
|
|
216
|
+
errors.push(err.message)
|
|
217
|
+
partial.comp = 'none'
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (partial.comp === 'none') return { normalized: { comp: 'none' }, errors }
|
|
221
|
+
|
|
222
|
+
// @ts-ignore
|
|
223
|
+
if (compositeCompNames.includes(partial.comp)) {
|
|
224
|
+
partial.title = partial.title ?? schemaFragment.title ?? null
|
|
225
|
+
partial.children = getChildren(getDefaultChildren(schemaFragment), partial.children)
|
|
226
|
+
} else if (partial.comp === 'list') {
|
|
227
|
+
partial.title = partial.title ?? schemaFragment.title ?? key
|
|
228
|
+
partial.listEditMode = partial.listEditMode ?? (schemaFragment.items.type === 'object' ? 'inline-single' : 'inline')
|
|
229
|
+
partial.listActions = partial.listActions ?? ['add', 'edit', 'delete', 'duplicate', 'sort']
|
|
230
|
+
} else {
|
|
231
|
+
partial.label = partial.label ?? schemaFragment.title ?? key
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (['select', 'autocomplete', 'combobox'].includes(partial.comp) && !partial.items) {
|
|
235
|
+
let items
|
|
236
|
+
if (schemaFragment.type === 'array') {
|
|
237
|
+
items = getItemsFromSchema(schemaFragment.items)
|
|
238
|
+
partial.multiple = true
|
|
239
|
+
} else {
|
|
240
|
+
items = getItemsFromSchema(schemaFragment)
|
|
241
|
+
}
|
|
242
|
+
if (items) {
|
|
243
|
+
if (partial.getItems && isPartialGetItemsObj(partial.getItems)) {
|
|
244
|
+
partial.getItems.expr = JSON.stringify(items)
|
|
245
|
+
} else {
|
|
246
|
+
partial.getItems = JSON.stringify(items)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (['combobox', 'number-combobox'].includes(partial.comp)) {
|
|
252
|
+
if (schemaFragment.type === 'array') {
|
|
253
|
+
partial.multiple = true
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (partial.comp === 'date-picker') {
|
|
258
|
+
if (schemaFragment.format === 'date') partial.format = 'date'
|
|
259
|
+
if (schemaFragment.format === 'date-time') partial.format = 'date-time'
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (['date-picker', 'date-time-picker', 'time-picker'].includes(partial.comp)) {
|
|
263
|
+
if ('formatMinimum' in schemaFragment) partial.min = partial.min ?? schemaFragment.formatMinimum
|
|
264
|
+
if ('formatMaximum' in schemaFragment) partial.max = partial.max ?? schemaFragment.formatMaximum
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (['number-field', 'slider'].includes(partial.comp)) {
|
|
268
|
+
if (schemaFragment.type === 'integer') partial.step = partial.step ?? 1
|
|
269
|
+
if ('minimum' in schemaFragment) partial.min = partial.min ?? schemaFragment.minimum
|
|
270
|
+
if ('maximum' in schemaFragment) partial.max = partial.max ?? schemaFragment.maximum
|
|
271
|
+
}
|
|
185
272
|
|
|
186
273
|
if (partial.if) partial.if = normalizeExpression(partial.if)
|
|
187
274
|
if (partial.getItems && isPartialGetItemsExpr(partial.getItems)) partial.getItems = normalizeExpression(partial.getItems)
|
|
@@ -190,6 +277,7 @@ function getCompObject (layoutKeyword, defaultCompObject, schemaFragment, markdo
|
|
|
190
277
|
if (partial.getItems.itemTitle) partial.getItems.itemTitle = normalizeExpression(partial.getItems.itemTitle)
|
|
191
278
|
if (partial.getItems.itemKey) partial.getItems.itemKey = normalizeExpression(partial.getItems.itemKey)
|
|
192
279
|
if (partial.getItems.itemValue) partial.getItems.itemValue = normalizeExpression(partial.getItems.itemValue)
|
|
280
|
+
if (partial.getItems.itemIcon) partial.getItems.itemIcon = normalizeExpression(partial.getItems.itemIcon)
|
|
193
281
|
if (partial.getItems.itemsResults) partial.getItems.itemsResults = normalizeExpression(partial.getItems.itemsResults)
|
|
194
282
|
}
|
|
195
283
|
if (partial.getItems && isPartialGetItemsFetch(partial.getItems)) {
|
|
@@ -236,48 +324,70 @@ function getCompObject (layoutKeyword, defaultCompObject, schemaFragment, markdo
|
|
|
236
324
|
}
|
|
237
325
|
}
|
|
238
326
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (partial.comp && defaultCompObject.comp !== partial.comp) {
|
|
242
|
-
const compProperties = normalizedLayoutSchema.$defs[partial.comp]?.properties
|
|
243
|
-
if (typeof compProperties === 'object') {
|
|
244
|
-
for (const key of Object.keys(compProperties).concat(['if', 'help', 'cols', 'props', 'slots', 'options'])) {
|
|
245
|
-
if (key in defaultCompObject) compObject[key] = defaultCompObject[key]
|
|
246
|
-
if (key in partial) compObject[key] = partial[key]
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
} else {
|
|
250
|
-
Object.assign(compObject, defaultCompObject, partial)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (isSectionLayout(defaultCompObject) && isCompositeLayout(compObject)) {
|
|
254
|
-
compObject.children = getChildren((defaultCompObject).children, partial.children)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (compObject.description && !compObject.help) compObject.help = compObject.description
|
|
258
|
-
if (compObject.help) compObject.help = markdown(compObject.help).trim()
|
|
327
|
+
if (schemaFragment.description && !partial.help) partial.help = schemaFragment.description
|
|
328
|
+
if (partial.help) partial.help = markdown(partial.help).trim()
|
|
259
329
|
|
|
260
|
-
if (typeof
|
|
261
|
-
if (typeof
|
|
330
|
+
if (typeof partial.cols === 'number') partial.cols = { xs: partial.cols }
|
|
331
|
+
if (typeof partial.cols === 'object' && partial.cols.xs === undefined) partial.cols.xs = 12
|
|
262
332
|
|
|
263
|
-
return
|
|
333
|
+
return { normalized: /** @type {CompObject} */(partial), errors }
|
|
264
334
|
}
|
|
265
335
|
|
|
266
336
|
/**
|
|
267
337
|
* @param {LayoutKeyword} layoutKeyword
|
|
268
|
-
* @param {CompObject} defaultCompObject
|
|
269
338
|
* @param {SchemaFragment} schemaFragment
|
|
339
|
+
* @param {string} schemaPath
|
|
270
340
|
* @param {(text: string) => string} markdown
|
|
271
|
-
* @
|
|
341
|
+
* @param {'oneOf'} [arrayChild]
|
|
342
|
+
* @returns {{normalized: NormalizedLayout, errors: string[]}}
|
|
272
343
|
*/
|
|
273
|
-
function getNormalizedLayout (layoutKeyword,
|
|
344
|
+
function getNormalizedLayout (layoutKeyword, schemaFragment, schemaPath, markdown, arrayChild) {
|
|
274
345
|
if (isPartialSwitch(layoutKeyword)) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
346
|
+
/** @type {CompObject[]} */
|
|
347
|
+
const normalizedSwitchCases = []
|
|
348
|
+
const errors = []
|
|
349
|
+
const switchCases = [...layoutKeyword.switch]
|
|
350
|
+
if (!switchCases.find(s => !s.if)) {
|
|
351
|
+
switchCases.push({})
|
|
352
|
+
}
|
|
353
|
+
for (let i = 0; i < switchCases.length; i++) {
|
|
354
|
+
const switchCase = switchCases[i]
|
|
355
|
+
const compObjectResult = getCompObject(switchCase, schemaFragment, schemaPath, markdown, arrayChild)
|
|
356
|
+
normalizedSwitchCases.push(compObjectResult.normalized)
|
|
357
|
+
for (const error of compObjectResult.errors) errors.push(`switch ${i} - ${error}`)
|
|
358
|
+
}
|
|
359
|
+
return { normalized: { switch: normalizedSwitchCases }, errors: [] }
|
|
278
360
|
} else {
|
|
279
|
-
return getCompObject(layoutKeyword,
|
|
361
|
+
return getCompObject(layoutKeyword, schemaFragment, schemaPath, markdown, arrayChild)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* @param {import('ajv').ErrorObject} error
|
|
367
|
+
* @param {(error: import('ajv').ErrorObject) => boolean} fn
|
|
368
|
+
* @returns {boolean}
|
|
369
|
+
*/
|
|
370
|
+
function matchValidationError (error, fn) {
|
|
371
|
+
if (error.keyword === 'errorMessage') {
|
|
372
|
+
error = error.params.errors[0]
|
|
280
373
|
}
|
|
374
|
+
return fn(error)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* @param {import('ajv').ErrorObject[]} errors
|
|
379
|
+
* @returns {string[]}
|
|
380
|
+
*/
|
|
381
|
+
function lighterValidationErrors (errors) {
|
|
382
|
+
const compositeErrors = errors.filter(e => matchValidationError(e, (e) => e.keyword === 'anyOf' || e.keyword === 'oneOf'))
|
|
383
|
+
// in case of a anyOf/oneOf error there are some subschemas errors that mostly prevent readability
|
|
384
|
+
for (const compositeError of compositeErrors) {
|
|
385
|
+
const explicitError = errors.find(e => matchValidationError(e, (e) => e.instancePath === compositeError.instancePath && e.keyword !== 'type'))
|
|
386
|
+
if (explicitError) {
|
|
387
|
+
errors = errors.filter(e => matchValidationError(e, (e) => e.instancePath !== compositeError.instancePath || e.keyword !== 'type'))
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return errors.map(e => e.message ?? e.keyword)
|
|
281
391
|
}
|
|
282
392
|
|
|
283
393
|
/**
|
|
@@ -285,29 +395,30 @@ function getNormalizedLayout (layoutKeyword, defaultCompObject, schemaFragment,
|
|
|
285
395
|
* @param {string} schemaPath
|
|
286
396
|
* @param {(text: string) => string} markdown
|
|
287
397
|
* @param {'oneOf'} [arrayChild]
|
|
288
|
-
* @returns {NormalizedLayout}
|
|
398
|
+
* @returns {{layout: NormalizedLayout, errors: string[]}}
|
|
289
399
|
*/
|
|
290
400
|
export function normalizeLayoutFragment (schemaFragment, schemaPath, markdown = (src) => src, arrayChild) {
|
|
291
401
|
let layoutKeyword
|
|
292
|
-
/** @type {CompObject} */
|
|
293
|
-
let defaultCompObject
|
|
294
402
|
if (arrayChild === 'oneOf') {
|
|
295
403
|
layoutKeyword = schemaFragment.oneOfLayout ?? {}
|
|
296
|
-
defaultCompObject = { comp: 'one-of-select' } // TODO: default label ?
|
|
297
404
|
} else {
|
|
298
405
|
layoutKeyword = schemaFragment.layout ?? {}
|
|
299
|
-
defaultCompObject = getDefaultCompObject(schemaFragment, schemaPath)
|
|
300
406
|
}
|
|
301
407
|
if (!validateLayoutKeyword(layoutKeyword)) {
|
|
302
408
|
console.error(`layout keyword validation errors at path ${schemaPath}`, layoutKeyword, validateLayoutKeyword.errors)
|
|
303
|
-
return
|
|
304
|
-
|
|
409
|
+
return {
|
|
410
|
+
layout: getNormalizedLayout({}, schemaFragment, schemaPath, markdown, arrayChild).normalized,
|
|
411
|
+
errors: lighterValidationErrors(validateLayoutKeyword.errors)
|
|
412
|
+
}
|
|
305
413
|
}
|
|
306
|
-
const normalizedLayout = getNormalizedLayout(layoutKeyword,
|
|
307
|
-
if (!validateNormalizedLayout(normalizedLayout)) {
|
|
414
|
+
const normalizedLayout = getNormalizedLayout(layoutKeyword, schemaFragment, schemaPath, markdown, arrayChild)
|
|
415
|
+
if (!validateNormalizedLayout(normalizedLayout.normalized)) {
|
|
308
416
|
console.error(`normalized layout validation errors at path ${schemaPath}`, normalizedLayout, validateNormalizedLayout.errors)
|
|
309
|
-
return
|
|
417
|
+
return {
|
|
418
|
+
layout: getNormalizedLayout({}, schemaFragment, schemaPath, markdown, arrayChild).normalized,
|
|
419
|
+
errors: lighterValidationErrors(validateNormalizedLayout.errors)
|
|
420
|
+
}
|
|
310
421
|
// throw new Error(`invalid layout at path ${schemaPath}`, { cause: validateNormalizedLayout.errors })
|
|
311
422
|
}
|
|
312
|
-
return normalizedLayout
|
|
423
|
+
return { layout: normalizedLayout.normalized, errors: [] }
|
|
313
424
|
}
|
|
@@ -9,6 +9,7 @@ import schema from './schema.js'
|
|
|
9
9
|
* @typedef {import('./types.js').GetItems} GetItems
|
|
10
10
|
* @typedef {import('./types.js').Expression} Expression
|
|
11
11
|
* @typedef {import('./types.js').Select} Select
|
|
12
|
+
* @typedef {import('./types.js').Autocomplete} Autocomplete
|
|
12
13
|
* @typedef {import('./types.js').SelectItem} SelectItem
|
|
13
14
|
* @typedef {import('./types.js').SelectItems} SelectItems
|
|
14
15
|
* @typedef {import('./types.js').GetItemsFetch} GetItemsFetch
|
|
@@ -30,6 +31,7 @@ import schema from './schema.js'
|
|
|
30
31
|
* @typedef {import('./types.js').VerticalTabs} VerticalTabs
|
|
31
32
|
* @typedef {import('./types.js').ExpansionPanels} ExpansionPanels
|
|
32
33
|
* @typedef {import('./types.js').List} List
|
|
34
|
+
* @typedef {import('./types.js').Combobox} Combobox
|
|
33
35
|
* @typedef {import('./types.js').Cols} Cols
|
|
34
36
|
* @typedef {import('./types.js').ColsObj} ColsObj
|
|
35
37
|
* @typedef {import('./types.js').StateNodeOptions} StateNodeOptions
|
|
@@ -61,9 +63,11 @@ export function isSectionLayout (layout) {
|
|
|
61
63
|
return layout.comp === 'section'
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
export const compositeCompNames = ['section', 'tabs', 'vertical-tabs', 'expansion-panels']
|
|
67
|
+
|
|
64
68
|
/** @type {(layout: CompObject) => layout is CompositeCompObject} */
|
|
65
69
|
export function isCompositeLayout (layout) {
|
|
66
|
-
return
|
|
70
|
+
return compositeCompNames.includes(layout.comp)
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
/** @type {(layout: CompObject) => layout is TextField} */
|
|
@@ -76,6 +80,11 @@ export function isSelectLayout (layout) {
|
|
|
76
80
|
return layout.comp === 'select'
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
/** @type {(layout: CompObject) => layout is Select | Combobox | Autocomplete} */
|
|
84
|
+
export function isItemsLayout (layout) {
|
|
85
|
+
return layout.comp === 'select' || layout.comp === 'combobox' || layout.comp === 'autocomplete'
|
|
86
|
+
}
|
|
87
|
+
|
|
79
88
|
/** @type {(getItems: GetItems) => getItems is Expression} */
|
|
80
89
|
export function isGetItemsExpression (getItems) {
|
|
81
90
|
return !!getItems.expr
|