@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
@@ -2,6 +2,7 @@ import type { Component } from 'vue'
2
2
  import type { FormFieldType } from '../types/form-builder'
3
3
 
4
4
  import BaseInput from '../components/BaseInput.vue'
5
+ import SecretInput from '../components/SecretInput.vue'
5
6
  import BaseTextarea from '../components/BaseTextarea.vue'
6
7
  import BaseSelect from '../components/BaseSelect.vue'
7
8
  import MultiSelect from '../components/MultiSelect.vue'
@@ -34,6 +35,7 @@ const registry: Record<FormFieldType, RegistryEntry> = {
34
35
  text: { component: BaseInput, defaults: { type: 'text' }, vModel: true },
35
36
  email: { component: BaseInput, defaults: { type: 'email' }, vModel: true },
36
37
  password: { component: BaseInput, defaults: { type: 'password' }, vModel: true },
38
+ secret: { component: SecretInput, defaults: {}, vModel: true },
37
39
  tel: { component: BaseInput, defaults: { type: 'tel' }, vModel: true },
38
40
  url: { component: BaseInput, defaults: { type: 'url' }, vModel: true },
39
41
  search: { component: BaseInput, defaults: { type: 'search' }, vModel: true },
@@ -75,6 +77,9 @@ export function getTypeDefault(type: FormFieldType): unknown {
75
77
  case 'multiselect':
76
78
  case 'tags':
77
79
  return []
80
+ case 'secret':
81
+ // A secret field has no empty string state: `null` means "not stored".
82
+ return null
78
83
  default:
79
84
  return ''
80
85
  }
@@ -528,3 +528,29 @@ export {
528
528
  type UsePluginJobCenterOptions,
529
529
  type UsePluginJobCenterReturn,
530
530
  } from './useJobsStatusTray'
531
+ export {
532
+ usePluginWorkspace,
533
+ type PluginWorkspaceSlotsLike,
534
+ type UsePluginWorkspaceOptions,
535
+ type UsePluginWorkspaceReturn,
536
+ } from './usePluginWorkspace'
537
+ export {
538
+ type PluginWorkspaceControlsModel,
539
+ type PluginWorkspaceShellModel,
540
+ type PluginWorkspaceSidebarModel,
541
+ } from './pluginWorkspaceModel'
542
+ export {
543
+ useFocusTrap,
544
+ type UseFocusTrapOptions,
545
+ type UseFocusTrapReturn,
546
+ } from './useFocusTrap'
547
+ export {
548
+ useRovingFocus,
549
+ type UseRovingFocusOptions,
550
+ type UseRovingFocusReturn,
551
+ } from './useRovingFocus'
552
+ export {
553
+ useMenuKeyboard,
554
+ type UseMenuKeyboardOptions,
555
+ type UseMenuKeyboardReturn,
556
+ } from './useMenuKeyboard'
@@ -0,0 +1,182 @@
1
+ import type {
2
+ AccountMenuItem,
3
+ PageSelectorItemInput,
4
+ PillNavItemInput,
5
+ PluginSwitcherInfo,
6
+ SidebarToolSection,
7
+ TopBarSettingsConfig,
8
+ TopBarVariant,
9
+ } from '../types'
10
+ import type { FormEnhancements, FormSchema } from '../types/form-builder'
11
+ import type {
12
+ ControlComponentBinding,
13
+ ControlComponentBindingsById,
14
+ ControlModel,
15
+ ControlModelBinding,
16
+ ControlSchema,
17
+ ControlWorkspaceOptions,
18
+ } from './useControlSchema'
19
+ import type { PluginWorkspaceSidebarVariant } from '../components/PluginWorkspaceView.props'
20
+
21
+ /**
22
+ * Scoped-slot payload shared by PluginWorkspaceView's `default`, `topbar`, and `sidebar`
23
+ * slots: the resolved active view plus the values and component bindings/props derived
24
+ * from the compact control schema. WorkspaceControlsInternal renders a component's default
25
+ * slot bound to exactly this shape.
26
+ */
27
+ export interface PluginWorkspaceControlsModel {
28
+ /** Currently resolved workspace view id. */
29
+ activeView: string
30
+ /** Sets the active view, updating any uncontrolled state and emitting `update:activeView`. */
31
+ setActiveView: (viewId: string) => void
32
+ /** Current control values, shared between the auto-rendered sidebar form and the default slot. */
33
+ values: Record<string, unknown>
34
+ /** Ordered SDK component bindings resolved from the current control values. */
35
+ componentBindings: ControlComponentBinding[]
36
+ /** SDK component bindings resolved from the current control values, keyed by binding id. */
37
+ componentBindingsById: ControlComponentBindingsById
38
+ /** Flat component props resolved from the current control values. */
39
+ componentProps: Record<string, unknown>
40
+ /** Named component prop mappings resolved from the current control values. */
41
+ componentPropsById: Record<string, Record<string, unknown>>
42
+ }
43
+
44
+ /**
45
+ * Resolved AppLayout + AppTopBar configuration consumed by WorkspaceShellInternal: top bar
46
+ * chrome and navigation, plus the AppLayout layout/collapse props that own the responsive
47
+ * mobile sidebar toggle.
48
+ */
49
+ export interface PluginWorkspaceShellModel {
50
+ /** AppLayout sidebar position. */
51
+ sidebarPosition: 'left' | 'right'
52
+ /** AppLayout sidebar width, forwarded so the layout can reserve space before AppSidebar mounts. */
53
+ sidebarWidth?: string
54
+ /** AppLayout floating style. */
55
+ floating: boolean
56
+ /** Convert the sidebar into an SDK-owned mobile overlay below the AppLayout breakpoint. */
57
+ responsiveSidebar: boolean
58
+ /** Accessible label for the mobile sidebar toggle. */
59
+ sidebarToggleLabel: string
60
+ /** Accessible label used when the mobile sidebar is open. */
61
+ sidebarCloseLabel: string
62
+ /** Whether AppLayout should render a `#sidebar` template at all. */
63
+ hasSidebarSurface: boolean
64
+ /** App or plugin title shown in the top bar. */
65
+ title: string
66
+ /** Secondary copy shown under the title. */
67
+ subtitle?: string
68
+ /** Top bar visual treatment. */
69
+ topBarVariant: TopBarVariant
70
+ /** Home link used by the top bar brand icon. */
71
+ homePath: string
72
+ /** Show the default MINT logo when no icon/logo slot is provided. */
73
+ showLogo: boolean
74
+ /** Preferred route-level page selector entries. */
75
+ pageSelector?: PageSelectorItemInput[]
76
+ /** Resolved active page selector id. */
77
+ currentPageSelectorId: string
78
+ /** Optional plugin switcher shown in the left navigation position. */
79
+ pluginSwitcher?: PluginSwitcherInfo
80
+ /** Resolved centered pill navigation, generated from the control schema when omitted. */
81
+ pillNav?: PillNavItemInput[]
82
+ /** Resolved active centered pill id. */
83
+ currentPillId: string
84
+ /** Show the theme toggle button. */
85
+ showThemeToggle: boolean
86
+ /** Show the settings button and modal. */
87
+ showSettings: boolean
88
+ /** Built-in settings modal configuration. */
89
+ settingsConfig?: TopBarSettingsConfig
90
+ /** Show the standalone badge when not integrated into the platform. */
91
+ showStandaloneLabel: boolean
92
+ /** Custom standalone badge label. */
93
+ standaloneLabel: string
94
+ /** Account dropdown entries. */
95
+ accountMenu?: AccountMenuItem[]
96
+ /** Show the notifications icon button. */
97
+ showNotifications: boolean
98
+ /** Draw a notification dot on the notifications icon. */
99
+ hasNotificationDot: boolean
100
+ /** Show the admin shortcut. */
101
+ showAdmin: boolean
102
+ /** Route used by the admin shortcut. */
103
+ adminPath: string
104
+ /** Account display name. */
105
+ userName?: string
106
+ /** Explicit account avatar initial. */
107
+ userInitial?: string
108
+ /** Account email shown in the avatar menu. */
109
+ userEmail?: string
110
+ /** Names of AppTopBar passthrough slots the caller actually provided. */
111
+ topBarSlotNames: string[]
112
+ /** Scoped-slot payload exposed to the `topbar` override slot. */
113
+ controls: PluginWorkspaceControlsModel
114
+ }
115
+
116
+ /**
117
+ * Resolved AppSidebar configuration consumed by WorkspaceSidebarInternal: sidebar chrome,
118
+ * panels/forms, collapse behavior, and the compact control-schema props that drive
119
+ * AppSidebar's auto-generated form.
120
+ */
121
+ export interface PluginWorkspaceSidebarModel {
122
+ /** Sidebar chrome title. */
123
+ title?: string
124
+ /** Sidebar chrome subtitle. */
125
+ subtitle?: string
126
+ /** Optional compact badge/count rendered in the sidebar header. */
127
+ badge?: string | number
128
+ /** AppSidebar visual preset. */
129
+ variant: PluginWorkspaceSidebarVariant
130
+ /** Map of view IDs to sidebar tool sections. */
131
+ panels: Record<string, SidebarToolSection[]>
132
+ /** Resolved active view, used to pick which panels/forms render. */
133
+ activeView: string
134
+ /** AppSidebar floating style (always false: PluginWorkspaceView docks the sidebar inside AppLayout). */
135
+ floating: boolean
136
+ /** Compact AppSidebar density. */
137
+ dense: boolean
138
+ /** Sidebar CSS width. */
139
+ width?: string
140
+ /** Sidebar position, mirrors the shell's `sidebarPosition`. */
141
+ side: 'left' | 'right'
142
+ /** Optional FormBuilder schemas keyed by section ID. */
143
+ forms: Record<string, FormSchema>
144
+ /** DOM id for the AppSidebar scrollable content area. */
145
+ contentId?: string
146
+ /** Render the sidebar shell even when no panel matches the active view. */
147
+ showWhenEmpty: boolean
148
+ /** Optional compact control model for the generated sidebar form. */
149
+ model?: ControlModel | ControlModelBinding
150
+ /** Compact control schema for the generated sidebar form. */
151
+ controlSchema?: ControlSchema
152
+ /** Options passed to compact control schema generation. */
153
+ controlOptions: ControlWorkspaceOptions
154
+ /** Shared values for the auto-rendered sidebar form. */
155
+ values: Record<string, unknown>
156
+ /** Runtime FormBuilder enhancements for the auto-rendered sidebar form. */
157
+ formEnhancements?: FormEnhancements<Record<string, unknown>>
158
+ /** Show submit/cancel actions inside the auto-rendered sidebar form. */
159
+ showFormActions: boolean
160
+ /** Loading/saving state for the auto-rendered sidebar form. */
161
+ formLoading: boolean
162
+ /** Disabled state for the auto-rendered sidebar form. */
163
+ formDisabled: boolean
164
+ /** Readonly state for the auto-rendered sidebar form. */
165
+ formReadonly: boolean
166
+ /** Size passed to the auto-rendered sidebar form. */
167
+ formSize: 'sm' | 'md' | 'lg'
168
+ /** Show a built-in collapse/expand button in the sidebar chrome. */
169
+ collapsible?: boolean
170
+ /** Controlled sidebar collapsed state. */
171
+ collapsed?: boolean
172
+ /** Initial sidebar collapsed state when uncontrolled. */
173
+ defaultCollapsed: boolean
174
+ /** Sidebar width when collapsed. */
175
+ collapsedWidth: string
176
+ /** Accessible label for the sidebar collapse action. */
177
+ collapseButtonLabel: string
178
+ /** Accessible label for the sidebar expand action. */
179
+ expandButtonLabel: string
180
+ /** Names of AppSidebar passthrough slots the caller actually provided. */
181
+ slotNames: string[]
182
+ }
@@ -165,7 +165,11 @@ function defaultTransformError(error: unknown): AsyncError {
165
165
  * const handleSubmit = () => submit(new FormData(formRef.value))
166
166
  * ```
167
167
  */
168
- /** Wraps an async function with reactive loading, error, and success state plus optional auto-execute. */
168
+ /**
169
+ * Wraps an async function with reactive loading, error, and success state plus optional auto-execute.
170
+ *
171
+ * @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `useRequestSyncState()` instead.
172
+ */
169
173
  export function useAsync<T>(
170
174
  asyncFn: (...args: unknown[]) => Promise<T>,
171
175
  options: UseAsyncOptions<T> = {}
@@ -27,6 +27,9 @@ import {
27
27
  type BioTemplateWorkspaceBindings,
28
28
  type UseBioTemplateWorkspaceReturn,
29
29
  } from './useBioTemplateWorkspace'
30
+ import {
31
+ liftWorkspaceComponentBindings,
32
+ } from './flattenWorkspaceBindings'
30
33
  import type {
31
34
  BioTemplateComponentBinding,
32
35
  BioTemplateComponentBindingsById,
@@ -120,14 +123,17 @@ export function useBioTemplatePackWorkspace(
120
123
  const pillNav = computed(() => workspace.value.pillNav)
121
124
  const topBarSettings = computed(() => workspace.value.topBarSettings)
122
125
  const bindings = computed(() => workspace.value.bindings)
123
- const componentBindings = computed(() => workspace.value.componentBindings)
124
- const componentBindingsById = computed(() => workspace.value.componentBindingsById)
125
- const componentImports = computed(() => workspace.value.componentImports)
126
- const componentProps = computed(() => workspace.value.componentProps)
127
- const componentPropsById = computed(() => workspace.value.componentPropsById)
128
- const componentPropsByComponent = computed(() => workspace.value.componentPropsByComponent)
129
- const componentSnippets = computed(() => workspace.value.componentSnippets)
130
- const componentUsage = computed(() => workspace.value.componentUsage)
126
+ const {
127
+ componentBindings,
128
+ componentBindingsById,
129
+ componentImports,
130
+ componentProps,
131
+ componentPropsById,
132
+ componentPropsByComponent,
133
+ componentSnippets,
134
+ componentUsage,
135
+ getComponentProps: getWorkspaceComponentProps,
136
+ } = liftWorkspaceComponentBindings(workspace)
131
137
 
132
138
  watch(collection, value => {
133
139
  workspaceRef.value = createWorkspace(value)
@@ -181,7 +187,7 @@ export function useBioTemplatePackWorkspace(
181
187
  componentPropsByComponent,
182
188
  componentSnippets,
183
189
  componentUsage,
184
- getComponentProps: (component, options) => workspace.value.getComponentProps(component, options),
190
+ getComponentProps: getWorkspaceComponentProps,
185
191
  applyCollection,
186
192
  resetToDefaultCollection,
187
193
  save: templateCollection.save,
@@ -34,6 +34,9 @@ import {
34
34
  type BioTemplateRendererBinding,
35
35
  type UseBioTemplateWorkspaceReturn,
36
36
  } from './useBioTemplateWorkspace'
37
+ import {
38
+ liftWorkspaceComponentBindings,
39
+ } from './flattenWorkspaceBindings'
37
40
  import type {
38
41
  BioTemplateComponentBinding,
39
42
  BioTemplateComponentBindingsById,
@@ -138,14 +141,17 @@ export function useBioTemplatePresetWorkspace(
138
141
  const collection = templateCollection.templateCollection
139
142
  const workspace = computed(() => useBioTemplateWorkspace(collection.value, resolvedControlOptions))
140
143
  const renderer = computed<BioTemplateRendererBinding>(() => ({ target: collection.value }))
141
- const componentBindings = computed(() => workspace.value.componentBindings)
142
- const componentBindingsById = computed(() => workspace.value.componentBindingsById)
143
- const componentImports = computed(() => workspace.value.componentImports)
144
- const componentProps = computed(() => workspace.value.componentProps)
145
- const componentPropsById = computed(() => workspace.value.componentPropsById)
146
- const componentPropsByComponent = computed(() => workspace.value.componentPropsByComponent)
147
- const componentSnippets = computed(() => workspace.value.componentSnippets)
148
- const componentUsage = computed(() => workspace.value.componentUsage)
144
+ const {
145
+ componentBindings,
146
+ componentBindingsById,
147
+ componentImports,
148
+ componentProps,
149
+ componentPropsById,
150
+ componentPropsByComponent,
151
+ componentSnippets,
152
+ componentUsage,
153
+ getComponentProps,
154
+ } = liftWorkspaceComponentBindings(workspace)
149
155
  const form = reactive({
150
156
  ...controls.form,
151
157
  modelValue: controlValues.value,
@@ -179,13 +185,6 @@ export function useBioTemplatePresetWorkspace(
179
185
  ...topBarSettings,
180
186
  }))
181
187
 
182
- function getComponentProps(
183
- component: string,
184
- options?: Parameters<UseBioTemplateWorkspaceReturn['getComponentProps']>[1],
185
- ) {
186
- return workspace.value.getComponentProps(component, options)
187
- }
188
-
189
188
  const bindings: BioTemplatePresetWorkspaceBindings = {
190
189
  renderer,
191
190
  form,
@@ -21,16 +21,12 @@ import {
21
21
  } from './useBioTemplateControls'
22
22
  import {
23
23
  useBioTemplateComponents,
24
- type BioTemplateComponentBinding,
25
- type BioTemplateComponentBindingsById,
26
- type BioTemplateComponentImport,
27
- type BioTemplateComponentPropsByComponent,
28
- type BioTemplateComponentPropsBinding,
29
- type BioTemplateComponentPropsById,
30
- type BioTemplateComponentSnippet,
31
- type BioTemplateComponentUsage,
32
24
  type UseBioTemplateComponentsReturn,
33
25
  } from './useBioTemplateComponents'
26
+ import {
27
+ flattenWorkspaceComponentBindings,
28
+ type BioTemplateWorkspaceComponentBindings,
29
+ } from './flattenWorkspaceBindings'
34
30
 
35
31
  export type BioTemplateWorkspaceTarget = BioTemplateControlTarget
36
32
 
@@ -38,24 +34,16 @@ export interface BioTemplateRendererBinding {
38
34
  target: BioTemplateEnvelope<unknown> | TemplateCollectionEnvelope
39
35
  }
40
36
 
41
- export interface BioTemplateWorkspaceBindings {
37
+ export interface BioTemplateWorkspaceBindings extends BioTemplateWorkspaceComponentBindings {
42
38
  renderer: BioTemplateRendererBinding | null
43
39
  form: ControlWorkspaceFormBinding
44
40
  sidebar: ControlWorkspaceSidebarBinding
45
41
  topBar: UseControlWorkspaceReturn<BioTemplateControlSchema>['bindings']['topBar']
46
42
  topBarSettings: ControlWorkspaceTopBarSettingsBinding
47
43
  pillNav: ControlWorkspacePillNavBinding
48
- componentBindings: BioTemplateComponentBinding[]
49
- componentBindingsById: BioTemplateComponentBindingsById
50
- componentProps: BioTemplateComponentPropsBinding[]
51
- componentPropsById: BioTemplateComponentPropsById
52
- componentPropsByComponent: BioTemplateComponentPropsByComponent
53
- componentSnippets: BioTemplateComponentSnippet[]
54
- componentUsage: BioTemplateComponentUsage | null
55
- getComponentProps: UseBioTemplateComponentsReturn['getComponentProps']
56
44
  }
57
45
 
58
- export interface UseBioTemplateWorkspaceReturn {
46
+ export interface UseBioTemplateWorkspaceReturn extends BioTemplateWorkspaceComponentBindings {
59
47
  target: BioTemplateWorkspaceTarget
60
48
  controls: UseControlSchemaReturn<BioTemplateControlSchema>
61
49
  workspace: UseControlWorkspaceReturn<BioTemplateControlSchema>
@@ -70,15 +58,6 @@ export interface UseBioTemplateWorkspaceReturn {
70
58
  topBarSettings: ControlWorkspaceTopBarSettingsBinding
71
59
  bindings: BioTemplateWorkspaceBindings
72
60
  renderer: BioTemplateRendererBinding | null
73
- componentBindings: BioTemplateComponentBinding[]
74
- componentBindingsById: BioTemplateComponentBindingsById
75
- componentImports: BioTemplateComponentImport[]
76
- componentProps: BioTemplateComponentPropsBinding[]
77
- componentPropsById: BioTemplateComponentPropsById
78
- componentPropsByComponent: BioTemplateComponentPropsByComponent
79
- componentSnippets: BioTemplateComponentSnippet[]
80
- componentUsage: BioTemplateComponentUsage | null
81
- getComponentProps: UseBioTemplateComponentsReturn['getComponentProps']
82
61
  }
83
62
 
84
63
  /** One-stop wiring for BioTemplateRenderer, FormBuilder, AppSidebar, and template-aware SDK components. */
@@ -90,21 +69,25 @@ export function useBioTemplateWorkspace(
90
69
  const workspace = useControlWorkspace(controls.controls, options)
91
70
  const components = useBioTemplateComponents(target)
92
71
  const renderer = isTemplateObjectTarget(target) ? { target } : null
93
- const bindings: BioTemplateWorkspaceBindings = {
94
- renderer,
95
- form: workspace.bindings.form,
96
- sidebar: workspace.bindings.sidebar,
97
- topBar: workspace.bindings.topBar,
98
- topBarSettings: workspace.bindings.topBarSettings,
99
- pillNav: workspace.bindings.pillNav,
72
+ const componentBindings = flattenWorkspaceComponentBindings({
100
73
  componentBindings: components.bindings,
101
74
  componentBindingsById: components.componentBindingsById,
75
+ componentImports: components.imports,
102
76
  componentProps: components.componentProps,
103
77
  componentPropsById: components.componentPropsById,
104
78
  componentPropsByComponent: components.componentPropsByComponent,
105
79
  componentSnippets: components.snippets,
106
80
  componentUsage: components.usage,
107
81
  getComponentProps: components.getComponentProps,
82
+ })
83
+ const bindings: BioTemplateWorkspaceBindings = {
84
+ renderer,
85
+ form: workspace.bindings.form,
86
+ sidebar: workspace.bindings.sidebar,
87
+ topBar: workspace.bindings.topBar,
88
+ topBarSettings: workspace.bindings.topBarSettings,
89
+ pillNav: workspace.bindings.pillNav,
90
+ ...componentBindings,
108
91
  }
109
92
 
110
93
  return {
@@ -122,15 +105,7 @@ export function useBioTemplateWorkspace(
122
105
  topBarSettings: workspace.topBarSettings,
123
106
  bindings,
124
107
  renderer,
125
- componentBindings: components.bindings,
126
- componentBindingsById: components.componentBindingsById,
127
- componentImports: components.imports,
128
- componentProps: components.componentProps,
129
- componentPropsById: components.componentPropsById,
130
- componentPropsByComponent: components.componentPropsByComponent,
131
- componentSnippets: components.snippets,
132
- componentUsage: components.usage,
133
- getComponentProps: components.getComponentProps,
108
+ ...componentBindings,
134
109
  }
135
110
  }
136
111
 
@@ -31,7 +31,11 @@ export interface UseCommandHistoryReturn<TCommand extends CommandHistoryCommand
31
31
  clear: () => void
32
32
  }
33
33
 
34
- /** Generic command-stack state for plugin editors with undo/redo controls. */
34
+ /**
35
+ * Generic command-stack state for plugin editors with undo/redo controls.
36
+ *
37
+ * @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; keep command stacks plugin-local.
38
+ */
35
39
  export function useCommandHistory<TCommand extends CommandHistoryCommand = CommandHistoryCommand>(
36
40
  options: UseCommandHistoryOptions = {},
37
41
  ): UseCommandHistoryReturn<TCommand> {
@@ -0,0 +1,91 @@
1
+ import { nextTick, watch, type Ref } from 'vue'
2
+ import { useEventListener } from './useEventListener'
3
+
4
+ export interface UseFocusTrapOptions {
5
+ /** Ref to the trap's container element. Focusable elements are queried within it. */
6
+ containerRef: Ref<HTMLElement | null>
7
+ /** Reactive flag — the trap activates when this becomes `true` and releases when it becomes `false`. */
8
+ active: Ref<boolean>
9
+ /** Restore focus to the element that was focused before activation, when the trap releases. Default `true`. */
10
+ restoreFocus?: boolean
11
+ }
12
+
13
+ export interface UseFocusTrapReturn {
14
+ /** Focusable descendants of the container, in DOM order, excluding roving-tabindex (`tabindex="-1"`) elements. */
15
+ getFocusableElements: () => HTMLElement[]
16
+ }
17
+
18
+ const FOCUSABLE_SELECTOR = [
19
+ 'a[href]',
20
+ 'button:not([disabled])',
21
+ 'input:not([disabled])',
22
+ 'select:not([disabled])',
23
+ 'textarea:not([disabled])',
24
+ '[tabindex]:not([tabindex="-1"])',
25
+ ].join(', ')
26
+
27
+ /**
28
+ * Traps Tab focus within a container while `active` is true: Tab wraps from the last
29
+ * focusable element back to the first (and Shift+Tab from the first to the last), the
30
+ * first focusable element (or the container itself, if none) receives focus when the
31
+ * trap activates, and focus returns to whatever was focused beforehand when it releases.
32
+ * Used by BaseModal and WellEditPopupInternal.
33
+ */
34
+ export function useFocusTrap(options: UseFocusTrapOptions): UseFocusTrapReturn {
35
+ const { containerRef, active } = options
36
+ const restoreFocus = options.restoreFocus ?? true
37
+ let previouslyFocused: HTMLElement | null = null
38
+
39
+ function getFocusableElements(): HTMLElement[] {
40
+ if (!containerRef.value) return []
41
+ // Roving tabindex (inactive tabs/rail items) stays out of the Tab cycle. Checking
42
+ // the attribute directly (rather than the `.tabIndex` IDL property) avoids relying
43
+ // on a browser/engine's default-tabindex computation for freshly-patched elements.
44
+ return Array.from(containerRef.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR))
45
+ .filter((el) => el.getAttribute('tabindex') !== '-1')
46
+ }
47
+
48
+ function handleKeydown(event: KeyboardEvent) {
49
+ if (event.key !== 'Tab' || !active.value || !containerRef.value) return
50
+
51
+ const focusable = getFocusableElements()
52
+ if (focusable.length === 0) {
53
+ event.preventDefault()
54
+ return
55
+ }
56
+
57
+ const first = focusable[0]
58
+ const last = focusable[focusable.length - 1]
59
+
60
+ if (event.shiftKey && document.activeElement === first) {
61
+ event.preventDefault()
62
+ last.focus()
63
+ } else if (!event.shiftKey && document.activeElement === last) {
64
+ event.preventDefault()
65
+ first.focus()
66
+ }
67
+ }
68
+
69
+ // `flush: 'sync'` so releasing the trap from `onBeforeUnmount` restores focus
70
+ // before the component's reactive scope is torn down — a batched (default
71
+ // 'pre') watcher would be scheduled but never flushed once that scope stops.
72
+ watch(active, async (isActive) => {
73
+ if (isActive) {
74
+ previouslyFocused = document.activeElement as HTMLElement | null
75
+ await nextTick()
76
+ const focusable = getFocusableElements()
77
+ if (focusable.length > 0) {
78
+ focusable[0].focus()
79
+ } else {
80
+ containerRef.value?.focus()
81
+ }
82
+ } else {
83
+ if (restoreFocus) previouslyFocused?.focus()
84
+ previouslyFocused = null
85
+ }
86
+ }, { flush: 'sync' })
87
+
88
+ useEventListener(() => document, 'keydown', handleKeydown)
89
+
90
+ return { getFocusableElements }
91
+ }
@@ -0,0 +1,90 @@
1
+ import { nextTick } from 'vue'
2
+
3
+ export interface UseMenuKeyboardOptions {
4
+ /** Returns the currently navigable items inside the open panel, in DOM order. */
5
+ items: () => HTMLElement[]
6
+ /** True while the panel is open. */
7
+ isOpen: () => boolean
8
+ /** Opens the panel. */
9
+ open: () => void
10
+ /** Closes the panel. When `returnFocus` is true, focus should return to the trigger. */
11
+ close: (returnFocus?: boolean) => void
12
+ }
13
+
14
+ export interface UseMenuKeyboardReturn {
15
+ /**
16
+ * Bind to the trigger's `@keydown`. ArrowDown/Enter/Space open the panel (when
17
+ * closed) and focus its first item; ArrowUp opens it focused on the last item.
18
+ */
19
+ handleTriggerKeydown: (event: KeyboardEvent) => void
20
+ /**
21
+ * Bind to the open panel's `@keydown`. ArrowUp/ArrowDown cycle focus with
22
+ * wraparound, Home/End jump to the first/last item, Escape closes and returns
23
+ * focus to the trigger, and Tab closes without moving focus.
24
+ */
25
+ handlePanelKeydown: (event: KeyboardEvent) => void
26
+ }
27
+
28
+ /**
29
+ * Shared keyboard contract for menu- and listbox-style disclosure panels (the
30
+ * WAI-ARIA menu button pattern). Used by AppAvatarMenu and DropdownButton; ActionMenu
31
+ * implements the same contract inline.
32
+ */
33
+ export function useMenuKeyboard(options: UseMenuKeyboardOptions): UseMenuKeyboardReturn {
34
+ function focusItem(position: 'first' | 'last') {
35
+ void nextTick(() => {
36
+ const items = options.items()
37
+ const target = position === 'first' ? items[0] : items[items.length - 1]
38
+ target?.focus()
39
+ })
40
+ }
41
+
42
+ function handleTriggerKeydown(event: KeyboardEvent) {
43
+ if (options.isOpen()) return
44
+
45
+ if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') {
46
+ event.preventDefault()
47
+ options.open()
48
+ focusItem('first')
49
+ return
50
+ }
51
+ if (event.key === 'ArrowUp') {
52
+ event.preventDefault()
53
+ options.open()
54
+ focusItem('last')
55
+ }
56
+ }
57
+
58
+ function handlePanelKeydown(event: KeyboardEvent) {
59
+ const items = options.items()
60
+ const currentIndex = items.indexOf(document.activeElement as HTMLElement)
61
+
62
+ if (event.key === 'Escape') {
63
+ event.preventDefault()
64
+ options.close(true)
65
+ return
66
+ }
67
+ if (event.key === 'Tab') {
68
+ options.close()
69
+ return
70
+ }
71
+ if (event.key === 'Home') {
72
+ event.preventDefault()
73
+ items[0]?.focus()
74
+ return
75
+ }
76
+ if (event.key === 'End') {
77
+ event.preventDefault()
78
+ items[items.length - 1]?.focus()
79
+ return
80
+ }
81
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return
82
+
83
+ event.preventDefault()
84
+ const direction = event.key === 'ArrowDown' ? 1 : -1
85
+ const nextIndex = (Math.max(currentIndex, 0) + direction + items.length) % items.length
86
+ items[nextIndex]?.focus()
87
+ }
88
+
89
+ return { handleTriggerKeydown, handlePanelKeydown }
90
+ }
@@ -32,7 +32,11 @@ export interface UseOptimisticMutationReturn<TVariables, TData, TSnapshot = unkn
32
32
  reset: () => void
33
33
  }
34
34
 
35
- /** Run async mutations with optional optimistic local updates and rollback. */
35
+ /**
36
+ * Run async mutations with optional optimistic local updates and rollback.
37
+ *
38
+ * @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `useRequestSyncState()` instead.
39
+ */
36
40
  export function useOptimisticMutation<TVariables, TData, TSnapshot = unknown>(
37
41
  options: UseOptimisticMutationOptions<TVariables, TData, TSnapshot>,
38
42
  ): UseOptimisticMutationReturn<TVariables, TData, TSnapshot> {