@morscherlab/mint-sdk 1.0.0-beta.3 → 1.0.0-beta.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 (165) hide show
  1. package/README.md +9 -2
  2. package/dist/__tests__/composables/experiment-utils.test.d.ts +1 -0
  3. package/dist/__tests__/composables/useApi.test.d.ts +1 -0
  4. package/dist/components/AppContainer.vue.d.ts +1 -1
  5. package/dist/components/AppLayout.vue.d.ts +20 -1
  6. package/dist/components/AppSidebar.vue.d.ts +56 -4
  7. package/dist/components/AppTopBar.vue.d.ts +7 -25
  8. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +3 -1
  9. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -0
  10. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +5 -0
  11. package/dist/components/ComponentBindingRenderer.vue.d.ts +44 -0
  12. package/dist/components/ControlWorkspaceView.vue.d.ts +24 -7
  13. package/dist/components/DoseDesignWorkspaceView.vue.d.ts +149 -0
  14. package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
  15. package/dist/components/FormBuilder.vue.d.ts +9 -9
  16. package/dist/components/PlateMapEditor.vue.d.ts +1 -1
  17. package/dist/components/PluginWorkspaceView.vue.d.ts +310 -0
  18. package/dist/components/SettingsModal.vue.d.ts +1 -1
  19. package/dist/components/WellPlate.vue.d.ts +2 -2
  20. package/dist/components/index.d.ts +3 -12
  21. package/dist/components/index.js +3 -3
  22. package/dist/components/{AppPageSelector.vue.d.ts → internal/AppPageSelectorInternal.vue.d.ts} +1 -1
  23. package/dist/components/{AppPillNav.vue.d.ts → internal/AppPillNavInternal.vue.d.ts} +3 -1
  24. package/dist/components/{CalendarGridPanel.vue.d.ts → internal/CalendarGridPanelInternal.vue.d.ts} +1 -1
  25. package/dist/components/internal/FormSectionRenderer.vue.d.ts +4 -4
  26. package/dist/components/{WellEditPopup.vue.d.ts → internal/WellEditPopupInternal.vue.d.ts} +1 -1
  27. package/dist/{components-D_Sr0adg.js → components-BkGF4B4y.js} +4484 -3967
  28. package/dist/components-BkGF4B4y.js.map +1 -0
  29. package/dist/composables/experiment-utils.d.ts +8 -0
  30. package/dist/composables/index.d.ts +5 -7
  31. package/dist/composables/index.js +4 -4
  32. package/dist/composables/useAppExperiment.d.ts +31 -2
  33. package/dist/composables/useBioTemplateComponents.d.ts +5 -3
  34. package/dist/composables/useBioTemplatePackWorkspace.d.ts +3 -2
  35. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +6 -5
  36. package/dist/composables/useBioTemplateWorkspace.d.ts +5 -4
  37. package/dist/composables/useControlSchema.d.ts +43 -21
  38. package/dist/composables/usePluginClient.d.ts +5 -2
  39. package/dist/{composables-C3dpXQN5.js → composables-CHsME9H1.js} +40 -28
  40. package/dist/composables-CHsME9H1.js.map +1 -0
  41. package/dist/index.d.ts +5 -12
  42. package/dist/index.js +5 -5
  43. package/dist/install.js +2 -2
  44. package/dist/styles.css +3625 -3651
  45. package/dist/templates/componentBindings.d.ts +13 -0
  46. package/dist/templates/index.d.ts +3 -3
  47. package/dist/templates/index.js +2 -2
  48. package/dist/{templates-50NPjaxL.js → templates-B5jmTWuk.js} +111 -56
  49. package/dist/templates-B5jmTWuk.js.map +1 -0
  50. package/dist/types/components.d.ts +6 -25
  51. package/dist/types/index.d.ts +1 -1
  52. package/dist/{useScheduleDrag-D4oWdh41.js → useScheduleDrag-BgzpQT53.js} +160 -117
  53. package/dist/useScheduleDrag-BgzpQT53.js.map +1 -0
  54. package/package.json +1 -1
  55. package/src/__tests__/components/ActionItem.test.ts +6 -6
  56. package/src/__tests__/components/AppLayout.test.ts +44 -0
  57. package/src/__tests__/components/AppPageSelector.test.ts +8 -8
  58. package/src/__tests__/components/AppPillNav.test.ts +53 -6
  59. package/src/__tests__/components/AppSidebar.test.ts +126 -0
  60. package/src/__tests__/components/AppToastContainer.test.ts +0 -11
  61. package/src/__tests__/components/AppTopBar.test.ts +182 -119
  62. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +7 -1
  63. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +15 -1
  64. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +26 -1
  65. package/src/__tests__/components/CalendarGridPanel.test.ts +3 -3
  66. package/src/__tests__/components/ComponentBindingRenderer.test.ts +161 -0
  67. package/src/__tests__/components/ControlWorkspaceView.test.ts +134 -63
  68. package/src/__tests__/components/DateTimePicker.test.ts +2 -2
  69. package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +185 -0
  70. package/src/__tests__/components/PluginWorkspaceView.test.ts +548 -0
  71. package/src/__tests__/composables/experiment-utils.test.ts +30 -0
  72. package/src/__tests__/composables/useApi.test.ts +30 -0
  73. package/src/__tests__/composables/useAppExperiment.test.ts +100 -1
  74. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +6 -3
  75. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +6 -6
  76. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +6 -1
  77. package/src/__tests__/composables/useControlSchema.test.ts +150 -36
  78. package/src/__tests__/composables/usePluginClient.test.ts +99 -2
  79. package/src/__tests__/docs/frontendDocsCatalog.test.ts +120 -25
  80. package/src/__tests__/templates/templates.test.ts +12 -0
  81. package/src/components/AppAvatarMenu.vue +3 -3
  82. package/src/components/AppLayout.story.vue +39 -0
  83. package/src/components/AppLayout.vue +83 -2
  84. package/src/components/AppPluginSwitcher.vue +5 -5
  85. package/src/components/AppSidebar.story.vue +113 -5
  86. package/src/components/AppSidebar.vue +144 -24
  87. package/src/components/AppTopBar.story.vue +2 -5
  88. package/src/components/AppTopBar.vue +35 -425
  89. package/src/components/BioTemplateExperimentWorkspaceView.story.vue +2 -2
  90. package/src/components/BioTemplateExperimentWorkspaceView.vue +6 -0
  91. package/src/components/BioTemplatePackWorkspaceView.story.vue +4 -4
  92. package/src/components/BioTemplatePackWorkspaceView.vue +1 -0
  93. package/src/components/BioTemplatePresetWorkspaceView.story.vue +14 -2
  94. package/src/components/BioTemplatePresetWorkspaceView.vue +11 -2
  95. package/src/components/BioTemplateRenderer.vue +15 -227
  96. package/src/components/ComponentBindingRenderer.story.vue +57 -0
  97. package/src/components/ComponentBindingRenderer.vue +308 -0
  98. package/src/components/ControlWorkspaceView.story.vue +20 -9
  99. package/src/components/ControlWorkspaceView.vue +43 -12
  100. package/src/components/DatePicker.vue +2 -2
  101. package/src/components/DateTimePicker.vue +2 -2
  102. package/src/components/DoseDesignWorkspaceView.story.vue +77 -0
  103. package/src/components/DoseDesignWorkspaceView.vue +255 -0
  104. package/src/components/ExperimentPopover.vue +2 -6
  105. package/src/components/ExperimentSelectorModal.vue +6 -5
  106. package/src/components/FormBuilder.story.vue +190 -0
  107. package/src/components/PluginWorkspaceView.story.vue +334 -0
  108. package/src/components/PluginWorkspaceView.vue +708 -0
  109. package/src/components/SettingsModal.story.vue +87 -0
  110. package/src/components/WellPlate.vue +2 -2
  111. package/src/components/index.ts +3 -12
  112. package/src/components/{AppPageSelector.vue → internal/AppPageSelectorInternal.vue} +9 -9
  113. package/src/components/internal/AppPillNavInternal.vue +194 -0
  114. package/src/components/{CalendarGridPanel.vue → internal/CalendarGridPanelInternal.vue} +1 -1
  115. package/src/components/{WellEditPopup.vue → internal/WellEditPopupInternal.vue} +3 -3
  116. package/src/composables/experiment-utils.ts +26 -0
  117. package/src/composables/index.ts +21 -7
  118. package/src/composables/useApi.ts +9 -2
  119. package/src/composables/useAppExperiment.ts +85 -13
  120. package/src/composables/useBioTemplateComponents.ts +12 -0
  121. package/src/composables/useBioTemplatePackWorkspace.ts +6 -2
  122. package/src/composables/useBioTemplatePresetWorkspace.ts +10 -21
  123. package/src/composables/useBioTemplateWorkspace.ts +6 -4
  124. package/src/composables/useControlSchema.ts +157 -69
  125. package/src/composables/usePluginClient.ts +50 -9
  126. package/src/index.ts +6 -563
  127. package/src/styles/components/app-layout.css +82 -0
  128. package/src/styles/components/app-pill-nav.css +70 -0
  129. package/src/styles/components/app-sidebar.css +119 -0
  130. package/src/styles/components/app-top-bar.css +0 -235
  131. package/src/styles/index.css +0 -1
  132. package/src/templates/componentBindings.ts +38 -0
  133. package/src/templates/index.ts +4 -0
  134. package/src/types/components.ts +6 -31
  135. package/src/types/index.ts +2 -6
  136. package/dist/__tests__/composables/usePluginApi.test.d.ts +0 -13
  137. package/dist/components/FormFieldRenderer.vue.d.ts +0 -28
  138. package/dist/components/FormSection.vue.d.ts +0 -30
  139. package/dist/components/GroupingModal.vue.d.ts +0 -12
  140. package/dist/components/SettingsButton.vue.d.ts +0 -30
  141. package/dist/components/ToastNotification.vue.d.ts +0 -2
  142. package/dist/components-D_Sr0adg.js.map +0 -1
  143. package/dist/composables/usePluginApi.d.ts +0 -22
  144. package/dist/composables-C3dpXQN5.js.map +0 -1
  145. package/dist/templates-50NPjaxL.js.map +0 -1
  146. package/dist/useScheduleDrag-D4oWdh41.js.map +0 -1
  147. package/src/__tests__/components/FormCompatibility.test.ts +0 -94
  148. package/src/__tests__/components/GroupingModal.test.ts +0 -73
  149. package/src/__tests__/components/SettingsButton.test.ts +0 -44
  150. package/src/__tests__/composables/usePluginApi.test.ts +0 -81
  151. package/src/components/AppPillNav.vue +0 -71
  152. package/src/components/FormFieldRenderer.vue +0 -35
  153. package/src/components/FormSection.vue +0 -37
  154. package/src/components/GroupingModal.story.vue +0 -52
  155. package/src/components/GroupingModal.vue +0 -61
  156. package/src/components/SettingsButton.story.vue +0 -58
  157. package/src/components/SettingsButton.vue +0 -64
  158. package/src/components/ToastNotification.vue +0 -9
  159. package/src/composables/usePluginApi.ts +0 -32
  160. package/src/styles/components/settings-button.css +0 -31
  161. /package/dist/__tests__/components/{FormCompatibility.test.d.ts → ComponentBindingRenderer.test.d.ts} +0 -0
  162. /package/dist/__tests__/components/{GroupingModal.test.d.ts → DoseDesignWorkspaceView.test.d.ts} +0 -0
  163. /package/dist/__tests__/components/{SettingsButton.test.d.ts → PluginWorkspaceView.test.d.ts} +0 -0
  164. /package/dist/components/{ActionItem.vue.d.ts → internal/ActionItemInternal.vue.d.ts} +0 -0
  165. /package/src/components/{ActionItem.vue → internal/ActionItemInternal.vue} +0 -0
