@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
@@ -1,4 +1,7 @@
1
- import { computed, reactive, ref, watch, type ComputedRef, type Ref } from 'vue'
1
+ import type {
2
+ ComputedRef,
3
+ Ref,
4
+ } from 'vue'
2
5
  import type {
3
6
  PillNavItem,
4
7
  SidebarToolSection,
@@ -15,9 +18,55 @@ import type {
15
18
  FormSchema,
16
19
  FormSectionSchema,
17
20
  } from '../types/form-builder'
18
- import { getTypeDefault } from './formBuilderRegistry'
21
+ import {
22
+ defaultValueForControl,
23
+ normalizeControls,
24
+ normalizeControlDefinition,
25
+ } from './controlSchemaNormalize'
26
+ import {
27
+ controlToFormField,
28
+ } from './controlSchemaFormFields'
29
+ import {
30
+ controlsToFormSchema,
31
+ controlsToSectionFormSchema,
32
+ controlsToSectionFormSchemas,
33
+ controlsToSettingsSchema,
34
+ controlsToSidebarPanels,
35
+ controlsToTopBarSettingsConfig,
36
+ controlsToViewItems,
37
+ } from './controlSchemaAdapters'
38
+ import {
39
+ omitUndefined,
40
+ recordValue,
41
+ } from './controlSchemaUtils'
19
42
  import type { WellConcentration } from './useDoseCalculator'
20
43
 
44
+ export {
45
+ mergeControlWorkspaceOptions,
46
+ } from './controlWorkspaceOptions'
47
+
48
+ export {
49
+ useControlWorkspace,
50
+ } from './useControlWorkspace'
51
+
52
+ export {
53
+ controlValuesToComponentBindings,
54
+ controlValuesToComponentBindingsById,
55
+ controlValuesToComponentProps,
56
+ } from './controlComponentBindings'
57
+
58
+ export {
59
+ controlsToFormSchema,
60
+ controlsToSectionFormSchema,
61
+ controlsToSectionFormSchemas,
62
+ controlsToSettingsSchema,
63
+ controlsToSidebarPanels,
64
+ controlsToTopBarSettingsConfig,
65
+ controlsToViewIds,
66
+ controlsToViewItems,
67
+ getDefaultControlView,
68
+ } from './controlSchemaAdapters'
69
+
21
70
  export type ControlOptionValue = string | number | boolean
22
71
  export type ControlOption = ControlOptionValue | SelectOption<unknown>
23
72
  export type ControlPrimitive = string | number | boolean
@@ -361,15 +410,6 @@ type ControlValue<TControl> =
361
410
 
362
411
  type OptionValue<TOption> = TOption extends SelectOption<infer TValue> ? TValue : TOption
363
412
 
364
- interface NormalizedControl {
365
- name: string
366
- definition: ControlDefinition
367
- type: FormFieldType
368
- sectionId: string
369
- viewId: string
370
- order: number
371
- }
372
-
373
413
  /** Preserve literal control keys while marking an object as a MINT control schema. */
374
414
  export function defineControls<TControls extends ControlSchema>(controls: TControls): TControls {
375
415
  return controls
@@ -481,52 +521,6 @@ export function getControlDefaults<TControls extends ControlSchema>(
481
521
  return values as ControlValues<TControls>
482
522
  }
483
523
 
484
- /** Map control workspace values into component props for direct `v-bind` usage. */
485
- export function controlValuesToComponentProps<TValues extends Record<string, unknown>>(
486
- values: TValues,
487
- mapping?: ControlComponentPropsMap<TValues>,
488
- ): Record<string, unknown> {
489
- if (mapping === undefined) return { ...values }
490
-
491
- if (Array.isArray(mapping)) {
492
- const props: Record<string, unknown> = {}
493
- for (const key of mapping) {
494
- props[key] = values[key]
495
- }
496
- return props
497
- }
498
-
499
- const props: Record<string, unknown> = {}
500
- for (const [prop, source] of Object.entries(mapping) as Array<[string, ControlComponentPropSource<TValues>]>) {
501
- props[prop] = typeof source === 'function' ? source(values) : values[source]
502
- }
503
- return props
504
- }
505
-
506
- /** Map control workspace values into named SDK component bindings for direct slot rendering. */
507
- export function controlValuesToComponentBindings<TValues extends Record<string, unknown>>(
508
- values: TValues,
509
- bindings?: ControlComponentBindingsConfig<TValues>,
510
- ): ControlComponentBinding[] {
511
- if (bindings === undefined) return []
512
-
513
- return normalizeControlComponentBindingConfigs(bindings).map(binding => ({
514
- id: binding.id,
515
- component: binding.component,
516
- props: controlValuesToComponentProps(values, binding.props),
517
- }))
518
- }
519
-
520
- /** Map control workspace values into SDK component bindings keyed by binding id. */
521
- export function controlValuesToComponentBindingsById<TValues extends Record<string, unknown>>(
522
- values: TValues,
523
- bindings?: ControlComponentBindingsConfig<TValues>,
524
- ): ControlComponentBindingsById {
525
- return Object.fromEntries(
526
- controlValuesToComponentBindings(values, bindings).map(binding => [binding.id, binding]),
527
- )
528
- }
529
-
530
524
  /** Return a default WellPlate prop mapping for generated control workspaces. */
531
525
  export function defineWellPlateControlProps<TValues extends Record<string, unknown> = Record<string, unknown>>(
532
526
  options: WellPlateControlPropsOptions<TValues> = {},
@@ -660,201 +654,6 @@ export function defineDoseDesignControlModel(
660
654
  })
661
655
  }
662
656
 
663
- function isControlModelBindingInput<TControls extends ControlSchema>(
664
- value: TControls | (ControlModelBinding & { controls: TControls }),
665
- ): value is ControlModelBinding & { controls: TControls } {
666
- return (
667
- typeof value === 'object'
668
- && value !== null
669
- && 'controls' in value
670
- && 'controlOptions' in value
671
- )
672
- }
673
-
674
- export function mergeControlWorkspaceOptions(
675
- base?: ControlWorkspaceOptions,
676
- override?: ControlWorkspaceOptions,
677
- ): ControlWorkspaceOptions {
678
- return {
679
- ...(base ?? {}),
680
- ...(override ?? {}),
681
- initialValues: {
682
- ...(base?.initialValues ?? {}),
683
- ...(override?.initialValues ?? {}),
684
- },
685
- topBarSettings: {
686
- ...(base?.topBarSettings ?? {}),
687
- ...(override?.topBarSettings ?? {}),
688
- },
689
- }
690
- }
691
-
692
- /** Convert a compact control schema into a FormBuilder schema. */
693
- export function controlsToFormSchema(
694
- controls: ControlSchema,
695
- options: ControlSchemaOptions = {},
696
- ): ControlFormSchema {
697
- const normalized = normalizeControls(controls, options)
698
- const sectionIds = orderedUnique(normalized.map(control => control.sectionId))
699
- const sections = sectionIds.map((sectionId): FormSectionSchema => {
700
- const sectionControls = normalized.filter(control => control.sectionId === sectionId)
701
- const config = sectionConfig(sectionId, sectionControls, options)
702
- return {
703
- id: sectionId,
704
- title: config.title ?? config.label ?? humanize(sectionId),
705
- description: config.description,
706
- columns: config.columns ?? options.columns ?? 1,
707
- defaultOpen: config.defaultOpen,
708
- condition: config.condition,
709
- fields: sectionControls.map(controlToFormField),
710
- }
711
- })
712
-
713
- return {
714
- sections,
715
- submitLabel: options.submitLabel,
716
- cancelLabel: options.cancelLabel,
717
- showCancel: options.showCancel,
718
- }
719
- }
720
-
721
- /** Convert controls into AppSidebar panels grouped by view and section. */
722
- export function controlsToSidebarPanels(
723
- controls: ControlSchema,
724
- options: ControlSchemaOptions = {},
725
- ): Record<string, SidebarToolSection[]> {
726
- const normalized = normalizeControls(controls, options)
727
- .filter(control => isSidebarEnabled(control.definition.sidebar))
728
- const viewIds = orderedUnique(normalized.map(control => control.viewId))
729
- const panels: Record<string, SidebarToolSection[]> = {}
730
-
731
- for (const viewId of viewIds) {
732
- const viewControls = normalized.filter(control => control.viewId === viewId)
733
- const sectionIds = orderedUnique(viewControls.map(control => control.sectionId))
734
- panels[viewId] = sectionIds.map((sectionId): SidebarToolSection => {
735
- const sectionControls = viewControls.filter(control => control.sectionId === sectionId)
736
- const config = sectionConfig(sectionId, sectionControls, options)
737
- const sidebarConfig = firstSidebarConfig(sectionControls)
738
- return {
739
- id: sectionId,
740
- label: sidebarConfig?.label ?? config.label ?? config.title ?? humanize(sectionId),
741
- subtitle: sidebarConfig?.subtitle ?? config.subtitle,
742
- icon: sidebarConfig?.icon ?? config.icon,
743
- iconColor: sidebarConfig?.iconColor ?? config.iconColor,
744
- iconBg: sidebarConfig?.iconBg ?? config.iconBg,
745
- defaultOpen: sidebarConfig?.defaultOpen ?? config.defaultOpen,
746
- showToggle: sidebarConfig?.showToggle ?? config.showToggle,
747
- }
748
- })
749
- }
750
-
751
- return panels
752
- }
753
-
754
- /** Convert controls into a SettingsModal schema grouped by the same sections. */
755
- export function controlsToSettingsSchema(
756
- controls: ControlSchema,
757
- options: ControlSchemaOptions = {},
758
- ): SettingsModalSchema {
759
- const normalized = normalizeControls(controls, options)
760
- const sectionIds = orderedUnique(normalized.map(control => control.sectionId))
761
-
762
- return {
763
- groups: sectionIds.map((sectionId) => {
764
- const sectionControls = normalized.filter(control => control.sectionId === sectionId)
765
- const config = sectionConfig(sectionId, sectionControls, options)
766
- return {
767
- id: sectionId,
768
- label: config.label ?? config.title ?? humanize(sectionId),
769
- description: config.description ?? config.subtitle,
770
- icon: typeof config.icon === 'string' ? config.icon : undefined,
771
- fields: sectionControls.map(controlToFormField),
772
- columns: config.columns ?? options.columns ?? 1,
773
- condition: config.condition,
774
- access: config.access,
775
- visibleFor: config.visibleFor,
776
- requiresAdmin: config.requiresAdmin,
777
- permissions: config.permissions,
778
- anyPermissions: config.anyPermissions,
779
- }
780
- }),
781
- }
782
- }
783
-
784
- /** Convert controls into an AppTopBar settingsConfig object that passes compact controls through to SettingsModal. */
785
- export function controlsToTopBarSettingsConfig(
786
- controls: ControlSchema,
787
- options: ControlSchemaOptions = {},
788
- config: Omit<TopBarSettingsConfig, 'schema' | 'controls' | 'controlOptions'> = {},
789
- ): TopBarSettingsConfig {
790
- return {
791
- ...config,
792
- controls,
793
- controlOptions: options,
794
- }
795
- }
796
-
797
- /** Return generated control view IDs that have at least one sidebar panel. */
798
- export function controlsToViewIds(
799
- controls: ControlSchema,
800
- options: ControlSchemaOptions = {},
801
- ): string[] {
802
- return controlsToViewItems(controls, options).map(item => item.id)
803
- }
804
-
805
- /** Return AppTopBar pillNav-compatible view items for switching generated control sidebars. */
806
- export function controlsToViewItems(
807
- controls: ControlSchema,
808
- options: ControlSchemaOptions = {},
809
- ): PillNavItem[] {
810
- return Object.entries(controlsToSidebarPanels(controls, options))
811
- .filter(([, sections]) => sections.length > 0)
812
- .map(([id]) => controlViewItem(id, options))
813
- }
814
-
815
- /** Return the first generated sidebar view ID, or an empty string when controls render no sidebar panels. */
816
- export function getDefaultControlView(
817
- controls: ControlSchema,
818
- options: ControlSchemaOptions = {},
819
- ): string {
820
- return controlsToViewIds(controls, options)[0] ?? ''
821
- }
822
-
823
- /** Return a headerless single-section FormBuilder schema for rendering inside an AppSidebar section slot. */
824
- export function controlsToSectionFormSchema(
825
- controls: ControlSchema,
826
- sectionId: string,
827
- options: ControlSchemaOptions = {},
828
- ): ControlFormSchema {
829
- const schema = controlsToFormSchema(controls, options)
830
- return {
831
- sections: schema.sections
832
- .filter(section => section.id === sectionId)
833
- .map(section => ({ ...section, title: '', description: undefined })),
834
- submitLabel: schema.submitLabel,
835
- cancelLabel: schema.cancelLabel,
836
- showCancel: schema.showCancel,
837
- }
838
- }
839
-
840
- /** Return headerless FormBuilder schemas keyed by section ID for AppSidebar auto-rendering. */
841
- export function controlsToSectionFormSchemas(
842
- controls: ControlSchema,
843
- options: ControlSchemaOptions = {},
844
- ): Record<string, ControlFormSchema> {
845
- const schema = controlsToFormSchema(controls, options)
846
- const schemas: Record<string, ControlFormSchema> = {}
847
- for (const section of schema.sections) {
848
- schemas[section.id] = {
849
- sections: [{ ...section, title: '', description: undefined }],
850
- submitLabel: schema.submitLabel,
851
- cancelLabel: schema.cancelLabel,
852
- showCancel: schema.showCancel,
853
- }
854
- }
855
- return schemas
856
- }
857
-
858
657
  /** Prepare FormBuilder, SettingsModal, AppTopBar settings, AppSidebar, and initial values from one compact control model. */
859
658
  export function useControlSchema<TControls extends ControlSchema>(
860
659
  controls: TControls,
@@ -902,195 +701,6 @@ export function useControlSchema<TControls extends ControlSchema>(
902
701
  }
903
702
  }
904
703
 
905
- /** Prepare shared reactive values plus AppTopBar/AppSidebar/FormBuilder bindings from one simple controls data model. */
906
- export function useControlWorkspace<TControls extends ControlSchema>(
907
- controlsOrModel: TControls | (ControlModelBinding & { controls: TControls }),
908
- options: ControlWorkspaceOptions = {},
909
- ): UseControlWorkspaceReturn<TControls> {
910
- const model = isControlModelBindingInput<TControls>(controlsOrModel) ? controlsOrModel : undefined
911
- const controls: TControls = model ? model.controls : controlsOrModel as TControls
912
- const workspaceOptions = model
913
- ? mergeControlWorkspaceOptions(model.controlOptions, options)
914
- : options
915
- const { initialValues, topBarSettings, ...schemaOptions } = workspaceOptions
916
- const schema = useControlSchema(controls, schemaOptions)
917
- const activeView = ref(schema.defaultView)
918
- const values = reactive({
919
- ...schema.initialValues,
920
- ...(initialValues ?? {}),
921
- }) as ControlValues<TControls> & Record<string, unknown>
922
-
923
- function setActiveView(viewId: string) {
924
- syncActiveView(viewId)
925
- }
926
-
927
- function syncActiveView(viewId: string) {
928
- if (!schema.viewIds.includes(viewId)) return
929
- if (activeView.value !== viewId) activeView.value = viewId
930
- if (sidebar.activeView !== viewId) sidebar.activeView = viewId
931
- if (pillNav.currentItemId !== viewId) pillNav.currentItemId = viewId
932
- }
933
-
934
- function setValues(nextValues: Record<string, unknown>) {
935
- Object.assign(values, nextValues)
936
- }
937
-
938
- function resetValues(nextValues: Record<string, unknown> = {}) {
939
- replaceRecord(values, {
940
- ...schema.initialValues,
941
- ...nextValues,
942
- })
943
- }
944
-
945
- function getComponentProps(
946
- mapping?: ControlComponentPropsMap<ControlValues<TControls> & Record<string, unknown>>,
947
- ): Record<string, unknown> {
948
- return controlValuesToComponentProps(values, mapping)
949
- }
950
-
951
- function getComponentPropsById(
952
- mappings?: ControlComponentPropsByIdMap<ControlValues<TControls> & Record<string, unknown>>,
953
- ): Record<string, Record<string, unknown>> {
954
- if (mappings === undefined) return {}
955
-
956
- return Object.fromEntries(
957
- Object.entries(mappings).map(([id, mapping]) => [
958
- id,
959
- controlValuesToComponentProps(values, mapping),
960
- ]),
961
- )
962
- }
963
-
964
- function getComponentBindings(
965
- bindings?: ControlComponentBindingsConfig<ControlValues<TControls> & Record<string, unknown>>,
966
- ): ControlComponentBinding[] {
967
- return controlValuesToComponentBindings(values, bindings)
968
- }
969
-
970
- function getComponentBindingsById(
971
- bindings?: ControlComponentBindingsConfig<ControlValues<TControls> & Record<string, unknown>>,
972
- ): ControlComponentBindingsById {
973
- return controlValuesToComponentBindingsById(values, bindings)
974
- }
975
- const componentProps = computed(() => (
976
- model?.componentProps === undefined ? {} : getComponentProps(model.componentProps)
977
- ))
978
- const componentPropsById = computed(() => getComponentPropsById(model?.componentPropsById))
979
- const componentBindings = computed(() => getComponentBindings(model?.componentBindings))
980
- const componentBindingsById = computed(() => getComponentBindingsById(model?.componentBindings))
981
- const form = {
982
- ...schema.form,
983
- modelValue: values,
984
- 'onUpdate:modelValue': setValues,
985
- } as ControlWorkspaceFormBinding
986
-
987
- const topBarSettingsConfig: TopBarSettingsConfig = {
988
- ...schema.topBarSettingsConfig,
989
- ...(topBarSettings ?? {}),
990
- values,
991
- }
992
- const sidebar = reactive({
993
- ...schema.sidebar,
994
- activeView: activeView.value,
995
- modelValue: values,
996
- 'onUpdate:modelValue': setValues,
997
- values,
998
- 'onUpdate:values': setValues,
999
- }) as ControlWorkspaceSidebarBinding
1000
- const pillNav = reactive({
1001
- items: schema.viewItems,
1002
- currentItemId: activeView.value,
1003
- onSelect: (item: PillNavItem) => setActiveView(item.id),
1004
- }) as ControlWorkspacePillNavBinding
1005
- const topBarSettingsBinding = {
1006
- showSettings: true,
1007
- settingsConfig: topBarSettingsConfig,
1008
- onSettingsValuesChange: setValues,
1009
- } as ControlWorkspaceTopBarSettingsBinding
1010
- const topBarProps = computed<ControlWorkspaceTopBarBinding>(() => ({
1011
- pillNav: pillNav.items,
1012
- currentPillId: pillNav.currentItemId,
1013
- onPillSelect: pillNav.onSelect,
1014
- ...topBarSettingsBinding,
1015
- }))
1016
- const bindings: ControlWorkspaceComponentBindings = {
1017
- form,
1018
- sidebar,
1019
- topBar: topBarProps,
1020
- topBarSettings: topBarSettingsBinding,
1021
- pillNav,
1022
- componentBindings,
1023
- componentBindingsById,
1024
- componentProps,
1025
- componentPropsById,
1026
- }
1027
-
1028
- watch(activeView, syncActiveView, { flush: 'sync' })
1029
- watch(() => sidebar.activeView, syncActiveView, { flush: 'sync' })
1030
- watch(() => pillNav.currentItemId, syncActiveView, { flush: 'sync' })
1031
-
1032
- return {
1033
- ...schema,
1034
- schema,
1035
- values,
1036
- activeView,
1037
- topBarSettingsConfig,
1038
- form,
1039
- sidebar,
1040
- topBar: topBarProps,
1041
- pillNav,
1042
- topBarSettings: topBarSettingsBinding,
1043
- bindings,
1044
- componentBindings,
1045
- componentBindingsById,
1046
- componentProps,
1047
- componentPropsById,
1048
- setActiveView,
1049
- setValues,
1050
- resetValues,
1051
- getComponentProps,
1052
- getComponentPropsById,
1053
- getComponentBindings,
1054
- getComponentBindingsById,
1055
- }
1056
- }
1057
-
1058
- function normalizeControls(
1059
- controls: ControlSchema,
1060
- options: ControlSchemaOptions = {},
1061
- ): NormalizedControl[] {
1062
- return Object.entries(controls)
1063
- .map(([name, input], index): NormalizedControl => {
1064
- const definition = normalizeControlDefinition(input)
1065
- const type = definition.type ?? inferControlType(definition)
1066
- return {
1067
- name,
1068
- definition,
1069
- type,
1070
- sectionId: definition.section ?? options.section ?? 'controls',
1071
- viewId: definition.view ?? options.view ?? 'default',
1072
- order: definition.order ?? index,
1073
- }
1074
- })
1075
- .sort((a, b) => a.order - b.order)
1076
- }
1077
-
1078
- function normalizeControlDefinition(input: ControlInput): ControlDefinition {
1079
- if (isControlOptionArray(input)) {
1080
- if (input.length === 0) return { type: 'tags', default: [] }
1081
- return {
1082
- default: optionValue(input[0]),
1083
- options: input,
1084
- }
1085
- }
1086
-
1087
- if (typeof input === 'string' || typeof input === 'number' || typeof input === 'boolean') {
1088
- return { default: input }
1089
- }
1090
-
1091
- return input
1092
- }
1093
-
1094
704
  function appendModelControls(
1095
705
  target: ControlSchema,
1096
706
  source: ControlSchema,
@@ -1132,118 +742,6 @@ function registerSectionOptions(
1132
742
  })
1133
743
  }
