@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.
- package/dist/{EmptyState-p90quq8Z.js → EmptyState-NBqf2_8z.js} +2 -2
- package/dist/{EmptyState-p90quq8Z.js.map → EmptyState-NBqf2_8z.js.map} +1 -1
- package/dist/{ExperimentPopover-tLwJ7XF4.js → ExperimentPopover-CfOdh5Rm.js} +3 -3
- package/dist/{ExperimentPopover-tLwJ7XF4.js.map → ExperimentPopover-CfOdh5Rm.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-B6P2msT4.js → ExperimentSelectorModal-DvIagrxY.js} +4 -4
- package/dist/{ExperimentSelectorModal-B6P2msT4.js.map → ExperimentSelectorModal-DvIagrxY.js.map} +1 -1
- package/dist/{SettingsModal-CkNM8qAI.js → SettingsModal-W3TYfnP9.js} +4 -7
- package/dist/SettingsModal-W3TYfnP9.js.map +1 -0
- package/dist/__tests__/components/PluginWorkspaceView.internal.test.d.ts +1 -0
- package/dist/__tests__/components/SecretInput.test.d.ts +1 -0
- package/dist/__tests__/components/WellEditPopupInternal.test.d.ts +1 -0
- package/dist/components/AppAvatarMenu.vue.d.ts +4 -0
- package/dist/components/AppSidebar.vue.d.ts +1 -1
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
- package/dist/components/DropdownButton.vue.d.ts +2 -0
- package/dist/components/FitPanel.vue.d.ts +1 -1
- package/dist/components/PluginWorkspaceView.props.d.ts +29 -1
- package/dist/components/PluginWorkspaceView.vue.d.ts +5 -5
- package/dist/components/SecretInput.vue.d.ts +28 -0
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -1
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.js +8 -8
- package/dist/components/internal/WellEditPopupInternal.vue.d.ts +3 -1
- package/dist/components/internal/WorkspaceControlsInternal.vue.d.ts +23 -0
- package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +70 -0
- package/dist/components/internal/WorkspaceSidebarInternal.vue.d.ts +43 -0
- package/dist/{components-MGNL6tRn.js → components-B80gV0P6.js} +575 -561
- package/dist/components-B80gV0P6.js.map +1 -0
- package/dist/composables/flattenWorkspaceBindings.d.ts +61 -0
- package/dist/composables/index.d.ts +5 -0
- package/dist/composables/index.js +9 -9
- package/dist/composables/pluginWorkspaceModel.d.ts +164 -0
- package/dist/composables/useAsync.d.ts +5 -1
- package/dist/composables/useBioTemplateWorkspace.d.ts +4 -20
- package/dist/composables/useCommandHistory.d.ts +5 -1
- package/dist/composables/useFocusTrap.d.ts +21 -0
- package/dist/composables/useMenuKeyboard.d.ts +29 -0
- package/dist/composables/useOptimisticMutation.d.ts +5 -1
- package/dist/composables/usePluginWorkspace.d.ts +51 -0
- package/dist/composables/useResourceCrud.d.ts +5 -1
- package/dist/composables/useRovingFocus.d.ts +24 -0
- package/dist/composables/useWellPainting.d.ts +5 -1
- package/dist/composables/useWellPlateAdapter.d.ts +5 -1
- package/dist/composables/useWellPlateValidation.d.ts +5 -1
- package/dist/composables/workspaceRecipes.d.ts +188 -0
- package/dist/{composables-CKxOcvgh.js → composables-C8mmD5ta.js} +65 -17
- package/dist/composables-C8mmD5ta.js.map +1 -0
- package/dist/{experiment-utils-CLnzPPq5.js → experiment-utils-DV3DrvE_.js} +70 -51
- package/dist/experiment-utils-DV3DrvE_.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +13 -13
- package/dist/install.js +7 -7
- package/dist/styles.css +100 -49
- package/dist/templates/builderReadUtils.d.ts +1 -1
- package/dist/templates/index.js +4 -4
- package/dist/{templates-BTJ9C9Kb.js → templates-CK72aKO1.js} +14 -23
- package/dist/templates-CK72aKO1.js.map +1 -0
- package/dist/types/form-builder.d.ts +1 -1
- package/dist/{useControlSchema-CXhxTul7.js → useControlSchema-_OVhavKq.js} +280 -63
- package/dist/useControlSchema-_OVhavKq.js.map +1 -0
- package/dist/{useDropdownState-DSb3BC3N.js → useDropdownState-D5G4UvWd.js} +2 -2
- package/dist/{useDropdownState-DSb3BC3N.js.map → useDropdownState-D5G4UvWd.js.map} +1 -1
- package/dist/{useExperimentSelector-D0pjj9WH.js → useExperimentSelector-DSEckf_g.js} +3 -3
- package/dist/{useExperimentSelector-D0pjj9WH.js.map → useExperimentSelector-DSEckf_g.js.map} +1 -1
- package/dist/{useFormBuilder-CuOFB3aP.js → useFormBuilder-Bd714C7y.js} +2 -2
- package/dist/{useFormBuilder-CuOFB3aP.js.map → useFormBuilder-Bd714C7y.js.map} +1 -1
- package/dist/{useProtocolTemplates-BWhQIWV5.js → useProtocolTemplates-Dq5Pzwyd.js} +481 -46
- package/dist/useProtocolTemplates-Dq5Pzwyd.js.map +1 -0
- package/dist/utils/pluginSecrets.d.ts +53 -0
- package/dist/utils/records.d.ts +4 -0
- package/package.json +1 -1
- package/src/__tests__/components/AppAvatarMenu.test.ts +123 -0
- package/src/__tests__/components/BaseTabs.test.ts +67 -0
- package/src/__tests__/components/DropdownButton.test.ts +134 -0
- package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +230 -0
- package/src/__tests__/components/SecretInput.test.ts +87 -0
- package/src/__tests__/components/WellEditPopupInternal.test.ts +108 -0
- package/src/__tests__/composables/usePluginClient.test.ts +140 -0
- package/src/components/AppAvatarMenu.vue +34 -3
- package/src/components/BaseModal.vue +8 -56
- package/src/components/BaseTabs.vue +26 -1
- package/src/components/BioTemplateExperimentWorkspaceView.vue +10 -21
- package/src/components/BioTemplatePackWorkspaceView.vue +11 -15
- package/src/components/BioTemplatePresetWorkspaceView.vue +8 -13
- package/src/components/ComponentBindingRenderer.vue +1 -3
- package/src/components/ControlWorkspaceView.vue +11 -22
- package/src/components/DoseDesignWorkspaceView.vue +6 -25
- package/src/components/DropdownButton.vue +32 -2
- package/src/components/PluginWorkspaceView.props.ts +38 -0
- package/src/components/PluginWorkspaceView.vue +53 -224
- package/src/components/SecretInput.story.vue +79 -0
- package/src/components/SecretInput.vue +150 -0
- package/src/components/index.ts +9 -0
- package/src/components/internal/WellEditPopupInternal.vue +20 -1
- package/src/components/internal/WorkspaceControlsInternal.vue +20 -0
- package/src/components/internal/WorkspaceShellInternal.vue +125 -0
- package/src/components/internal/WorkspaceSidebarInternal.vue +82 -0
- package/src/composables/experimentDesignData.ts +1 -3
- package/src/composables/flattenWorkspaceBindings.ts +104 -0
- package/src/composables/formBuilderRegistry.ts +5 -0
- package/src/composables/index.ts +26 -0
- package/src/composables/pluginWorkspaceModel.ts +182 -0
- package/src/composables/useAsync.ts +5 -1
- package/src/composables/useBioTemplatePackWorkspace.ts +15 -9
- package/src/composables/useBioTemplatePresetWorkspace.ts +14 -15
- package/src/composables/useBioTemplateWorkspace.ts +18 -43
- package/src/composables/useCommandHistory.ts +5 -1
- package/src/composables/useFocusTrap.ts +91 -0
- package/src/composables/useMenuKeyboard.ts +90 -0
- package/src/composables/useOptimisticMutation.ts +5 -1
- package/src/composables/usePluginClient.ts +42 -4
- package/src/composables/usePluginWorkspace.ts +208 -0
- package/src/composables/useResourceCrud.ts +5 -1
- package/src/composables/useRovingFocus.ts +66 -0
- package/src/composables/useWellPainting.ts +5 -1
- package/src/composables/useWellPlateAdapter.ts +5 -1
- package/src/composables/useWellPlateValidation.ts +5 -1
- package/src/composables/workspaceRecipes.ts +267 -0
- package/src/index.ts +16 -0
- package/src/styles/components/secret-input.css +60 -0
- package/src/styles/index.css +1 -0
- package/src/templates/builderReadUtils.ts +1 -3
- package/src/templates/componentTargetResolvers.ts +1 -3
- package/src/templates/controlSchemaTargets.ts +1 -3
- package/src/types/form-builder.ts +1 -0
- package/src/utils/formModelSync.ts +1 -3
- package/src/utils/pluginIcon.ts +1 -3
- package/src/utils/pluginSecrets.ts +102 -0
- package/src/utils/records.ts +9 -0
- package/dist/SettingsModal-CkNM8qAI.js.map +0 -1
- package/dist/components-MGNL6tRn.js.map +0 -1
- package/dist/composables-CKxOcvgh.js.map +0 -1
- package/dist/experiment-utils-CLnzPPq5.js.map +0 -1
- package/dist/templates-BTJ9C9Kb.js.map +0 -1
- package/dist/useControlSchema-CXhxTul7.js.map +0 -1
- 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(
|
|
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
|
-
{
|
|
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(
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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> {
|