@json-layout/vocabulary 1.2.1 → 1.4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@json-layout/vocabulary",
3
- "version": "1.2.1",
3
+ "version": "1.4.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": {
@@ -28,6 +28,9 @@ export const standardComponents = [
28
28
  {
29
29
  name: 'none'
30
30
  },
31
+ {
32
+ name: 'slot'
33
+ },
31
34
  {
32
35
  name: 'section',
33
36
  composite: true
@@ -19,6 +19,16 @@ export type None = {
19
19
  hint?: string | null;
20
20
  [k: string]: unknown;
21
21
  });
22
+ export type Slot = {
23
+ comp?: "slot";
24
+ [k: string]: unknown;
25
+ } & BaseCompObject &
26
+ (BaseCompObject & {
27
+ comp: string;
28
+ label?: string;
29
+ hint?: string | null;
30
+ [k: string]: unknown;
31
+ });
22
32
  export type Section = {
23
33
  comp?: "section";
24
34
  [k: string]: unknown;
@@ -7,6 +7,7 @@ import {ajv} from '../validate.js'
7
7
  * @typedef {import('./types.js').PartialChildren} PartialChildren
8
8
  * @typedef {import('./types.js').PartialChild} PartialChild
9
9
  * @typedef {import('./types.js').PartialChildComposite} PartialChildComposite
10
+ * @typedef {import('./types.js').PartialChildSlot} PartialChildSlot
10
11
  * @typedef {import('./types.js').PartialSwitch} PartialSwitch
11
12
  * @typedef {import('./types.js').PartialCompObject} PartialCompObject
12
13
  * @typedef {import('./types.js').PartialGetItems} PartialGetItems
@@ -14,6 +15,8 @@ import {ajv} from '../validate.js'
14
15
  * @typedef {import('./types.js').PartialExpression} PartialExpression
15
16
  * @typedef {import('./types.js').PartialGetItemsObj} PartialGetItemsObj
16
17
  * @typedef {import('./types.js').PartialSlotMarkdown} PartialSlotMarkdown
18
+ * @typedef {import('./types.js').PartialSlotText} PartialSlotText
19
+ * @typedef {import('./types.js').PartialSlotName} PartialSlotName
17
20
  * @typedef {import('./types.js').PartialSlot} PartialSlot
18
21
  * @typedef {import('./types.js').PartialSelectItem} PartialSelectItem
19
22
  * @typedef {{ errors: any, (layoutKeyword: any): layoutKeyword is LayoutKeyword }} ValidateLayoutKeyword
@@ -40,7 +43,12 @@ export function isPartialChildren (layoutKeyword) {
40
43
 
41
44
  /** @type {(partialChild: PartialChild) => partialChild is PartialChildComposite} */
42
45
  export function isPartialChildComposite (partialChild) {
43
- return typeof partialChild !== 'string' && "children" in partialChild
46
+ return typeof partialChild !== 'string' && 'children' in partialChild
47
+ }
48
+
49
+ /** @type {(partialChild: PartialChild) => partialChild is PartialChildSlot} */
50
+ export function isPartialChildSlot (partialChild) {
51
+ return typeof partialChild !== 'string' && ('text' in partialChild || 'markdown' in partialChild || 'name' in partialChild)
44
52
  }
45
53
 
46
54
  /** @type {(layoutKeyword: LayoutKeyword) => layoutKeyword is PartialCompObject} */
@@ -66,4 +74,14 @@ export function isPartialGetItemsFetch (getItems) {
66
74
  /** @type {(partialSlot: PartialSlot) => partialSlot is PartialSlotMarkdown} */
67
75
  export function isPartialSlotMarkdown (partialSlot) {
68
76
  return typeof partialSlot == 'object' && !!/** @type {PartialSlotMarkdown} */(partialSlot).markdown
77
+ }
78
+
79
+ /** @type {(partialSlot: PartialSlot) => partialSlot is PartialSlotText} */
80
+ export function isPartialSlotText (partialSlot) {
81
+ return typeof partialSlot == 'object' && !!/** @type {PartialSlotText} */(partialSlot).text
82
+ }
83
+
84
+ /** @type {(partialSlot: PartialSlot) => partialSlot is PartialSlotName} */
85
+ export function isPartialSlotName (partialSlot) {
86
+ return typeof partialSlot == 'object' && !!/** @type {PartialSlotName} */(partialSlot).name
69
87
  }
@@ -176,6 +176,14 @@ export default {
176
176
  },
177
177
  "if": {
178
178
  "$ref": "#/$defs/partial-expression"
179
+ },
180
+ "slots": {
181
+ "type": "object",
182
+ "patternProperties": {
183
+ ".*": {
184
+ "$ref": "#/$defs/partial-slot"
185
+ }
186
+ }
179
187
  }
180
188
  }
181
189
  },
@@ -208,19 +216,58 @@ export default {
208
216
  },
209
217
  "if": {
210
218
  "$ref": "#/$defs/partial-expression"
219
+ },
220
+ "slots": {
221
+ "type": "object",
222
+ "patternProperties": {
223
+ ".*": {
224
+ "$ref": "#/$defs/partial-slot"
225
+ }
226
+ }
211
227
  }
212
228
  }
213
229
  },
230
+ "partial-child-slot": {
231
+ "type": "object",
232
+ "properties": {
233
+ "cols": {
234
+ "$ref": "#/$defs/partial-cols"
235
+ },
236
+ "if": {
237
+ "$ref": "#/$defs/partial-expression"
238
+ }
239
+ },
240
+ "oneOf": [
241
+ {
242
+ "$ref": "#/$defs/partial-slot-text"
243
+ },
244
+ {
245
+ "$ref": "#/$defs/partial-slot-markdown"
246
+ },
247
+ {
248
+ "$ref": "#/$defs/partial-slot-name"
249
+ }
250
+ ]
251
+ },
214
252
  "partial-child": {
215
253
  "anyOf": [
216
254
  {
217
255
  "type": "string"
218
256
  },
257
+ {
258
+ "type": "array",
259
+ "items": {
260
+ "$ref": "#/$defs/partial-child"
261
+ }
262
+ },
219
263
  {
220
264
  "$ref": "#/$defs/partial-child-ref"
221
265
  },
222
266
  {
223
267
  "$ref": "#/$defs/partial-child-composite"
268
+ },
269
+ {
270
+ "$ref": "#/$defs/partial-child-slot"
224
271
  }
225
272
  ]
226
273
  },
@@ -424,6 +471,9 @@ export default {
424
471
  "properties": {
425
472
  "name": {
426
473
  "type": "string"
474
+ },
475
+ "props": {
476
+ "type": "object"
427
477
  }
428
478
  }
429
479
  }
@@ -73,7 +73,13 @@
73
73
  "properties": {
74
74
  "key": {"type": ["string", "integer"]},
75
75
  "cols": {"$ref": "#/$defs/partial-cols"},
76
- "if": {"$ref": "#/$defs/partial-expression"}
76
+ "if": {"$ref": "#/$defs/partial-expression"},
77
+ "slots": {
78
+ "type": "object",
79
+ "patternProperties": {
80
+ ".*": {"$ref": "#/$defs/partial-slot"}
81
+ }
82
+ }
77
83
  }
78
84
  },
