@morscherlab/mint-sdk 1.2.0-beta.4 → 1.2.0-beta.7

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 (137) hide show
  1. package/dist/{EmptyState-p90quq8Z.js → EmptyState-NBqf2_8z.js} +2 -2
  2. package/dist/{EmptyState-p90quq8Z.js.map → EmptyState-NBqf2_8z.js.map} +1 -1
  3. package/dist/{ExperimentPopover-tLwJ7XF4.js → ExperimentPopover-CfOdh5Rm.js} +3 -3
  4. package/dist/{ExperimentPopover-tLwJ7XF4.js.map → ExperimentPopover-CfOdh5Rm.js.map} +1 -1
  5. package/dist/{ExperimentSelectorModal-B6P2msT4.js → ExperimentSelectorModal-DvIagrxY.js} +4 -4
  6. package/dist/{ExperimentSelectorModal-B6P2msT4.js.map → ExperimentSelectorModal-DvIagrxY.js.map} +1 -1
  7. package/dist/{SettingsModal-CkNM8qAI.js → SettingsModal-W3TYfnP9.js} +4 -7
  8. package/dist/SettingsModal-W3TYfnP9.js.map +1 -0
  9. package/dist/__tests__/components/PluginWorkspaceView.internal.test.d.ts +1 -0
  10. package/dist/__tests__/components/SecretInput.test.d.ts +1 -0
  11. package/dist/__tests__/components/WellEditPopupInternal.test.d.ts +1 -0
  12. package/dist/components/AppAvatarMenu.vue.d.ts +4 -0
  13. package/dist/components/AppSidebar.vue.d.ts +1 -1
  14. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
  15. package/dist/components/DropdownButton.vue.d.ts +2 -0
  16. package/dist/components/FitPanel.vue.d.ts +1 -1
  17. package/dist/components/PluginWorkspaceView.props.d.ts +29 -1
  18. package/dist/components/PluginWorkspaceView.vue.d.ts +5 -5
  19. package/dist/components/SecretInput.vue.d.ts +28 -0
  20. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -1
  21. package/dist/components/index.d.ts +9 -0
  22. package/dist/components/index.js +8 -8
  23. package/dist/components/internal/WellEditPopupInternal.vue.d.ts +3 -1
  24. package/dist/components/internal/WorkspaceControlsInternal.vue.d.ts +23 -0
  25. package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +70 -0
  26. package/dist/components/internal/WorkspaceSidebarInternal.vue.d.ts +43 -0
  27. package/dist/{components-MGNL6tRn.js → components-B80gV0P6.js} +575 -561
  28. package/dist/components-B80gV0P6.js.map +1 -0
  29. package/dist/composables/flattenWorkspaceBindings.d.ts +61 -0
  30. package/dist/composables/index.d.ts +5 -0
  31. package/dist/composables/index.js +9 -9
  32. package/dist/composables/pluginWorkspaceModel.d.ts +164 -0
  33. package/dist/composables/useAsync.d.ts +5 -1
  34. package/dist/composables/useBioTemplateWorkspace.d.ts +4 -20
  35. package/dist/composables/useCommandHistory.d.ts +5 -1
  36. package/dist/composables/useFocusTrap.d.ts +21 -0
  37. package/dist/composables/useMenuKeyboard.d.ts +29 -0
  38. package/dist/composables/useOptimisticMutation.d.ts +5 -1
  39. package/dist/composables/usePluginWorkspace.d.ts +51 -0
  40. package/dist/composables/useResourceCrud.d.ts +5 -1
  41. package/dist/composables/useRovingFocus.d.ts +24 -0
  42. package/dist/composables/useWellPainting.d.ts +5 -1
  43. package/dist/composables/useWellPlateAdapter.d.ts +5 -1
  44. package/dist/composables/useWellPlateValidation.d.ts +5 -1
  45. package/dist/composables/workspaceRecipes.d.ts +188 -0
  46. package/dist/{composables-CKxOcvgh.js → composables-C8mmD5ta.js} +65 -17
  47. package/dist/composables-C8mmD5ta.js.map +1 -0
  48. package/dist/{experiment-utils-CLnzPPq5.js → experiment-utils-DV3DrvE_.js} +70 -51
  49. package/dist/experiment-utils-DV3DrvE_.js.map +1 -0
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.js +13 -13
  52. package/dist/install.js +7 -7
  53. package/dist/styles.css +100 -49
  54. package/dist/templates/builderReadUtils.d.ts +1 -1
  55. package/dist/templates/index.js +4 -4
  56. package/dist/{templates-BTJ9C9Kb.js → templates-CK72aKO1.js} +14 -23
  57. package/dist/templates-CK72aKO1.js.map +1 -0
  58. package/dist/types/form-builder.d.ts +1 -1
  59. package/dist/{useControlSchema-CXhxTul7.js → useControlSchema-_OVhavKq.js} +280 -63
  60. package/dist/useControlSchema-_OVhavKq.js.map +1 -0
  61. package/dist/{useDropdownState-DSb3BC3N.js → useDropdownState-D5G4UvWd.js} +2 -2
  62. package/dist/{useDropdownState-DSb3BC3N.js.map → useDropdownState-D5G4UvWd.js.map} +1 -1
  63. package/dist/{useExperimentSelector-D0pjj9WH.js → useExperimentSelector-DSEckf_g.js} +3 -3
  64. package/dist/{useExperimentSelector-D0pjj9WH.js.map → useExperimentSelector-DSEckf_g.js.map} +1 -1
  65. package/dist/{useFormBuilder-CuOFB3aP.js → useFormBuilder-Bd714C7y.js} +2 -2
  66. package/dist/{useFormBuilder-CuOFB3aP.js.map → useFormBuilder-Bd714C7y.js.map} +1 -1
  67. package/dist/{useProtocolTemplates-BWhQIWV5.js → useProtocolTemplates-Dq5Pzwyd.js} +481 -46
  68. package/dist/useProtocolTemplates-Dq5Pzwyd.js.map +1 -0
  69. package/dist/utils/pluginSecrets.d.ts +53 -0
  70. package/dist/utils/records.d.ts +4 -0
  71. package/package.json +1 -1
  72. package/src/__tests__/components/AppAvatarMenu.test.ts +123 -0
  73. package/src/__tests__/components/BaseTabs.test.ts +67 -0
  74. package/src/__tests__/components/DropdownButton.test.ts +134 -0
  75. package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +230 -0
  76. package/src/__tests__/components/SecretInput.test.ts +87 -0
  77. package/src/__tests__/components/WellEditPopupInternal.test.ts +108 -0
  78. package/src/__tests__/composables/usePluginClient.test.ts +140 -0
  79. package/src/__tests__/docs/frontendDocsCatalog.test.ts +22 -1
  80. package/src/components/AppAvatarMenu.vue +34 -3
  81. package/src/components/BaseModal.vue +8 -56
  82. package/src/components/BaseTabs.vue +26 -1
  83. package/src/components/BioTemplateExperimentWorkspaceView.vue +10 -21
  84. package/src/components/BioTemplatePackWorkspaceView.vue +11 -15
  85. package/src/components/BioTemplatePresetWorkspaceView.vue +8 -13
  86. package/src/components/ComponentBindingRenderer.vue +1 -3
  87. package/src/components/ControlWorkspaceView.vue +11 -22
  88. package/src/components/DoseDesignWorkspaceView.vue +6 -25
  89. package/src/components/DropdownButton.vue +32 -2
  90. package/src/components/PluginWorkspaceView.props.ts +38 -0
  91. package/src/components/PluginWorkspaceView.vue +53 -224
  92. package/src/components/SecretInput.story.vue +79 -0
  93. package/src/components/SecretInput.vue +150 -0
  94. package/src/components/index.ts +9 -0
  95. package/src/components/internal/WellEditPopupInternal.vue +20 -1
  96. package/src/components/internal/WorkspaceControlsInternal.vue +20 -0
  97. package/src/components/internal/WorkspaceShellInternal.vue +125 -0
  98. package/src/components/internal/WorkspaceSidebarInternal.vue +82 -0
  99. package/src/composables/experimentDesignData.ts +1 -3
  100. package/src/composables/flattenWorkspaceBindings.ts +104 -0
  101. package/src/composables/formBuilderRegistry.ts +5 -0
  102. package/src/composables/index.ts +26 -0
  103. package/src/composables/pluginWorkspaceModel.ts +182 -0
  104. package/src/composables/useAsync.ts +5 -1
  105. package/src/composables/useBioTemplatePackWorkspace.ts +15 -9
  106. package/src/composables/useBioTemplatePresetWorkspace.ts +14 -15
  107. package/src/composables/useBioTemplateWorkspace.ts +18 -43
  108. package/src/composables/useCommandHistory.ts +5 -1
  109. package/src/composables/useFocusTrap.ts +91 -0
  110. package/src/composables/useMenuKeyboard.ts +90 -0
  111. package/src/composables/useOptimisticMutation.ts +5 -1
  112. package/src/composables/usePluginClient.ts +42 -4
  113. package/src/composables/usePluginWorkspace.ts +208 -0
  114. package/src/composables/useResourceCrud.ts +5 -1
  115. package/src/composables/useRovingFocus.ts +66 -0
  116. package/src/composables/useWellPainting.ts +5 -1
  117. package/src/composables/useWellPlateAdapter.ts +5 -1
  118. package/src/composables/useWellPlateValidation.ts +5 -1
  119. package/src/composables/workspaceRecipes.ts +267 -0
  120. package/src/index.ts +16 -0
  121. package/src/styles/components/secret-input.css +60 -0
  122. package/src/styles/index.css +1 -0
  123. package/src/templates/builderReadUtils.ts +1 -3
  124. package/src/templates/componentTargetResolvers.ts +1 -3
  125. package/src/templates/controlSchemaTargets.ts +1 -3
  126. package/src/types/form-builder.ts +1 -0
  127. package/src/utils/formModelSync.ts +1 -3
  128. package/src/utils/pluginIcon.ts +1 -3
  129. package/src/utils/pluginSecrets.ts +102 -0
  130. package/src/utils/records.ts +9 -0
  131. package/dist/SettingsModal-CkNM8qAI.js.map +0 -1
  132. package/dist/components-MGNL6tRn.js.map +0 -1
  133. package/dist/composables-CKxOcvgh.js.map +0 -1
  134. package/dist/experiment-utils-CLnzPPq5.js.map +0 -1
  135. package/dist/templates-BTJ9C9Kb.js.map +0 -1
  136. package/dist/useControlSchema-CXhxTul7.js.map +0 -1
  137. package/dist/useProtocolTemplates-BWhQIWV5.js.map +0 -1
