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