79
85
  "partial-child-composite": {
@@ -85,14 +91,34 @@
85
91
  "subtitle": {"type": ["string", "null"]},
86
92
  "children": {"$ref": "#/$defs/partial-children"},
87
93
  "cols": {"$ref": "#/$defs/partial-cols"},
88
- "if": {"$ref": "#/$defs/partial-expression"}
94
+ "if": {"$ref": "#/$defs/partial-expression"},
95
+ "slots": {
96
+ "type": "object",
97
+ "patternProperties": {
98
+ ".*": {"$ref": "#/$defs/partial-slot"}
99
+ }
100
+ }
89
101
  }
90
102
  },
103
+ "partial-child-slot": {
104
+ "type": "object",
105
+ "properties": {
106
+ "cols": {"$ref": "#/$defs/partial-cols"},
107
+ "if": {"$ref": "#/$defs/partial-expression"}
108
+ },
109
+ "oneOf": [
110
+ {"$ref": "#/$defs/partial-slot-text"},
111
+ {"$ref": "#/$defs/partial-slot-markdown"},
112
+ {"$ref": "#/$defs/partial-slot-name"}
113
+ ]
114
+ },
91
115
  "partial-child": {
92
116
  "anyOf": [
93
117
  {"type": "string"},
118
+ {"type": "array", "items": {"$ref": "#/$defs/partial-child"}},
94
119
  {"$ref": "#/$defs/partial-child-ref"},
95
- {"$ref": "#/$defs/partial-child-composite"}
120
+ {"$ref": "#/$defs/partial-child-composite"},
121
+ {"$ref": "#/$defs/partial-child-slot"}
96
122
  ]
97
123
  },
