@morscherlab/mint-sdk 1.0.0-rc.2 → 1.0.0-rc.4

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 (178) hide show
  1. package/dist/components/AppSidebar.vue.d.ts +3 -3
  2. package/dist/components/ControlWorkspaceView.vue.d.ts +3 -3
  3. package/dist/components/DoseDesignWorkspaceView.vue.d.ts +1 -1
  4. package/dist/components/PluginWorkspaceView.vue.d.ts +3 -3
  5. package/dist/components/index.js +2 -2
  6. package/dist/{components-BhK-dW99.js → components-DafPc4rM.js} +3 -3
  7. package/dist/{components-BhK-dW99.js.map → components-DafPc4rM.js.map} +1 -1
  8. package/dist/composables/controlComponentBindings.d.ts +7 -0
  9. package/dist/composables/controlSchemaAdapters.d.ts +20 -0
  10. package/dist/composables/controlSchemaFormFields.d.ts +3 -0
  11. package/dist/composables/controlSchemaLayout.d.ts +7 -0
  12. package/dist/composables/controlSchemaNormalize.d.ts +15 -0
  13. package/dist/composables/controlSchemaUtils.d.ts +9 -0
  14. package/dist/composables/controlWorkspaceOptions.d.ts +2 -0
  15. package/dist/composables/index.js +3 -3
  16. package/dist/composables/useControlSchema.d.ts +4 -30
  17. package/dist/composables/useControlWorkspace.d.ts +5 -0
  18. package/dist/{composables-Bg7CFuNz.js → composables-BMkPQhVK.js} +2 -2
  19. package/dist/{composables-Bg7CFuNz.js.map → composables-BMkPQhVK.js.map} +1 -1
  20. package/dist/index.js +4 -4
  21. package/dist/install.js +2 -2
  22. package/dist/templates/adapters.d.ts +14 -47
  23. package/dist/templates/assayLookups.d.ts +3 -0
  24. package/dist/templates/assayMatrixAdapters.d.ts +6 -0
  25. package/dist/templates/assayMatrixBuilder.d.ts +2 -0
  26. package/dist/templates/assayNormalizers.d.ts +4 -0
  27. package/dist/templates/builderDefaults.d.ts +1 -0
  28. package/dist/templates/builderIdUtils.d.ts +4 -0
  29. package/dist/templates/builderPresetControls.d.ts +10 -0
  30. package/dist/templates/builderReadUtils.d.ts +8 -0
  31. package/dist/templates/builders.d.ts +26 -67
  32. package/dist/templates/calibrationCurveAdapters.d.ts +4 -0
  33. package/dist/templates/calibrationCurveBuilder.d.ts +2 -0
  34. package/dist/templates/calibrationNormalizers.d.ts +5 -0
  35. package/dist/templates/componentBindingCatalog.d.ts +25 -0
  36. package/dist/templates/componentBindingHelpers.d.ts +17 -0
  37. package/dist/templates/componentDoseResponseProps.d.ts +3 -0
  38. package/dist/templates/componentGenericProps.d.ts +8 -0
  39. package/dist/templates/componentPlateHelpers.d.ts +5 -0
  40. package/dist/templates/componentPlateMapProps.d.ts +3 -0
  41. package/dist/templates/componentPropsFactory.d.ts +3 -0
  42. package/dist/templates/componentQpcrPlateProps.d.ts +3 -0
  43. package/dist/templates/componentTargetResolvers.d.ts +5 -0
  44. package/dist/templates/componentTemplateProps.d.ts +3 -0
  45. package/dist/templates/controlSchemaClone.d.ts +4 -0
  46. package/dist/templates/controlSchemaConstants.d.ts +10 -0
  47. package/dist/templates/controlSchemaMerge.d.ts +3 -0
  48. package/dist/templates/controlSchemaTargets.d.ts +17 -0
  49. package/dist/templates/controlSchemaTypes.d.ts +4 -0
  50. package/dist/templates/controlSchemas.d.ts +4 -4
  51. package/dist/templates/defaultBioTemplateBuilder.d.ts +2 -0
  52. package/dist/templates/doseResponseAdapters.d.ts +4 -0
  53. package/dist/templates/doseResponseBuilder.d.ts +2 -0
  54. package/dist/templates/elisaAssayCollectionBuilder.d.ts +2 -0
  55. package/dist/templates/flowCytometryAssayCollectionBuilder.d.ts +2 -0
  56. package/dist/templates/flowCytometryPanelBuilder.d.ts +2 -0
  57. package/dist/templates/flowNormalizers.d.ts +8 -0
  58. package/dist/templates/flowPanelAdapters.d.ts +4 -0
  59. package/dist/templates/index.js +1 -1
  60. package/dist/templates/instrumentRunAdapterHelpers.d.ts +8 -0
  61. package/dist/templates/instrumentRunAdapters.d.ts +8 -0
  62. package/dist/templates/instrumentRunBuilder.d.ts +2 -0
  63. package/dist/templates/lcmsBatchCollectionBuilder.d.ts +2 -0
  64. package/dist/templates/plateGeometry.d.ts +4 -0
  65. package/dist/templates/plateMapAdapters.d.ts +3 -0
  66. package/dist/templates/plateMapBuilder.d.ts +2 -0
  67. package/dist/templates/presetControlSchemas.d.ts +534 -0
  68. package/dist/templates/protocolAdapters.d.ts +5 -0
  69. package/dist/templates/protocolNormalizers.d.ts +6 -0
  70. package/dist/templates/protocolStepsBuilder.d.ts +2 -0
  71. package/dist/templates/qpcrAdapters.d.ts +5 -0
  72. package/dist/templates/qpcrExpressionCollectionBuilder.d.ts +2 -0
  73. package/dist/templates/qpcrPlateBuilder.d.ts +2 -0
  74. package/dist/templates/reagentAdapters.d.ts +5 -0
  75. package/dist/templates/reagentListBuilder.d.ts +2 -0
  76. package/dist/templates/runNormalizers.d.ts +10 -0
  77. package/dist/templates/sampleNormalizers.d.ts +4 -0
  78. package/dist/templates/samplePrepAdapters.d.ts +4 -0
  79. package/dist/templates/samplePrepBuilder.d.ts +2 -0
  80. package/dist/templates/sampleSheetAdapters.d.ts +5 -0
  81. package/dist/templates/sampleSheetBuilder.d.ts +2 -0
  82. package/dist/templates/targetedMetabolomicsCollectionBuilder.d.ts +2 -0
  83. package/dist/templates/targetedMetabolomicsHelpers.d.ts +5 -0
  84. package/dist/templates/templateControlSchemas.d.ts +400 -0
  85. package/dist/templates/templateEnvelopes.d.ts +9 -0
  86. package/dist/templates/templatePackCollectionBuilder.d.ts +2 -0
  87. package/dist/templates/templatePresetCollectionBuilder.d.ts +18 -0
  88. package/dist/templates/templateValidators.d.ts +13 -0
  89. package/dist/templates/timeCourseAdapters.d.ts +5 -0
  90. package/dist/templates/timeCourseBuilder.d.ts +2 -0
  91. package/dist/templates/wellPlateScreenCollectionBuilder.d.ts +2 -0
  92. package/dist/templates/westernBlotAssayCollectionBuilder.d.ts +2 -0
  93. package/dist/{templates-BorLR_7p.js → templates-bUAWMn5L.js} +3574 -3428
  94. package/dist/templates-bUAWMn5L.js.map +1 -0
  95. package/dist/{useProtocolTemplates-n6AJqSqv.js → useProtocolTemplates-QZtHFFH2.js} +2 -2
  96. package/dist/{useProtocolTemplates-n6AJqSqv.js.map → useProtocolTemplates-QZtHFFH2.js.map} +1 -1
  97. package/package.json +1 -1
  98. package/src/composables/controlComponentBindings.ts +80 -0
  99. package/src/composables/controlSchemaAdapters.ts +196 -0
  100. package/src/composables/controlSchemaFormFields.ts +61 -0
  101. package/src/composables/controlSchemaLayout.ts +59 -0
  102. package/src/composables/controlSchemaNormalize.ts +101 -0
  103. package/src/composables/controlSchemaUtils.ts +36 -0
  104. package/src/composables/controlWorkspaceOptions.ts +21 -0
  105. package/src/composables/useControlSchema.ts +51 -628
  106. package/src/composables/useControlWorkspace.ts +207 -0
  107. package/src/templates/adapters.ts +89 -930
  108. package/src/templates/assayLookups.ts +33 -0
  109. package/src/templates/assayMatrixAdapters.ts +78 -0
  110. package/src/templates/assayMatrixBuilder.ts +59 -0
  111. package/src/templates/assayNormalizers.ts +34 -0
  112. package/src/templates/builderDefaults.ts +11 -0
  113. package/src/templates/builderIdUtils.ts +20 -0
  114. package/src/templates/builderPresetControls.ts +165 -0
  115. package/src/templates/builderReadUtils.ts +57 -0
  116. package/src/templates/builders.ts +122 -2350
  117. package/src/templates/calibrationCurveAdapters.ts +59 -0
  118. package/src/templates/calibrationCurveBuilder.ts +99 -0
  119. package/src/templates/calibrationNormalizers.ts +60 -0
  120. package/src/templates/componentBindingCatalog.ts +90 -0
  121. package/src/templates/componentBindingHelpers.ts +93 -0
  122. package/src/templates/componentBindings.ts +12 -461
  123. package/src/templates/componentDoseResponseProps.ts +42 -0
  124. package/src/templates/componentGenericProps.ts +77 -0
  125. package/src/templates/componentPlateHelpers.ts +29 -0
  126. package/src/templates/componentPlateMapProps.ts +32 -0
  127. package/src/templates/componentPropsFactory.ts +21 -0
  128. package/src/templates/componentQpcrPlateProps.ts +28 -0
  129. package/src/templates/componentTargetResolvers.ts +69 -0
  130. package/src/templates/componentTemplateProps.ts +78 -0
  131. package/src/templates/controlSchemaClone.ts +32 -0
  132. package/src/templates/controlSchemaConstants.ts +11 -0
  133. package/src/templates/controlSchemaMerge.ts +40 -0
  134. package/src/templates/controlSchemaTargets.ts +87 -0
  135. package/src/templates/controlSchemaTypes.ts +20 -0
  136. package/src/templates/controlSchemas.ts +22 -704
  137. package/src/templates/defaultBioTemplateBuilder.ts +124 -0
  138. package/src/templates/doseResponseAdapters.ts +45 -0
  139. package/src/templates/doseResponseBuilder.ts +44 -0
  140. package/src/templates/elisaAssayCollectionBuilder.ts +62 -0
  141. package/src/templates/flowCytometryAssayCollectionBuilder.ts +41 -0
  142. package/src/templates/flowCytometryPanelBuilder.ts +53 -0
  143. package/src/templates/flowNormalizers.ts +58 -0
  144. package/src/templates/flowPanelAdapters.ts +58 -0
  145. package/src/templates/instrumentRunAdapterHelpers.ts +94 -0
  146. package/src/templates/instrumentRunAdapters.ts +163 -0
  147. package/src/templates/instrumentRunBuilder.ts +97 -0
  148. package/src/templates/lcmsBatchCollectionBuilder.ts +38 -0
  149. package/src/templates/plateGeometry.ts +62 -0
  150. package/src/templates/plateMapAdapters.ts +36 -0
  151. package/src/templates/plateMapBuilder.ts +43 -0
  152. package/src/templates/presetControlSchemas.ts +258 -0
  153. package/src/templates/protocolAdapters.ts +69 -0
  154. package/src/templates/protocolNormalizers.ts +37 -0
  155. package/src/templates/protocolStepsBuilder.ts +36 -0
  156. package/src/templates/qpcrAdapters.ts +104 -0
  157. package/src/templates/qpcrExpressionCollectionBuilder.ts +33 -0
  158. package/src/templates/qpcrPlateBuilder.ts +96 -0
  159. package/src/templates/reagentAdapters.ts +77 -0
  160. package/src/templates/reagentListBuilder.ts +30 -0
  161. package/src/templates/runNormalizers.ts +63 -0
  162. package/src/templates/sampleNormalizers.ts +58 -0
  163. package/src/templates/samplePrepAdapters.ts +63 -0
  164. package/src/templates/samplePrepBuilder.ts +51 -0
  165. package/src/templates/sampleSheetAdapters.ts +75 -0
  166. package/src/templates/sampleSheetBuilder.ts +23 -0
  167. package/src/templates/targetedMetabolomicsCollectionBuilder.ts +79 -0
  168. package/src/templates/targetedMetabolomicsHelpers.ts +102 -0
  169. package/src/templates/templateControlSchemas.ts +320 -0
  170. package/src/templates/templateEnvelopes.ts +137 -0
  171. package/src/templates/templatePackCollectionBuilder.ts +23 -0
  172. package/src/templates/templatePresetCollectionBuilder.ts +139 -0
  173. package/src/templates/templateValidators.ts +414 -0
  174. package/src/templates/timeCourseAdapters.ts +73 -0
  175. package/src/templates/timeCourseBuilder.ts +64 -0
  176. package/src/templates/wellPlateScreenCollectionBuilder.ts +36 -0
  177. package/src/templates/westernBlotAssayCollectionBuilder.ts +68 -0
  178. package/dist/templates-BorLR_7p.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morscherlab/mint-sdk",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0-rc.4",
