@json-layout/vocabulary 0.14.0 → 0.16.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.
Files changed (46) hide show
  1. package/package.json +5 -5
  2. package/src/components/compile-types.js +61 -0
  3. package/src/components/index.js +226 -0
  4. package/src/components/types.ts +378 -0
  5. package/src/index.js +3 -0
  6. package/src/layout-keyword/index.js +9 -2
  7. package/src/layout-keyword/schema.js +67 -62
  8. package/src/layout-keyword/schema.json +35 -50
  9. package/src/layout-keyword/types.ts +111 -42
  10. package/src/normalize.js +95 -54
  11. package/src/normalized-layout/index.js +18 -64
  12. package/src/normalized-layout/schema.js +235 -762
  13. package/src/normalized-layout/schema.json +118 -324
  14. package/src/normalized-layout/types.ts +163 -233
  15. package/src/types.ts +11 -0
  16. package/src/validate.js +42 -0
  17. package/types/components/compile-types.d.ts +13 -0
  18. package/types/components/compile-types.d.ts.map +1 -0
  19. package/types/components/index.d.ts +52 -0
  20. package/types/components/index.d.ts.map +1 -0
  21. package/types/components/types.d.ts +329 -0
  22. package/types/components/types.d.ts.map +1 -0
  23. package/types/index.d.ts +2 -0
  24. package/types/index.d.ts.map +1 -1
  25. package/types/layout-keyword/index.d.ts +6 -1
  26. package/types/layout-keyword/index.d.ts.map +1 -1
  27. package/types/layout-keyword/schema.d.ts +43 -26
  28. package/types/layout-keyword/types.d.ts +111 -18
  29. package/types/layout-keyword/types.d.ts.map +1 -1
  30. package/types/normalize.d.ts +3 -2
  31. package/types/normalize.d.ts.map +1 -1
  32. package/types/normalized-layout/index.d.ts +19 -65
  33. package/types/normalized-layout/index.d.ts.map +1 -1
  34. package/types/normalized-layout/schema.d.ts +171 -526
  35. package/types/normalized-layout/types.d.ts +163 -208
  36. package/types/normalized-layout/types.d.ts.map +1 -1
  37. package/types/types.d.ts +10 -0
  38. package/types/types.d.ts.map +1 -1
  39. package/types/validate.d.ts +8 -0
  40. package/types/validate.d.ts.map +1 -0
  41. package/src/layout-keyword/validate.js +0 -2
  42. package/src/normalized-layout/validate.js +0 -3
  43. package/types/layout-keyword/validate.d.ts +0 -23
  44. package/types/layout-keyword/validate.d.ts.map +0 -1
  45. package/types/normalized-layout/validate.d.ts +0 -23
  46. package/types/normalized-layout/validate.d.ts.map +0 -1
package/src/normalize.js CHANGED
@@ -1,10 +1,11 @@
1
- import { validateLayoutKeyword, isComponentName, isPartialCompObject, isPartialChildren, isPartialSwitch, isPartialGetItemsExpr, isPartialGetItemsObj, isPartialSlotMarkdown, isPartialGetItemsFetch } from './layout-keyword/index.js'
2
- import { validateNormalizedLayout, compositeCompNames } from './normalized-layout/index.js'
1
+ import { validateLayoutKeyword, isComponentName, isPartialCompObject, isPartialChildren, isPartialSwitch, isPartialGetItemsExpr, isPartialGetItemsObj, isPartialSlotMarkdown, isPartialGetItemsFetch, isPartialChildComposite } from './layout-keyword/index.js'
2
+ import { validateNormalizedLayout } from './normalized-layout/index.js'
3
+ import { getComponentValidate } from './validate.js'
3
4
 
