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

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 (136) 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/components/AppAvatarMenu.vue +34 -3
  80. package/src/components/BaseModal.vue +8 -56
  81. package/src/components/BaseTabs.vue +26 -1
  82. package/src/components/BioTemplateExperimentWorkspaceView.vue +10 -21
  83. package/src/components/BioTemplatePackWorkspaceView.vue +11 -15
  84. package/src/components/BioTemplatePresetWorkspaceView.vue +8 -13
  85. package/src/components/ComponentBindingRenderer.vue +1 -3
  86. package/src/components/ControlWorkspaceView.vue +11 -22
  87. package/src/components/DoseDesignWorkspaceView.vue +6 -25
  88. package/src/components/DropdownButton.vue +32 -2
  89. package/src/components/PluginWorkspaceView.props.ts +38 -0
  90. package/src/components/PluginWorkspaceView.vue +53 -224
  91. package/src/components/SecretInput.story.vue +79 -0
  92. package/src/components/SecretInput.vue +150 -0
  93. package/src/components/index.ts +9 -0
  94. package/src/components/internal/WellEditPopupInternal.vue +20 -1
  95. package/src/components/internal/WorkspaceControlsInternal.vue +20 -0
  96. package/src/components/internal/WorkspaceShellInternal.vue +125 -0
  97. package/src/components/internal/WorkspaceSidebarInternal.vue +82 -0
  98. package/src/composables/experimentDesignData.ts +1 -3
  99. package/src/composables/flattenWorkspaceBindings.ts +104 -0
  100. package/src/composables/formBuilderRegistry.ts +5 -0
  101. package/src/composables/index.ts +26 -0
  102. package/src/composables/pluginWorkspaceModel.ts +182 -0
  103. package/src/composables/useAsync.ts +5 -1
  104. package/src/composables/useBioTemplatePackWorkspace.ts +15 -9
  105. package/src/composables/useBioTemplatePresetWorkspace.ts +14 -15
  106. package/src/composables/useBioTemplateWorkspace.ts +18 -43
  107. package/src/composables/useCommandHistory.ts +5 -1
  108. package/src/composables/useFocusTrap.ts +91 -0
  109. package/src/composables/useMenuKeyboard.ts +90 -0
  110. package/src/composables/useOptimisticMutation.ts +5 -1
  111. package/src/composables/usePluginClient.ts +42 -4
  112. package/src/composables/usePluginWorkspace.ts +208 -0
  113. package/src/composables/useResourceCrud.ts +5 -1
  114. package/src/composables/useRovingFocus.ts +66 -0
  115. package/src/composables/useWellPainting.ts +5 -1
  116. package/src/composables/useWellPlateAdapter.ts +5 -1
  117. package/src/composables/useWellPlateValidation.ts +5 -1
  118. package/src/composables/workspaceRecipes.ts +267 -0
  119. package/src/index.ts +16 -0
  120. package/src/styles/components/secret-input.css +60 -0
  121. package/src/styles/index.css +1 -0
  122. package/src/templates/builderReadUtils.ts +1 -3
  123. package/src/templates/componentTargetResolvers.ts +1 -3
  124. package/src/templates/controlSchemaTargets.ts +1 -3
  125. package/src/types/form-builder.ts +1 -0
  126. package/src/utils/formModelSync.ts +1 -3
  127. package/src/utils/pluginIcon.ts +1 -3
  128. package/src/utils/pluginSecrets.ts +102 -0
  129. package/src/utils/records.ts +9 -0
  130. package/dist/SettingsModal-CkNM8qAI.js.map +0 -1
  131. package/dist/components-MGNL6tRn.js.map +0 -1
  132. package/dist/composables-CKxOcvgh.js.map +0 -1
  133. package/dist/experiment-utils-CLnzPPq5.js.map +0 -1
  134. package/dist/templates-BTJ9C9Kb.js.map +0 -1
  135. package/dist/useControlSchema-CXhxTul7.js.map +0 -1
  136. package/dist/useProtocolTemplates-BWhQIWV5.js.map +0 -1