1134
744
 
1135
- function isControlOptionArray(input: ControlInput): input is readonly ControlOption[] {
1136
- return Array.isArray(input)
1137
- }
1138
-
1139
- function inferControlType(definition: ControlDefinition): FormFieldType {
1140
- const value = definition.default ?? definition.defaultValue
1141
- if (definition.options?.length) return Array.isArray(value) ? 'multiselect' : 'select'
1142
- if (typeof value === 'boolean') return 'toggle'
1143
- if (typeof value === 'number') return 'number'
1144
- if (Array.isArray(value)) return 'tags'
1145
- return 'text'
1146
- }
1147
-
1148
- function defaultValueForControl(definition: ControlDefinition, type: FormFieldType): unknown {
1149
- if (definition.default !== undefined) return definition.default
1150
- if (definition.defaultValue !== undefined) return definition.defaultValue
1151
- return getTypeDefault(type)
1152
- }
1153
-
1154
- function controlToFormField(control: NormalizedControl): FormFieldSchema {
1155
- const { name, definition, type } = control
1156
- const props = fieldProps(definition)
1157
- return {
1158
- name,
1159
- label: definition.label ?? humanize(name),
1160
- type,
1161
- defaultValue: defaultValueForControl(definition, type),
1162
- placeholder: definition.placeholder,
1163
- hint: definition.hint,
1164
- size: definition.size,
1165
- disabled: definition.disabled,
1166
- readonly: definition.readonly,
1167
- validation: validationForControl(definition),
1168
- condition: definition.condition,
1169
- access: definition.access,
1170
- visibleFor: definition.visibleFor,
1171
- requiresAdmin: definition.requiresAdmin,
1172
- permissions: definition.permissions,
1173
- anyPermissions: definition.anyPermissions,
1174
- colSpan: definition.colSpan,
1175
- props,
1176
- }
1177
- }
1178
-
1179
- function fieldProps(definition: ControlDefinition): Record<string, unknown> {
1180
- const props: Record<string, unknown> = {}
1181
- if (definition.min !== undefined) props.min = numericValue(definition.min)
1182
- if (definition.max !== undefined) props.max = numericValue(definition.max)
1183
- if (definition.options) props.options = definition.options.map(normalizeOption)
1184
- return { ...props, ...(definition.props ?? {}) }
1185
- }
1186
-
1187
- function validationForControl(definition: ControlDefinition): FieldValidation | undefined {
1188
- const validation: FieldValidation = { ...(definition.validation ?? {}) }
1189
- if (definition.required !== undefined) validation.required = definition.required
1190
- if (definition.min !== undefined) validation.min = definition.min
1191
- if (definition.max !== undefined) validation.max = definition.max
1192
- if (definition.minLength !== undefined) validation.minLength = definition.minLength
1193
- if (definition.maxLength !== undefined) validation.maxLength = definition.maxLength
1194
- if (definition.email !== undefined) validation.email = definition.email
1195
- if (definition.pattern !== undefined) validation.pattern = definition.pattern
1196
- return Object.keys(validation).length > 0 ? validation : undefined
1197
- }
1198
-
1199
- function sectionConfig(
1200
- sectionId: string,
1201
- controls: NormalizedControl[],
1202
- options: ControlSchemaOptions,
1203
- ): ControlSectionConfig {
1204
- const configured = options.sections?.[sectionId]
1205
- const first = controls[0]?.definition
1206
- return {
1207
- ...configured,
1208
- id: sectionId,
1209
- label: configured?.label ?? first?.sectionLabel,
1210
- title: configured?.title ?? first?.sectionLabel,
1211
- description: configured?.description ?? first?.sectionDescription,
1212
- subtitle: configured?.subtitle ?? first?.sectionSubtitle,
1213
- }
1214
- }
1215
-
1216
- function firstSidebarConfig(controls: NormalizedControl[]): ControlSidebarConfig | undefined {
1217
- for (const control of controls) {
1218
- const sidebar = control.definition.sidebar
1219
- if (sidebar && typeof sidebar === 'object' && sidebar.enabled !== false) return sidebar
1220
- }
1221
- return undefined
1222
- }
1223
-
1224
- function isSidebarEnabled(sidebar: ControlDefinition['sidebar']): boolean {
1225
- if (sidebar === false) return false
1226
- if (sidebar && typeof sidebar === 'object') return sidebar.enabled !== false
1227
- return true
1228
- }
1229
-
1230
- function normalizeOption(option: ControlOption): SelectOption<unknown> {
1231
- if (typeof option === 'object' && option !== null && 'label' in option) {
1232
- return option
1233
- }
1234
- return {
1235
- value: option,
1236
- label: humanize(String(option)),
1237
- }
1238
- }
1239
-
1240
- function optionValue(option: ControlOption): ControlOptionValue | unknown {
1241
- if (typeof option === 'object' && option !== null && 'value' in option) {
1242
- return option.value
1243
- }
1244
- return option
1245
- }
1246
-
1247
745
  function compactComponentPropsMap<TValues extends Record<string, unknown>>(
1248
746
  mapping: Record<string, ControlComponentPropSource<TValues> | undefined>,
1249
747
  ): ControlComponentPropsMap<TValues> {
@@ -1254,31 +752,6 @@ function compactComponentPropsMap<TValues extends Record<string, unknown>>(
1254
752
  )
1255
753
  }
