@morscherlab/mint-sdk 1.0.0-rc.1 → 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 (300) hide show
  1. package/README.md +9 -1
  2. package/dist/__tests__/components/LcmsSequenceTable.test.d.ts +1 -0
  3. package/dist/__tests__/components/ProgressBar.test.d.ts +1 -0
  4. package/dist/__tests__/components/RackEditor.test.d.ts +1 -0
  5. package/dist/__tests__/components/SequenceProgressBar.test.d.ts +1 -0
  6. package/dist/__tests__/composables/useExperimentSamples.test.d.ts +1 -0
  7. package/dist/__tests__/utils/instrument.test.d.ts +1 -0
  8. package/dist/__tests__/utils/lcms.test.d.ts +1 -0
  9. package/dist/__tests__/utils/permissions.test.d.ts +1 -0
  10. package/dist/__tests__/utils/rack.test.d.ts +1 -0
  11. package/dist/{auth-CBG3bWEc.js → auth-B7g4J4ZF.js} +99 -5
  12. package/dist/auth-B7g4J4ZF.js.map +1 -0
  13. package/dist/components/AppSidebar.vue.d.ts +3 -3
  14. package/dist/components/AutoGroupModal.vue.d.ts +1 -1
  15. package/dist/components/BaseCheckbox.vue.d.ts +1 -1
  16. package/dist/components/BaseToggle.vue.d.ts +2 -2
  17. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
  18. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -1
  19. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +1 -1
  20. package/dist/components/ControlWorkspaceView.vue.d.ts +3 -3
  21. package/dist/components/DoseDesignWorkspaceView.vue.d.ts +2 -2
  22. package/dist/components/FormulaInput.vue.d.ts +1 -1
  23. package/dist/components/InstrumentAlertLog.vue.d.ts +22 -0
  24. package/dist/components/InstrumentStateBadge.vue.d.ts +11 -0
  25. package/dist/components/InstrumentStatusCard.vue.d.ts +13 -0
  26. package/dist/components/LcmsSequenceTable.vue.d.ts +26 -0
  27. package/dist/components/PluginWorkspaceView.vue.d.ts +3 -3
  28. package/dist/components/ProgressBar.vue.d.ts +1 -0
  29. package/dist/components/RackEditor.vue.d.ts +41 -3
  30. package/dist/components/ReagentList.vue.d.ts +1 -1
  31. package/dist/components/SampleSelector.vue.d.ts +5 -2
  32. package/dist/components/SegmentedControl.vue.d.ts +2 -0
  33. package/dist/components/SequenceInput.vue.d.ts +1 -1
  34. package/dist/components/SequenceProgressBar.vue.d.ts +15 -0
  35. package/dist/components/SettingsModal.vue.d.ts +3 -1
  36. package/dist/components/TagsInput.vue.d.ts +1 -1
  37. package/dist/components/WellPlate.vue.d.ts +42 -3
  38. package/dist/components/index.d.ts +5 -0
  39. package/dist/components/index.js +3 -3
  40. package/dist/{components-5KSfsVqf.js → components-DafPc4rM.js} +2091 -1051
  41. package/dist/components-DafPc4rM.js.map +1 -0
  42. package/dist/composables/controlComponentBindings.d.ts +7 -0
  43. package/dist/composables/controlSchemaAdapters.d.ts +20 -0
  44. package/dist/composables/controlSchemaFormFields.d.ts +3 -0
  45. package/dist/composables/controlSchemaLayout.d.ts +7 -0
  46. package/dist/composables/controlSchemaNormalize.d.ts +15 -0
  47. package/dist/composables/controlSchemaUtils.d.ts +9 -0
  48. package/dist/composables/controlWorkspaceOptions.d.ts +2 -0
  49. package/dist/composables/experimentDesignData.d.ts +17 -0
  50. package/dist/composables/index.d.ts +2 -0
  51. package/dist/composables/index.js +4 -4
  52. package/dist/composables/useControlSchema.d.ts +15 -30
  53. package/dist/composables/useControlWorkspace.d.ts +5 -0
  54. package/dist/composables/useExperimentData.d.ts +11 -3
  55. package/dist/composables/useExperimentSamples.d.ts +42 -0
  56. package/dist/composables/usePlatformContext.d.ts +54 -0
  57. package/dist/{composables-D4Myb30a.js → composables-BMkPQhVK.js} +5 -3
  58. package/dist/composables-BMkPQhVK.js.map +1 -0
  59. package/dist/index.d.ts +4 -0
  60. package/dist/index.js +168 -6
  61. package/dist/index.js.map +1 -0
  62. package/dist/install.js +2 -2
  63. package/dist/instrument.d.ts +7 -0
  64. package/dist/lcms.d.ts +27 -0
  65. package/dist/permissions.d.ts +46 -0
  66. package/dist/stores/auth.d.ts +74 -2
  67. package/dist/stores/index.js +1 -1
  68. package/dist/styles.css +3316 -1216
  69. package/dist/templates/adapters.d.ts +14 -47
  70. package/dist/templates/assayLookups.d.ts +3 -0
  71. package/dist/templates/assayMatrixAdapters.d.ts +6 -0
  72. package/dist/templates/assayMatrixBuilder.d.ts +2 -0
  73. package/dist/templates/assayNormalizers.d.ts +4 -0
  74. package/dist/templates/builderDefaults.d.ts +1 -0
  75. package/dist/templates/builderIdUtils.d.ts +4 -0
  76. package/dist/templates/builderPresetControls.d.ts +10 -0
  77. package/dist/templates/builderReadUtils.d.ts +8 -0
  78. package/dist/templates/builders.d.ts +26 -63
  79. package/dist/templates/calibrationCurveAdapters.d.ts +4 -0
  80. package/dist/templates/calibrationCurveBuilder.d.ts +2 -0
  81. package/dist/templates/calibrationNormalizers.d.ts +5 -0
  82. package/dist/templates/componentBindingCatalog.d.ts +25 -0
  83. package/dist/templates/componentBindingHelpers.d.ts +17 -0
  84. package/dist/templates/componentDoseResponseProps.d.ts +3 -0
  85. package/dist/templates/componentGenericProps.d.ts +8 -0
  86. package/dist/templates/componentPlateHelpers.d.ts +5 -0
  87. package/dist/templates/componentPlateMapProps.d.ts +3 -0
  88. package/dist/templates/componentPropsFactory.d.ts +3 -0
  89. package/dist/templates/componentQpcrPlateProps.d.ts +3 -0
  90. package/dist/templates/componentTargetResolvers.d.ts +5 -0
  91. package/dist/templates/componentTemplateProps.d.ts +3 -0
  92. package/dist/templates/controlSchemaClone.d.ts +4 -0
  93. package/dist/templates/controlSchemaConstants.d.ts +10 -0
  94. package/dist/templates/controlSchemaMerge.d.ts +3 -0
  95. package/dist/templates/controlSchemaTargets.d.ts +17 -0
  96. package/dist/templates/controlSchemaTypes.d.ts +4 -0
  97. package/dist/templates/controlSchemas.d.ts +4 -4
  98. package/dist/templates/defaultBioTemplateBuilder.d.ts +2 -0
  99. package/dist/templates/doseResponseAdapters.d.ts +4 -0
  100. package/dist/templates/doseResponseBuilder.d.ts +2 -0
  101. package/dist/templates/elisaAssayCollectionBuilder.d.ts +2 -0
  102. package/dist/templates/flowCytometryAssayCollectionBuilder.d.ts +2 -0
  103. package/dist/templates/flowCytometryPanelBuilder.d.ts +2 -0
  104. package/dist/templates/flowNormalizers.d.ts +8 -0
  105. package/dist/templates/flowPanelAdapters.d.ts +4 -0
  106. package/dist/templates/index.d.ts +2 -2
  107. package/dist/templates/index.js +2 -2
  108. package/dist/templates/instrumentRunAdapterHelpers.d.ts +8 -0
  109. package/dist/templates/instrumentRunAdapters.d.ts +8 -0
  110. package/dist/templates/instrumentRunBuilder.d.ts +2 -0
  111. package/dist/templates/lcmsBatchCollectionBuilder.d.ts +2 -0
  112. package/dist/templates/plateGeometry.d.ts +4 -0
  113. package/dist/templates/plateMapAdapters.d.ts +3 -0
  114. package/dist/templates/plateMapBuilder.d.ts +2 -0
  115. package/dist/templates/presetControlSchemas.d.ts +534 -0
  116. package/dist/templates/presets.d.ts +12 -0
  117. package/dist/templates/protocolAdapters.d.ts +5 -0
  118. package/dist/templates/protocolNormalizers.d.ts +6 -0
  119. package/dist/templates/protocolStepsBuilder.d.ts +2 -0
  120. package/dist/templates/qpcrAdapters.d.ts +5 -0
  121. package/dist/templates/qpcrExpressionCollectionBuilder.d.ts +2 -0
  122. package/dist/templates/qpcrPlateBuilder.d.ts +2 -0
  123. package/dist/templates/reagentAdapters.d.ts +5 -0
  124. package/dist/templates/reagentListBuilder.d.ts +2 -0
  125. package/dist/templates/runNormalizers.d.ts +10 -0
  126. package/dist/templates/sampleNormalizers.d.ts +4 -0
  127. package/dist/templates/samplePrepAdapters.d.ts +4 -0
  128. package/dist/templates/samplePrepBuilder.d.ts +2 -0
  129. package/dist/templates/sampleSheetAdapters.d.ts +5 -0
  130. package/dist/templates/sampleSheetBuilder.d.ts +2 -0
  131. package/dist/templates/targetedMetabolomicsCollectionBuilder.d.ts +2 -0
  132. package/dist/templates/targetedMetabolomicsHelpers.d.ts +5 -0
  133. package/dist/templates/templateControlSchemas.d.ts +400 -0
  134. package/dist/templates/templateEnvelopes.d.ts +9 -0
  135. package/dist/templates/templatePackCollectionBuilder.d.ts +2 -0
  136. package/dist/templates/templatePresetCollectionBuilder.d.ts +18 -0
  137. package/dist/templates/templateValidators.d.ts +13 -0
  138. package/dist/templates/timeCourseAdapters.d.ts +5 -0
  139. package/dist/templates/timeCourseBuilder.d.ts +2 -0
  140. package/dist/templates/types.d.ts +16 -1
  141. package/dist/templates/wellPlateScreenCollectionBuilder.d.ts +2 -0
  142. package/dist/templates/westernBlotAssayCollectionBuilder.d.ts +2 -0
  143. package/dist/{templates-BSlxwV2c.js → templates-bUAWMn5L.js} +3610 -3154
  144. package/dist/templates-bUAWMn5L.js.map +1 -0
  145. package/dist/types/auth.d.ts +2 -0
  146. package/dist/types/components.d.ts +32 -3
  147. package/dist/types/form-builder.d.ts +2 -1
  148. package/dist/types/index.d.ts +4 -1
  149. package/dist/types/instrument.d.ts +56 -0
  150. package/dist/types/platform.d.ts +3 -0
  151. package/dist/{useExperimentData-BbbdI5xT.js → useProtocolTemplates-QZtHFFH2.js} +534 -359
  152. package/dist/useProtocolTemplates-QZtHFFH2.js.map +1 -0
  153. package/dist/utils/rack.d.ts +47 -0
  154. package/package.json +1 -1
  155. package/src/__tests__/components/AppTopBar.test.ts +15 -0
  156. package/src/__tests__/components/BaseTabs.test.ts +15 -0
  157. package/src/__tests__/components/LcmsSequenceTable.test.ts +57 -0
  158. package/src/__tests__/components/ProgressBar.test.ts +18 -0
  159. package/src/__tests__/components/RackEditor.test.ts +125 -0
  160. package/src/__tests__/components/SampleSelector.test.ts +25 -0
  161. package/src/__tests__/components/SegmentedControl.test.ts +45 -0
  162. package/src/__tests__/components/SequenceProgressBar.test.ts +39 -0
  163. package/src/__tests__/components/SettingsModal.test.ts +83 -2
  164. package/src/__tests__/composables/useControlSchema.test.ts +4 -0
  165. package/src/__tests__/composables/useExperimentData.test.ts +23 -0
  166. package/src/__tests__/composables/useExperimentSamples.test.ts +91 -0
  167. package/src/__tests__/templates/templates.test.ts +86 -0
  168. package/src/__tests__/utils/instrument.test.ts +47 -0
  169. package/src/__tests__/utils/lcms.test.ts +73 -0
  170. package/src/__tests__/utils/permissions.test.ts +50 -0
  171. package/src/__tests__/utils/rack.test.ts +120 -0
  172. package/src/components/AppTopBar.vue +1 -0
  173. package/src/components/BaseTabs.vue +22 -1
  174. package/src/components/InstrumentAlertLog.vue +191 -0
  175. package/src/components/InstrumentStateBadge.vue +50 -0
  176. package/src/components/InstrumentStatusCard.vue +188 -0
  177. package/src/components/LcmsSequenceTable.vue +191 -0
  178. package/src/components/ProgressBar.vue +3 -0
  179. package/src/components/RackEditor.vue +73 -2
  180. package/src/components/SampleSelector.vue +28 -9
  181. package/src/components/SegmentedControl.story.vue +17 -0
  182. package/src/components/SegmentedControl.vue +14 -3
  183. package/src/components/SequenceProgressBar.vue +71 -0
  184. package/src/components/SettingsModal.vue +42 -2
  185. package/src/components/WellPlate.vue +142 -21
  186. package/src/components/index.ts +5 -0
  187. package/src/components/internal/WellEditPopupInternal.vue +1 -0
  188. package/src/composables/controlComponentBindings.ts +80 -0
  189. package/src/composables/controlSchemaAdapters.ts +196 -0
  190. package/src/composables/controlSchemaFormFields.ts +61 -0
  191. package/src/composables/controlSchemaLayout.ts +59 -0
  192. package/src/composables/controlSchemaNormalize.ts +101 -0
  193. package/src/composables/controlSchemaUtils.ts +36 -0
  194. package/src/composables/controlWorkspaceOptions.ts +21 -0
  195. package/src/composables/experimentDesignData.ts +182 -0
  196. package/src/composables/index.ts +14 -0
  197. package/src/composables/useAuth.ts +4 -0
  198. package/src/composables/useAutoGroup.ts +5 -1
  199. package/src/composables/useControlSchema.ts +62 -618
  200. package/src/composables/useControlWorkspace.ts +207 -0
  201. package/src/composables/useExperimentData.ts +57 -16
  202. package/src/composables/useExperimentSamples.ts +142 -0
  203. package/src/index.ts +27 -0
  204. package/src/instrument.ts +90 -0
  205. package/src/lcms.ts +108 -0
  206. package/src/permissions.ts +143 -0
  207. package/src/stores/auth.ts +31 -3
  208. package/src/styles/components/instrument-monitor.css +478 -0
  209. package/src/styles/components/lcms-sequence-table.css +189 -0
  210. package/src/styles/components/sequence-progress-bar.css +63 -0
  211. package/src/styles/components/tabs.css +9 -0
  212. package/src/styles/components/well-edit-popup.css +7 -1
  213. package/src/styles/components/well-plate.css +5 -0
  214. package/src/styles/index.css +3 -0
  215. package/src/templates/adapters.ts +89 -930
  216. package/src/templates/assayLookups.ts +33 -0
  217. package/src/templates/assayMatrixAdapters.ts +78 -0
  218. package/src/templates/assayMatrixBuilder.ts +59 -0
  219. package/src/templates/assayNormalizers.ts +34 -0
  220. package/src/templates/builderDefaults.ts +11 -0
  221. package/src/templates/builderIdUtils.ts +20 -0
  222. package/src/templates/builderPresetControls.ts +165 -0
  223. package/src/templates/builderReadUtils.ts +57 -0
  224. package/src/templates/builders.ts +122 -2149
  225. package/src/templates/calibrationCurveAdapters.ts +59 -0
  226. package/src/templates/calibrationCurveBuilder.ts +99 -0
  227. package/src/templates/calibrationNormalizers.ts +60 -0
  228. package/src/templates/componentBindingCatalog.ts +90 -0
  229. package/src/templates/componentBindingHelpers.ts +93 -0
  230. package/src/templates/componentBindings.ts +12 -461
  231. package/src/templates/componentDoseResponseProps.ts +42 -0
  232. package/src/templates/componentGenericProps.ts +77 -0
  233. package/src/templates/componentPlateHelpers.ts +29 -0
  234. package/src/templates/componentPlateMapProps.ts +32 -0
  235. package/src/templates/componentPropsFactory.ts +21 -0
  236. package/src/templates/componentQpcrPlateProps.ts +28 -0
  237. package/src/templates/componentTargetResolvers.ts +69 -0
  238. package/src/templates/componentTemplateProps.ts +78 -0
  239. package/src/templates/controlSchemaClone.ts +32 -0
  240. package/src/templates/controlSchemaConstants.ts +11 -0
  241. package/src/templates/controlSchemaMerge.ts +40 -0
  242. package/src/templates/controlSchemaTargets.ts +87 -0
  243. package/src/templates/controlSchemaTypes.ts +20 -0
  244. package/src/templates/controlSchemas.ts +22 -636
  245. package/src/templates/defaultBioTemplateBuilder.ts +124 -0
  246. package/src/templates/doseResponseAdapters.ts +45 -0
  247. package/src/templates/doseResponseBuilder.ts +44 -0
  248. package/src/templates/elisaAssayCollectionBuilder.ts +62 -0
  249. package/src/templates/flowCytometryAssayCollectionBuilder.ts +41 -0
  250. package/src/templates/flowCytometryPanelBuilder.ts +53 -0
  251. package/src/templates/flowNormalizers.ts +58 -0
  252. package/src/templates/flowPanelAdapters.ts +58 -0
  253. package/src/templates/index.ts +2 -0
  254. package/src/templates/instrumentRunAdapterHelpers.ts +94 -0
  255. package/src/templates/instrumentRunAdapters.ts +163 -0
  256. package/src/templates/instrumentRunBuilder.ts +97 -0
  257. package/src/templates/lcmsBatchCollectionBuilder.ts +38 -0
  258. package/src/templates/plateGeometry.ts +62 -0
  259. package/src/templates/plateMapAdapters.ts +36 -0
  260. package/src/templates/plateMapBuilder.ts +43 -0
  261. package/src/templates/presetControlSchemas.ts +258 -0
  262. package/src/templates/presets.ts +23 -0
  263. package/src/templates/protocolAdapters.ts +69 -0
  264. package/src/templates/protocolNormalizers.ts +37 -0
  265. package/src/templates/protocolStepsBuilder.ts +36 -0
  266. package/src/templates/qpcrAdapters.ts +104 -0
  267. package/src/templates/qpcrExpressionCollectionBuilder.ts +33 -0
  268. package/src/templates/qpcrPlateBuilder.ts +96 -0
  269. package/src/templates/reagentAdapters.ts +77 -0
  270. package/src/templates/reagentListBuilder.ts +30 -0
  271. package/src/templates/runNormalizers.ts +63 -0
  272. package/src/templates/sampleNormalizers.ts +58 -0
  273. package/src/templates/samplePrepAdapters.ts +63 -0
  274. package/src/templates/samplePrepBuilder.ts +51 -0
  275. package/src/templates/sampleSheetAdapters.ts +75 -0
  276. package/src/templates/sampleSheetBuilder.ts +23 -0
  277. package/src/templates/targetedMetabolomicsCollectionBuilder.ts +79 -0
  278. package/src/templates/targetedMetabolomicsHelpers.ts +102 -0
  279. package/src/templates/templateControlSchemas.ts +320 -0
  280. package/src/templates/templateEnvelopes.ts +137 -0
  281. package/src/templates/templatePackCollectionBuilder.ts +23 -0
  282. package/src/templates/templatePresetCollectionBuilder.ts +139 -0
  283. package/src/templates/templateValidators.ts +414 -0
  284. package/src/templates/timeCourseAdapters.ts +73 -0
  285. package/src/templates/timeCourseBuilder.ts +64 -0
  286. package/src/templates/types.ts +17 -0
  287. package/src/templates/wellPlateScreenCollectionBuilder.ts +36 -0
  288. package/src/templates/westernBlotAssayCollectionBuilder.ts +68 -0
  289. package/src/types/auth.ts +3 -0
  290. package/src/types/components.ts +45 -3
  291. package/src/types/form-builder.ts +2 -1
  292. package/src/types/index.ts +35 -0
  293. package/src/types/instrument.ts +61 -0
  294. package/src/types/platform.ts +4 -0
  295. package/src/utils/rack.ts +209 -0
  296. package/dist/auth-CBG3bWEc.js.map +0 -1
  297. package/dist/components-5KSfsVqf.js.map +0 -1
  298. package/dist/composables-D4Myb30a.js.map +0 -1
  299. package/dist/templates-BSlxwV2c.js.map +0 -1
  300. package/dist/useExperimentData-BbbdI5xT.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  /** Interactive 96/384-well plate grid with drag-to-select, heatmap overlays, per-well editing, and sample-color mapping. */