@@ -15,6 +15,7 @@ import {
15
15
  import { usePlatformContext } from './usePlatformContext'
16
16
  import { useRequestSyncState } from './useRequestSyncState'
17
17
  import { useAuthStore } from '../stores/auth'
18
+ import { PLUGIN_SECRET_FORMAT_KEY } from '../utils/pluginSecrets'
18
19
  import {
19
20
  currentExperimentFromContext,
20
21
  getInjectedPlatformContext,
@@ -545,6 +546,26 @@ export function usePluginClient<TClient>(client: TClient): TClient {
545
546
 
546
547
  const PLATFORM_OWNED_SETTINGS_KEY = 'allowed_experiment_types'
547
548
 
549
+ /**
550
+ * Secret settings travel as `{ "$secret": "<id>" }` references, so an untouched
551
+ * secret round-trips without ever exposing its value. Declaring the format on
552
+ * every write keeps a `null` unambiguous: it clears the stored secret instead of
553
+ * meaning "unchanged", which is what the deprecated placeholder form meant.
554
+ */
555
+ function withSecretFormat(
556
+ payload: Record<string, unknown>,
557
+ secretFormat: string | null,
558
+ ): Record<string, unknown> {
559
+ if (!secretFormat) return payload
560
+ return { ...payload, [PLUGIN_SECRET_FORMAT_KEY]: secretFormat }
561
+ }
562
+
563
+ function responseSecretFormat(response: unknown): string | null {
564
+ if (!response || typeof response !== 'object') return null
565
+ const format = (response as Record<string, unknown>)[PLUGIN_SECRET_FORMAT_KEY]
566
+ return typeof format === 'string' && format ? format : null
567
+ }
568
+
548
569
  function cloneRecord(value: Record<string, unknown>): Record<string, unknown> {
549
570
  return JSON.parse(JSON.stringify(value)) as Record<string, unknown>
550
571
  }
@@ -618,6 +639,7 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
618
639
  const values = ref({}) as Ref<PluginSettingsValues<TSettings>>
619
640
  const savedValues = shallowRef<Record<string, unknown>>({})
620
641
  const revision = ref<string | null>(null)
642
+ const secretFormat = ref<string | null>(null)
621
643
  const request = useRequestSyncState('Plugin settings request failed.')
622
644
  const isLoading = ref(false)
623
645
  const isSaving = ref(false)
@@ -650,11 +672,16 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
650
672
  values.value = cloneRecord(nextValues) as PluginSettingsValues<TSettings>
651
673
  }
652
674
 
653
- function applyLoaded(nextValues: Record<string, unknown>, nextRevision: string | null): void {
675
+ function applyLoaded(
676
+ nextValues: Record<string, unknown>,
677
+ nextRevision: string | null,
678
+ nextSecretFormat: string | null = secretFormat.value,
679
+ ): void {
654
680
  const cloned = cloneRecord(nextValues)
655
681
  values.value = cloned as PluginSettingsValues<TSettings>
656
682
  savedValues.value = cloneRecord(cloned)
657
683
  revision.value = nextRevision
684
+ secretFormat.value = nextSecretFormat
658
685
  }
659
686
 
660
687
  async function load(): Promise<void> {
@@ -674,6 +701,7 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
674
701
  applyLoaded(
675
702
  responseSettingsPayload(response),
676
703
  responseSettingsRevision(response),
704
+ responseSecretFormat(response),
677
705
  )
678
706
  } catch {
679
707
  // Error state is handled by request.run().
@@ -707,7 +735,12 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
707
735
  revision?: string
708
736
  }>(
709
737
  `/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`,
710
- { config: topLevelSettingsPatch(payload, typedKeys) },
738
+ {
739
+ config: withSecretFormat(
740
+ topLevelSettingsPatch(payload, typedKeys),
741
+ secretFormat.value,
742
+ ),
743
+ },
711
744
  ),
712
745
  { success: 'save', errorMessage: 'Failed to save plugin settings' },
713
746
  )
@@ -730,7 +763,11 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
730
763
  }
731
764
 
732
765
  if (!response) return true
733
- applyLoaded(responseSettingsPayload(response), nextRevision)
766
+ applyLoaded(
767
+ responseSettingsPayload(response),
768
+ nextRevision,
769
+ responseSecretFormat(response) ?? secretFormat.value,
770
+ )
734
771
  return true
735
772
  } else {
736
773
  const ifMatch = revision.value
@@ -741,13 +778,14 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
741
778
  settings: Record<string, unknown>
742
779
  mode?: string
743
780
  revision?: string
744
- }>('/settings', payload, ifMatch),
781
+ }>('/settings', withSecretFormat(payload, secretFormat.value), ifMatch),
745
782
  { success: 'save', errorMessage: 'Failed to save plugin settings' },
746
783
  )
747
784
  }
748
785
  applyLoaded(
749
786
  responseSettingsPayload(response),
750
787
  responseSettingsRevision(response) ?? revision.value,
788
+ responseSecretFormat(response) ?? secretFormat.value,
751
789
  )
752
790
  return true
753
791
  } catch {
@@ -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> {