@morscherlab/mint-sdk 1.1.5 → 1.1.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 (100) hide show
  1. package/dist/{EmptyState-LMPS3keb.js → EmptyState-p90quq8Z.js} +2 -2
  2. package/dist/{EmptyState-LMPS3keb.js.map → EmptyState-p90quq8Z.js.map} +1 -1
  3. package/dist/{ExperimentPopover-D6_RGzTY.js → ExperimentPopover-tLwJ7XF4.js} +3 -3
  4. package/dist/{ExperimentPopover-D6_RGzTY.js.map → ExperimentPopover-tLwJ7XF4.js.map} +1 -1
  5. package/dist/{ExperimentSelectorModal-DcQ38zua.js → ExperimentSelectorModal-B6P2msT4.js} +12 -5
  6. package/dist/ExperimentSelectorModal-B6P2msT4.js.map +1 -0
  7. package/dist/{SettingsModal-Ccq2z-Ms.js → SettingsModal-D7LFUokT.js} +100 -156
  8. package/dist/SettingsModal-D7LFUokT.js.map +1 -0
  9. package/dist/__tests__/components/ActionMenu.test.d.ts +1 -0
  10. package/dist/__tests__/components/BaseModal.test.d.ts +1 -0
  11. package/dist/components/ActionMenu.vue.d.ts +21 -0
  12. package/dist/components/BaseModal.vue.d.ts +17 -1
  13. package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
  14. package/dist/components/GeneratedPluginView.vue.d.ts +3 -3
  15. package/dist/components/GeneratedResultRenderer.vue.d.ts +3 -3
  16. package/dist/components/RackEditor.vue.d.ts +1 -1
  17. package/dist/components/SampleLegend.vue.d.ts +1 -1
  18. package/dist/components/SmartGroupManual.vue.d.ts +1 -1
  19. package/dist/components/index.d.ts +1 -0
  20. package/dist/components/index.js +8 -8
  21. package/dist/{components-CV5zuWa8.js → components-CDomhGSj.js} +477 -71
  22. package/dist/components-CDomhGSj.js.map +1 -0
  23. package/dist/composables/controlSchemaDoseDesign.d.ts +8 -1
  24. package/dist/composables/controlSchemaModel.d.ts +10 -1
  25. package/dist/composables/index.js +8 -8
  26. package/dist/composables/useAnalysisArtifacts.d.ts +9 -1
  27. package/dist/composables/useAppExperiment.d.ts +7 -0
  28. package/dist/composables/useBioTemplatePackWorkspace.d.ts +9 -1
  29. package/dist/composables/useControlWorkspace.d.ts +9 -1
  30. package/dist/composables/useGeneratedAnalysis.d.ts +9 -0
  31. package/dist/{composables-CW8NRXLB.js → composables-CRR_JxQD.js} +4 -4
  32. package/dist/{composables-CW8NRXLB.js.map → composables-CRR_JxQD.js.map} +1 -1
  33. package/dist/{experiment-utils-7Yw-h0tD.js → experiment-utils-CLnzPPq5.js} +170 -13
  34. package/dist/experiment-utils-CLnzPPq5.js.map +1 -0
  35. package/dist/index.js +13 -13
  36. package/dist/install.js +7 -7
  37. package/dist/styles.css +764 -610
  38. package/dist/templates/index.js +4 -4
  39. package/dist/{templates-CJRFiWUX.js → templates-Cr-48-u2.js} +2 -2
  40. package/dist/{templates-CJRFiWUX.js.map → templates-Cr-48-u2.js.map} +1 -1
  41. package/dist/types/components.d.ts +32 -0
  42. package/dist/types/index.d.ts +1 -1
  43. package/dist/{useControlSchema-BEAgLyyx.js → useControlSchema-BMVb2XIQ.js} +58 -10
  44. package/dist/useControlSchema-BMVb2XIQ.js.map +1 -0
  45. package/dist/{useDropdownState-Be-ug_ci.js → useDropdownState-DSb3BC3N.js} +2 -2
  46. package/dist/{useDropdownState-Be-ug_ci.js.map → useDropdownState-DSb3BC3N.js.map} +1 -1
  47. package/dist/{useExperimentSelector-DFg_iEHI.js → useExperimentSelector-D0pjj9WH.js} +3 -3
  48. package/dist/{useExperimentSelector-DFg_iEHI.js.map → useExperimentSelector-D0pjj9WH.js.map} +1 -1
  49. package/dist/{useFormBuilder-CR9pBApW.js → useFormBuilder-DG3HOFGA.js} +2 -2
  50. package/dist/{useFormBuilder-CR9pBApW.js.map → useFormBuilder-DG3HOFGA.js.map} +1 -1
  51. package/dist/{useProtocolTemplates-DXeIHMwV.js → useProtocolTemplates-BMt0LlXf.js} +21 -6
  52. package/dist/useProtocolTemplates-BMt0LlXf.js.map +1 -0
  53. package/package.json +2 -1
  54. package/src/__tests__/components/ActionMenu.test.ts +120 -0
  55. package/src/__tests__/components/BaseModal.test.ts +133 -0
  56. package/src/__tests__/components/SettingsModal.test.ts +9 -7
  57. package/src/__tests__/docs/extractDocsComponents.test.ts +15 -1
  58. package/src/__tests__/docs/extractDocsParsing.test.ts +22 -0
  59. package/src/__tests__/docs/frontendDocsCatalog.test.ts +80 -0
  60. package/src/components/ActionMenu.story.vue +26 -0
  61. package/src/components/ActionMenu.vue +209 -0
  62. package/src/components/AppSidebar.vue +2 -13
  63. package/src/components/ArtifactSelectorModal.vue +8 -1
  64. package/src/components/BaseModal.story.vue +107 -1
  65. package/src/components/BaseModal.vue +152 -4
  66. package/src/components/BioTemplateExperimentWorkspaceView.vue +8 -1
  67. package/src/components/BioTemplatePackWorkspaceView.vue +8 -1
  68. package/src/components/BioTemplatePresetWorkspaceView.vue +8 -1
  69. package/src/components/BioTemplateRenderer.vue +8 -1
  70. package/src/components/ComponentBindingRenderer.vue +8 -1
  71. package/src/components/ControlWorkspaceView.vue +8 -1
  72. package/src/components/DoseDesignWorkspaceView.vue +8 -1
  73. package/src/components/ExperimentSelectorModal.vue +8 -1
  74. package/src/components/FormBuilder.vue +5 -0
  75. package/src/components/GeneratedPluginView.vue +12 -2
  76. package/src/components/GeneratedResultRenderer.vue +12 -2
  77. package/src/components/JobsStatusTray.vue +8 -1
  78. package/src/components/PluginWorkspaceView.vue +10 -1
  79. package/src/components/SettingsModal.vue +102 -149
  80. package/src/components/index.ts +1 -0
  81. package/src/composables/controlSchemaDoseDesign.ts +8 -1
  82. package/src/composables/controlSchemaModel.ts +10 -1
  83. package/src/composables/useAnalysisArtifacts.ts +9 -1
  84. package/src/composables/useAppExperiment.ts +7 -0
  85. package/src/composables/useBioTemplatePackWorkspace.ts +9 -1
  86. package/src/composables/useControlWorkspace.ts +9 -1
  87. package/src/composables/useGeneratedAnalysis.ts +9 -0
  88. package/src/styles/components/action-menu.css +59 -0
  89. package/src/styles/components/icon-button.css +7 -3
  90. package/src/styles/components/modal.css +293 -0
  91. package/src/styles/components/settings-modal.css +3 -273
  92. package/src/styles/index.css +1 -0
  93. package/src/types/components.ts +38 -0
  94. package/src/types/index.ts +3 -0
  95. package/dist/ExperimentSelectorModal-DcQ38zua.js.map +0 -1
  96. package/dist/SettingsModal-Ccq2z-Ms.js.map +0 -1
  97. package/dist/components-CV5zuWa8.js.map +0 -1
  98. package/dist/experiment-utils-7Yw-h0tD.js.map +0 -1
  99. package/dist/useControlSchema-BEAgLyyx.js.map +0 -1
  100. package/dist/useProtocolTemplates-DXeIHMwV.js.map +0 -1