3
3
  import { ref, computed } from 'vue'
4
- import type { WellPlateFormat, WellPlateSelectionMode, WellPlateSize, Well, HeatmapConfig, WellShape, WellEditField, WellEditData, WellLegendItem, ColumnCondition, RowCondition } from '../types'
4
+ import type { WellPlateFormat, WellPlateSelectionMode, WellPlateSize, Well, HeatmapConfig, WellShape, WellEditField, WellEditData, WellLegendItem, ColumnCondition, RowCondition, WellSampleDropData, WellSampleDropParser } from '../types'
5
5
  import { useEventListener } from '../composables/useEventListener'
6
6
  import WellEditPopupInternal from './internal/WellEditPopupInternal.vue'
7
7
 
@@ -29,6 +29,8 @@ interface Props {
29
29
  legendItems?: WellLegendItem[]
30
30
  columnConditions?: ColumnCondition[]
31
31
  rowConditions?: RowCondition[]
32
+ allowSampleDrop?: boolean
33
+ sampleDropParser?: WellSampleDropParser
32
34
  }
33
35
 
34
36
  // Drag state for moving wells
@@ -59,6 +61,8 @@ const props = withDefaults(defineProps<Props>(), {
59
61
  legendItems: undefined,
60
62
  columnConditions: () => [],
61
63
  rowConditions: () => [],
64
+ allowSampleDrop: false,
65
+ sampleDropParser: undefined,
62
66
  })