@@ -28,16 +28,16 @@ function initState() {
28
28
  <div class="pack-component-layout">
29
29
  <div class="pack-component-panel pack-component-panel--plate">
30
30
  <WellPlate
31
- v-if="bindings.componentPropsById['plate-map:WellPlate']"
32
- v-bind="bindings.componentPropsById['plate-map:WellPlate']"
31
+ v-if="bindings.componentBindingsById['plate-map:WellPlate']"
32
+ v-bind="bindings.componentBindingsById['plate-map:WellPlate'].props"
33
33
  size="sm"
34
34
  readonly
35
35
  />
36
36
  </div>
37
37
  <div class="pack-component-panel pack-component-panel--dose">
38
38
  <DoseCalculator
39
- v-if="bindings.componentPropsById['dose-response:DoseCalculator']"
40
- v-bind="bindings.componentPropsById['dose-response:DoseCalculator']"
39
+ v-if="bindings.componentBindingsById['dose-response:DoseCalculator']"
40
+ v-bind="bindings.componentBindingsById['dose-response:DoseCalculator'].props"
41
41
  />
42
42
  </div>
43
43
  </div>
@@ -14,6 +14,7 @@ interface BioTemplatePackWorkspaceSlotProps {
14
14
  bindings: UseBioTemplatePackWorkspaceReturn['bindings']['value']
15
15
  pack: UseBioTemplatePackWorkspaceReturn['pack']
16
16
  target: BioTemplateEnvelope<unknown> | TemplateCollectionEnvelope
17
+ componentBindingsById: UseBioTemplatePackWorkspaceReturn['componentBindingsById']['value']
17
18
  componentProps: UseBioTemplatePackWorkspaceReturn['componentProps']['value']
18
19
  componentPropsById: UseBioTemplatePackWorkspaceReturn['componentPropsById']['value']
19
20
  componentPropsByComponent: UseBioTemplatePackWorkspaceReturn['componentPropsByComponent']['value']
@@ -6,6 +6,8 @@ import WellPlate from './WellPlate.vue'
6
6
  import { useBioTemplatePresetWorkspace } from '../composables/useBioTemplatePresetWorkspace'
7
7
  import type { BioTemplateControlValues } from '../templates'
8
8
 
9
+ type SidebarVariant = 'analysis' | 'default'
10
+
9
11
  const wellplateWorkspace = useBioTemplatePresetWorkspace('wellplate-screen', {
10
12
  initialValues: {
11
13
  sampleNames: ['Vehicle', 'Treatment'],
@@ -28,6 +30,7 @@ const componentPresetValues = ref<BioTemplateControlValues>({
28
30
  function initState() {
29
31
  return {
30
32
  preset: 'wellplate-screen',
33
+ sidebarVariant: 'analysis' as SidebarVariant,
31
34
  showTemplateSummary: true,
32
35
  showComponentSummary: false,
33
36
  dense: true,
@@ -50,6 +53,7 @@ function workspaceForPreset(preset: string) {
50
53
  <div style="padding: 2rem; min-height: 720px; background: var(--bg-primary, #f8fafc);">
51
54
  <BioTemplatePresetWorkspaceView
52
55
  :workspace="workspaceForPreset(state.preset)"
56
+ :sidebar-variant="state.sidebarVariant"
53
57
  :show-template-summary="state.showTemplateSummary"
54
58
  :show-component-summary="state.showComponentSummary"
55
59
  :dense="state.dense"
@@ -69,6 +73,14 @@ function workspaceForPreset(preset: string) {
69
73
  { label: 'Western blot assay', value: 'western-blot-assay' },
70
74
  ]"
71
75
  />
76
+ <HstSelect
77
+ v-model="state.sidebarVariant"
78
+ title="Sidebar variant"
79
+ :options="[
80
+ { label: 'Analysis', value: 'analysis' },
81
+ { label: 'Default', value: 'default' },
82
+ ]"
83
+ />
72
84
  <HstCheckbox v-model="state.showTemplateSummary" title="Template summary" />
73
85
  <HstCheckbox v-model="state.showComponentSummary" title="Component summary" />
74
86
  <HstCheckbox v-model="state.dense" title="Dense" />
@@ -97,14 +109,14 @@ function workspaceForPreset(preset: string) {
97
109
  <div class="preset-component-layout">
98
110
  <div class="preset-component-panel preset-component-panel--plate">
99
111
  <WellPlate
100
- v-bind="bindings.componentPropsById.value['plate-map:WellPlate']"
112
+ v-bind="bindings.componentBindingsById.value['plate-map:WellPlate'].props"
101
113
  size="sm"
102
114
  readonly
103
115
  />
104
116
  </div>
105
117
  <div class="preset-component-panel preset-component-panel--dose">
106
118
  <DoseCalculator
107
- v-bind="bindings.componentPropsById.value['dose-response:DoseCalculator']"
119
+ v-bind="bindings.componentBindingsById.value['dose-response:DoseCalculator'].props"
108
120
  />
109
121
  </div>
110
122
  </div>
@@ -8,16 +8,19 @@ import type {
8
8
  } from '../composables/useBioTemplatePresetWorkspace'
9
9
  import { useBioTemplatePresetWorkspace } from '../composables/useBioTemplatePresetWorkspace'
10
10
  import AlertBox from './AlertBox.vue'
11
- import AppPillNav from './AppPillNav.vue'
12
11
  import AppSidebar from './AppSidebar.vue'
13
12
  import BaseButton from './BaseButton.vue'
14
13
  import BioTemplateRenderer from './BioTemplateRenderer.vue'
14
+ import AppPillNavInternal from './internal/AppPillNavInternal.vue'
15
+
16
+ type BioTemplatePresetSidebarVariant = 'default' | 'analysis'
15
17
 
16
18
  interface BioTemplatePresetWorkspaceSlotProps {
17
19
  workspace: UseBioTemplatePresetWorkspaceReturn
18
20
  bindings: UseBioTemplatePresetWorkspaceReturn['bindings']
19
21
  collection: UseBioTemplatePresetWorkspaceReturn['collection']['value']
20
22
  renderer: UseBioTemplatePresetWorkspaceReturn['renderer']['value']
23
+ componentBindingsById: UseBioTemplatePresetWorkspaceReturn['componentBindingsById']['value']
21
24
  componentProps: UseBioTemplatePresetWorkspaceReturn['componentProps']['value']
22
25
  componentPropsById: UseBioTemplatePresetWorkspaceReturn['componentPropsById']['value']
23
26
  componentPropsByComponent: UseBioTemplatePresetWorkspaceReturn['componentPropsByComponent']['value']
@@ -41,6 +44,8 @@ interface Props {
41
44
  label?: string
42
45
  /** Sidebar CSS width. */
43
46
  sidebarWidth?: string
47
+ /** AppSidebar visual preset. `analysis` matches the LEAF-style MINT analysis sidebar design language. */
48
+ sidebarVariant?: BioTemplatePresetSidebarVariant
44
49
  /** Compact sidebar and preview layout. */
45
50
  dense?: boolean
46
51
  /** Render preview components in read-only mode. */
@@ -74,6 +79,7 @@ const props = withDefaults(defineProps<Props>(), {
74
79
  modelValue: undefined,
75
80
  values: undefined,
76
81
  sidebarWidth: '320px',
82
+ sidebarVariant: 'analysis',
77
83
  dense: true,
78
84
  readonly: true,
79
85
  showStatus: true,
@@ -150,6 +156,7 @@ const isSaving = computed(() => unref(resolvedWorkspace.value.saving))
150
156
  const error = computed(() => unref(resolvedWorkspace.value.error))
151
157
  const lastSavedAt = computed(() => unref(resolvedWorkspace.value.lastSavedAt))
152
158
  const renderer = computed(() => unref(resolvedWorkspace.value.renderer))
159
+ const componentBindingsById = computed(() => unref(resolvedWorkspace.value.componentBindingsById))
153
160
  const componentProps = computed(() => unref(resolvedWorkspace.value.componentProps))
154
161
  const componentPropsById = computed(() => unref(resolvedWorkspace.value.componentPropsById))
155
162
  const componentPropsByComponent = computed(() => unref(resolvedWorkspace.value.componentPropsByComponent))
@@ -240,6 +247,7 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
240
247
  <div class="mint-bio-template-preset-workspace__main">
241
248
  <AppSidebar
242
249
  v-bind="resolvedWorkspace.sidebar"
250
+ :variant="sidebarVariant"
243
251
  :floating="false"
244
252
  :width="sidebarWidth"
245
253
  :dense="dense"
@@ -248,7 +256,7 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
248
256
  <p class="mint-bio-template-preset-workspace__sidebar-title">
249
257
  Controls
250
258
  </p>
251
- <AppPillNav
259
+ <AppPillNavInternal
252
260
  v-if="resolvedWorkspace.pillNav.items.length > 1"
253
261
  class="mint-bio-template-preset-workspace__view-nav"
254
262
  :items="resolvedWorkspace.pillNav.items"
@@ -266,6 +274,7 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
266
274
  :bindings="bindings"
267
275
  :collection="collection"
268
276
  :renderer="renderer"
277
+ :component-bindings-by-id="componentBindingsById"
269
278
  :component-props="componentProps"
270
279
  :component-props-by-id="componentPropsById"
271
280
  :component-props-by-component="componentPropsByComponent"
@@ -1,21 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  /** Render biology template envelopes with matching SDK components such as WellPlate, DoseCalculator, and DataFrame. */
3
- import { computed, type Component } from 'vue'
3
+ import { computed } from 'vue'
4
4
  import {
5
- toBioTemplateComponentProps,
6
- type BioTemplateComponentPropsBinding,
5
+ toBioTemplateComponentBindings,
7
6
  } from '../templates/componentBindings'
8
7
  import type {
9
8
  BioTemplateEnvelope,
10
9
  TemplateCollectionEnvelope,
11
10
  } from '../templates/types'
12
- import DataFrame from './DataFrame.vue'
13
- import DoseCalculator from './DoseCalculator.vue'
14
- import ExperimentTimeline from './ExperimentTimeline.vue'
15
- import PlateMapEditor from './PlateMapEditor.vue'
16
- import ReagentList from './ReagentList.vue'
17
- import SampleSelector from './SampleSelector.vue'
18
- import WellPlate from './WellPlate.vue'
11
+ import ComponentBindingRenderer from './ComponentBindingRenderer.vue'
19
12
 
20
13
  interface Props {
21
14
  /** Template envelope or template collection to render. */
@@ -36,11 +29,6 @@ interface Props {
36
29
  emptyText?: string
37
30
  }
38
31
 
39
- type RenderableBinding = BioTemplateComponentPropsBinding & {
40
- componentImpl: Component
41
- normalizedProps: Record<string, unknown>
42
- }
43
-
44
32
  const props = withDefaults(defineProps<Props>(), {
45
33
  include: () => [],
46
34
  exclude: () => [],
@@ -51,219 +39,19 @@ const props = withDefaults(defineProps<Props>(), {
51
39
  emptyText: 'No renderable biology template components.',
52
40
  })
53
41
 
54
- const componentRegistry: Record<string, Component> = {
55
- DataFrame,
56
- DoseCalculator,
57
- ExperimentTimeline,
58
- PlateMapEditor,
59
- ReagentList,
60
- SampleSelector,
61
- WellPlate,
62
- }
63
-
64
- const renderableBindings = computed<RenderableBinding[]>(() =>
65
- toBioTemplateComponentProps(props.target)
66
- .filter(binding => isIncluded(binding.component))
67
- .map((binding) => {
68
- const componentImpl = componentRegistry[binding.component]
69
- if (!componentImpl) return null
70
- return {
71
- ...binding,
72
- componentImpl,
73
- normalizedProps: normalizeProps(binding),
74
- }
75
- })
76
- .filter((binding): binding is RenderableBinding => binding !== null)
77
- )
78
-
79
- const rendererClasses = computed(() => [
80
- 'mint-bio-template-renderer',
81
- props.dense ? 'mint-bio-template-renderer--dense' : '',
82
- ])
83
-
84
- function isIncluded(componentName: string): boolean {
85
- if (props.include.length > 0 && !props.include.includes(componentName)) return false
86
- return !props.exclude.includes(componentName)
87
- }
88
-
89
- function normalizeProps(binding: BioTemplateComponentPropsBinding): Record<string, unknown> {
90
- const base = { ...binding.propsObject }
91
-
92
- switch (binding.component) {
93
- case 'WellPlate':
94
- return {
95
- ...base,
96
- readonly: props.readonly || Boolean(base.readonly),
97
- size: props.dense ? 'md' : (base.size ?? 'fill'),
98
- }
99
- case 'PlateMapEditor':
100
- return {
101
- ...base,
102
- size: props.dense ? 'md' : (base.size ?? 'fill'),
103
- showToolbar: props.readonly ? false : (base.showToolbar ?? true),
104
- showSidebar: props.readonly ? false : (base.showSidebar ?? true),
105
- allowAddPlates: props.readonly ? false : (base.allowAddPlates ?? true),
106
- allowAddSamples: props.readonly ? false : (base.allowAddSamples ?? true),
107
- }
108
- case 'DataFrame':
109
- return {
110
- ...base,
111
- size: props.dense ? 'sm' : (base.size ?? 'md'),
112
- maxHeight: base.maxHeight ?? (props.dense ? '280px' : undefined),
113
- }
114
- case 'ExperimentTimeline':
115
- return {
116
- ...base,
117
- editable: props.readonly ? false : base.editable,
118
- size: props.dense ? 'sm' : (base.size ?? 'md'),
119
- }
120
- case 'ReagentList':
121
- return {
122
- ...base,
123
- readonly: props.readonly || Boolean(base.readonly),
124
- }
125
- case 'SampleSelector':
126
- return {
127
- ...base,
128
- enableGrouping: props.readonly ? false : (base.enableGrouping ?? true),
129
- enableSmartGroup: props.readonly ? false : (base.enableSmartGroup ?? true),
130
- }
131
- default:
132
- return base
133
- }
134
- }
42
+ const componentBindings = computed(() => toBioTemplateComponentBindings(props.target))
135
43
  </script>
136
44
 
137
45
  <template>
138
- <div :class="rendererClasses">
139
- <section
140
- v-for="binding in renderableBindings"
141
- :key="binding.id"
142
- class="mint-bio-template-renderer__item"
143
- :data-template-id="binding.template_id"
144
- :data-template-component="binding.component"
145
- >
146
- <header
147
- v-if="showHeaders"
148
- class="mint-bio-template-renderer__header"
149
- >
150
- <div class="mint-bio-template-renderer__title-group">
151
- <p class="mint-bio-template-renderer__component">
152
- {{ binding.component }}
153
- </p>
154
- <p class="mint-bio-template-renderer__template">
155
- {{ binding.template_id }}
156
- </p>
157
- </div>
158
- <p
159
- v-if="showDescriptions"
160
- class="mint-bio-template-renderer__description"
161
- >
162
- {{ binding.description }}
163
- </p>
164
- </header>
165
-
166
- <div class="mint-bio-template-renderer__body">
167
- <component
168
- :is="binding.componentImpl"
169
- v-bind="binding.normalizedProps"
170
- />
171
- </div>
172
- </section>
173
-
174
- <p
175
- v-if="renderableBindings.length === 0"
176
- class="mint-bio-template-renderer__empty"
177
- >
178
- {{ emptyText }}
179
- </p>
180
- </div>
46
+ <ComponentBindingRenderer
47
+ class="mint-bio-template-renderer"
48
+ :bindings="componentBindings"
49
+ :include="include"
50
+ :exclude="exclude"
51
+ :dense="dense"
52
+ :readonly="readonly"
53
+ :show-headers="showHeaders"
54
+ :show-descriptions="showDescriptions"
55
+ :empty-text="emptyText"
56
+ />
181
57
  </template>
182
-
183
- <style scoped>
184
- .mint-bio-template-renderer {
185
- display: grid;
186
- grid-template-columns: repeat(auto-fit, minmax(min(100%, 28rem), 1fr));
187
- gap: 1rem;
188
- min-width: 0;
189
- }
190
-
191
- .mint-bio-template-renderer--dense {
192
- gap: 0.75rem;
193
- }
194
-
195
- .mint-bio-template-renderer__item {
196
- min-width: 0;
197
- overflow: hidden;
198
- border: 1px solid var(--border-color, #e5e7eb);
199
- border-radius: 0.5rem;
200
- background: var(--bg-card, #ffffff);
201
- box-shadow: var(--shadow-sm, 0 1px 2px rgb(15 23 42 / 0.06));
202
- }
203
-
204
- .mint-bio-template-renderer__header {
205
- display: flex;
206
- align-items: flex-start;
207
- justify-content: space-between;
208
- gap: 1rem;
209
- padding: 0.75rem 1rem;
210
- border-bottom: 1px solid var(--border-color, #e5e7eb);
211
- background: var(--bg-secondary, #f8fafc);
212
- }
213
-
214
- .mint-bio-template-renderer__title-group {
215
- min-width: 0;
216
- }
217
-
218
- .mint-bio-template-renderer__component {
219
- margin: 0;
220
- color: var(--text-primary, #0f172a);
221
- font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);
222
- font-size: 0.8125rem;
223
- font-weight: 600;
224
- }
225
-
226
- .mint-bio-template-renderer__template,
227
- .mint-bio-template-renderer__description,
228
- .mint-bio-template-renderer__empty {
229
- margin: 0;
230
- color: var(--text-secondary, #475569);
231
- font-size: 0.75rem;
232
- line-height: 1.4;
233
- }
234
-
235
- .mint-bio-template-renderer__description {
236
- max-width: 24rem;
237
- text-align: right;
238
- }
239
-
240
- .mint-bio-template-renderer__body {
241
- min-width: 0;
242
- padding: 1rem;
243
- }
244
-
245
- .mint-bio-template-renderer--dense .mint-bio-template-renderer__header,
246
- .mint-bio-template-renderer--dense .mint-bio-template-renderer__body {
247
- padding: 0.75rem;
248
- }
249
-
250
- .mint-bio-template-renderer__empty {
251
- padding: 1rem;
252
- border: 1px dashed var(--border-color, #e5e7eb);
253
- border-radius: 0.5rem;
254
- background: var(--bg-secondary, #f8fafc);
255
- text-align: center;
256
- }
257
-
258
- @media (max-width: 720px) {
259
- .mint-bio-template-renderer__header {
260
- display: block;
261
- }
262
-
263
- .mint-bio-template-renderer__description {
264
- max-width: none;
265
- margin-top: 0.25rem;
266
- text-align: left;
267
- }
268
- }
269
- </style>
@@ -0,0 +1,57 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from 'vue'
3
+ import ComponentBindingRenderer from './ComponentBindingRenderer.vue'
4
+ import {
5
+ defineDoseDesignControlModel,
6
+ useControlWorkspace,
7
+ } from '../composables/useControlSchema'
8
+
9
+ const model = defineDoseDesignControlModel({
10
+ selectedWells: ['A1', 'A2', 'B1'],
11
+ plateFormat: 96,
12
+ includeMolecularWeight: true,
13
+ })
14
+ const workspace = useControlWorkspace(model)
15
+
16
+ const state = ref({
17
+ dense: false,
18
+ readonly: true,
19
+ showHeaders: true,
20
+ showDescriptions: true,
21
+ layout: 'grid' as 'grid' | 'stack',
22
+ })
23
+
24
+ const bindings = computed(() => workspace.componentBindings.value)
25
+ </script>
26
+
27
+ <template>
28
+ <Story title="Layout/ComponentBindingRenderer">
29
+ <Variant title="Dose Design Bindings">
30
+ <div style="padding: 2rem; min-height: 640px; background: var(--bg-secondary, #f8fafc);">
31
+ <ComponentBindingRenderer
32
+ :bindings="bindings"
33
+ :dense="state.dense"
34
+ :readonly="state.readonly"
35
+ :show-headers="state.showHeaders"
36
+ :show-descriptions="state.showDescriptions"
37
+ :layout="state.layout"
38
+ />
39
+ </div>
40
+ </Variant>
41
+
42
+ <template #controls>
43
+ <HstCheckbox v-model="state.dense" title="Dense" />
44
+ <HstCheckbox v-model="state.readonly" title="Readonly" />
45
+ <HstCheckbox v-model="state.showHeaders" title="Headers" />
46
+ <HstCheckbox v-model="state.showDescriptions" title="Descriptions" />
47
+ <HstSelect
48
+ v-model="state.layout"
49
+ title="Layout"
50
+ :options="[
51
+ { label: 'Grid', value: 'grid' },
52
+ { label: 'Stack', value: 'stack' },
53
+ ]"
54
+ />
55
+ </template>
56
+ </Story>
57
+ </template>