98
124
  "partial-children": {
@@ -217,7 +243,8 @@
217
243
  "additionalProperties": false,
218
244
  "required": ["name"],
219
245
  "properties": {
220
- "name": {"type": "string"}
246
+ "name": {"type": "string"},
247
+ "props": {"type": "object"}
221
248
  }
222
249
  }
223
250
  }
@@ -12,7 +12,7 @@ export type ComponentName = string;
12
12
  * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
13
13
  * via the `definition` "partial-child".
14
14
  */
15
- export type PartialChild = string | PartialChildRef | PartialChildComposite;
15
+ export type PartialChild = string | PartialChild[] | PartialChildRef | PartialChildComposite | PartialChildSlot;
16
16
  /**
17
17
  * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
18
18
  * via the `definition` "partial-cols".
@@ -28,6 +28,30 @@ export type PartialColsNumber = number;
28
28
  * via the `definition` "partial-expression".
29
29
  */
30
30
  export type PartialExpression = string | PartialExpressionObj;
31
+ /**
32
+ * This interface was referenced by `undefined`'s JSON-Schema definition
33
+ * via the `patternProperty` ".*".
34
+ *
35
+ * This interface was referenced by `undefined`'s JSON-Schema definition
36
+ * via the `patternProperty` ".*".
37
+ *
38
+ * This interface was referenced by `undefined`'s JSON-Schema definition
39
+ * via the `patternProperty` ".*".
40
+ *
41
+ * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
42
+ * via the `definition` "partial-slot".
43
+ */
44
+ export type PartialSlot = string | PartialSlotText | PartialSlotMarkdown | PartialSlotName;
45
+ export type PartialChildSlot = {
46
+ cols?: PartialCols;
47
+ if?: PartialExpression;
48
+ [k: string]: unknown;
49
+ } & PartialChildSlot1;
50
+ /**
51
+ * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
52
+ * via the `definition` "partial-child-slot".
53
+ */
54
+ export type PartialChildSlot1 = PartialSlotText | PartialSlotMarkdown | PartialSlotName;
31
55
  /**
32
56
  * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
33
57
  * via the `definition` "partial-children".
@@ -68,14 +92,6 @@ export type PartialGetItemsObj = {
68
92
  | PartialExpressionObj
69
93
  | PartialGetItemsFetch
70
94
  );
71
- /**
72
- * This interface was referenced by `undefined`'s JSON-Schema definition
73
- * via the `patternProperty` ".*".
74
- *
75
- * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
76
- * via the `definition` "partial-slot".
77
- */
78
- export type PartialSlot = string | PartialSlotText | PartialSlotMarkdown | PartialSlotName;
79
95
 