@@ -1,5 +1,12 @@
1
1
  <script setup lang="ts">
2
- /** Modal for searching and selecting an experiment from the platform list with filters and keyboard nav. */
2
+ /**
3
+ * Modal for searching and selecting an experiment from the platform list with filters and keyboard nav.
4
+ *
5
+ * @example Recommended
6
+ * ```vue
7
+ * <ExperimentSelectorModal v-model="open" :allowed-experiment-types="['mass-spectrometry']" />
8
+ * ```
9
+ */
3
10
  import { ref, reactive, computed, watch, nextTick } from 'vue'
4
11
  import type { ModalSize, ExperimentSummary, ExperimentFilters } from '../types'
5
12
  import { useExperimentSelector } from '../composables/useExperimentSelector'
@@ -21,6 +21,11 @@
21
21
  * - `section:<id>` — replace an entire section body
22
22
  * - `section:<id>:after` — inject content after a section
23
23
  * - `actions` — replace the default FormActions bar when `showActions` is true
24
+ *
25
+ * @example Recommended
26
+ * ```vue
27
+ * <FormBuilder v-model="values" :model="formModel" />
28
+ * ```
24
29
  */
25
30
  import { ref, computed, watch } from 'vue'
26
31
  import type { FormSchema, FormEnhancements, UseFormBuilderReturn } from '../types/form-builder'