63
67
 
64
68
  const emit = defineEmits<{
@@ -70,6 +74,22 @@ const emit = defineEmits<{
70
74
  'well-move': [sourceWellId: string, targetWellId: string]
71
75
  'well-edit': [wellId: string, data: WellEditData]
72
76
  'well-clear': [wellId: string]
77
+ 'sample-drop': [wellId: string, data: WellSampleDropData, event: DragEvent]
78
+ }>()
79
+
80
+ interface WellEditorSlotProps {
81
+ wellId: string
82
+ wellData?: Partial<Well>
83
+ editFields: WellEditField[]
84
+ defaultInjectionVolume: number
85
+ position: { x: number; y: number }
86
+ save: (data: WellEditData) => void
87
+ clear: () => void
88
+ close: () => void
89
+ }
90
+
91
+ defineSlots<{
92
+ 'well-editor'?: (props: WellEditorSlotProps) => unknown
73
93
  }>()
74
94
 
75
95
  const plateRef = ref<HTMLElement | null>(null)
@@ -164,6 +184,7 @@ const defaultLegendItems: WellLegendItem[] = [
164
184
  { type: 'sample', label: 'Sample', color: '#10b981' },
165
185
  { type: 'blank', label: 'Blank', color: '#f97316' },
166
186
  { type: 'qc', label: 'QC', color: '#8b5cf6' },
187
+ { type: 'iqc', label: 'IQC', color: '#ec4899' },
167
188
  ]
168
189
 
169
190
  const activeLegendItems = computed(() => props.legendItems ?? defaultLegendItems)
@@ -285,6 +306,7 @@ const defaultSampleTypeColors: Record<string, { bg: string; border: string }> =
285
306
  control: { bg: 'rgba(59, 130, 246, 0.15)', border: 'rgba(59, 130, 246, 0.4)' },
286
307
  blank: { bg: 'rgba(249, 115, 22, 0.15)', border: 'rgba(249, 115, 22, 0.4)' },
287
308
  qc: { bg: 'rgba(139, 92, 246, 0.15)', border: 'rgba(139, 92, 246, 0.4)' },
309
+ iqc: { bg: 'rgba(236, 72, 153, 0.15)', border: 'rgba(236, 72, 153, 0.4)' },
288
310
  }