4
5
  /**
5
6
  * @typedef {import('./index.js').Child} Child
6
7
  * @typedef {import('./index.js').Children} Children
7
- * @typedef {import('./index.js').CompObject} CompObject
8
+ * @typedef {import('./index.js').BaseCompObject} BaseCompObject
8
9
  * @typedef {import('./index.js').Expression} Expression
9
10
  * @typedef {import('./index.js').NormalizedLayout} NormalizedLayout
10
11
  * @typedef {import('./index.js').LayoutKeyword} LayoutKeyword
@@ -55,20 +56,20 @@ function getChildren (defaultChildren, partialChildren) {
55
56
  return partialChildren.map(partialChild => {
56
57
  if (typeof partialChild === 'string') { // simple string/key referencing a known child
57
58
  const matchingDefaultChild = defaultChildren.find(c => c.key === partialChild)
58
- if (!matchingDefaultChild) throw new Error(`child unknown ${partialChild}`)
59
+ if (!matchingDefaultChild) throw new Error(`unknown child "${partialChild}"`)
59
60
  return matchingDefaultChild
60
61
  } else {
61
62
  if (typeof partialChild.cols === 'number') partialChild.cols = { sm: partialChild.cols }
62
63
  if (typeof partialChild.cols === 'object' && partialChild.cols.xs === undefined) partialChild.cols.xs = 12
63
64
  if (partialChild.key) { // object referencing known child and overwriting cols
64
65
  const matchingDefaultChild = defaultChildren.find(c => c.key === partialChild.key)
65
- if (!matchingDefaultChild) throw new Error(`child unknown ${partialChild.key}`)
66
+ if (!matchingDefaultChild) throw new Error(`unknown child "${partialChild.key}"`)
66
67
  return /** @type {Child} */ (partialChild)