@@ -0,0 +1,208 @@
1
+ import { computed } from 'vue'
2
+ import type { ComputedRef } from 'vue'
3
+ import type { PageSelectorItem, PillNavItem } from '../types'
4
+ import type {
5
+ PluginWorkspaceViewEmit,
6
+ PluginWorkspaceViewProps,
7
+ } from '../components/PluginWorkspaceView.props'
8
+ import { usePluginWorkspaceControls } from '../components/PluginWorkspaceView.controls'
9
+ import { usePluginWorkspaceNavigation } from '../components/PluginWorkspaceView.navigation'
10
+ import {
11
+ getPluginWorkspaceSidebarSlotNames,
12
+ getPluginWorkspaceTopBarSlotNames,
13
+ hasPluginWorkspaceSidebarSurface,
14
+ } from '../components/PluginWorkspaceView.shell'
15
+ import type {
16
+ PluginWorkspaceControlsModel,
17
+ PluginWorkspaceShellModel,
18
+ PluginWorkspaceSidebarModel,
19
+ } from './pluginWorkspaceModel'
20
+
21
+ /**
22
+ * Loosely-typed view of a component's `useSlots()` result: only slot presence and names are
23
+ * read here, so this accepts any `defineSlots()` shape without fighting Vue's `Slot` render
24
+ * function type. Mirrors the `SlotKeyMap` helper type in PluginWorkspaceView.shell.ts.
25
+ */
26
+ export type PluginWorkspaceSlotsLike = Readonly<Record<string, unknown>>
27
+
28
+ export interface UsePluginWorkspaceOptions {
29
+ /** PluginWorkspaceView's own reactive props object (from `defineProps`). */
30
+ props: PluginWorkspaceViewProps
31
+ /** PluginWorkspaceView's own slots (from `useSlots()`), used to detect provided passthrough slots. */
32
+ slots: PluginWorkspaceSlotsLike
33
+ /** PluginWorkspaceView's own typed `emit` function (from `defineEmits<PluginWorkspaceViewEmits>()`). */
34
+ emit: PluginWorkspaceViewEmit
35
+ }
36
+
37
+ export interface UsePluginWorkspaceReturn {
38
+ /** Model consumed by WorkspaceShellInternal's `shell` prop. */
39
+ shell: ComputedRef<PluginWorkspaceShellModel>
40
+ /** Model consumed by WorkspaceSidebarInternal's `sidebar` prop. */
41
+ sidebar: ComputedRef<PluginWorkspaceSidebarModel>
42
+ /** Model consumed by WorkspaceControlsInternal's `controls` prop, and by the raw `default` slot. */
43
+ controls: ComputedRef<PluginWorkspaceControlsModel>
44
+ /** Whether AppLayout should render a sidebar surface at all. */
45
+ hasSidebarSurface: ComputedRef<boolean>
46
+ /** Applies an AppSidebar `update:model-value` payload to the shared control values and emits `update:modelValue`/`update:values`. */
47
+ handleControlValuesUpdate: (values: Record<string, unknown>) => void
48
+ /** Resolves an AppTopBar `page-selector-select` payload into the active view and emits `page-selector-select`. */
49
+ handlePageSelectorSelect: (page: PageSelectorItem) => void
50
+ /** Resolves an AppTopBar `pill-select` payload into the active view and emits `pill-select`. */
51
+ handlePillSelect: (item: PillNavItem) => void
52
+ }
53
+
54
+ /**
55
+ * Maps PluginWorkspaceView's flat 72-prop surface into the three typed model objects
56
+ * (`shell`, `sidebar`, `controls`) rendered by its internal pieces
57
+ * (WorkspaceShellInternal, WorkspaceSidebarInternal, WorkspaceControlsInternal). Reuses the
58
+ * existing `usePluginWorkspaceControls` / `usePluginWorkspaceNavigation` composables and the
59
+ * PluginWorkspaceView.shell slot-detection helpers, so it changes only how that state is
60
+ * assembled and exposed, not the underlying behavior.
61
+ *
62
+ * @example Recommended
63
+ * ```ts
64
+ * const props = withDefaults(defineProps<PluginWorkspaceViewProps>(), { ... })
65
+ * const emit = defineEmits<PluginWorkspaceViewEmits>()
66
+ * const slots = useSlots()
67
+ * const { shell, sidebar, controls } = usePluginWorkspace({ props, slots, emit })
68
+ * ```
69
+ */
70
+ export function usePluginWorkspace(options: UsePluginWorkspaceOptions): UsePluginWorkspaceReturn {
71
+ const { props, slots, emit } = options
72
+
73
+ const controlsState = usePluginWorkspaceControls({
74
+ model: () => props.model,
75
+ controls: () => props.controls,
76
+ controlOptions: () => props.controlOptions ?? {},
77
+ modelValue: () => props.modelValue,
78
+ values: () => props.values,
79
+ componentBindings: () => props.componentBindings,
80
+ componentProps: () => props.componentProps,
81
+ componentPropsById: () => props.componentPropsById,
82
+ emitValues: (values) => {
83
+ emit('update:modelValue', values)
84
+ emit('update:values', values)
85
+ },
86
+ })
87
+
88
+ const navigationState = usePluginWorkspaceNavigation({
89
+ activeView: () => props.activeView,
90
+ defaultActiveView: () => props.defaultActiveView,
91
+ currentPillId: () => props.currentPillId,
92
+ currentPageSelectorId: () => props.currentPageSelectorId,
93
+ pillNav: () => props.pillNav,
94
+ pageSelector: () => props.pageSelector,
95
+ panelIds: () => Object.keys(props.panels ?? {}),
96
+ generatedPillNav: controlsState.generatedPillNav,
97
+ generatedDefaultView: controlsState.generatedDefaultView,
98
+ emitActiveView: viewId => emit('update:activeView', viewId),
99
+ emitPageSelectorSelect: page => emit('page-selector-select', page),
100
+ emitPillSelect: item => emit('pill-select', item),
101
+ })
102
+
103
+ const topBarSlotNames = computed<string[]>(() => getPluginWorkspaceTopBarSlotNames(slots))
104
+ const sidebarSlotNames = computed<string[]>(() => getPluginWorkspaceSidebarSlotNames(slots))
105
+
106
+ const hasSidebarSurface = computed(() => hasPluginWorkspaceSidebarSurface({
107
+ showSidebar: props.showSidebar ?? true,
108
+ panels: props.panels ?? {},
109
+ forms: props.forms ?? {},
110
+ model: props.model,
111
+ controls: props.controls,
112
+ showSidebarWhenEmpty: props.showSidebarWhenEmpty ?? false,
113
+ sidebarContentId: props.sidebarContentId,
114
+ sidebarTitle: props.sidebarTitle,
115
+ sidebarSubtitle: props.sidebarSubtitle,
116
+ sidebarBadge: props.sidebarBadge,
117
+ hasSidebarSlot: Boolean(slots.sidebar),
118
+ sidebarSlotCount: sidebarSlotNames.value.length,
119
+ }))
120
+
121
+ const controls = computed<PluginWorkspaceControlsModel>(() => ({
122
+ activeView: navigationState.resolvedActiveView.value,
123
+ setActiveView: navigationState.setActiveView,
124
+ values: controlsState.internalControlValues,
125
+ componentBindings: controlsState.resolvedComponentBindings.value,
126
+ componentBindingsById: controlsState.resolvedComponentBindingsById.value,
127
+ componentProps: controlsState.resolvedComponentProps.value,
128
+ componentPropsById: controlsState.resolvedComponentPropsById.value,
129
+ }))
130
+
131
+ const shell = computed<PluginWorkspaceShellModel>(() => ({
132
+ sidebarPosition: props.sidebarPosition ?? 'left',
133
+ sidebarWidth: props.sidebarWidth,
134
+ floating: props.floating ?? true,
135
+ responsiveSidebar: props.responsiveSidebar ?? true,
136
+ sidebarToggleLabel: props.sidebarToggleLabel ?? 'Open workspace sidebar',
137
+ sidebarCloseLabel: props.sidebarCloseLabel ?? 'Close workspace sidebar',
138
+ hasSidebarSurface: hasSidebarSurface.value,
139
+ title: props.title ?? 'Workspace',
140
+ subtitle: props.subtitle,
141
+ topBarVariant: props.topBarVariant ?? 'card',
142
+ homePath: props.homePath ?? '/',
143
+ showLogo: props.showLogo ?? true,
144
+ pageSelector: props.pageSelector,
145
+ currentPageSelectorId: navigationState.resolvedCurrentPageSelectorId.value,
146
+ pluginSwitcher: props.pluginSwitcher,
147
+ pillNav: navigationState.resolvedPillNav.value,
148
+ currentPillId: navigationState.resolvedCurrentPillId.value,
149
+ showThemeToggle: props.showThemeToggle ?? true,
150
+ showSettings: props.showSettings ?? true,
151
+ settingsConfig: props.settingsConfig,
152
+ showStandaloneLabel: props.showStandaloneLabel ?? false,
153
+ standaloneLabel: props.standaloneLabel ?? 'Standalone',
154
+ accountMenu: props.accountMenu,
155
+ showNotifications: props.showNotifications ?? false,
156
+ hasNotificationDot: props.hasNotificationDot ?? false,
157
+ showAdmin: props.showAdmin ?? false,
158
+ adminPath: props.adminPath ?? '/admin',
159
+ userName: props.userName,
160
+ userInitial: props.userInitial,
161
+ userEmail: props.userEmail,
162
+ topBarSlotNames: topBarSlotNames.value,
163
+ controls: controls.value,
164
+ }))
165
+
166
+ const sidebar = computed<PluginWorkspaceSidebarModel>(() => ({
167
+ title: props.sidebarTitle,
168
+ subtitle: props.sidebarSubtitle,
169
+ badge: props.sidebarBadge,
170
+ variant: props.sidebarVariant ?? 'analysis',
171
+ panels: props.panels ?? {},
172
+ activeView: navigationState.resolvedActiveView.value,
173
+ floating: false,
174
+ dense: props.dense ?? true,
175
+ width: props.sidebarWidth,
176
+ side: props.sidebarPosition ?? 'left',
177
+ forms: props.forms ?? {},
178
+ contentId: props.sidebarContentId,
179
+ showWhenEmpty: (props.showSidebarWhenEmpty ?? false) || props.sidebarContentId !== undefined,
180
+ model: props.model,
181
+ controlSchema: props.controls,
182
+ controlOptions: props.controlOptions ?? {},
183
+ values: controlsState.internalControlValues,
184
+ formEnhancements: props.formEnhancements,
185
+ showFormActions: props.showFormActions ?? false,
186
+ formLoading: props.formLoading ?? false,
187
+ formDisabled: props.formDisabled ?? false,
188
+ formReadonly: props.formReadonly ?? false,
189
+ formSize: props.formSize ?? 'sm',
190
+ collapsible: props.sidebarCollapsible,
191
+ collapsed: props.sidebarCollapsed,
192
+ defaultCollapsed: props.defaultSidebarCollapsed ?? false,
193
+ collapsedWidth: props.sidebarCollapsedWidth ?? '3rem',
194
+ collapseButtonLabel: props.sidebarCollapseButtonLabel ?? 'Collapse sidebar',
195
+ expandButtonLabel: props.sidebarExpandButtonLabel ?? 'Expand sidebar',
196
+ slotNames: sidebarSlotNames.value,
197
+ }))
198
+
199
+ return {
200
+ shell,
201
+ sidebar,
202
+ controls,
203
+ hasSidebarSurface,
204
+ handleControlValuesUpdate: controlsState.handleControlValuesUpdate,
205
+ handlePageSelectorSelect: navigationState.handlePageSelectorSelect,
206
+ handlePillSelect: navigationState.handlePillSelect,
207
+ }
208
+ }
@@ -38,7 +38,11 @@ export interface UseResourceCrudReturn<TItem, TCreate, TUpdate, TKey extends Res
38
38
  clearError: () => void