@@ -1,4 +1,12 @@
1
1
  <script setup lang="ts">
2
+ /**
3
+ * Standard generated workspace for a plugin that exposes an SDK job manifest.
4
+ *
5
+ * @example Recommended
6
+ * ```vue
7
+ * <GeneratedPluginView base-url="/api/peak-qc" />
8
+ * ```
9
+ */
2
10
  import { computed, onMounted, reactive, ref, watch } from 'vue'
3
11
 
4
12
  import { generatedJobDefaults, generatedJobFormSchema, normalizeGeneratedJobInput } from '../generated/schema'
@@ -20,11 +28,13 @@ import JobsStatusTray from './JobsStatusTray.vue'
20
28
  import LoadingSpinner from './LoadingSpinner.vue'
21
29
  import PluginWorkspaceView from './PluginWorkspaceView.vue'
22
30
 
23
- const props = withDefaults(defineProps<{
31
+ interface Props {
24
32
  baseUrl: string
25
33
  showStandaloneLabel?: boolean
26
34
  transport?: GeneratedAnalysisTransport
27
- }>(), {
35
+ }
36
+
37
+ const props = withDefaults(defineProps<Props>(), {
28
38
  showStandaloneLabel: false,
29
39
  })
30
40
 
@@ -1,4 +1,12 @@
1
1
  <script setup lang="ts">
2
+ /**
3
+ * Render standardized text, table, image, Plotly, JSON, and artifact job results.
4
+ *
5
+ * @example Recommended
6
+ * ```vue
7
+ * <GeneratedResultRenderer :result="result" />
8
+ * ```
9
+ */
2
10
  import { computed } from 'vue'
3
11
 
4
12
  import type { DataFrameColumn } from '../types'
@@ -7,9 +15,11 @@ import ChartContainer from './ChartContainer.vue'
7
15
  import DataFrame from './DataFrame.vue'
8
16
  import GeneratedPlotlyResult from './GeneratedPlotlyResult.vue'
9
17
 
10
- const props = defineProps<{
18
+ interface Props {
11
19
  result: GeneratedAnalysisResult
12
- }>()
20
+ }
21
+
22
+ const props = defineProps<Props>()
13
23
 
14
24
  const tableColumns = computed<DataFrameColumn[]>(() => {
15
25
  if (props.result.kind !== 'table') return []
@@ -1,5 +1,12 @@
1
1
  <script setup lang="ts">
2
- /** Floating, source-driven job status tray for plugin and platform workflows. */
2
+ /**
3
+ * Floating, source-driven job status tray for plugin and platform workflows.
4
+ *
5
+ * @example Recommended
6
+ * ```vue
7
+ * <JobsStatusTray :source="jobs" />
8
+ * ```
9
+ */
3
10
  import { computed, nextTick, ref, toRef, useId } from 'vue'
4
11
 
5
12
  import {
@@ -1,5 +1,14 @@
1
1
  <script setup lang="ts">
2
- /** Standard plugin workspace shell that combines AppLayout, AppTopBar, and AppSidebar with LEAF-style defaults. */
2
+ /**
3
+ * Standard plugin workspace shell that combines AppLayout, AppTopBar, and AppSidebar with LEAF-style defaults.
4
+ *
5
+ * @example Recommended
6
+ * ```vue
7
+ * <PluginWorkspaceView title="Analysis" experiment-shell>
8
+ * <router-view />
9
+ * </PluginWorkspaceView>
10
+ * ```
11
+ */
3
12
  import { computed, useSlots } from 'vue'
4
13
  import type {
5
14
  AccountMenuItem,
@@ -17,6 +17,11 @@
17
17
  *
18
18
  * Layout: `horizontal` (underline tabs, default) or `vertical` (sidebar rail
19
19
  * with optional icons + descriptions, recommended for 5+ groups).
20
+ *
21
+ * @example Recommended
22
+ * ```vue
23
+ * <SettingsModal v-model="open" v-model:values="values" :model="settingsModel" />
24
+ * ```
20
25
  */
21
26
  import { ref, computed, watch } from 'vue'
22
27
  import BaseModal from './BaseModal.vue'
@@ -45,6 +50,8 @@ import type {
45
50
  ThemeMode,
46
51
  ColorPalette,
47
52
  TableDensity,
53
+ ModalLayout,
54
+ ModalTab,
48
55
  SettingsTab,
49
56
  SettingsTabInput,
50
57
  SettingsModalLayout,
@@ -107,11 +114,6 @@ const { user: platformUser } = usePlatformContext()
107
114
 
108
115
  const isVertical = computed(() => props.layout === 'vertical')
109
116
 
110
- // Per-instance prefix for ARIA tab/tabpanel id wiring. Stable across renders.
111
- const uid = `mint-settings-${Math.random().toString(36).slice(2, 9)}`
112
- const tabId = (id: string) => `${uid}-tab-${id}`
113
- const panelId = (id: string) => `${uid}-panel-${id}`
114
-
115
117
  const resolvedModel = computed<ControlModelBinding | undefined>(() => {
116
118
  return resolveControlModel(props.model)
117
119
  })
@@ -223,8 +225,21 @@ const allTabs = computed<SettingsTab[]>(() => {
223
225
 
224
226
  const activeTab = ref(allTabs.value[0]?.id || APPEARANCE_TAB_ID)
225
227
 
226
- const activeTabMeta = computed<SettingsTab | undefined>(() =>
227
- allTabs.value.find((t) => t.id === activeTab.value),
228
+ // BaseModal owns the tab chrome: 'vertical' keeps its public name and maps to
229
+ // the shell rail; horizontal maps to the shell strip (plain when single-tab).
230
+ const baseLayout = computed<ModalLayout>(() => {
231
+ if (isVertical.value) return 'rail'
232
+ return allTabs.value.length > 1 ? 'tabs' : 'plain'
233
+ })
234
+
235
+ const modalTabs = computed<ModalTab[]>(() =>
236
+ allTabs.value.map((tab) => ({
237
+ id: tab.id,
238
+ label: tab.label,
239
+ icon: tab.icon,
240
+ description: tab.description,
241
+ meta: tab.meta,
242
+ })),
228
243
  )
229
244
 
230
245
  // If the active tab vanishes (group hidden by condition), drop back to the first available.
@@ -278,166 +293,104 @@ function canShowForCurrentUser(item: AccessControlled): boolean {
278
293
  :model-value="modelValue"
279
294
  :title="title"
280
295
  :size="size"
296
+ :layout="baseLayout"
297
+ :tabs="modalTabs"
298
+ :active-tab="activeTab"
299
+ @update:active-tab="activeTab = $event"
281
300
  @update:model-value="emit('update:modelValue', $event)"
282
301
  @close="handleClose"
283
302
  >
284
- <div :class="['mint-settings-modal', `mint-settings-modal--${layout}`]">
303
+ <!-- Body: the active pane only — tab strip / rail / footer are BaseModal's shell. -->
304
+ <div class="mint-settings-modal">
285
305
  <div
286
- v-if="!isVertical && allTabs.length > 1"
287
- class="mint-settings-modal__tabs"
306
+ v-if="isSchemaDriven && activeGroup"
307
+ class="mint-settings-modal__group-grid"
308
+ :style="{ '--mint-settings-cols': activeGroup.columns ?? 1 }"
288
309
  >
289
- <button
290
- v-for="tab in allTabs"
291
- :key="tab.id"
292
- type="button"
293
- :class="['mint-settings-modal__tab', { 'mint-settings-modal__tab--active': activeTab === tab.id }]"
294
- @click="activeTab = tab.id"
295
- >
296
- {{ tab.label }}
297
- </button>
310
+ <template v-for="field in activeGroupVisibleFields" :key="field.name">
311
+ <div :style="field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined">
312
+ <slot
313
+ :name="`field:${field.name}`"
314
+ :field="field"
315
+ :form="builder.form"
316
+ :field-props="builder.form.getFieldProps(field.name)"
317
+ >
318
+ <FormFieldRendererInternal
319
+ :field="field"
320
+ :resolved-props="builder.getResolvedFieldProps(field)"
321
+ :form="builder.form"
322
+ />
323
+ </slot>
324
+ </div>
325
+ </template>
298
326
  </div>
299
327
 
300
- <div
301
- v-else-if="isVertical"
302
- class="mint-settings-modal__rail"
303
- role="tablist"
304
- aria-orientation="vertical"
305
- aria-label="Settings sections"
306
- >
307
- <button
308
- v-for="tab in allTabs"
309
- :id="tabId(tab.id)"
310
- :key="tab.id"
311
- type="button"
312
- role="tab"
313
- :class="['mint-settings-modal__rail-item', { 'mint-settings-modal__rail-item--active': activeTab === tab.id }]"
314
- :aria-selected="activeTab === tab.id"
315
- :aria-controls="panelId(tab.id)"
316
- :tabindex="activeTab === tab.id ? 0 : -1"
317
- @click="activeTab = tab.id"
318
- >
319
- <span class="mint-settings-modal__rail-item-icon" aria-hidden="true">
320
- <span v-if="tab.icon" v-html="tab.icon" />
321
- </span>
322
- <span class="mint-settings-modal__rail-item-text">
323
- <span class="mint-settings-modal__rail-item-label">{{ tab.label }}</span>
324
- <span v-if="tab.description" class="mint-settings-modal__rail-item-description">
325
- {{ tab.description }}
326
- </span>
327
- </span>
328
- </button>
329
- </div>
328
+ <template v-else-if="!isSchemaDriven">
329
+ <template v-for="tab in manualTabs" :key="tab.id">
330
+ <div v-show="activeTab === tab.id">
331
+ <slot :name="`tab-${tab.id}`" />
332
+ </div>
333
+ </template>
334
+ </template>
335
+
336
+ <div v-if="showAppearance" v-show="activeTab === APPEARANCE_TAB_ID">
337
+ <div class="mint-settings-modal__section">
338
+ <div class="mint-settings-modal__section-label">Theme</div>
339
+ <div class="mint-settings-modal__option-group">
340
+ <button
341
+ v-for="opt in themeOptions"
342
+ :key="opt.value"
343
+ type="button"
344
+ :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.theme === opt.value }]"
345
+ @click="settings.theme = opt.value"
346
+ >
347
+ {{ opt.label }}
348
+ </button>
349
+ </div>
350
+ </div>
330
351
 
331
- <!-- Vertical mode wraps content in a tabpanel <section> with a header bar; horizontal mode stays a plain <div>. -->
332
- <component
333
- :is="isVertical ? 'section' : 'div'"
334
- :class="isVertical ? 'mint-settings-modal__pane' : 'mint-settings-modal__content'"
335
- :id="isVertical && activeTabMeta ? panelId(activeTabMeta.id) : undefined"
336
- :role="isVertical ? 'tabpanel' : undefined"
337
- :aria-labelledby="isVertical && activeTabMeta ? tabId(activeTabMeta.id) : undefined"
338
- :tabindex="isVertical ? 0 : undefined"
339
- >
340
- <header
341
- v-if="isVertical && activeTabMeta?.label"
342
- class="mint-settings-modal__pane-header"
343
- >
344
- <h4 class="mint-settings-modal__pane-title">{{ activeTabMeta.label }}</h4>
345
- <p v-if="activeTabMeta.description" class="mint-settings-modal__pane-subtitle">
346
- {{ activeTabMeta.description }}
347
- </p>
348
- </header>
349
-
350
- <div :class="isVertical ? 'mint-settings-modal__pane-body' : null">
351
- <div
352
- v-if="isSchemaDriven && activeGroup"
353
- class="mint-settings-modal__group-grid"
354
- :style="{ '--mint-settings-cols': activeGroup.columns ?? 1 }"
355
- >
356
- <template v-for="field in activeGroupVisibleFields" :key="field.name">
357
- <div :style="field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined">
358
- <slot
359
- :name="`field:${field.name}`"
360
- :field="field"
361
- :form="builder.form"
362
- :field-props="builder.form.getFieldProps(field.name)"
363
- >
364
- <FormFieldRendererInternal
365
- :field="field"
366
- :resolved-props="builder.getResolvedFieldProps(field)"
367
- :form="builder.form"
368
- />
369
- </slot>
370
- </div>
371
- </template>
352
+ <div class="mint-settings-modal__section">
353
+ <div class="mint-settings-modal__section-label">Color Palette</div>
354
+ <div class="mint-settings-modal__option-group">
355
+ <button
356
+ v-for="(palette, key) in colorPalettes"
357
+ :key="key"
358
+ type="button"
359
+ :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.colorPalette === key }]"
360
+ @click="settings.colorPalette = key as ColorPalette"
361
+ >
362
+ {{ palette.name }}
363
+ </button>
372
364
  </div>
365
+ </div>
373
366
 
374
- <template v-else-if="!isSchemaDriven">
375
- <template v-for="tab in manualTabs" :key="tab.id">
376
- <div v-show="activeTab === tab.id">
377
- <slot :name="`tab-${tab.id}`" />
378
- </div>
379
- </template>
380
- </template>
381
-
382
- <div v-if="showAppearance" v-show="activeTab === APPEARANCE_TAB_ID">
383
- <div class="mint-settings-modal__section">
384
- <div class="mint-settings-modal__section-label">Theme</div>
385
- <div class="mint-settings-modal__option-group">
386
- <button
387
- v-for="opt in themeOptions"
388
- :key="opt.value"
389
- type="button"
390
- :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.theme === opt.value }]"
391
- @click="settings.theme = opt.value"
392
- >
393
- {{ opt.label }}
394
- </button>
395
- </div>
396
- </div>
397
-
398
- <div class="mint-settings-modal__section">
399
- <div class="mint-settings-modal__section-label">Color Palette</div>
400
- <div class="mint-settings-modal__option-group">
401
- <button
402
- v-for="(palette, key) in colorPalettes"
403
- :key="key"
404
- type="button"
405
- :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.colorPalette === key }]"
406
- @click="settings.colorPalette = key as ColorPalette"
407
- >
408
- {{ palette.name }}
409
- </button>
410
- </div>
411
- </div>
412
-
413
- <div class="mint-settings-modal__section">
414
- <div class="mint-settings-modal__section-label">Table Density</div>
415
- <div class="mint-settings-modal__option-group">
416
- <button
417
- v-for="opt in densityOptions"
418
- :key="opt.value"
419
- type="button"
420
- :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.tableDensity === opt.value }]"
421
- @click="settings.tableDensity = opt.value"
422
- >
423
- {{ opt.label }}
424
- </button>
425
- </div>
426
- <p class="mint-settings-modal__note">Adjusts row height in data tables.</p>
427
- </div>
428
-
429
- <slot name="appearance" />
367
+ <div class="mint-settings-modal__section">
368
+ <div class="mint-settings-modal__section-label">Table Density</div>
369
+ <div class="mint-settings-modal__option-group">
370
+ <button
371
+ v-for="opt in densityOptions"
372
+ :key="opt.value"
373
+ type="button"
374
+ :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.tableDensity === opt.value }]"
375
+ @click="settings.tableDensity = opt.value"
376
+ >
377
+ {{ opt.label }}
378
+ </button>
430
379
  </div>