289
311
 
290
312
  const heatmapColors: Record<string, string[]> = {
@@ -373,6 +395,7 @@ function getSampleTypeIndicator(well: Well): string | null {
373
395
  control: 'C',
374
396
  blank: 'B',
375
397
  qc: 'Q',
398
+ iqc: 'I',
376
399
  }
377
400
  return typeMap[well.sampleType] || well.sampleType.charAt(0).toUpperCase()
378
401
  }
@@ -511,10 +534,12 @@ function handleDragStart(well: Well, event: DragEvent) {
511
534
  }
512
535
 
513
536
  function handleDragOver(well: Well, event: DragEvent) {
514
- if (props.selectionMode !== 'drag' || !dragSourceWell.value) return
537
+ const isMovingWell = props.selectionMode === 'drag' && !!dragSourceWell.value
538
+ if (!isMovingWell && !props.allowSampleDrop) return
539
+
515
540
  event.preventDefault()
516
541
  if (event.dataTransfer) {
517
- event.dataTransfer.dropEffect = 'move'
542
+ event.dataTransfer.dropEffect = isMovingWell ? 'move' : 'copy'
518
543
  }
519
544
  dragTargetWell.value = well.id
520
545
  }
@@ -524,14 +549,27 @@ function handleDragLeave() {
524
549
  }