39
39
  }
40
40
 
41
- /** Common list/create/update/delete state wrapper for generated plugin clients. */
41
+ /**
42
+ * Common list/create/update/delete state wrapper for generated plugin clients.
43
+ *
44
+ * @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `useGeneratedPluginClient()` instead.
45
+ */
42
46
  export function useResourceCrud<
43
47
  TItem,
44
48
  TCreate = Partial<TItem>,
@@ -0,0 +1,66 @@
1
+ import { nextTick } from 'vue'
2
+
3
+ export interface UseRovingFocusOptions {
4
+ /** Selectable item ids, in DOM order. Already excludes items skipped entirely (e.g. disabled tabs). */
5
+ ids: () => string[]
6
+ /** The id that is currently active/selected. */
7
+ activeId: () => string | undefined
8
+ /** Activates an id (e.g. emit `update:modelValue`). The composable focuses the matching element afterward. */
9
+ activate: (id: string) => void
10
+ /** Returns the focusable element for an id, queried lazily so it reflects the post-activation DOM. */
11
+ getElement: (id: string) => HTMLElement | null | undefined
12
+ /** Arrow-key axis: `'horizontal'` (Left/Right, default) or `'vertical'` (Up/Down). */
13
+ orientation?: 'horizontal' | 'vertical'
14
+ }
15
+
16
+ export interface UseRovingFocusReturn {
17
+ /** Bind to each item's `@keydown`. */
18
+ handleKeydown: (event: KeyboardEvent) => void
19
+ }
20
+
21
+ /**
22
+ * WAI-ARIA roving-tabindex navigation with automatic activation (the tabs pattern):
23
+ * Left/Right (or Up/Down when `orientation` is `'vertical'`) move to the adjacent item
24
+ * and activate it immediately, wrapping past the ends; Home/End jump to the first/last
25
+ * item. Only the active item should carry `tabindex="0"`; give every other item
26
+ * `tabindex="-1"`. Used by BaseTabs.
27
+ */
28
+ export function useRovingFocus(options: UseRovingFocusOptions): UseRovingFocusReturn {
29
+ const orientation = options.orientation ?? 'horizontal'
30
+ const prevKey = orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'
31
+ const nextKey = orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight'
32
+
33
+ function moveTo(id: string | undefined) {
34
+ if (id === undefined) return
35
+ options.activate(id)
36
+ void nextTick(() => options.getElement(id)?.focus())
37
+ }
38
+
39
+ function handleKeydown(event: KeyboardEvent) {
40
+ const ids = options.ids()
41
+ if (ids.length === 0) return
42
+ const currentIndex = ids.indexOf(options.activeId() ?? '')
43
+
44
+ if (event.key === 'Home') {
45
+ event.preventDefault()
46
+ moveTo(ids[0])
47
+ return
48
+ }
49
+ if (event.key === 'End') {
50
+ event.preventDefault()
51
+ moveTo(ids[ids.length - 1])
52
+ return
53
+ }
54
+ if (event.key === prevKey) {
55
+ event.preventDefault()
56
+ moveTo(ids[currentIndex <= 0 ? ids.length - 1 : currentIndex - 1])
57
+ return
58
+ }
59
+ if (event.key === nextKey) {
60
+ event.preventDefault()
61
+ moveTo(ids[currentIndex === -1 || currentIndex >= ids.length - 1 ? 0 : currentIndex + 1])
62
+ }
63
+ }
64
+
65
+ return { handleKeydown }
66
+ }
@@ -46,7 +46,11 @@ const PLATE_DIMENSIONS: Record<WellPlateFormat, { rows: number; cols: number }>
46
46
  384: { rows: 16, cols: 24 },