1256
754
 
1257
- function normalizeControlComponentBindingConfigs<TValues extends Record<string, unknown>>(
1258
- bindings: ControlComponentBindingsConfig<TValues>,
1259
- ): Array<Required<Pick<ControlComponentBindingConfig<TValues>, 'id' | 'component'>> & Pick<ControlComponentBindingConfig<TValues>, 'props'>> {
1260
- if (Array.isArray(bindings)) {
1261
- const usedIds = new Map<string, number>()
1262
- return bindings.map(binding => ({
1263
- id: uniqueComponentBindingId(binding.id ?? binding.component, usedIds),
1264
- component: binding.component,
1265
- props: binding.props,
1266
- }))
1267
- }
1268
-
1269
- return Object.entries(bindings).map(([id, binding]) => ({
1270
- id,
1271
- component: binding.component,
1272
- props: binding.props,
1273
- }))
1274
- }
1275
-
1276
- function uniqueComponentBindingId(id: string, usedIds: Map<string, number>): string {
1277
- const count = usedIds.get(id) ?? 0
1278
- usedIds.set(id, count + 1)
1279
- return count === 0 ? id : `${id}-${count + 1}`
1280
- }
1281
-
1282
755
  function sourceKey<TValues extends Record<string, unknown>>(key: string): keyof TValues & string {
1283
756
  return key as keyof TValues & string
1284
757
  }