525
550
 
526
551
  function handleDrop(well: Well, event: DragEvent) {
552
+ const isMovingWell = props.selectionMode === 'drag' && !!dragSourceWell.value
553
+ if (!isMovingWell && !props.allowSampleDrop) return
554
+
527
555
  event.preventDefault()
528
- if (props.selectionMode !== 'drag' || !dragSourceWell.value) return
529
556
 
530
- const sourceId = dragSourceWell.value
531
- const targetId = well.id
557
+ if (isMovingWell && dragSourceWell.value) {
558
+ const sourceId = dragSourceWell.value
559
+ const targetId = well.id
560
+
561
+ if (sourceId !== targetId) {
562
+ emit('well-move', sourceId, targetId)
563
+ }
532
564
 
533
- if (sourceId !== targetId) {
534
- emit('well-move', sourceId, targetId)
565
+ dragSourceWell.value = null
566
+ dragTargetWell.value = null
567
+ return
568
+ }
569
+
570
+ const droppedSample = parseDroppedSample(event, well.id)
571
+ if (droppedSample) {
572
+ emit('sample-drop', well.id, droppedSample, event)
535
573
  }
536
574
 
537
575
  dragSourceWell.value = null
@@ -543,6 +581,76 @@ function handleDragEnd() {
543
581
  dragTargetWell.value = null
544
582
  }
545
583
 
584
+ function parseDroppedSample(event: DragEvent, wellId: string): WellSampleDropData | null {
585
+ if (props.sampleDropParser) {
586
+ return props.sampleDropParser(event, { wellId, event }) ?? null
587
+ }
588
+
589
+ const transfer = event.dataTransfer
590
+ if (!transfer) return null
591
+
592
+ const json = transfer.getData('application/json') || transfer.getData('text/json')
593
+ if (json) {
594
+ try {
595
+ return normalizeDroppedSample(JSON.parse(json))
596
+ } catch {
597
+ return null
598
+ }
599
+ }
600
+
601
+ const text = transfer.getData('text/plain')?.trim()
602
+ if (!text) return null
603
+ return {
604
+ sampleName: text,
605
+ label: text,
606
+ raw: text,
607
+ }
608
+ }
609
+
610
+ function normalizeDroppedSample(raw: unknown): WellSampleDropData | null {
611
+ if (!raw || typeof raw !== 'object') return null
612
+
613
+ const record = raw as Record<string, unknown>
614
+ const sampleName =
615
+ getString(record.sampleName) ??
616
+ getString(record.sample_name) ??
617
+ getString(record.name) ??
618
+ getString(record.label)
619
+ const label = getString(record.label) ?? sampleName
620
+ const sampleType =
621
+ getString(record.sampleType) ??
622
+ getString(record.sample_type) ??
623
+ (record.type === 'sample' ? 'sample' : undefined)
624
+
625
+ if (!sampleName && !label && !getString(record.id)) return null
626
+
627
+ return {
628
+ id: getString(record.id),
629
+ sampleName,
630
+ label,
631
+ sampleType,
632
+ injectionVolume: getNumber(record.injectionVolume ?? record.injection_volume),
633
+ injectionCount: getNumber(record.injectionCount ?? record.injection_count),
634
+ customMethod: getString(record.customMethod ?? record.custom_method) ?? null,
635
+ metadata: getRecord(record.metadata),
636
+ raw,
637
+ }
638
+ }
639
+
640
+ function getString(value: unknown): string | undefined {
641
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined
642
+ }
643
+
644
+ function getNumber(value: unknown): number | undefined {
645
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined
646
+ }
647
+
648
+ function getRecord(value: unknown): Record<string, unknown> | undefined {
649
+ return value && typeof value === 'object' && !Array.isArray(value)
650
+ ? value as Record<string, unknown>
651
+ : undefined
652
+ }
653
+
546
654
  function handleKeyDown(event: KeyboardEvent) {
547
655
  if (props.disabled || props.readonly) return
548
656
 
@@ -726,9 +834,9 @@ const tableStyle = computed(() => ({
726
834
  @mouseleave="handleWellMouseLeave"
727
835
  @contextmenu="handleContextMenu(well, $event)"
728
836
  @dragstart="handleDragStart(well, $event)"
729
- @dragover.prevent="handleDragOver(well, $event)"
837
+ @dragover="handleDragOver(well, $event)"
730
838
  @dragleave="handleDragLeave"
731
- @drop.prevent="handleDrop(well, $event)"
839
+ @drop="handleDrop(well, $event)"
732
840
  @dragend="handleDragEnd"
733
841
  @keydown.enter="handleWellClick(well, $event as unknown as MouseEvent)"
734
842
  @keydown.space.prevent="handleWellClick(well, $event as unknown as MouseEvent)"
@@ -805,17 +913,30 @@ const tableStyle = computed(() => ({
805
913
  </div>
806
914
 
807
915
  <!-- Edit popup -->
808
- <WellEditPopupInternal
809
- v-if="editable && editingWellId"
810
- :well-id="editingWellId"
811
- :well-data="wells[editingWellId]"
812
- :edit-fields="editFields"
813
- :default-injection-volume="defaultInjectionVolume"
814
- :position="editPopupPosition"
815
- @save="handleEditSave"
816
- @clear="handleEditClear"
817
- @close="handleEditClose"
818
- />
916
+ <template v-if="editable && editingWellId">
917
+ <slot
918
+ name="well-editor"
919
+ :well-id="editingWellId"
920
+ :well-data="wells[editingWellId]"
921
+ :edit-fields="editFields"
922
+ :default-injection-volume="defaultInjectionVolume"
923
+ :position="editPopupPosition"
924
+ :save="handleEditSave"
925
+ :clear="handleEditClear"
926
+ :close="handleEditClose"
927
+ >
928
+ <WellEditPopupInternal
929
+ :well-id="editingWellId"
930
+ :well-data="wells[editingWellId]"
931
+ :edit-fields="editFields"
932
+ :default-injection-volume="defaultInjectionVolume"
933
+ :position="editPopupPosition"
934
+ @save="handleEditSave"
935
+ @clear="handleEditClear"
936
+ @close="handleEditClose"
937
+ />
938
+ </slot>
939
+ </template>
819
940
  </div>
820
941
  </template>
821
942
 
@@ -89,6 +89,11 @@ export { default as ProtocolStepEditor } from './ProtocolStepEditor.vue'
89
89
  // Scientific display components
90
90
  export { default as ScientificNumber } from './ScientificNumber.vue'
91
91
  export { default as ChemicalFormula } from './ChemicalFormula.vue'
92
+ export { default as SequenceProgressBar } from './SequenceProgressBar.vue'
93
+ export { default as InstrumentAlertLog } from './InstrumentAlertLog.vue'
94
+ export { default as InstrumentStateBadge } from './InstrumentStateBadge.vue'
95
+ export { default as InstrumentStatusCard } from './InstrumentStatusCard.vue'
96
+ export { default as LcmsSequenceTable } from './LcmsSequenceTable.vue'
92
97
 
93
98
  // Scientific input components
94
99
  export { default as FormulaInput } from './FormulaInput.vue'
@@ -90,6 +90,7 @@ const sampleTypeButtons = [
90
90
  { type: 'sample', label: 'S', tooltip: 'Sample' },
91
91
  { type: 'blank', label: 'B', tooltip: 'Blank' },
92
92
  { type: 'qc', label: 'Q', tooltip: 'QC' },
93
+ { type: 'iqc', label: 'I', tooltip: 'IQC' },
93
94
  ]
94
95
  </script>
95
96
 
@@ -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
+ }