67
68
  } else { // a composite component definition, not directly related to a known child
68
69
  const child = partialChild
69
- if (partialChild.children) {
70
- if (!partialChild.comp) child.comp = 'section'
71
- child.children = getChildren(defaultChildren, partialChild.children)
70
+ if (isPartialChildComposite(child)) {
71
+ if (!child.comp) child.comp = 'section'
72
+ child.children = getChildren(defaultChildren, child.children)
72
73
  }
73
74
  if (!('key' in partialChild)) {
74
75
  child.key = `$comp-${compI}`
@@ -232,42 +233,39 @@ export const getSchemaFragmentType = (schemaFragment) => {
232
233
  * @param {LayoutKeyword} layoutKeyword
233
234
  * @param {SchemaFragment} schemaFragment
234
235
  * @param {string} schemaPath
236
+ * @param {Record<string, import('./types.js').ComponentInfo>} components
235
237
  * @param {(text: string) => string} markdown
236
238
  * @param {string[]} optionsKeys
237
239
  * @param {'oneOf'} [arrayChild]
238
- * @returns {{normalized: CompObject, errors: string[]}}
240
+ * @returns {BaseCompObject}
239
241
  */
240
- function getCompObject (layoutKeyword, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild) {
241
- /** @type {string[]} */
242
- const errors = []
242
+ function getCompObject (layoutKeyword, schemaFragment, schemaPath, components, markdown, optionsKeys, arrayChild) {
243
243
  const key = schemaPath.slice(schemaPath.lastIndexOf('/') + 1)
244
244
 
245
245
  const { type, nullable } = getSchemaFragmentType(schemaFragment)
246
246
 
247
- if ('const' in schemaFragment) return { normalized: { comp: 'none' }, errors }
248
- if (!type) return { normalized: { comp: 'none' }, errors }
247
+ if ('const' in schemaFragment) return { comp: 'none' }
248
+ if (!type) return { comp: 'none' }
249
249
 
250
250
  const partial = getPartialCompObject(layoutKeyword)
251
251
 
252
252
  if (type === 'array' && !schemaFragment.items && partial.comp !== 'file-input') {
253
- return { normalized: { comp: 'none' }, errors }
253
+ return { comp: 'none' }
254
254
  }
255
255
 
256
256
  // chose the default component for a schema fragment
257
257
  if (!partial.comp) {
258
- try {
259
- partial.comp = getDefaultComp(partial, schemaFragment, arrayChild)
260
- } catch (/** @type {any} */err) {
261
- errors.push(err.message)
262
- partial.comp = 'none'
263
- }
258
+ partial.comp = getDefaultComp(partial, schemaFragment, arrayChild)
264
259
  }
265
- if (partial.comp === 'none') return { normalized: { comp: 'none' }, errors }
260
+ const component = components[partial.comp]
261
+ if (!component) {
262
+ throw new Error(`unknown component "${partial.comp}"`)
263
+ }
264
+ if (partial.comp === 'none') return { comp: 'none' }
266
265
 
267
266
  if (nullable) partial.nullable = nullable
268
267
 
269
- // @ts-ignore
270
- if (compositeCompNames.includes(partial.comp)) {
268
+ if (component.composite) {
271
269
  if (!('title' in partial)) partial.title = schemaFragment.title ?? null
272
270
  partial.children = getChildren(getDefaultChildren(schemaFragment), partial.children)
273
271
  } else if (partial.comp === 'list') {
@@ -275,10 +273,12 @@ function getCompObject (layoutKeyword, schemaFragment, schemaPath, markdown, opt
275
273
  partial.listEditMode = partial.listEditMode ?? (schemaFragment.items.type === 'object' ? 'inline-single' : 'inline')
276
274
  partial.listActions = partial.listActions ?? ['add', 'edit', 'delete', 'duplicate', 'sort']
277
275
  } else {
278
- if (!('label' in partial)) partial.label = schemaFragment.title ?? key
276
+ if (!('label' in partial) && partial.comp !== 'one-of-select') {
277
+ partial.label = schemaFragment.title ?? key
278
+ }
279
279
  }
280
280
 
281
- if (['select', 'autocomplete', 'combobox'].includes(partial.comp) && !partial.items) {
281
+ if (component.itemsBased && !partial.items) {
282
282
  let items
283
283
  if (type === 'array') {
284
284
  items = getItemsFromSchema(schemaFragment.items)
@@ -294,7 +294,7 @@ function getCompObject (layoutKeyword, schemaFragment, schemaPath, markdown, opt
294
294
  }
295
295
  }
296
296
 
297
- if (['select', 'autocomplete', 'combobox', 'number-combobox', 'file-input'].includes(partial.comp)) {
297
+ if (component.multipleCompat) {
298
298
  if (type === 'array' || partial.separator) {
299
299
  partial.multiple = true
300
300
  }
@@ -401,36 +401,42 @@ function getCompObject (layoutKeyword, schemaFragment, schemaPath, markdown, opt
401
401
  if (typeof partial.cols === 'number') partial.cols = { xs: partial.cols }
402
402
  if (typeof partial.cols === 'object' && partial.cols.xs === undefined) partial.cols.xs = 12
403
403
 
404
- return { normalized: /** @type {CompObject} */(partial), errors }
404
+ const validateComponent = getComponentValidate(component)
405
+ if (!validateComponent(partial)) {
406
+ const error = new Error(`component "${component.name}" validation errors`)
407
+ error.cause = lighterValidationErrors(validateComponent.errors)
408
+ throw error
409
+ }
410
+
411
+ return /** @type {BaseCompObject} */(partial)
405
412
  }
406
413
 
407
414
  /**
408
415
  * @param {LayoutKeyword} layoutKeyword
409
416
  * @param {SchemaFragment} schemaFragment
410
417
  * @param {string} schemaPath
418
+ * @param {Record<string, import('./types.js').ComponentInfo>} components
411
419
  * @param {(text: string) => string} markdown
412
420
  * @param {string[]} optionsKeys
413
421
  * @param {'oneOf'} [arrayChild]
414
- * @returns {{normalized: NormalizedLayout, errors: string[]}}
422
+ * @returns {NormalizedLayout}}
415
423
  */
416
- function getNormalizedLayout (layoutKeyword, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild) {
424
+ function getNormalizedLayout (layoutKeyword, schemaFragment, schemaPath, components, markdown, optionsKeys, arrayChild) {
417
425
  if (isPartialSwitch(layoutKeyword)) {
418
- /** @type {CompObject[]} */
426
+ /** @type {BaseCompObject[]} */
419
427
  const normalizedSwitchCases = []
420
- const errors = []
421
428
  const switchCases = [...layoutKeyword.switch]
422
429
  if (!switchCases.find(s => !s.if)) {
423
430
  switchCases.push({})
424
431
  }
425
432
  for (let i = 0; i < switchCases.length; i++) {
426
433
  const switchCase = switchCases[i]
427
- const compObjectResult = getCompObject(switchCase, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild)
428
- normalizedSwitchCases.push(compObjectResult.normalized)
429
- for (const error of compObjectResult.errors) errors.push(`switch ${i} - ${error}`)
434
+ const compObjectResult = getCompObject(switchCase, schemaFragment, schemaPath, components, markdown, optionsKeys, arrayChild)
435
+ normalizedSwitchCases.push(compObjectResult)
430
436
  }
431
- return { normalized: { switch: normalizedSwitchCases }, errors: [] }
437
+ return { switch: normalizedSwitchCases }
432
438
  } else {
433
- return getCompObject(layoutKeyword, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild)
439
+ return getCompObject(layoutKeyword, schemaFragment, schemaPath, components, markdown, optionsKeys, arrayChild)
434
440
  }
435
441
  }
436
442
 
@@ -447,10 +453,11 @@ function matchValidationError (error, fn) {
447
453
  }
448
454
 
449
455
  /**
450
- * @param {import('ajv').ErrorObject[]} errors
456
+ * @param {import('ajv').ErrorObject[] | null | undefined} errors
451
457
  * @returns {string[]}
452
458
  */
453
459
  function lighterValidationErrors (errors) {
460
+ if (!errors) return []
454
461
  const compositeErrors = errors.filter(e => matchValidationError(e, (e) => e.keyword === 'anyOf' || e.keyword === 'oneOf'))
455
462
  // in case of a anyOf/oneOf error there are some subschemas errors that mostly prevent readability
456
463
  for (const compositeError of compositeErrors) {
@@ -459,7 +466,13 @@ function lighterValidationErrors (errors) {
459
466
  errors = errors.filter(e => matchValidationError(e, (e) => e.instancePath !== compositeError.instancePath || e.keyword !== 'type'))
460
467
  }
461
468
  }
462
- return errors.map(e => e.message ?? e.keyword)
469
+ const messages = []
470
+ for (const error of errors) {
471
+ let message = error.message ?? error.keyword
472
+ if (error.params) message += ' ' + JSON.stringify(error.params)
473
+ messages.push(message)
474
+ }
475
+ return messages
463
476
  }
464
477
 
465
478
  const defaultOptionsKeys = ['readOnly', 'summary', 'titleDepth', 'density', 'removeAdditional', 'validateOn', 'updateOne', 'debounceInputMs', 'initialValidation', 'defaultOn', 'readOnlyPropertiesMode']
@@ -467,12 +480,13 @@ const defaultOptionsKeys = ['readOnly', 'summary', 'titleDepth', 'density', 'rem
467
480
  /**
468
481
  * @param {SchemaFragment} schemaFragment
469
482
  * @param {string} schemaPath
483
+ * @param {Record<string, import('./types.js').ComponentInfo>} components
470
484
  * @param {(text: string) => string} markdown
471
485
  * @param {string[]} [optionsKeys]
472
486
  * @param {'oneOf'} [arrayChild]
473
- * @returns {{layout: NormalizedLayout, errors: string[]}}
487
+ * @returns {NormalizedLayout}
474
488
  */
475
- export function normalizeLayoutFragment (schemaFragment, schemaPath, markdown = (src) => src, optionsKeys, arrayChild) {
489
+ function normalizeValidLayoutFragment (schemaFragment, schemaPath, components, markdown, optionsKeys, arrayChild) {
476
490
  optionsKeys = optionsKeys ? optionsKeys.concat(defaultOptionsKeys) : defaultOptionsKeys
477
491
  let layoutKeyword
478
492
  if (arrayChild === 'oneOf') {
@@ -481,20 +495,47 @@ export function normalizeLayoutFragment (schemaFragment, schemaPath, markdown =
481
495
  layoutKeyword = schemaFragment.layout ?? {}
482
496
  }
483
497
  if (!validateLayoutKeyword(layoutKeyword)) {
484
- console.error(`layout keyword validation errors at path ${schemaPath}`, layoutKeyword, validateLayoutKeyword.errors)
485
- return {
486
- layout: getNormalizedLayout({}, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild).normalized,
487
- errors: lighterValidationErrors(validateLayoutKeyword.errors)
488
- }
498
+ const error = new Error('layout keyword validation errors at path')
499
+ error.cause = lighterValidationErrors(validateLayoutKeyword.errors)
500
+ throw error
501
+ }
502
+ const normalizedLayout = getNormalizedLayout(layoutKeyword, schemaFragment, schemaPath, components, markdown, optionsKeys, arrayChild)
503
+
504
+ if (!validateNormalizedLayout(normalizedLayout)) {
505
+ const error = new Error('normalized layout validation errors at path')
506
+ error.cause = lighterValidationErrors(validateNormalizedLayout.errors)
507
+ throw error
489
508
  }
490
- const normalizedLayout = getNormalizedLayout(layoutKeyword, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild)
491
- if (!validateNormalizedLayout(normalizedLayout.normalized)) {
492
- console.error(`normalized layout validation errors at path ${schemaPath}`, normalizedLayout, validateNormalizedLayout.errors)
493
- return {
494
- layout: getNormalizedLayout({}, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild).normalized,
495
- errors: lighterValidationErrors(validateNormalizedLayout.errors)
509
+ return normalizedLayout
510
+ }
511
+
512
+ /**
513
+ * @param {SchemaFragment} schemaFragment
514
+ * @param {string} schemaPath
515
+ * @param {Record<string, import('./types.js').ComponentInfo>} components
516
+ * @param {(text: string) => string} markdown
517
+ * @param {string[]} [optionsKeys]
518
+ * @param {'oneOf'} [arrayChild]
519
+ * @returns {{layout: NormalizedLayout, errors: string[]}}
520
+ */
521
+ export function normalizeLayoutFragment (schemaFragment, schemaPath, components, markdown = (src) => src, optionsKeys, arrayChild) {
522
+ /** @type {string[]} */
523
+ const errors = []
524
+ try {
525
+ const layout = normalizeValidLayoutFragment(schemaFragment, schemaPath, components, markdown, optionsKeys, arrayChild)
526
+ return { layout, errors }
527
+ } catch (/** @type {any} */err) {
528
+ try {
529
+ errors.push(err.message)
530
+ if (err.cause && Array.isArray(err.cause)) errors.push(...err.cause)
531
+ errors.push('failed to normalize layout, use default component')
532
+ const layout = normalizeValidLayoutFragment({ ...schemaFragment, layout: {} }, schemaPath, components, markdown, optionsKeys, arrayChild)
533
+ return { layout, errors }
534
+ } catch (/** @type {any} */err) {
535
+ errors.push(err.message)
536
+ if (err.cause && Array.isArray(err.cause)) errors.push(...err.cause)
537
+ errors.push('failed to produce default layout, hide this fragment')
538
+ return { layout: { comp: 'none' }, errors }
496
539
  }
497
- // throw new Error(`invalid layout at path ${schemaPath}`, { cause: validateNormalizedLayout.errors })
498
540
  }
499
- return { layout: normalizedLayout.normalized, errors: [] }
500
541
  }
@@ -1,40 +1,20 @@
1
- import validate from './validate.js'
2
1
  import schema from './schema.js'
2
+ import {ajv} from '../validate.js'
3
3
 
4
4
  /**
5
5
  * @typedef {import('./types.js').NormalizedLayout} NormalizedLayout
6
6
  * @typedef {import('./types.js').SwitchStruct} SwitchStruct
7
- * @typedef {import('./types.js').CompObject} CompObject
8
- * @typedef {import('./types.js').Section} Section
9
- * @typedef {import('./types.js').GetItems} GetItems
10
- * @typedef {import('./types.js').Expression} Expression
11
- * @typedef {import('./types.js').Select} Select
12
- * @typedef {import('./types.js').Autocomplete} Autocomplete
7
+ * @typedef {import('./types.js').BaseCompObject} BaseCompObject
13
8
  * @typedef {import('./types.js').SelectItem} SelectItem
14
9
  * @typedef {import('./types.js').SelectItems} SelectItems
15
10
  * @typedef {import('./types.js').GetItemsFetch} GetItemsFetch
16
- * @typedef {import('./types.js').TextField} TextField
17
- * @typedef {import('./types.js').Textarea} Textarea
18
- * @typedef {import('./types.js').NumberField} NumberField
19
- * @typedef {import('./types.js').Slider} Slider
20
- * @typedef {import('./types.js').Checkbox} Checkbox
21
- * @typedef {import('./types.js').Switch} Switch
22
- * @typedef {import('./types.js').DatePicker} DatePicker
23
- * @typedef {import('./types.js').TimePicker} TimePicker
24
- * @typedef {import('./types.js').DateTimePicker} DateTimePicker
25
- * @typedef {import('./types.js').ColorPicker} ColorPicker
26
- * @typedef {import('./types.js').OneOfSelect} OneOfSelect
27
11
  * @typedef {import('./types.js').Child} Child
28
12
  * @typedef {import('./types.js').Children} Children
29
13
  * @typedef {import('./types.js').CompositeCompObject} CompositeCompObject
30
- * @typedef {import('./types.js').Tabs} Tabs
31
- * @typedef {import('./types.js').VerticalTabs} VerticalTabs
32
- * @typedef {import('./types.js').ExpansionPanels} ExpansionPanels
33
- * @typedef {import('./types.js').Stepper} Stepper
34
- * @typedef {import('./types.js').List} List
35
- * @typedef {import('./types.js').Combobox} Combobox
36
- * @typedef {import('./types.js').Markdown} Markdown
37
- * @typedef {import('./types.js').FileInput} FileInput
14
+ * @typedef {import('./types.js').ItemsBasedCompObject} ItemsBasedCompObject
15
+ * @typedef {import('./types.js').FocusableCompObject} FocusableCompObject
16
+ * @typedef {import('./types.js').GetItems} GetItems
17
+ * @typedef {import('./types.js').Expression} Expression
38
18
  * @typedef {import('./types.js').Cols} Cols
39
19
  * @typedef {import('./types.js').ColsObj} ColsObj
40
20
  * @typedef {import('./types.js').StateNodeOptionsBase} StateNodeOptionsBase
@@ -43,7 +23,7 @@ import schema from './schema.js'
43
23
  * @typedef {{ errors: any, (layoutKeyword: any): layoutKeyword is NormalizedLayout }} ValidateNormalizedLayout
44
24
  */
45
25
 
46
- export const /** @type {ValidateNormalizedLayout} */ validateNormalizedLayout = /** @type {any} */ (validate)
26
+ export const /** @type {ValidateNormalizedLayout} */ validateNormalizedLayout = /** @type {any} */ (ajv.getSchema(schema.$id))
47
27
 
48
28
  export const normalizedLayoutSchema = /** @type {any} */ (schema)
49
29
 
@@ -52,55 +32,29 @@ export function isSwitchStruct (layout) {
52
32
  return typeof layout === 'object' && 'switch' in layout
53
33
  }
54
34
 
55
- /** @type {(layout: NormalizedLayout) => layout is CompObject} */
35
+ /** @type {(layout: NormalizedLayout) => layout is BaseCompObject} */
56
36
  export function isCompObject (layout) {
57
37
  return !isSwitchStruct(layout)
58
38
  }
59
39
 
60
40
  /** @type {(child: Child) => child is Child & CompositeCompObject} */
61
41
  export function childIsCompObject (child) {
62
- return !!child.comp
63
- }
64
-
65
- /** @type {(layout: CompObject) => layout is Section} */
66
- export function isSectionLayout (layout) {
67
- return layout.comp === 'section'
68
- }
69
-
70
- export const compositeCompNames = ['section', 'tabs', 'vertical-tabs', 'expansion-panels', 'stepper']
71
-
72
- // these components can received keybord inputs and emit blur events
73
- // they will be debounced when validateOn=blur is applicable and data binding will be updated on blur
74
- export const editableCompNames = ['text-field', 'number-field', 'textarea', 'markdown']
75
-
76
- /** @type {(layout: CompObject) => layout is CompositeCompObject} */
77
- export function isCompositeLayout (layout) {
78
- return compositeCompNames.includes(layout.comp)
79
- }
80
-
81
- /** @type {(layout: CompObject) => layout is TextField} */
82
- export function isTextFieldLayout (layout) {
83
- return layout.comp === 'text-field'
84
- }
85
-
86
- /** @type {(layout: CompObject) => layout is Select} */
87
- export function isSelectLayout (layout) {
88
- return layout.comp === 'select'
42
+ return 'comp' in child
89
43
  }
90
44
 
91
- /** @type {(layout: CompObject) => layout is FileInput} */
92
- export function isFileLayout (layout) {
93
- return layout.comp === 'file-input'
45
+ /** @type {(layout: BaseCompObject, components: Record<string, import('../types.js').ComponentInfo>) => layout is CompositeCompObject} */
46
+ export function isCompositeLayout (layout, components) {
47
+ return !!components[layout.comp]?.composite
94
48
  }
95
49
 
96
- /** @type {(layout: CompObject) => layout is CompObject & {autofocus: boolean}} */
97
- export function isFocusableLayout (layout) {
98
- return ['text-field', 'number-field', 'textarea', 'select', 'combobox', 'number-combobox', 'autocomplete', 'markdown'].includes(layout.comp)
50
+ /** @type {(layout: BaseCompObject, components: Record<string, import('../types.js').ComponentInfo>) => layout is FocusableCompObject} */
51
+ export function isFocusableLayout (layout, components) {
52
+ return !!components[layout.comp]?.focusable
99
53
  }
100
54
 
101
- /** @type {(layout: CompObject) => layout is Select | Combobox | Autocomplete} */
102
- export function isItemsLayout (layout) {
103
- return layout.comp === 'select' || layout.comp === 'combobox' || layout.comp === 'autocomplete'
55
+ /** @type {(layout: BaseCompObject, components: Record<string, import('../types.js').ComponentInfo>) => layout is ItemsBasedCompObject} */
56
+ export function isItemsLayout (layout, components) {
57
+ return !!components[layout.comp]?.itemsBased
104
58
  }
105
59
 
106
60
  /** @type {(getItems: GetItems) => getItems is Expression} */