@@ -1331,53 +804,3 @@ function updateWellsFromDoseResults<TValues extends Record<string, unknown>>(
1331
804
  writableValues[source] = nextWells
1332
805
  }
1333
806
  }
1334
-
1335
- function recordValue(value: unknown): Record<string, unknown> {
1336
- return typeof value === 'object' && value !== null && !Array.isArray(value)
1337
- ? value as Record<string, unknown>
1338
- : {}
1339
- }
1340
-
1341
- function numericValue(value: number | { value: number; message: string }): number {
1342
- return typeof value === 'number' ? value : value.value
1343
- }
1344
-
1345
- function orderedUnique(values: string[]): string[] {
1346
- return [...new Set(values)]
1347
- }
1348
-
1349
- function controlViewItem(viewId: string, options: ControlSchemaOptions): PillNavItem {
1350
- const config = options.views?.[viewId]
1351
- return {
1352
- id: viewId,
1353
- label: config?.label ?? humanize(viewId),
1354
- ...(config?.icon !== undefined ? { icon: config.icon } : {}),
1355
- ...(config?.to !== undefined ? { to: config.to } : {}),
1356
- ...(config?.href !== undefined ? { href: config.href } : {}),
1357
- ...(config?.disabled !== undefined ? { disabled: config.disabled } : {}),
1358
- ...(config?.children !== undefined ? { children: config.children } : {}),
1359
- }
1360
- }
1361
-
1362
- function replaceRecord(target: Record<string, unknown>, values: Record<string, unknown>) {
1363
- for (const key of Object.keys(target)) {
1364
- delete target[key]
1365
- }
1366
- Object.assign(target, values)
1367
- }
1368
-
1369
- function omitUndefined<T extends object>(record: T): T {
1370
- const next: Record<string, unknown> = {}
1371
- for (const [key, value] of Object.entries(record as Record<string, unknown>)) {
1372
- if (value !== undefined) next[key] = value
1373
- }
1374
- return next as T
1375
- }
1376
-
1377
- function humanize(value: string): string {
1378
- return value
1379
- .replace(/[_-]+/g, ' ')
1380
- .replace(/([a-z])([A-Z])/g, '$1 $2')
1381
- .trim()
1382
- .replace(/\w\S*/g, word => word.charAt(0).toUpperCase() + word.slice(1))
1383
- }