4
4
  "description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec INtegrated Toolkit.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,80 @@
1
+ import type {
2
+ ControlComponentBinding,
3
+ ControlComponentBindingConfig,
4
+ ControlComponentBindingRecordConfig,
5
+ ControlComponentBindingsById,
6
+ ControlComponentBindingsConfig,
7
+ ControlComponentPropsMap,
8
+ ControlComponentPropSource,
9
+ } from './useControlSchema'
10
+
11
+ /** Map control workspace values into component props for direct `v-bind` usage. */
12
+ export function controlValuesToComponentProps<TValues extends Record<string, unknown>>(
13
+ values: TValues,
14
+ mapping?: ControlComponentPropsMap<TValues>,
15
+ ): Record<string, unknown> {
16
+ if (mapping === undefined) return { ...values }
17
+
18
+ if (Array.isArray(mapping)) {
19
+ const props: Record<string, unknown> = {}
20
+ for (const key of mapping) {
21
+ props[key] = values[key]
22
+ }
23
+ return props
24
+ }
25
+
26
+ const props: Record<string, unknown> = {}
27
+ for (const [prop, source] of Object.entries(mapping) as Array<[string, ControlComponentPropSource<TValues>]>) {
28
+ props[prop] = typeof source === 'function' ? source(values) : values[source]
29
+ }
30
+ return props
31
+ }
32
+
33
+ /** Map control workspace values into named SDK component bindings for direct slot rendering. */
34
+ export function controlValuesToComponentBindings<TValues extends Record<string, unknown>>(
35
+ values: TValues,
36
+ bindings?: ControlComponentBindingsConfig<TValues>,
37
+ ): ControlComponentBinding[] {
38
+ if (bindings === undefined) return []
39
+
40
+ return normalizeControlComponentBindingConfigs(bindings).map(binding => ({
41
+ id: binding.id,
42
+ component: binding.component,
43
+ props: controlValuesToComponentProps(values, binding.props),
44
+ }))
45
+ }
46
+
47
+ /** Map control workspace values into SDK component bindings keyed by binding id. */
48
+ export function controlValuesToComponentBindingsById<TValues extends Record<string, unknown>>(
49
+ values: TValues,
50
+ bindings?: ControlComponentBindingsConfig<TValues>,
51
+ ): ControlComponentBindingsById {
52
+ return Object.fromEntries(
53
+ controlValuesToComponentBindings(values, bindings).map(binding => [binding.id, binding]),
54
+ )
55
+ }
56
+
57
+ function normalizeControlComponentBindingConfigs<TValues extends Record<string, unknown>>(
58
+ bindings: ControlComponentBindingsConfig<TValues>,
59
+ ): Array<Required<Pick<ControlComponentBindingConfig<TValues>, 'id' | 'component'>> & Pick<ControlComponentBindingConfig<TValues>, 'props'>> {
60
+ if (Array.isArray(bindings)) {
61
+ const usedIds = new Map<string, number>()
62
+ return bindings.map(binding => ({
63
+ id: uniqueComponentBindingId(binding.id ?? binding.component, usedIds),
64
+ component: binding.component,
65
+ props: binding.props,
66
+ }))
67
+ }
68
+
69
+ return Object.entries(bindings).map(([id, binding]: [string, ControlComponentBindingRecordConfig<TValues>]) => ({
70
+ id,
71
+ component: binding.component,
72
+ props: binding.props,
73
+ }))
74
+ }
75
+
76
+ function uniqueComponentBindingId(id: string, usedIds: Map<string, number>): string {
77
+ const count = usedIds.get(id) ?? 0
78
+ usedIds.set(id, count + 1)
79
+ return count === 0 ? id : `${id}-${count + 1}`
80
+ }
@@ -0,0 +1,196 @@
1
+ import type {
2
+ PillNavItem,
3
+ SidebarToolSection,
4
+ SettingsModalSchema,
5
+ TopBarSettingsConfig,
6
+ } from '../types'
7
+ import type {
8
+ FormSectionSchema,
9
+ } from '../types/form-builder'
10
+ import {
11
+ controlToFormField,
12
+ } from './controlSchemaFormFields'
13
+ import {
14
+ controlViewItem,
15
+ firstSidebarConfig,
16
+ isSidebarEnabled,
17
+ sectionConfig,
18
+ } from './controlSchemaLayout'
19
+ import {
20
+ normalizeControls,
21
+ } from './controlSchemaNormalize'
22
+ import {
23
+ humanize,
24
+ orderedUnique,
25
+ } from './controlSchemaUtils'
26
+ import type {
27
+ ControlFormSchema,
28
+ ControlSchema,
29
+ ControlSchemaOptions,
30
+ } from './useControlSchema'
31
+
32
+ /** Convert a compact control schema into a FormBuilder schema. */
33
+ export function controlsToFormSchema(
34
+ controls: ControlSchema,
35
+ options: ControlSchemaOptions = {},
36
+ ): ControlFormSchema {
37
+ const normalized = normalizeControls(controls, options)
38
+ const sectionIds = orderedUnique(normalized.map(control => control.sectionId))
39
+ const sections = sectionIds.map((sectionId): FormSectionSchema => {
40
+ const sectionControls = normalized.filter(control => control.sectionId === sectionId)
41
+ const config = sectionConfig(sectionId, sectionControls, options)
42
+ return {
43
+ id: sectionId,
44
+ title: config.title ?? config.label ?? humanize(sectionId),
45
+ description: config.description,
46
+ columns: config.columns ?? options.columns ?? 1,
47
+ defaultOpen: config.defaultOpen,
48
+ condition: config.condition,
49
+ fields: sectionControls.map(controlToFormField),
50
+ }
51
+ })
52
+
53
+ return {
54
+ sections,
55
+ submitLabel: options.submitLabel,
56
+ cancelLabel: options.cancelLabel,
57
+ showCancel: options.showCancel,
58
+ }
59
+ }
60
+
61
+ /** Convert controls into AppSidebar panels grouped by view and section. */
62
+ export function controlsToSidebarPanels(
63
+ controls: ControlSchema,
64
+ options: ControlSchemaOptions = {},
65
+ ): Record<string, SidebarToolSection[]> {
66
+ const normalized = normalizeControls(controls, options)
67
+ .filter(control => isSidebarEnabled(control.definition.sidebar))
68
+ const viewIds = orderedUnique(normalized.map(control => control.viewId))
69
+ const panels: Record<string, SidebarToolSection[]> = {}
70
+
71
+ for (const viewId of viewIds) {
72
+ const viewControls = normalized.filter(control => control.viewId === viewId)
73
+ const sectionIds = orderedUnique(viewControls.map(control => control.sectionId))
74
+ panels[viewId] = sectionIds.map((sectionId): SidebarToolSection => {
75
+ const sectionControls = viewControls.filter(control => control.sectionId === sectionId)
76
+ const config = sectionConfig(sectionId, sectionControls, options)
77
+ const sidebarConfig = firstSidebarConfig(sectionControls)
78
+ return {
79
+ id: sectionId,
80
+ label: sidebarConfig?.label ?? config.label ?? config.title ?? humanize(sectionId),
81
+ subtitle: sidebarConfig?.subtitle ?? config.subtitle,
82
+ icon: sidebarConfig?.icon ?? config.icon,
83
+ iconColor: sidebarConfig?.iconColor ?? config.iconColor,
84
+ iconBg: sidebarConfig?.iconBg ?? config.iconBg,
85
+ defaultOpen: sidebarConfig?.defaultOpen ?? config.defaultOpen,
86
+ showToggle: sidebarConfig?.showToggle ?? config.showToggle,
87
+ }
88
+ })
89
+ }
90
+
91
+ return panels
92
+ }
93
+
94
+ /** Convert controls into a SettingsModal schema grouped by the same sections. */
95
+ export function controlsToSettingsSchema(
96
+ controls: ControlSchema,
97
+ options: ControlSchemaOptions = {},
98
+ ): SettingsModalSchema {
99
+ const normalized = normalizeControls(controls, options)
100
+ const sectionIds = orderedUnique(normalized.map(control => control.sectionId))
101
+
102
+ return {
103
+ groups: sectionIds.map((sectionId) => {
104
+ const sectionControls = normalized.filter(control => control.sectionId === sectionId)
105
+ const config = sectionConfig(sectionId, sectionControls, options)
106
+ return {
107
+ id: sectionId,
108
+ label: config.label ?? config.title ?? humanize(sectionId),
109
+ description: config.description ?? config.subtitle,
110
+ icon: typeof config.icon === 'string' ? config.icon : undefined,
111
+ fields: sectionControls.map(controlToFormField),
112
+ columns: config.columns ?? options.columns ?? 1,
113
+ condition: config.condition,
114
+ access: config.access,
115
+ visibleFor: config.visibleFor,
116
+ requiresAdmin: config.requiresAdmin,
117
+ permissions: config.permissions,
118
+ anyPermissions: config.anyPermissions,
119
+ }
120
+ }),
121
+ }
122
+ }
123
+
124
+ /** Convert controls into an AppTopBar settingsConfig object that passes compact controls through to SettingsModal. */
125
+ export function controlsToTopBarSettingsConfig(
126
+ controls: ControlSchema,
127
+ options: ControlSchemaOptions = {},
128
+ config: Omit<TopBarSettingsConfig, 'schema' | 'controls' | 'controlOptions'> = {},
129
+ ): TopBarSettingsConfig {
130
+ return {
131
+ ...config,
132
+ controls,
133
+ controlOptions: options,
134
+ }
135
+ }
136
+
137
+ /** Return generated control view IDs that have at least one sidebar panel. */
138
+ export function controlsToViewIds(
139
+ controls: ControlSchema,
140
+ options: ControlSchemaOptions = {},
141
+ ): string[] {
142
+ return controlsToViewItems(controls, options).map(item => item.id)
143
+ }
144
+
145
+ /** Return AppTopBar pillNav-compatible view items for switching generated control sidebars. */
146
+ export function controlsToViewItems(
147
+ controls: ControlSchema,
148
+ options: ControlSchemaOptions = {},
149
+ ): PillNavItem[] {
150
+ return Object.entries(controlsToSidebarPanels(controls, options))
151
+ .filter(([, sections]) => sections.length > 0)
152
+ .map(([id]) => controlViewItem(id, options))
153
+ }
154
+
155
+ /** Return the first generated sidebar view ID, or an empty string when controls render no sidebar panels. */
156
+ export function getDefaultControlView(
157
+ controls: ControlSchema,
158
+ options: ControlSchemaOptions = {},
159
+ ): string {
160
+ return controlsToViewIds(controls, options)[0] ?? ''
161
+ }
162
+
163
+ /** Return a headerless single-section FormBuilder schema for rendering inside an AppSidebar section slot. */
164
+ export function controlsToSectionFormSchema(
165
+ controls: ControlSchema,
166
+ sectionId: string,
167
+ options: ControlSchemaOptions = {},
168
+ ): ControlFormSchema {
169
+ const schema = controlsToFormSchema(controls, options)
170
+ return {
171
+ sections: schema.sections
172
+ .filter(section => section.id === sectionId)
173
+ .map(section => ({ ...section, title: '', description: undefined })),
174
+ submitLabel: schema.submitLabel,
175
+ cancelLabel: schema.cancelLabel,
176
+ showCancel: schema.showCancel,
177
+ }
178
+ }
179
+
180
+ /** Return headerless FormBuilder schemas keyed by section ID for AppSidebar auto-rendering. */
181
+ export function controlsToSectionFormSchemas(
182
+ controls: ControlSchema,
183
+ options: ControlSchemaOptions = {},
184
+ ): Record<string, ControlFormSchema> {
185
+ const schema = controlsToFormSchema(controls, options)
186
+ const schemas: Record<string, ControlFormSchema> = {}
187
+ for (const section of schema.sections) {
188
+ schemas[section.id] = {
189
+ sections: [{ ...section, title: '', description: undefined }],
190
+ submitLabel: schema.submitLabel,
191
+ cancelLabel: schema.cancelLabel,
192
+ showCancel: schema.showCancel,
193
+ }
194
+ }
195
+ return schemas
196
+ }
@@ -0,0 +1,61 @@
1
+ import type {
2
+ FieldValidation,
3
+ FormFieldSchema,
4
+ } from '../types/form-builder'
5
+ import {
6
+ defaultValueForControl,
7
+ normalizeOption,
8
+ type NormalizedControl,
9
+ } from './controlSchemaNormalize'
10
+ import {
11
+ humanize,
12
+ numericValue,
13
+ } from './controlSchemaUtils'
14
+ import type {
15
+ ControlDefinition,
16
+ } from './useControlSchema'
17
+
18
+ export function controlToFormField(control: NormalizedControl): FormFieldSchema {
19
+ const { name, definition, type } = control
20
+ const props = fieldProps(definition)
21
+ return {
22
+ name,
23
+ label: definition.label ?? humanize(name),
24
+ type,
25
+ defaultValue: defaultValueForControl(definition, type),
26
+ placeholder: definition.placeholder,
27
+ hint: definition.hint,
28
+ size: definition.size,
29
+ disabled: definition.disabled,
30
+ readonly: definition.readonly,
31
+ validation: validationForControl(definition),
32
+ condition: definition.condition,
33
+ access: definition.access,
34
+ visibleFor: definition.visibleFor,
35
+ requiresAdmin: definition.requiresAdmin,
36
+ permissions: definition.permissions,
37
+ anyPermissions: definition.anyPermissions,
38
+ colSpan: definition.colSpan,
39
+ props,
40
+ }
41
+ }
42
+
43
+ function fieldProps(definition: ControlDefinition): Record<string, unknown> {
44
+ const props: Record<string, unknown> = {}
45
+ if (definition.min !== undefined) props.min = numericValue(definition.min)
46
+ if (definition.max !== undefined) props.max = numericValue(definition.max)
47
+ if (definition.options) props.options = definition.options.map(normalizeOption)
48
+ return { ...props, ...(definition.props ?? {}) }
49
+ }
50
+
51
+ function validationForControl(definition: ControlDefinition): FieldValidation | undefined {
52
+ const validation: FieldValidation = { ...(definition.validation ?? {}) }
53
+ if (definition.required !== undefined) validation.required = definition.required
54
+ if (definition.min !== undefined) validation.min = definition.min
55
+ if (definition.max !== undefined) validation.max = definition.max
56
+ if (definition.minLength !== undefined) validation.minLength = definition.minLength
57
+ if (definition.maxLength !== undefined) validation.maxLength = definition.maxLength
58
+ if (definition.email !== undefined) validation.email = definition.email
59
+ if (definition.pattern !== undefined) validation.pattern = definition.pattern
60
+ return Object.keys(validation).length > 0 ? validation : undefined
61
+ }
@@ -0,0 +1,59 @@
1
+ import type {
2
+ PillNavItem,
3
+ } from '../types'
4
+ import type {
5
+ NormalizedControl,
6
+ } from './controlSchemaNormalize'
7
+ import {
8
+ humanize,
9
+ } from './controlSchemaUtils'
10
+ import type {
11
+ ControlDefinition,
12
+ ControlSchemaOptions,
13
+ ControlSectionConfig,
14
+ ControlSidebarConfig,
15
+ } from './useControlSchema'
16
+
17
+ export function sectionConfig(
18
+ sectionId: string,
19
+ controls: NormalizedControl[],
20
+ options: ControlSchemaOptions,
21
+ ): ControlSectionConfig {
22
+ const configured = options.sections?.[sectionId]
23
+ const first = controls[0]?.definition
24
+ return {
25
+ ...configured,
26
+ id: sectionId,
27
+ label: configured?.label ?? first?.sectionLabel,
28
+ title: configured?.title ?? first?.sectionLabel,
29
+ description: configured?.description ?? first?.sectionDescription,
30
+ subtitle: configured?.subtitle ?? first?.sectionSubtitle,
31
+ }
32
+ }
33
+
34
+ export function firstSidebarConfig(controls: NormalizedControl[]): ControlSidebarConfig | undefined {
35
+ for (const control of controls) {
36
+ const sidebar = control.definition.sidebar
37
+ if (sidebar && typeof sidebar === 'object' && sidebar.enabled !== false) return sidebar
38
+ }
39
+ return undefined
40
+ }
41
+
42
+ export function isSidebarEnabled(sidebar: ControlDefinition['sidebar']): boolean {
43
+ if (sidebar === false) return false
44
+ if (sidebar && typeof sidebar === 'object') return sidebar.enabled !== false
45
+ return true
46
+ }
47
+
48
+ export function controlViewItem(viewId: string, options: ControlSchemaOptions): PillNavItem {
49
+ const config = options.views?.[viewId]
50
+ return {
51
+ id: viewId,
52
+ label: config?.label ?? humanize(viewId),
53
+ ...(config?.icon !== undefined ? { icon: config.icon } : {}),
54
+ ...(config?.to !== undefined ? { to: config.to } : {}),
55
+ ...(config?.href !== undefined ? { href: config.href } : {}),
56
+ ...(config?.disabled !== undefined ? { disabled: config.disabled } : {}),
57
+ ...(config?.children !== undefined ? { children: config.children } : {}),
58
+ }
59
+ }
@@ -0,0 +1,101 @@
1
+ import type {
2
+ SelectOption,
3
+ } from '../types'
4
+ import type {
5
+ FormFieldType,
6
+ } from '../types/form-builder'
7
+ import {
8
+ getTypeDefault,
9
+ } from './formBuilderRegistry'
10
+ import {
11
+ humanize,
12
+ } from './controlSchemaUtils'
13
+ import type {
14
+ ControlDefinition,
15
+ ControlInput,
16
+ ControlOption,
17
+ ControlOptionValue,
18
+ ControlSchema,
19
+ ControlSchemaOptions,
20
+ } from './useControlSchema'
21
+
22
+ export interface NormalizedControl {
23
+ name: string
24
+ definition: ControlDefinition
25
+ type: FormFieldType
26
+ sectionId: string
27
+ viewId: string
28
+ order: number
29
+ }
30
+
31
+ export function normalizeControls(
32
+ controls: ControlSchema,
33
+ options: ControlSchemaOptions = {},
34
+ ): NormalizedControl[] {
35
+ return Object.entries(controls)
36
+ .map(([name, input], index): NormalizedControl => {
37
+ const definition = normalizeControlDefinition(input)
38
+ const type = definition.type ?? inferControlType(definition)
39
+ return {
40
+ name,
41
+ definition,
42
+ type,
43
+ sectionId: definition.section ?? options.section ?? 'controls',
44
+ viewId: definition.view ?? options.view ?? 'default',
45
+ order: definition.order ?? index,
46
+ }
47
+ })
48
+ .sort((a, b) => a.order - b.order)
49
+ }
50
+
51
+ export function normalizeControlDefinition(input: ControlInput): ControlDefinition {
52
+ if (isControlOptionArray(input)) {
53
+ if (input.length === 0) return { type: 'tags', default: [] }
54
+ return {
55
+ default: optionValue(input[0]),
56
+ options: input,
57
+ }
58
+ }
59
+
60
+ if (typeof input === 'string' || typeof input === 'number' || typeof input === 'boolean') {
61
+ return { default: input }
62
+ }
63
+
64
+ return input
65
+ }
66
+
67
+ export function defaultValueForControl(definition: ControlDefinition, type: FormFieldType): unknown {
68
+ if (definition.default !== undefined) return definition.default
69
+ if (definition.defaultValue !== undefined) return definition.defaultValue
70
+ return getTypeDefault(type)
71
+ }
72
+
73
+ export function normalizeOption(option: ControlOption): SelectOption<unknown> {
74
+ if (typeof option === 'object' && option !== null && 'label' in option) {
75
+ return option
76
+ }
77
+ return {
78
+ value: option,
79
+ label: humanize(String(option)),
80
+ }
81
+ }
82
+
83
+ function isControlOptionArray(input: ControlInput): input is readonly ControlOption[] {
84
+ return Array.isArray(input)
85
+ }
86
+
87
+ function inferControlType(definition: ControlDefinition): FormFieldType {
88
+ const value = definition.default ?? definition.defaultValue
89
+ if (definition.options?.length) return Array.isArray(value) ? 'multiselect' : 'select'
90
+ if (typeof value === 'boolean') return 'toggle'
91
+ if (typeof value === 'number') return 'number'
92
+ if (Array.isArray(value)) return 'tags'
93
+ return 'text'
94
+ }
95
+
96
+ function optionValue(option: ControlOption): ControlOptionValue | unknown {
97
+ if (typeof option === 'object' && option !== null && 'value' in option) {
98
+ return option.value
99
+ }
100
+ return option
101
+ }
@@ -0,0 +1,36 @@
1
+ export function recordValue(value: unknown): Record<string, unknown> {
2
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
3
+ ? value as Record<string, unknown>
4
+ : {}
5
+ }
6
+
7
+ export function numericValue(value: number | { value: number; message: string }): number {
8
+ return typeof value === 'number' ? value : value.value
9
+ }
10
+
11
+ export function orderedUnique(values: string[]): string[] {
12
+ return [...new Set(values)]
13
+ }
14
+
15
+ export function replaceRecord(target: Record<string, unknown>, values: Record<string, unknown>) {
16
+ for (const key of Object.keys(target)) {
17
+ delete target[key]
18
+ }
19
+ Object.assign(target, values)
20
+ }
21
+
22
+ export function omitUndefined<T extends object>(record: T): T {
23
+ const next: Record<string, unknown> = {}
24
+ for (const [key, value] of Object.entries(record as Record<string, unknown>)) {
25
+ if (value !== undefined) next[key] = value
26
+ }
27
+ return next as T
28
+ }
29
+
30
+ export function humanize(value: string): string {
31
+ return value
32
+ .replace(/[_-]+/g, ' ')
33
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
34
+ .trim()
35
+ .replace(/\w\S*/g, word => word.charAt(0).toUpperCase() + word.slice(1))
36
+ }
@@ -0,0 +1,21 @@
1
+ import type {
2
+ ControlWorkspaceOptions,
3
+ } from './useControlSchema'
4
+
5
+ export function mergeControlWorkspaceOptions(
6
+ base?: ControlWorkspaceOptions,
7
+ override?: ControlWorkspaceOptions,
8
+ ): ControlWorkspaceOptions {
9
+ return {
10
+ ...(base ?? {}),
11
+ ...(override ?? {}),
12
+ initialValues: {
13
+ ...(base?.initialValues ?? {}),
14
+ ...(override?.initialValues ?? {}),
15
+ },
16
+ topBarSettings: {
17
+ ...(base?.topBarSettings ?? {}),
18
+ ...(override?.topBarSettings ?? {}),
19
+ },
20
+ }
21
+ }