380
+ <p class="mint-settings-modal__note">Adjusts row height in data tables.</p>
431
381
  </div>
432
- </component>
382
+
383
+ <slot name="appearance" />
384
+ </div>
433
385
  </div>
434
- <div v-if="$slots.footer" class="mint-settings-modal__footer">
386
+
387
+ <template v-if="$slots.footer" #footer>
435
388
  <slot
436
389
  name="footer"
437
390
  :values="builder.form.data"
438
391
  :close="handleClose"
439
392
  />
440
- </div>
393
+ </template>
441
394
  </BaseModal>
442
395
  </template>
443
396
 
@@ -30,6 +30,7 @@ export { default as AlertBox } from './AlertBox.vue'
30
30
  export { default as AppToastContainer } from './AppToastContainer.vue'
31
31
 
32
32
  // Action components
33
+ export { default as ActionMenu } from './ActionMenu.vue'
33
34
  export { default as IconButton } from './IconButton.vue'
34
35
  export { default as ThemeToggle } from './ThemeToggle.vue'
35
36
 
@@ -86,7 +86,14 @@ export function defineWellPlateDoseComponentBindings<TValues extends Record<stri
86
86
  ]
87
87
  }
88
88
 
89
- /** Return a complete ControlWorkspaceView model for WellPlate + DoseCalculator dose design. */
89
+ /**
90
+ * Return a complete ControlWorkspaceView model for WellPlate + DoseCalculator dose design.
91
+ *
92
+ * @example Recommended
93
+ * ```ts
94
+ * const workspaceModel = defineDoseDesignControlModel({ selectedWells: ['A1', 'A2'] })
95
+ * ```
96
+ */
90
97
  export function defineDoseDesignControlModel(
91
98
  options: DoseDesignControlModelOptions = {},
92
99
  ): ControlModelBinding {
@@ -33,7 +33,16 @@ export function resolveControlModel(
33
33
  return isControlModelBinding(model) ? model : defineControlModel(model)
34
34
  }
35
35
 
36
- /** Create a complete workspace component binding from a simple controls data model for ControlWorkspaceView, generated forms, and sidebars. */
36
+ /**
37
+ * Create a complete workspace component binding from a simple controls data model for ControlWorkspaceView, generated forms, and sidebars.
38
+ *
39
+ * @example Recommended
40
+ * ```ts
41
+ * const workspaceModel = defineControlModel({
42
+ * controls: { threshold: { type: 'number', default: 0.05 } },
43
+ * })
44
+ * ```
45
+ */
37
46
  export function defineControlModel(model: ControlModel): ControlModelBinding {
38
47
  const {
39
48
  controls: rootControls,
@@ -85,7 +85,15 @@ export interface UseAnalysisArtifactsReturn {
85
85
  clear: () => void
86
86
  }
87
87
 
88
- /** Lists and manages analysis artifacts through the user-facing platform API. */
88
+ /**
89
+ * Lists and manages analysis artifacts through the user-facing platform API.
90
+ *
91
+ * @example Recommended
92
+ * ```ts
93
+ * const artifacts = useAnalysisArtifacts({ pluginId: 'LEAF' })
94
+ * await artifacts.list()
95
+ * ```
96
+ */
89
97
  export function useAnalysisArtifacts(
90
98
  options: UseAnalysisArtifactsOptions = {},
91
99
  ): UseAnalysisArtifactsReturn {
@@ -93,6 +93,13 @@ export const APP_EXPERIMENT_KEY: InjectionKey<AppExperimentState> = Symbol('app-
93
93
  * The composable never writes a selection of its own. `ExperimentSelectorModal`
94
94
  * commits to the store and this reads back, so a displayed name can only ever
95
95
  * have come from the platform.
96
+ *
97
+ * @example Recommended
98
+ * ```ts
99
+ * const appExperiment = useAppExperiment({
100
+ * onSelect: experiment => loadExperiment(experiment.id),
101
+ * })
102
+ * ```
96
103
  */
97
104
  export function useAppExperiment(options: UseAppExperimentOptions = {}): UseAppExperimentReturn {
98
105
  const store = useExperimentStore()
@@ -79,7 +79,15 @@ export interface UseBioTemplatePackWorkspaceReturn {
79
79
  requireCurrentExperimentId: UseTemplateCollectionReturn['requireCurrentExperimentId']
80
80
  }
81
81
 
82
- /** Create a complete workspace for a curated biology template pack from a single pack id. */
82
+ /**
83
+ * Create a complete workspace for a curated biology template pack from a single pack id.
84
+ *
85
+ * @example Recommended
86
+ * ```ts
87
+ * const pack = useBioTemplatePackWorkspace('cell-culture-screen')
88
+ * await pack.loadCurrent()
89
+ * ```
90
+ */
83
91
  export function useBioTemplatePackWorkspace(
84
92
  packId: TemplatePackId | string,
85
93
  options: UseBioTemplatePackWorkspaceOptions = {},
@@ -44,7 +44,15 @@ import type {
44
44
  UseControlWorkspaceReturn,
45
45
  } from './controlSchemaTypes'
46
46
 
47
- /** Prepare shared reactive values plus AppTopBar/AppSidebar/FormBuilder bindings from one simple controls data model. */
47
+ /**
48
+ * Prepare shared reactive values plus AppTopBar/AppSidebar/FormBuilder bindings from one simple controls data model.
49
+ *
50
+ * @example Recommended
51
+ * ```ts
52
+ * const workspace = useControlWorkspace(workspaceModel)
53
+ * const { form, sidebar, topBar } = workspace.bindings
54
+ * ```
55
+ */
48
56
  export function useControlWorkspace<TControls extends ControlSchema>(
49
57
  controlsOrModel: TControls | (ControlModelBinding & { controls: TControls }),
50
58
  options: ControlWorkspaceOptions = {},
@@ -161,6 +161,15 @@ export function useGeneratedAnalysis(
161
161
  return createPluginJobs(options, '/generated/manifest')
162
162
  }
163
163
 
164
+ /**
165
+ * Connect a custom plugin frontend to the SDK-owned job lifecycle.
166
+ *
167
+ * @example Recommended
168
+ * ```ts
169
+ * const jobs = usePluginJobs({ baseUrl: '/api/leaf/jobs' })
170
+ * await jobs.initialize()
171
+ * ```
172
+ */
164
173
  export function usePluginJobs(
165
174
  options: UsePluginJobsOptions,
166
175
  ): UsePluginJobsReturn {
@@ -0,0 +1,59 @@
1
+ /* ActionMenu — compact square trigger with a teleported, viewport-safe menu. */
2
+
3
+ .mint-action-menu {
4
+ display: inline-flex;
5
+ flex-shrink: 0;
6
+ }
7
+
8
+ .mint-action-menu__trigger-icon {
9
+ width: 1rem;
10
+ height: 1rem;
11
+ }
12
+
13
+ .mint-action-menu__panel {
14
+ position: fixed;
15
+ z-index: 1000;
16
+ display: flex;
17
+ min-width: 10rem;
18
+ max-width: min(18rem, calc(100vw - 1rem));
19
+ flex-direction: column;
20
+ padding: 0.25rem;
21
+ border: 1px solid var(--border-color);
22
+ border-radius: var(--radius-md);
23
+ background: var(--bg-secondary);
24
+ box-shadow: var(--shadow-md);
25
+ }
26
+
27
+ .mint-action-menu__item {
28
+ display: flex;
29
+ width: 100%;
30
+ align-items: center;
31
+ padding: 0.4375rem 0.625rem;
32
+ border: 0;
33
+ border-radius: 0.375rem;
34
+ background: transparent;
35
+ color: var(--text-primary);
36
+ font-family: inherit;
37
+ font-size: 0.78125rem;
38
+ line-height: 1.25;
39
+ text-align: left;
40
+ text-decoration: none;
41
+ cursor: pointer;
42
+ transition: background-color var(--mint-transition);
43
+ }
44
+
45
+ .mint-action-menu__item:hover:not(:disabled),
46
+ .mint-action-menu__item:focus-visible {
47
+ outline: none;
48
+ background: var(--bg-hover);
49
+ }
50
+
51
+ .mint-action-menu__item--danger {
52
+ color: var(--mint-error);
53
+ }
54
+
55
+ .mint-action-menu__item:disabled {
56
+ color: var(--text-muted);
57
+ cursor: not-allowed;
58
+ opacity: var(--mint-disabled-opacity);
59
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  .mint-icon-button {
4
4
  display: inline-flex;
5
+ flex-shrink: 0;
5
6
  align-items: center;
6
7
  justify-content: center;
7
8
  border-radius: var(--radius-md);
@@ -126,18 +127,21 @@
126
127
 
127
128
  /* Sizes */
128
129
  .mint-icon-button--sm {
130
+ width: var(--form-height-sm);
131
+ height: var(--form-height-sm);
129
132
  padding: 0.375rem;
130
- min-height: var(--form-height-sm);
131
133
  }
132
134
 
133
135
  .mint-icon-button--md {
136
+ width: var(--form-height-md);
137
+ height: var(--form-height-md);
134
138
  padding: 0.5rem;
135
- min-height: var(--form-height-md);
136
139
  }
137
140
 
138
141
  .mint-icon-button--lg {
142
+ width: var(--form-height-lg);
143
+ height: var(--form-height-lg);
139
144
  padding: 0.75rem;
140
- min-height: var(--form-height-lg);
141
145
  }
142
146
 
143
147
  .mint-icon-button__icon--sm,