47
47
  }
48
48
 
49
- /** Pointer painting/rectangle selection state for WellPlate-based editors. */
49
+ /**
50
+ * Pointer painting/rectangle selection state for WellPlate-based editors.
51
+ *
52
+ * @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `useWellPlateEditor()` instead.
53
+ */
50
54
  export function useWellPainting<TMode extends string = string, TPayload = unknown>(
51
55
  options: UseWellPaintingOptions<TMode, TPayload>,
52
56
  ): UseWellPaintingReturn<TMode, TPayload> {
@@ -28,7 +28,11 @@ export interface UseWellPlateAdapterReturn {
28
28
  wells: ComputedRef<Record<string, Partial<Well>>>
29
29
  }
30
30
 
31
- /** Convert domain rows into WellPlate's wells prop without hand-written adapters. */
31
+ /**
32
+ * Convert domain rows into WellPlate's wells prop without hand-written adapters.
33
+ *
34
+ * @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `useWellPlateEditor()` instead.
35
+ */
32
36
  export function useWellPlateAdapter<TEntry>(
33
37
  options: WellPlateAdapterOptions<TEntry>,
34
38
  ): UseWellPlateAdapterReturn {
@@ -33,7 +33,11 @@ export interface UseWellPlateValidationReturn<TType extends string = string> {
33
33
  errorCount: ComputedRef<number>
34
34
  }
35
35
 
36
- /** Rule-driven validation state for WellPlate layout/design screens. */
36
+ /**
37
+ * Rule-driven validation state for WellPlate layout/design screens.
38
+ *
39
+ * @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `useWellPlateEditor()` instead.
40
+ */
37
41
  export function useWellPlateValidation<TContext, TType extends string = string>(
38
42
  options: UseWellPlateValidationOptions<TContext, TType>,
39
43
  ): UseWellPlateValidationReturn<TType> {
@@ -0,0 +1,267 @@
1
+ import type { TopBarVariant } from '../types'
2
+ import type { FormEnhancements } from '../types/form-builder'
3
+ import type {
4
+ ControlModel,
5
+ ControlModelBinding,
6
+ ControlSchema,
7
+ ControlWorkspaceOptions,
8
+ UseControlWorkspaceReturn,
9
+ } from './useControlSchema'
10
+ import { mergeControlWorkspaceOptions, resolveControlModel } from './useControlSchema'
11
+
12
+ /**
13
+ * Merges `initialValues`/external `modelValue`/`values` on top of a base
14
+ * `ControlWorkspaceOptions.initialValues`, matching the layering FormBuilder expects: base
15
+ * defaults, then caller-supplied initial values, then live external values win last. Shared by
16
+ * {@link controlWorkspaceProps} and {@link bioTemplatePresetWorkspaceProps} so both "generate a
17
+ * workspace from options, initialValues, and v-model" views merge options identically.
18
+ */
19
+ export function resolveControlOptionsWithExternalValues<TOptions extends ControlWorkspaceOptions>(
20
+ base: TOptions,
21
+ initialValues: Record<string, unknown> | undefined,
22
+ externalValues: Record<string, unknown> | undefined,
23
+ ): TOptions {
24
+ if (initialValues === undefined && externalValues === undefined) return base
25
+
26
+ return {
27
+ ...base,
28
+ initialValues: {
29
+ ...(base.initialValues ?? {}),
30
+ ...(initialValues ?? {}),
31
+ ...(externalValues ?? {}),
32
+ },
33
+ }
34
+ }
35
+
36
+ export interface ControlWorkspaceModelProps {
37
+ model?: ControlModel | ControlModelBinding
38
+ controls?: ControlSchema
39
+ controlOptions?: ControlWorkspaceOptions
40
+ initialValues?: Record<string, unknown>
41
+ modelValue?: Record<string, unknown>
42
+ values?: Record<string, unknown>
43
+ }
44
+
45
+ export interface ResolvedControlWorkspaceModel {
46
+ /** `modelValue` if provided, else `values` — the single external v-model source of truth. */
47
+ externalValues: Record<string, unknown> | undefined
48
+ /** `model` resolved through `resolveControlModel()`, or undefined for a raw `controls` schema. */
49
+ resolvedModel: ControlModelBinding | undefined
50
+ /** `controls` if provided, else the resolved model's controls, else `{}`. */
51
+ resolvedControls: ControlSchema
52
+ /** `controlOptions` merged over the resolved model's own options, with initialValues/external values layered on top. */
53
+ resolvedControlOptions: ControlWorkspaceOptions
54
+ }
55
+
56
+ /**
57
+ * Resolves ControlWorkspaceView's `model`/`controls`/`controlOptions`/`initialValues`/
58
+ * `modelValue`/`values` props into the schema + options pair `useControlWorkspace()` needs.
59
+ * Pure and framework-free so callers wrap each field in their own `computed()`.
60
+ *
61
+ * @example Recommended
62
+ * ```ts
63
+ * const resolved = computed(() => controlWorkspaceProps(props))
64
+ * const resolvedControls = computed(() => resolved.value.resolvedControls)
65
+ * ```
66
+ */
67
+ export function controlWorkspaceProps(props: ControlWorkspaceModelProps): ResolvedControlWorkspaceModel {
68
+ const externalValues = props.modelValue ?? props.values
69
+ const resolvedModel = resolveControlModel(props.model)
70
+ const resolvedControls: ControlSchema = props.controls ?? resolvedModel?.controls ?? {}
71
+ const baseControlOptions = mergeControlWorkspaceOptions(resolvedModel?.controlOptions, props.controlOptions)
72
+ const resolvedControlOptions = resolveControlOptionsWithExternalValues(
73
+ baseControlOptions,
74
+ props.initialValues,
75
+ externalValues,
76
+ )
77
+
78
+ return { externalValues, resolvedModel, resolvedControls, resolvedControlOptions }
79
+ }
80
+
81
+ export interface DoseDesignWorkspacePassthroughProps {
82
+ workspace?: UseControlWorkspaceReturn<ControlSchema>
83
+ controlOptions?: ControlWorkspaceOptions
84
+ initialValues?: Record<string, unknown>
85
+ modelValue?: Record<string, unknown>
86
+ values?: Record<string, unknown>
87
+ title?: string
88
+ subtitle?: string
89
+ topBarVariant?: TopBarVariant
90
+ sidebarWidth?: string
91
+ sidebarVariant?: 'default' | 'analysis'
92
+ responsiveSidebar?: boolean
93
+ sidebarPosition?: 'left' | 'right'
94
+ sidebarTitle?: string
95
+ sidebarSubtitle?: string
96
+ sidebarBadge?: string | number
97
+ floating?: boolean
98
+ dense?: boolean
99
+ showSettings?: boolean
100
+ showFormActions?: boolean
101
+ formEnhancements?: FormEnhancements<Record<string, unknown>>
102
+ formLoading?: boolean
103
+ formDisabled?: boolean
104
+ formReadonly?: boolean
105
+ formSize?: 'sm' | 'md' | 'lg'
106
+ }
107
+
108
+ /**
109
+ * Maps DoseDesignWorkspaceView's props straight through to the identically-named
110
+ * ControlWorkspaceView props it wraps — the ~25-key pass-through list that used to be spelled
111
+ * out attribute-by-attribute in the template. `model` is the caller's already-resolved dose
112
+ * design model, not the raw (possibly absent) `props.model`.
113
+ *
114
+ * @example Recommended
115
+ * ```vue
116
+ * <ControlWorkspaceView v-bind="doseDesignWorkspaceProps(props, resolvedModel)" />
117
+ * ```
118
+ */
119
+ export function doseDesignWorkspaceProps(
120
+ props: DoseDesignWorkspacePassthroughProps,
121
+ model: ControlModel | ControlModelBinding,
122
+ ) {
123
+ return {
124
+ model,
125
+ workspace: props.workspace,
126
+ controlOptions: props.controlOptions,
127
+ initialValues: props.initialValues,
128
+ modelValue: props.modelValue,
129
+ values: props.values,
130
+ title: props.title,
131
+ subtitle: props.subtitle,
132
+ topBarVariant: props.topBarVariant,
133
+ sidebarWidth: props.sidebarWidth,
134
+ sidebarVariant: props.sidebarVariant,
135
+ responsiveSidebar: props.responsiveSidebar,
136
+ sidebarPosition: props.sidebarPosition,
137
+ sidebarTitle: props.sidebarTitle,
138
+ sidebarSubtitle: props.sidebarSubtitle,
139
+ sidebarBadge: props.sidebarBadge,
140
+ floating: props.floating,
141
+ dense: props.dense,
142
+ showSettings: props.showSettings,
143
+ showFormActions: props.showFormActions,
144
+ formEnhancements: props.formEnhancements,
145
+ formLoading: props.formLoading,
146
+ formDisabled: props.formDisabled,
147
+ formReadonly: props.formReadonly,
148
+ formSize: props.formSize,
149
+ }
150
+ }
151
+
152
+ export interface BioTemplateExperimentWorkspaceStatusProps {
153
+ status?: {
154
+ loading?: boolean
155
+ saving?: boolean
156
+ error?: string | null
157
+ hasExperiment?: boolean
158
+ currentExperimentId?: number
159
+ lastLoadedAt?: Date | null
160
+ lastSavedAt?: Date | null
161
+ }
162
+ loading?: boolean
163
+ error?: string | null
164
+ hasExperiment?: boolean
165
+ currentExperimentId?: number
166
+ lastLoadedAt?: Date | null
167
+ lastSavedAt?: Date | null
168
+ }
169
+
170
+ export interface ResolvedBioTemplateExperimentStatus {
171
+ loading: boolean
172
+ error: string | null | undefined
173
+ hasExperiment: boolean
174
+ currentExperimentId: number | undefined
175
+ lastLoadedAt: Date | null | undefined
176
+ lastSavedAt: Date | null | undefined
177
+ }
178
+
179
+ /**
180
+ * Merges BioTemplateExperimentWorkspaceView's grouped `status` prop over its individual
181
+ * `loading`/`error`/`hasExperiment`/... props: `status` wins whenever the corresponding field
182
+ * on it is set, otherwise the flat prop (or its default) is used. Centralizes the six
183
+ * independent fallback rules generated plugin pages rely on when they pass a request/experiment
184
+ * `status` object instead of wiring each field by hand.
185
+ */
186
+ export function bioTemplateExperimentWorkspaceProps(
187
+ props: BioTemplateExperimentWorkspaceStatusProps,
188
+ ): ResolvedBioTemplateExperimentStatus {
189
+ const status = props.status
190
+ return {
191
+ loading: status?.loading !== undefined || status?.saving !== undefined
192
+ ? Boolean(status?.loading) || Boolean(status?.saving)
193
+ : Boolean(props.loading),
194
+ error: status?.error !== undefined ? status.error : props.error,
195
+ hasExperiment: status?.hasExperiment ?? props.hasExperiment ?? false,
196
+ currentExperimentId: status?.currentExperimentId ?? props.currentExperimentId,
197
+ lastLoadedAt: status?.lastLoadedAt !== undefined ? status.lastLoadedAt : props.lastLoadedAt,
198
+ lastSavedAt: status?.lastSavedAt !== undefined ? status.lastSavedAt : props.lastSavedAt,
199
+ }
200
+ }
201
+
202
+ export interface BioTemplatePackWorkspacePassthroughProps {
203
+ message?: string
204
+ dense?: boolean
205
+ readonly?: boolean
206
+ showStatus?: boolean
207
+ showTemplateSummary?: boolean
208
+ loadLabel?: string
209
+ resetLabel?: string
210
+ saveLabel?: string
211
+ }
212
+
213
+ /**
214
+ * Assembles the prop object BioTemplatePackWorkspaceView binds onto the
215
+ * BioTemplateExperimentWorkspaceView it wraps: its own display props passed straight through,
216
+ * plus the pack-derived `target`/`label`/`status`/`actions` computed by the caller.
217
+ */
218
+ export function bioTemplatePackWorkspaceProps<TTarget, TStatus, TActions>(
219
+ props: BioTemplatePackWorkspacePassthroughProps,
220
+ derived: {
221
+ target: TTarget
222
+ label: string
223
+ status: TStatus
224
+ actions: TActions
225
+ },
226
+ ) {
227
+ return {
228
+ target: derived.target,
229
+ kind: 'collection' as const,
230
+ label: derived.label,
231
+ message: props.message,
232
+ status: derived.status,
233
+ actions: derived.actions,
234
+ dense: props.dense,
235
+ readonly: props.readonly,
236
+ showStatus: props.showStatus,
237
+ showTemplateSummary: props.showTemplateSummary,
238
+ loadLabel: props.loadLabel,
239
+ resetLabel: props.resetLabel,
240
+ saveLabel: props.saveLabel,
241
+ }
242
+ }
243
+
244
+ export interface BioTemplatePresetWorkspaceOptionsProps<TOptions extends ControlWorkspaceOptions> {
245
+ workspaceOptions?: TOptions
246
+ initialValues?: Record<string, unknown>
247
+ modelValue?: Record<string, unknown>
248
+ values?: Record<string, unknown>
249
+ }
250
+
251
+ /**
252
+ * Resolves BioTemplatePresetWorkspaceView's `workspaceOptions`/`initialValues`/`modelValue`/
253
+ * `values` props into the single options object `useBioTemplatePresetWorkspace()` needs,
254
+ * reusing the same base/initialValues/external-values layering as {@link controlWorkspaceProps}.
255
+ */
256
+ export function bioTemplatePresetWorkspaceProps<TOptions extends ControlWorkspaceOptions>(
257
+ props: BioTemplatePresetWorkspaceOptionsProps<TOptions>,
258
+ ): { externalValues: Record<string, unknown> | undefined; resolvedWorkspaceOptions: TOptions } {
259
+ const externalValues = props.modelValue ?? props.values
260
+ const resolvedWorkspaceOptions = resolveControlOptionsWithExternalValues(
261
+ (props.workspaceOptions ?? {}) as TOptions,
262
+ props.initialValues,
263
+ externalValues,
264
+ )
265
+
266
+ return { externalValues, resolvedWorkspaceOptions }
267
+ }
package/src/index.ts CHANGED
@@ -41,6 +41,22 @@ export {
41
41
  deriveShade,
42
42
  type Hsl,
43
43
  } from './utils/color'
44
+ export {
45
+ PLUGIN_SECRET_FORMAT_KEY,
46
+ PLUGIN_SECRET_FORMAT_REF,
47
+ PLUGIN_SECRET_REF_KEY,
48
+ clearPluginSecret,
49
+ isPluginSecretLocked,
50
+ isPluginSecretRef,
51
+ keepPluginSecret,
52
+ pluginSecretId,
53
+ pluginSecretLabel,
54
+ pluginSecretState,
55
+ setPluginSecret,
56
+ usesPluginSecretReferences,
57
+ type PluginSecretRef,
58
+ type PluginSecretState,
59
+ } from './utils/pluginSecrets'
44
60
  export {
45
61
  LCMS_DEFAULT_CONTROL_POSITIONS,
46
62
  createLcmsControlWellEditData,
@@ -0,0 +1,60 @@
1
+ /* SecretInput — plugin secret settings field with a set / not set / environment badge. */
2
+
3
+ .mint-secret-input {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: 0.5rem;
7
+ }
8
+
9
+ .mint-secret-input__status {
10
+ display: flex;
11
+ align-items: center;
12
+ gap: 0.5rem;
13
+ flex-wrap: wrap;
14
+ }
15
+
16
+ .mint-secret-input__badge {
17
+ display: inline-flex;
18
+ align-items: center;
19
+ border-radius: 999px;
20
+ padding: 0.125rem 0.5rem;
21
+ font-size: 0.75rem;
22
+ font-weight: 500;
23
+ line-height: 1.4;
24
+ border: 1px solid transparent;
25
+ background-color: var(--bg-tertiary);
26
+ color: var(--text-secondary);
27
+ }
28
+
29
+ .mint-secret-input__badge--set {
30
+ background-color: color-mix(in srgb, var(--mint-success) 12%, transparent);
31
+ border-color: color-mix(in srgb, var(--mint-success) 35%, transparent);
32
+ color: var(--mint-success);
33
+ }
34
+
35
+ .mint-secret-input__badge--environment {
36
+ background-color: color-mix(in srgb, var(--mint-info) 12%, transparent);
37
+ border-color: color-mix(in srgb, var(--mint-info) 35%, transparent);
38
+ color: var(--mint-info);
39
+ }
40
+
41
+ .mint-secret-input__badge--pending {
42
+ background-color: color-mix(in srgb, var(--mint-warning) 12%, transparent);
43
+ border-color: color-mix(in srgb, var(--mint-warning) 35%, transparent);
44
+ color: var(--mint-warning);
45
+ }
46
+
47
+ .mint-secret-input__hint {
48
+ font-size: 0.75rem;
49
+ color: var(--text-muted);
50
+ }
51
+
52
+ .mint-secret-input__actions {
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 0.5rem;
56
+ }
57
+
58
+ .mint-secret-input--error .mint-secret-input__badge {
59
+ border-color: color-mix(in srgb, var(--mint-error) 45%, transparent);
60
+ }
@@ -30,6 +30,7 @@
30
30
  @import './components/radio-group.css';
31
31
  @import './components/sample-legend.css';
32
32
  @import './components/section-card.css';
33
+ @import './components/secret-input.css';
33
34
  @import './components/select.css';
34
35
  @import './components/skeleton.css';
35
36
  @import './components/slider.css';