80
96
  export interface HttpsJsonLayoutGithubIoLayoutKeyword {
81
97
  [k: string]: unknown;
@@ -88,6 +104,9 @@ export interface PartialChildRef {
88
104
  key: string | number;
89
105
  cols?: PartialCols;
90
106
  if?: PartialExpression;
107
+ slots?: {
108
+ [k: string]: PartialSlot;
109
+ };
91
110
  }
92
111
  /**
93
112
  * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
@@ -110,6 +129,30 @@ export interface PartialExpressionObj {
110
129
  expr: string;
111
130
  [k: string]: unknown;
112
131
  }
132
+ /**
133
+ * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
134
+ * via the `definition` "partial-slot-text".
135
+ */
136
+ export interface PartialSlotText {
137
+ text: string;
138
+ }
139
+ /**
140
+ * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
141
+ * via the `definition` "partial-slot-markdown".
142
+ */
143
+ export interface PartialSlotMarkdown {
144
+ markdown: string;
145
+ }
146
+ /**
147
+ * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
148
+ * via the `definition` "partial-slot-name".
149
+ */
150
+ export interface PartialSlotName {
151
+ name: string;
152
+ props?: {
153
+ [k: string]: unknown;
154
+ };
155
+ }
113
156
  /**
114
157
  * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
115
158
  * via the `definition` "partial-child-composite".
@@ -121,6 +164,9 @@ export interface PartialChildComposite {
121
164
  children: PartialChildren;
122
165
  cols?: PartialCols;
123
166
  if?: PartialExpression;
167
+ slots?: {
168
+ [k: string]: PartialSlot;
169
+ };
124
170
  [k: string]: unknown;
125
171
  }
126
172
  /**
@@ -173,27 +219,6 @@ export interface PartialGetItemsFetch {
173
219
  url: PartialExpression;
174
220
  [k: string]: unknown;
175
221
  }
176
- /**
177
- * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
178
- * via the `definition` "partial-slot-text".
179
- */
180
- export interface PartialSlotText {
181
- text: string;
182
- }
183
- /**
184
- * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
185
- * via the `definition` "partial-slot-markdown".
186
- */
187
- export interface PartialSlotMarkdown {
188
- markdown: string;
189
- }
190
- /**
191
- * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
192
- * via the `definition` "partial-slot-name".
193
- */
194
- export interface PartialSlotName {
195
- name: string;
196
- }
197
222
  /**
198
223
  * This interface was referenced by `HttpsJsonLayoutGithubIoLayoutKeyword`'s JSON-Schema
199
224
  * via the `definition` "partial-switch".
package/src/normalize.js CHANGED
@@ -1,4 +1,4 @@
1
- import { validateLayoutKeyword, isComponentName, isPartialCompObject, isPartialChildren, isPartialSwitch, isPartialGetItemsExpr, isPartialGetItemsObj, isPartialSlotMarkdown, isPartialGetItemsFetch, isPartialChildComposite } from './layout-keyword/index.js'
1
+ import { validateLayoutKeyword, isComponentName, isPartialCompObject, isPartialChildren, isPartialSwitch, isPartialGetItemsExpr, isPartialGetItemsObj, isPartialSlotMarkdown, isPartialGetItemsFetch, isPartialChildComposite, isPartialChildSlot, isPartialSlotText, isPartialSlotName } from './layout-keyword/index.js'
2
2
  import { validateNormalizedLayout } from './normalized-layout/index.js'
3
3
  import { getComponentValidate } from './validate.js'
4
4
 
@@ -66,18 +66,31 @@ function getDefaultChildren (schemaFragment, type) {
66
66
 
67
67
  /**
68
68
  * @param {Children} defaultChildren
69
- * @param {PartialChildren} [partialChildren]
69
+ * @param {PartialChildren | undefined} partialChildren
70
+ * @param {(text: string) => string} markdown
70
71
  * @returns {Children}
71
72
  */
72
- function getChildren (defaultChildren, partialChildren) {
73
+ function getChildren (defaultChildren, partialChildren, markdown) {
73
74
  if (!partialChildren) return defaultChildren
74
75
  let compI = 0
76
+ let slotI = 0
75
77
  return partialChildren.map(partialChild => {
76
78
  if (typeof partialChild === 'string') { // simple string/key referencing a known child
77
79
  const matchingDefaultChild = defaultChildren.find(c => c.key === partialChild)
78
80
  if (!matchingDefaultChild) throw new Error(`unknown child "${partialChild}"`)
79
81
  return matchingDefaultChild
82
+ } else if (Array.isArray(partialChild)) {
83
+ compI++
84
+ return {
85
+ comp: 'section',
86
+ key: `$comp-${compI}`,
87
+ children: getChildren(defaultChildren, partialChild, markdown)
88
+ }
80
89
  } else {
90
+ if ('slots' in partialChild && !isPartialChildSlot(partialChild)) {
91
+ partialChild.slots = normalizePartialSlots(partialChild.slots, markdown)
92
+ }
93
+ if ('cols' in partialChild) partialChild.cols = normalizePartialCols(partialChild.cols)
81
94
  if (partialChild.if) partialChild.if = normalizeExpression(partialChild.if)
82
95
  if (typeof partialChild.cols === 'number') partialChild.cols = { sm: partialChild.cols }
83
96
  if (typeof partialChild.cols === 'object' && partialChild.cols.xs === undefined) partialChild.cols.xs = 12
@@ -85,15 +98,25 @@ function getChildren (defaultChildren, partialChildren) {
85
98
  const matchingDefaultChild = defaultChildren.find(c => c.key === partialChild.key)
86
99
  if (!matchingDefaultChild) throw new Error(`unknown child "${partialChild.key}"`)
87
100
  return /** @type {Child} */ (partialChild)
88
- } else { // a composite component definition, not directly related to a known child
101
+ } else if (isPartialChildSlot(partialChild)) { // a slot defined as a child
102
+ /** @type {Child} */
103
+ slotI++
104
+ return {
105
+ key: `$slot-${slotI}`,
106
+ comp: 'slot',
107
+ cols: normalizePartialCols(partialChild.cols),
108
+ slots: normalizePartialSlots({ component: partialChild }, markdown)
109
+ }
110
+ } else {
89
111
  const child = partialChild
90
112
  if (isPartialChildComposite(child)) {
113
+ // a composite component definition, not directly related to a known child
91
114
  if (!child.comp) child.comp = 'section'
92
- child.children = getChildren(defaultChildren, child.children)
93
- }
94
- if (!('key' in partialChild)) {
95
- child.key = `$comp-${compI}`
96
- compI++
115
+ child.children = getChildren(defaultChildren, child.children, markdown)
116
+ if (!('key' in partialChild)) {
117
+ compI++
118
+ child.key = `$comp-${compI}`
119
+ }
97
120
  }
98
121
  return /** @type {Child} */ (child)
99
122
  }
@@ -109,6 +132,7 @@ function getChildren (defaultChildren, partialChildren) {
109
132
  * @returns {import('./index.js').ComponentName}
110
133
  */
111
134
  function getDefaultComp (partial, schemaFragment, type, schemaChild) {
135
+ if (partial.slots?.component) return 'slot'
112
136
  const hasSimpleType = type && ['string', 'integer', 'number'].includes(type)
113
137
  if (schemaChild === 'oneOf') return 'one-of-select'
114
138
  if (schemaChild === 'patternProperties') return 'list'
@@ -191,6 +215,46 @@ function normalizeExpression (expression, defaultType = 'js-eval', defaultDataAl
191
215
  else return { pure: defaultPure, type: defaultType, dataAlias: defaultDataAlias, ...expression }
192
216
  }
193
217
 
218
+ /**
219
+ * @param {PartialCompObject['slots']} partialSlots
220
+ * @param {(text: string) => string} markdown
221
+ * @returns {import('./normalized-layout/types.js').Slots | undefined}
222
+ */
223
+ function normalizePartialSlots (partialSlots, markdown) {
224
+ if (!partialSlots) return
225
+ /** @type {import('./normalized-layout/types.js').Slots} */
226
+ const slots = {}
227
+ for (const [name, slot] of Object.entries(partialSlots)) {
228
+ if (typeof slot === 'string') {
229
+ if (['before', 'after'].includes(name)) {
230
+ slots[name] = { markdown: markdown(slot).trim() }
231
+ } else {
232
+ slots[name] = { name: slot }
233
+ }
234
+ } else if (isPartialSlotText(slot)) {
235
+ slots[name] = { text: slot.text }
236
+ } else if (isPartialSlotName(slot)) {
237
+ slots[name] = { name: slot.name, props: slot.props }
238
+ } else if (isPartialSlotMarkdown(slot)) {
239
+ slots[name] = { markdown: markdown(slot.markdown).trim() }
240
+ }
241
+ }
242
+ return slots
243
+ }
244
+
245
+ /**
246
+ * @param {PartialCompObject['cols']} partialCols
247
+ * @returns {import('./normalized-layout/types.js').ColsObj | undefined}
248
+ */
249
+ function normalizePartialCols (partialCols) {
250
+ if (typeof partialCols === 'number') {
251
+ return { xs: 12, sm: partialCols }
252
+ }
253
+ if (typeof partialCols === 'object') {
254
+ return { xs: 12, ...partialCols }
255
+ }
256
+ }
257
+
194
258
  /**
195
259
  * @param {SchemaFragment} schemaFragment
196
260
  * @returns {import('./index.js').PartialSelectItem[] | null}
@@ -299,7 +363,7 @@ function getCompObject (key, layoutKeyword, schemaFragment, type, nullable, sche
299
363
  if (nullable) partial.nullable = nullable
300
364
 
301
365
  if (component.composite) {
302
- const children = getChildren(getDefaultChildren(schemaFragment, type), partial.children)
366
+ const children = getChildren(getDefaultChildren(schemaFragment, type), partial.children, markdown)
303
367
  partial.children = children
304
368
  if (!('title' in partial)) {
305
369
  if (children.length === 1 && children[0].key === '$patternProperties') {
@@ -311,7 +375,7 @@ function getCompObject (key, layoutKeyword, schemaFragment, type, nullable, sche
311
375
  } else if (partial.comp === 'list') {
312
376
  if (schemaChild === 'patternProperties') {
313
377
  if (!('title' in partial)) {
314
- const children = getChildren(getDefaultChildren(schemaFragment, type), partial.children)
378
+ const children = getChildren(getDefaultChildren(schemaFragment, type), partial.children, markdown)
315
379
  if (children.length === 1 && children[0].key === '$patternProperties') {
316
380
  partial.title = schemaFragment.title ?? null
317
381
  }
@@ -330,7 +394,7 @@ function getCompObject (key, layoutKeyword, schemaFragment, type, nullable, sche
330
394
  partial.listEditMode = partial.listEditMode ?? (itemsType === 'object' ? 'inline-single' : 'inline')
331
395
  partial.listActions = partial.listActions ?? ['add', 'edit', 'delete', 'duplicate', 'sort']
332
396
  }
333
- } else {
397
+ } else if (partial.comp !== 'slot') {
334
398
  if (!('label' in partial) && !schemaChild) {
335
399
  partial.label = schemaFragment.title ?? ('' + key)
336
400
  }
@@ -442,21 +506,7 @@ function getCompObject (key, layoutKeyword, schemaFragment, type, nullable, sche
442
506
  partial.format = 'date-time'
443
507
  }
444
508
 
445
- if (partial.slots) {
446
- for (const [name, slot] of Object.entries(partial.slots)) {
447
- if (typeof slot === 'string') {
448
- if (['before', 'after'].includes(name)) {
449
- partial.slots[name] = { markdown: slot }
450
- } else {
451
- partial.slots[name] = { name: slot }
452
- }
453
- }
454
- const slotObj = partial.slots[name]
455
- if (isPartialSlotMarkdown(slotObj)) {
456
- slotObj.markdown = markdown(slotObj.markdown).trim()
457
- }
458
- }
459
- }
509
+ if (partial.slots) partial.slots = normalizePartialSlots(partial.slots, markdown)
460
510
 
461
511
  if (schemaFragment.description) {
462
512
  if (component.composite && !!partial.title && useDescription.includes('subtitle')) {
@@ -476,8 +526,7 @@ function getCompObject (key, layoutKeyword, schemaFragment, type, nullable, sche
476
526
  if (partial.help) partial.help = markdown(partial.help).trim()
477
527
  if (partial.subtitle) partial.subtitle = markdown(partial.subtitle).trim()
478
528
 
479
- if (typeof partial.cols === 'number') partial.cols = { xs: partial.cols }
480
- if (typeof partial.cols === 'object' && partial.cols.xs === undefined) partial.cols.xs = 12
529
+ if ('cols' in partial) partial.cols = normalizePartialCols(partial.cols)
481
530
 
482
531
  const validateComponent = getComponentValidate(component)
483
532
  if (!validateComponent(partial)) {
@@ -10,7 +10,9 @@ import {ajv} from '../validate.js'
10
10
  * @typedef {import('./types.js').GetItemsFetch} GetItemsFetch
11
11
  * @typedef {import('./types.js').Child} Child
12
12
  * @typedef {import('./types.js').Children} Children
13
+ * @typedef {import('./types.js').ChildSlot} ChildSlot
13
14
  * @typedef {import('./types.js').CompositeCompObject} CompositeCompObject
15
+ * @typedef {import('./types.js').SlotCompObject} SlotCompObject
14
16
  * @typedef {import('./types.js').ItemsBasedCompObject} ItemsBasedCompObject
15
17
  * @typedef {import('./types.js').FocusableCompObject} FocusableCompObject
16
18
  * @typedef {import('./types.js').GetItems} GetItems
@@ -20,6 +22,7 @@ import {ajv} from '../validate.js'
20
22
  * @typedef {import('./types.js').StateNodeOptionsBase} StateNodeOptionsBase
21
23
  * @typedef {import('./types.js').StateNodePropsLib} StateNodePropsLib
22
24
  * @typedef {import('./types.js').Slot} Slot
25
+ * @typedef {import('./types.js').Slots} Slots
23
26
  * @typedef {{ errors: any, (layoutKeyword: any): layoutKeyword is NormalizedLayout }} ValidateNormalizedLayout
24
27
  */
25
28
 
@@ -38,8 +41,13 @@ export function isCompObject (layout) {
38
41
  }
39
42
 
40
43
  /** @type {(child: Child) => child is Child & CompositeCompObject} */
41
- export function childIsCompObject (child) {
42
- return 'comp' in child
44
+ export function childIsCompositeCompObject (child) {
45
+ return 'comp' in child && 'children' in child
46
+ }
47
+
48
+ /** @type {(child: Child) => child is Child & SlotCompObject} */
49
+ export function childIsSlotCompObject (child) {
50
+ return 'comp' in child && child.comp === 'slot'
43
51
  }
44
52
 
45
53
  /** @type {(layout: BaseCompObject, components: Record<string, import('../types.js').ComponentInfo>) => layout is CompositeCompObject} */