@morscherlab/mint-sdk 1.2.0-beta.8 → 1.2.0-beta.9

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 (307) hide show
  1. package/README.md +30 -0
  2. package/dist/{ExperimentPopover-CfOdh5Rm.js → ExperimentPopover-oL0fg_A0.js} +5 -3
  3. package/dist/ExperimentPopover-oL0fg_A0.js.map +1 -0
  4. package/dist/{ExperimentSelectorModal-DvIagrxY.js → ExperimentSelectorModal-BeEOomw_.js} +4 -4
  5. package/dist/ExperimentSelectorModal-BeEOomw_.js.map +1 -0
  6. package/dist/{SettingsModal-W3TYfnP9.js → SettingsModal-Dphh3RrI.js} +4 -4
  7. package/dist/SettingsModal-Dphh3RrI.js.map +1 -0
  8. package/dist/{EmptyState-NBqf2_8z.js → Skeleton-DxtwJ5gt.js} +110 -92
  9. package/dist/Skeleton-DxtwJ5gt.js.map +1 -0
  10. package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
  11. package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
  12. package/dist/__tests__/styles/cssTokens.test.d.ts +1 -0
  13. package/dist/components/ActionMenu.vue.d.ts +1 -1
  14. package/dist/components/AppPluginSwitcher.vue.d.ts +2 -0
  15. package/dist/components/AuditTrail.vue.d.ts +13 -0
  16. package/dist/components/BatchProgressList.vue.d.ts +17 -0
  17. package/dist/components/DataFrame.vue.d.ts +14 -2
  18. package/dist/components/DatePicker.vue.d.ts +1 -1
  19. package/dist/components/DateTimePicker.vue.d.ts +2 -0
  20. package/dist/components/ExperimentDataViewer.vue.d.ts +26 -1
  21. package/dist/components/ExperimentPopover.vue.d.ts +1 -0
  22. package/dist/components/ExperimentTimeline.vue.d.ts +29 -1
  23. package/dist/components/LcmsSequenceTable.vue.d.ts +29 -1
  24. package/dist/components/ProgressBar.vue.d.ts +1 -1
  25. package/dist/components/ProtocolStepEditor.vue.d.ts +1 -0
  26. package/dist/components/ReagentList.vue.d.ts +29 -1
  27. package/dist/components/SampleHierarchyTree.vue.d.ts +29 -1
  28. package/dist/components/SampleSelector.drag.d.ts +13 -0
  29. package/dist/components/SampleSelector.tree.d.ts +2 -0
  30. package/dist/components/SampleSelector.vue.d.ts +34 -1
  31. package/dist/components/ScheduleCalendar.vue.d.ts +20 -0
  32. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -0
  33. package/dist/components/TimePicker.vue.d.ts +1 -0
  34. package/dist/components/WellPlate.geometry.d.ts +15 -0
  35. package/dist/components/WellPlate.interaction.d.ts +13 -0
  36. package/dist/components/WellPlate.vue.d.ts +29 -0
  37. package/dist/components/index.js +6 -6
  38. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -0
  39. package/dist/components/internal/DataStateInternal.vue.d.ts +43 -0
  40. package/dist/{components-B80gV0P6.js → components-CgAUzF8F.js} +1930 -1688
  41. package/dist/components-CgAUzF8F.js.map +1 -0
  42. package/dist/composables/controlSchemaTypes.d.ts +34 -0
  43. package/dist/composables/index.d.ts +1 -0
  44. package/dist/composables/index.js +9 -9
  45. package/dist/composables/useDropdownState.d.ts +18 -2
  46. package/dist/composables/useEventStream.d.ts +9 -0
  47. package/dist/composables/useListReorder.d.ts +90 -0
  48. package/dist/composables/useMenuKeyboard.d.ts +2 -2
  49. package/dist/composables/usePluginConfig.d.ts +3 -1
  50. package/dist/composables/useRequestSyncState.d.ts +42 -2
  51. package/dist/composables/useScheduleDrag.d.ts +1 -0
  52. package/dist/{composables-C8mmD5ta.js → composables-B_pia3hO.js} +13 -69
  53. package/dist/composables-B_pia3hO.js.map +1 -0
  54. package/dist/{useApi-CDBq6z23.js → errors-_W0-LbA6.js} +102 -3
  55. package/dist/errors-_W0-LbA6.js.map +1 -0
  56. package/dist/{experiment-Y9Iz7KYi.js → experiment-Cwl9AYxw.js} +3 -8
  57. package/dist/experiment-Cwl9AYxw.js.map +1 -0
  58. package/dist/experiment-utils-DV3DrvE_.js.map +1 -1
  59. package/dist/index.js +14 -14
  60. package/dist/install.js +2 -7
  61. package/dist/install.js.map +1 -1
  62. package/dist/permissions.d.ts +42 -1
  63. package/dist/permissions.js +20 -10
  64. package/dist/permissions.js.map +1 -1
  65. package/dist/stores/index.d.ts +1 -1
  66. package/dist/stores/index.js +3 -3
  67. package/dist/stores/settings.d.ts +31 -1
  68. package/dist/styles.css +9572 -27877
  69. package/dist/templates/index.js +2 -4
  70. package/dist/{templates-CK72aKO1.js → templates-Qq113pyZ.js} +291 -85
  71. package/dist/templates-Qq113pyZ.js.map +1 -0
  72. package/dist/{useControlSchema-_OVhavKq.js → useControlSchema-aMZlb4Sc.js} +197 -1089
  73. package/dist/useControlSchema-aMZlb4Sc.js.map +1 -0
  74. package/dist/{useDropdownState-D5G4UvWd.js → useDropdownState-DitCtrTT.js} +21 -5
  75. package/dist/useDropdownState-DitCtrTT.js.map +1 -0
  76. package/dist/{useExperimentSelector-DSEckf_g.js → useExperimentSelector-I8HC8hUu.js} +3 -3
  77. package/dist/{useExperimentSelector-DSEckf_g.js.map → useExperimentSelector-I8HC8hUu.js.map} +1 -1
  78. package/dist/{useFormBuilder-Bd714C7y.js → useFormBuilder-TCxNnqeb.js} +2 -2
  79. package/dist/{useFormBuilder-Bd714C7y.js.map → useFormBuilder-TCxNnqeb.js.map} +1 -1
  80. package/dist/{useProtocolTemplates-Dq5Pzwyd.js → useProtocolTemplates-BmX_iVBR.js} +185 -157
  81. package/dist/useProtocolTemplates-BmX_iVBR.js.map +1 -0
  82. package/dist/utils/color.d.ts +13 -0
  83. package/dist/utils/errors.d.ts +9 -0
  84. package/package.json +1 -1
  85. package/src/__tests__/components/ActionMenu.test.ts +94 -0
  86. package/src/__tests__/components/DataFrame.test.ts +183 -11
  87. package/src/__tests__/components/DataStates.test.ts +176 -0
  88. package/src/__tests__/components/DatePicker.test.ts +76 -0
  89. package/src/__tests__/components/DateTimePicker.test.ts +36 -0
  90. package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
  91. package/src/__tests__/components/GroupAssigner.test.ts +53 -0
  92. package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
  93. package/src/__tests__/components/RackEditor.test.ts +35 -0
  94. package/src/__tests__/components/ReagentList.test.ts +64 -2
  95. package/src/__tests__/components/RetiredStyles.test.ts +31 -0
  96. package/src/__tests__/components/SampleSelector.drag.test.ts +51 -1
  97. package/src/__tests__/components/ScheduleCalendar.test.ts +63 -0
  98. package/src/__tests__/components/SegmentedControl.test.ts +88 -0
  99. package/src/__tests__/components/TimePicker.test.ts +61 -0
  100. package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
  101. package/src/__tests__/composables/useDropdownState.test.ts +58 -1
  102. package/src/__tests__/composables/useListReorder.test.ts +145 -0
  103. package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
  104. package/src/__tests__/stores/settings.test.ts +71 -1
  105. package/src/__tests__/styles/cssTokens.test.ts +131 -0
  106. package/src/__tests__/utils/permissions.test.ts +68 -0
  107. package/src/components/ActionMenu.vue +45 -88
  108. package/src/components/AlertBox.vue +0 -4
  109. package/src/components/AppAvatarMenu.vue +2 -14
  110. package/src/components/AppLayout.vue +0 -4
  111. package/src/components/AppPluginSwitcher.story.vue +87 -0
  112. package/src/components/AppPluginSwitcher.vue +2 -5
  113. package/src/components/AppSidebar.vue +0 -4
  114. package/src/components/AppToastContainer.vue +6 -8
  115. package/src/components/AppTopBar.vue +0 -4
  116. package/src/components/ArtifactSaveDialog.vue +0 -4
  117. package/src/components/ArtifactSelectorModal.vue +0 -4
  118. package/src/components/AuditTrail.story.vue +18 -0
  119. package/src/components/AuditTrail.vue +27 -7
  120. package/src/components/AutoGroupModal.vue +0 -4
  121. package/src/components/Avatar.vue +0 -4
  122. package/src/components/BaseButton.vue +0 -4
  123. package/src/components/BaseCheckbox.vue +0 -4
  124. package/src/components/BaseInput.vue +0 -4
  125. package/src/components/BaseModal.vue +0 -4
  126. package/src/components/BasePill.vue +0 -4
  127. package/src/components/BaseRadioGroup.vue +0 -4
  128. package/src/components/BaseSelect.vue +0 -4
  129. package/src/components/BaseSlider.vue +0 -4
  130. package/src/components/BaseTabs.vue +0 -4
  131. package/src/components/BaseTextarea.vue +0 -4
  132. package/src/components/BaseToggle.vue +0 -4
  133. package/src/components/BatchProgressList.story.vue +18 -0
  134. package/src/components/BatchProgressList.vue +32 -4
  135. package/src/components/Breadcrumb.vue +0 -4
  136. package/src/components/Calendar.vue +0 -4
  137. package/src/components/ChartContainer.story.vue +1 -1
  138. package/src/components/ChartContainer.vue +0 -4
  139. package/src/components/ChemicalFormula.vue +0 -4
  140. package/src/components/CollapsibleCard.vue +0 -4
  141. package/src/components/ColorSlider.vue +0 -4
  142. package/src/components/ConcentrationInput.vue +0 -4
  143. package/src/components/ConfirmDialog.vue +0 -4
  144. package/src/components/DataFrame.story.vue +14 -0
  145. package/src/components/DataFrame.vue +97 -23
  146. package/src/components/DatePicker.vue +73 -27
  147. package/src/components/DateTimePicker.vue +45 -17
  148. package/src/components/Divider.vue +0 -4
  149. package/src/components/DoseCalculator.vue +0 -4
  150. package/src/components/DropdownButton.vue +2 -14
  151. package/src/components/EmptyState.vue +0 -4
  152. package/src/components/ExperimentCodeBadge.vue +0 -4
  153. package/src/components/ExperimentDataViewer.story.vue +3 -0
  154. package/src/components/ExperimentDataViewer.vue +33 -13
  155. package/src/components/ExperimentPopover.vue +2 -5
  156. package/src/components/ExperimentSelectorModal.vue +0 -4
  157. package/src/components/ExperimentTimeline.story.vue +18 -0
  158. package/src/components/ExperimentTimeline.vue +81 -75
  159. package/src/components/FileUploader.vue +0 -4
  160. package/src/components/FitPanel.vue +0 -4
  161. package/src/components/FormActions.story.vue +96 -0
  162. package/src/components/FormActions.vue +0 -4
  163. package/src/components/FormBuilder.vue +0 -4
  164. package/src/components/FormField.vue +0 -4
  165. package/src/components/FormulaInput.vue +0 -4
  166. package/src/components/GeneratedPathInput.story.vue +68 -0
  167. package/src/components/GeneratedPlotlyResult.story.vue +96 -0
  168. package/src/components/GeneratedPlotlyResult.vue +3 -3
  169. package/src/components/GeneratedPluginView.story.vue +145 -0
  170. package/src/components/GeneratedPluginView.vue +10 -10
  171. package/src/components/GeneratedResultRenderer.story.vue +117 -0
  172. package/src/components/GeneratedResultRenderer.vue +4 -4
  173. package/src/components/GroupAssigner.vue +67 -5
  174. package/src/components/IconButton.vue +0 -4
  175. package/src/components/InstrumentAlertLog.vue +0 -4
  176. package/src/components/InstrumentStateBadge.vue +0 -4
  177. package/src/components/InstrumentStatusCard.vue +0 -4
  178. package/src/components/JobsStatusTray.vue +0 -4
  179. package/src/components/LcmsSequenceTable.vue +81 -50
  180. package/src/components/LoadingSpinner.vue +0 -4
  181. package/src/components/MobileSupportGate.vue +0 -4
  182. package/src/components/MoleculeInput.vue +0 -4
  183. package/src/components/MultiSelect.vue +0 -4
  184. package/src/components/NumberInput.vue +0 -4
  185. package/src/components/PlateMapEditor.vue +3 -14
  186. package/src/components/PluginIcon.vue +0 -4
  187. package/src/components/ProgressBar.vue +0 -4
  188. package/src/components/ProtocolStepEditor.vue +2 -4
  189. package/src/components/RackEditor.vue +51 -70
  190. package/src/components/ReagentEditor.vue +0 -4
  191. package/src/components/ReagentList.story.vue +18 -0
  192. package/src/components/ReagentList.vue +127 -82
  193. package/src/components/ResourceCard.vue +0 -4
  194. package/src/components/RuntimeUpdateNotice.story.vue +49 -0
  195. package/src/components/RuntimeUpdateNotice.vue +4 -4
  196. package/src/components/SampleHierarchyTree.story.vue +18 -0
  197. package/src/components/SampleHierarchyTree.vue +32 -9
  198. package/src/components/SampleLegend.vue +2 -8
  199. package/src/components/SampleSelector.drag.ts +51 -0
  200. package/src/components/SampleSelector.story.vue +6 -0
  201. package/src/components/SampleSelector.tree.ts +2 -0
  202. package/src/components/SampleSelector.vue +49 -10
  203. package/src/components/SampleSelectorGroupNode.vue +19 -0
  204. package/src/components/ScheduleCalendar.story.vue +6 -0
  205. package/src/components/ScheduleCalendar.vue +82 -7
  206. package/src/components/ScientificNumber.vue +0 -4
  207. package/src/components/SectionCard.vue +0 -4
  208. package/src/components/SegmentedControl.vue +48 -5
  209. package/src/components/SequenceInput.vue +0 -4
  210. package/src/components/SequenceProgressBar.story.vue +110 -0
  211. package/src/components/SequenceProgressBar.vue +0 -4
  212. package/src/components/SettingsModal.vue +0 -4
  213. package/src/components/Skeleton.vue +0 -4
  214. package/src/components/SmartGroupFieldRecipe.vue +8 -6
  215. package/src/components/SmartGroupManual.vue +0 -4
  216. package/src/components/StatusIndicator.vue +0 -4
  217. package/src/components/StepWizard.vue +0 -4
  218. package/src/components/TagsInput.vue +4 -6
  219. package/src/components/ThemeToggle.vue +0 -4
  220. package/src/components/TimePicker.vue +36 -7
  221. package/src/components/TimeRangeInput.vue +0 -4
  222. package/src/components/Tooltip.story.vue +3 -3
  223. package/src/components/Tooltip.vue +0 -4
  224. package/src/components/UnitInput.vue +0 -4
  225. package/src/components/WellPlate.geometry.ts +60 -1
  226. package/src/components/WellPlate.interaction.ts +44 -0
  227. package/src/components/WellPlate.story.vue +8 -0
  228. package/src/components/WellPlate.vue +53 -7
  229. package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -5
  230. package/src/components/internal/AppTopBarPillNavInternal.vue +0 -4
  231. package/src/components/internal/DataStateInternal.vue +74 -0
  232. package/src/components/internal/FormSectionRenderer.vue +0 -4
  233. package/src/components/internal/WellEditPopupInternal.vue +0 -4
  234. package/src/composables/controlSchemaTypes.ts +34 -0
  235. package/src/composables/index.ts +10 -0
  236. package/src/composables/useDropdownState.ts +35 -5
  237. package/src/composables/useEventStream.ts +14 -2
  238. package/src/composables/useFileImport.ts +2 -7
  239. package/src/composables/useGeneratedAnalysis.ts +8 -9
  240. package/src/composables/useListReorder.story.vue +143 -0
  241. package/src/composables/useListReorder.ts +242 -0
  242. package/src/composables/useMenuKeyboard.ts +2 -2
  243. package/src/composables/useOptimisticMutation.ts +1 -15
  244. package/src/composables/usePluginConfig.ts +3 -1
  245. package/src/composables/useRackEditor.ts +2 -11
  246. package/src/composables/useRequestSyncState.ts +98 -28
  247. package/src/composables/useResourceCrud.ts +1 -15
  248. package/src/composables/useScheduleDrag.ts +37 -0
  249. package/src/composables/useWellPainting.ts +5 -29
  250. package/src/composables/useWellPlateEditor.ts +1 -11
  251. package/src/permissions.ts +48 -9
  252. package/src/stores/experiment.ts +2 -7
  253. package/src/stores/index.ts +9 -1
  254. package/src/stores/settings.ts +93 -1
  255. package/src/styles/components/audit-trail.css +5 -5
  256. package/src/styles/components/batch-progress-list.css +7 -0
  257. package/src/styles/components/color-slider.css +2 -2
  258. package/src/styles/components/data-state.css +30 -0
  259. package/src/styles/components/dataframe.css +29 -35
  260. package/src/styles/components/experiment-data-viewer.css +4 -4
  261. package/src/styles/components/experiment-timeline.css +3 -30
  262. package/src/styles/components/form-builder.css +5 -5
  263. package/src/styles/components/lcms-sequence-table.css +16 -1
  264. package/src/styles/components/multi-select.css +1 -1
  265. package/src/styles/components/reagent-list.css +38 -31
  266. package/src/styles/components/sample-hierarchy-tree.css +5 -19
  267. package/src/styles/components/sample-selector.css +9 -2
  268. package/src/styles/components/schedule-calendar.css +8 -0
  269. package/src/styles/components/scientific-number.css +1 -1
  270. package/src/styles/components/well-plate.css +8 -0
  271. package/src/styles/index.css +8 -0
  272. package/src/styles/variables.css +29 -0
  273. package/src/templates/builderPresetControls.ts +1 -1
  274. package/src/templates/componentPlateHelpers.ts +3 -1
  275. package/src/templates/doseResponseAdapters.ts +2 -13
  276. package/src/templates/elisaAssayCollectionBuilder.ts +2 -2
  277. package/src/templates/plateMapBuilder.ts +2 -2
  278. package/src/templates/qpcrPlateBuilder.ts +1 -1
  279. package/src/templates/templateValidators.ts +1 -1
  280. package/src/templates/timeCourseBuilder.ts +2 -2
  281. package/src/templates/wellPlateScreenCollectionBuilder.ts +2 -2
  282. package/src/utils/color.ts +51 -0
  283. package/src/utils/errors.ts +29 -0
  284. package/dist/EmptyState-NBqf2_8z.js.map +0 -1
  285. package/dist/ExperimentPopover-CfOdh5Rm.js.map +0 -1
  286. package/dist/ExperimentSelectorModal-DvIagrxY.js.map +0 -1
  287. package/dist/SettingsModal-W3TYfnP9.js.map +0 -1
  288. package/dist/components-B80gV0P6.js.map +0 -1
  289. package/dist/composables/autoGroupConstants.d.ts +0 -1
  290. package/dist/composables/autoGroupGrouping.d.ts +0 -3
  291. package/dist/composables/useAutoGroupInputSources.d.ts +0 -32
  292. package/dist/composables-C8mmD5ta.js.map +0 -1
  293. package/dist/experiment-Y9Iz7KYi.js.map +0 -1
  294. package/dist/templates/builderDefaults.d.ts +0 -1
  295. package/dist/templates/plateGeometry.d.ts +0 -4
  296. package/dist/templates-CK72aKO1.js.map +0 -1
  297. package/dist/useApi-CDBq6z23.js.map +0 -1
  298. package/dist/useControlSchema-_OVhavKq.js.map +0 -1
  299. package/dist/useDropdownState-D5G4UvWd.js.map +0 -1
  300. package/dist/useProtocolTemplates-Dq5Pzwyd.js.map +0 -1
  301. package/src/__tests__/composables/useAutoGroupInputSources.test.ts +0 -127
  302. package/src/composables/autoGroupConstants.ts +0 -4
  303. package/src/composables/autoGroupGrouping.ts +0 -148
  304. package/src/composables/useAutoGroupInputSources.ts +0 -147
  305. package/src/templates/builderDefaults.ts +0 -11
  306. package/src/templates/plateGeometry.ts +0 -62
  307. /package/dist/__tests__/{composables/useAutoGroupInputSources.test.d.ts → components/DataStates.test.d.ts} +0 -0
@@ -1,10 +1,18 @@
1
- import { ref, type Ref } from 'vue'
1
+ import { nextTick, ref, type Ref } from 'vue'
2
2
  import { useEventListener } from './useEventListener'
3
3
 
4
4
  export interface UseDropdownStateOptions {
5
5
  closeOnOutsideClick?: boolean
6
6
  closeOnEscape?: boolean
7
+ /** Close the panel when the page scrolls or the viewport resizes. Default `false`. */
8
+ closeOnScroll?: boolean
7
9
  insideRefs?: Array<Ref<HTMLElement | null>>
10
+ /**
11
+ * Element focus returns to when the panel closes with `close(true)` (Escape).
12
+ * Pass one when the trigger lives outside `rootRef`; otherwise bind the
13
+ * returned `triggerRef` to the trigger element.
14
+ */
15
+ triggerRef?: Ref<HTMLElement | null>
8
16
  onOpen?: () => void
9
17
  onClose?: () => void
10
18
  }
@@ -12,19 +20,29 @@ export interface UseDropdownStateOptions {
12
20
  export interface UseDropdownStateReturn {
13
21
  isOpen: Ref<boolean>
14
22
  rootRef: Ref<HTMLElement | null>
23
+ /** Bind to the trigger element so Escape can return focus to it. */
24
+ triggerRef: Ref<HTMLElement | null>
15
25
  open: () => void
16
- close: () => void
26
+ /** Closes the panel; `returnFocus` moves focus back to `triggerRef`. */
27
+ close: (returnFocus?: boolean) => void
17
28
  toggle: () => void
18
29
  handleClickOutside: (event: MouseEvent) => void
19
30
  handleKeydown: (event: KeyboardEvent) => void
20
31
  }
21
32
 
22
- /** Shared disclosure state for dropdown-style SDK components. */
33
+ /**
34
+ * Shared disclosure state for dropdown-style SDK components: open/close state,
35
+ * outside-click and Escape closing, optional scroll/resize closing, and focus
36
+ * return to the trigger. `close(returnFocus)` mirrors `useMenuKeyboard`'s
37
+ * contract, so the two compose directly.
38
+ */
23
39
  export function useDropdownState(options: UseDropdownStateOptions = {}): UseDropdownStateReturn {
24
40
  const isOpen = ref(false)
25
41
  const rootRef = ref<HTMLElement | null>(null)
42
+ const triggerRef = options.triggerRef ?? ref<HTMLElement | null>(null)
26
43
  const closeOnOutsideClick = options.closeOnOutsideClick ?? true
27
44
  const closeOnEscape = options.closeOnEscape ?? true
45
+ const closeOnScroll = options.closeOnScroll ?? false
28
46
 
29
47
  function open(): void {
30
48
  if (isOpen.value) return
@@ -32,10 +50,13 @@ export function useDropdownState(options: UseDropdownStateOptions = {}): UseDrop
32
50
  options.onOpen?.()
33
51
  }
34
52
 
35
- function close(): void {
53
+ function close(returnFocus = false): void {
36
54
  if (!isOpen.value) return
37
55
  isOpen.value = false
38
56
  options.onClose?.()
57
+ if (returnFocus) {
58
+ void nextTick(() => triggerRef.value?.focus())
59
+ }
39
60
  }
40
61
 
41
62
  function toggle(): void {
@@ -60,20 +81,29 @@ export function useDropdownState(options: UseDropdownStateOptions = {}): UseDrop
60
81
 
61
82
  function handleKeydown(event: KeyboardEvent): void {
62
83
  if (closeOnEscape && event.key === 'Escape') {
63
- close()
84
+ close(true)
64
85
  }
65
86
  }
66
87
 
88
+ function handleViewportChange(): void {
89
+ if (isOpen.value) close()
90
+ }
91
+
67
92
  if (closeOnOutsideClick) {
68
93
  useEventListener(() => document, 'click', handleClickOutside)
69
94
  }
70
95
  if (closeOnEscape) {
71
96
  useEventListener(() => document, 'keydown', handleKeydown)
72
97
  }
98
+ if (closeOnScroll) {
99
+ useEventListener(() => window, 'scroll', handleViewportChange, true)
100
+ useEventListener(() => window, 'resize', handleViewportChange)
101
+ }
73
102
 
74
103
  return {
75
104
  isOpen,
76
105
  rootRef,
106
+ triggerRef,
77
107
  open,
78
108
  close,
79
109
  toggle,
@@ -7,6 +7,7 @@ import {
7
7
  shallowRef,
8
8
  type Ref,
9
9
  } from 'vue'
10
+ import { tryUseSettingsStore } from '../stores/settings'
10
11
 
11
12
  export type EventStreamTransport = (
12
13
  input: RequestInfo | URL,
@@ -26,7 +27,16 @@ export interface EventStreamOptions<TData = string> {
26
27
  headers?: HeadersInit
27
28
  credentials?: RequestCredentials
28
29
  immediate?: boolean
30
+ /**
31
+ * Reconnect after a dropped stream. Left unset, it follows the platform's
32
+ * `wsAutoReconnect` setting, defaulting to `true` without a settings store.
33
+ */
29
34
  reconnect?: boolean
35
+ /**
36
+ * Delay before the first reconnect attempt. Left unset, it follows the
37
+ * platform's `wsReconnectInterval` setting, defaulting to 2000 ms without a
38
+ * settings store. A server-sent `retry:` field still overrides it.
39
+ */
30
40
  reconnectDelayMs?: number
31
41
  parseJson?: boolean
32
42
  shouldReconnect?: (cause?: unknown) => boolean
@@ -114,7 +124,9 @@ export function useEventStream<TData = string>(
114
124
  let controller: AbortController | null = null
115
125
  let reader: ReadableStreamDefaultReader<Uint8Array> | null = null
116
126
  let reconnectTimer: ReturnType<typeof setTimeout> | null = null
117
- let reconnectDelayMs = options.reconnectDelayMs ?? 2_000
127
+ const settings = tryUseSettingsStore()
128
+ const reconnectEnabled = options.reconnect ?? settings?.wsAutoReconnect ?? true
129
+ let reconnectDelayMs = options.reconnectDelayMs ?? settings?.wsReconnectInterval ?? 2_000
118
130
  let stopped = true
119
131
  let generation = 0
120
132
 
@@ -135,7 +147,7 @@ export function useEventStream<TData = string>(
135
147
 
136
148
  function scheduleReconnect(connectionGeneration: number, cause?: unknown): void {
137
149
  if (stopped || generation !== connectionGeneration) return
138
- if (options.reconnect === false || options.shouldReconnect?.(cause) === false) {
150
+ if (!reconnectEnabled || options.shouldReconnect?.(cause) === false) {
139
151
  stopped = true
140
152
  return
141
153
  }
@@ -1,4 +1,5 @@
1
1
  import { ref, shallowRef, type Ref, type ShallowRef } from 'vue'
2
+ import { readErrorMessage } from '../utils/errors'
2
3
 
3
4
  export interface DelimitedTable {
4
5
  columns: string[]
@@ -63,7 +64,7 @@ export function useFileImport<TResult = string>(
63
64
  result.value = parsed
64
65
  return parsed
65
66
  } catch (err) {
66
- const message = readErrorMessage(err)
67
+ const message = readErrorMessage(err, 'Failed to import file.')
67
68
  error.value = message
68
69
  throw err
69
70
  } finally {
@@ -223,9 +224,3 @@ function formatBytes(bytes: number): string {
223
224
  if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`
224
225
  return `${Math.round(bytes / (1024 * 1024))} MB`
225
226
  }
226
-
227
- function readErrorMessage(value: unknown): string {
228
- if (value instanceof Error && value.message) return value.message
229
- if (typeof value === 'string' && value.trim()) return value
230
- return 'Failed to import file.'
231
- }
@@ -13,7 +13,7 @@ import type {
13
13
  PluginJobCenterSource,
14
14
  } from './useJobsStatusTray'
15
15
  import { useEventStream, type EventStreamMessage } from './useEventStream'
16
- import { normalizeJobState } from '../jobs'
16
+ import { isActiveJobStatus, normalizeJobState } from '../jobs'
17
17
  import type {
18
18
  GeneratedAnalysisJob,
19
19
  GeneratedAnalysisJobInput,
@@ -111,7 +111,6 @@ export interface UsePluginJobsReturn extends PluginJobCenterSource {
111
111
 
112
112
  export type UseGeneratedAnalysisReturn = UsePluginJobsReturn
113
113
 
114
- const ACTIVE_STATUSES = new Set(['awaiting_input', 'queued', 'running'])
115
114
  const AUTH_TOKEN_KEY = 'mint-auth-token'
116
115
  const SESSION_NOT_FOUND_DETAIL = 'Session not found'
117
116
  const MAX_RESULT_CACHE_ENTRIES = 32
@@ -473,12 +472,12 @@ function createPluginJobs(
473
472
  .then((payload) => {
474
473
  if (disposed) return jobs.value
475
474
  if (revision !== jobsRevision) {
476
- if (jobs.value.some(job => ACTIVE_STATUSES.has(job.status))) schedulePoll()
475
+ if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
477
476
  return jobs.value
478
477
  }
479
478
  replaceJobs(payload.jobs)
480
479
  error.value = null
481
- if (!paused && !jobStream.isConnected.value && jobs.value.some(job => ACTIVE_STATUSES.has(job.status))) {
480
+ if (!paused && !jobStream.isConnected.value && jobs.value.some(job => isActiveJobStatus(job.status))) {
482
481
  schedulePoll()
483
482
  }
484
483
  return jobs.value
@@ -530,7 +529,7 @@ function createPluginJobs(
530
529
  const current = typeof job === 'string'
531
530
  ? jobs.value.find(candidate => candidate.job_id === jobId)
532
531
  : jobs.value.find(candidate => candidate.job_id === jobId) ?? job
533
- if (current && !ACTIVE_STATUSES.has(current.status)) {
532
+ if (current && !isActiveJobStatus(current.status)) {
534
533
  return terminalWaitResult(current, options.rejectOnFailure === true)
535
534
  }
536
535
  if (paused) return Promise.reject(new Error('Plugin jobs runtime is paused'))
@@ -567,7 +566,7 @@ function createPluginJobs(
567
566
  }
568
567
 
569
568
  function settleJobWaiters(job: GeneratedAnalysisJob): void {
570
- if (ACTIVE_STATUSES.has(job.status)) return
569
+ if (isActiveJobStatus(job.status)) return
571
570
  const waiting = jobWaiters.get(job.job_id)
572
571
  if (!waiting) return
573
572
  jobWaiters.delete(job.job_id)
@@ -915,7 +914,7 @@ function createPluginJobs(
915
914
  await refreshJobs()
916
915
  } catch (cause) {
917
916
  error.value = errorMessage(cause, 'Failed to refresh analysis jobs')
918
- if (jobs.value.some(job => ACTIVE_STATUSES.has(job.status))) schedulePoll()
917
+ if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
919
918
  }
920
919
  }, pollInterval)
921
920
  }
@@ -927,7 +926,7 @@ function createPluginJobs(
927
926
  }
928
927
 
929
928
  function schedulePollForActiveJobs(): void {
930
- if (jobs.value.some(job => ACTIVE_STATUSES.has(job.status))) schedulePoll()
929
+ if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
931
930
  }
932
931
 
933
932
  function applyJobStreamMessage(message: EventStreamMessage<PluginJobStreamData>): void {
@@ -1005,7 +1004,7 @@ function createPluginJobs(
1005
1004
  scheduleManagedDownloadRefresh(jobId, managedDownloadRefreshDelay(result))
1006
1005
  }
1007
1006
  if (streamEnabled) jobStream.start()
1008
- else if (jobs.value.some(job => ACTIVE_STATUSES.has(job.status))) schedulePoll()
1007
+ else if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
1009
1008
  }
1010
1009
 
1011
1010
  function dispose(): void {
@@ -0,0 +1,143 @@
1
+ <script setup lang="ts">
2
+ /** Demonstrates `useListReorder`: drag a row, or focus a handle and press Alt with the arrow keys. */
3
+ import { ref } from 'vue'
4
+ import { useListReorder } from './useListReorder'
5
+
6
+ interface Step { id: string; name: string }
7
+
8
+ const steps = ref<Step[]>([
9
+ { id: 's1', name: 'Thaw samples' },
10
+ { id: 's2', name: 'Quench metabolism' },
11
+ { id: 's3', name: 'Extract metabolites' },
12
+ { id: 's4', name: 'Dry down' },
13
+ ])
14
+
15
+ const handles = new Map<string, HTMLElement>()
16
+
17
+ function setHandle(id: string, el: Element | null) {
18
+ if (el instanceof HTMLElement) handles.set(id, el)
19
+ else handles.delete(id)
20
+ }
21
+
22
+ const {
23
+ draggingId,
24
+ dragOverId,
25
+ announcement,
26
+ announcerStyle,
27
+ onDragStart,
28
+ onDragOver,
29
+ onDragLeave,
30
+ onDrop,
31
+ onDragEnd,
32
+ onKeydown,
33
+ } = useListReorder<Step>({
34
+ items: () => steps.value,
35
+ getId: (step) => step.id,
36
+ getLabel: (step) => step.name,
37
+ getElement: (id) => handles.get(id),
38
+ itemNoun: 'Step',
39
+ onReorder: ({ items }) => {
40
+ steps.value = items
41
+ },
42
+ })
43
+
44
+ function initState() {
45
+ return { showAnnouncement: true }
46
+ }
47
+ </script>
48
+
49
+ <template>
50
+ <Story title="Composables/useListReorder">
51
+ <Variant title="Playground" :init-state="initState">
52
+ <template #default="{ state }">
53
+ <div style="padding: 2rem; max-width: 600px;">
54
+ <p class="mint-story-hint">
55
+ Drag a row, or focus its handle and press Alt with the up/down arrow keys.
56
+ </p>
57
+ <ul class="mint-story-list">
58
+ <li
59
+ v-for="(step, index) in steps"
60
+ :key="step.id"
61
+ class="mint-story-row"
62
+ :class="{
63
+ 'mint-story-row--dragging': draggingId === step.id,
64
+ 'mint-story-row--over': dragOverId === step.id,
65
+ }"
66
+ draggable="true"
67
+ @dragstart="onDragStart($event, step, index)"
68
+ @dragover="onDragOver($event, step, index)"
69
+ @dragleave="onDragLeave"
70
+ @drop="onDrop($event, step, index)"
71
+ @dragend="onDragEnd"
72
+ >
73
+ <button
74
+ :ref="(el) => setHandle(step.id, el as Element | null)"
75
+ type="button"
76
+ class="mint-story-handle"
77
+ :aria-label="`Reorder ${step.name}. Press Alt with the arrow keys to move it.`"
78
+ @keydown="onKeydown($event, step, index)"
79
+ >
80
+
81
+ </button>
82
+ <span>{{ index + 1 }}. {{ step.name }}</span>
83
+ </li>
84
+ </ul>
85
+
86
+ <p v-if="state.showAnnouncement" class="mint-story-hint">
87
+ Live region: <em>{{ announcement || '—' }}</em>
88
+ </p>
89
+ <div role="status" aria-live="polite" :style="announcerStyle">{{ announcement }}</div>
90
+ </div>
91
+ </template>
92
+
93
+ <template #controls="{ state }">
94
+ <HstCheckbox v-model="state.showAnnouncement" title="Show live-region text" />
95
+ </template>
96
+ </Variant>
97
+ </Story>
98
+ </template>
99
+
100
+ <style scoped>
101
+ .mint-story-list {
102
+ list-style: none;
103
+ margin: 0;
104
+ padding: 0;
105
+ display: flex;
106
+ flex-direction: column;
107
+ gap: 0.25rem;
108
+ }
109
+
110
+ .mint-story-row {
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 0.5rem;
114
+ padding: 0.5rem 0.75rem;
115
+ border: 1px solid var(--border-color);
116
+ border-radius: 0.5rem;
117
+ background: var(--bg-primary);
118
+ color: var(--text-primary);
119
+ }
120
+
121
+ .mint-story-row--dragging {
122
+ opacity: 0.5;
123
+ }
124
+
125
+ .mint-story-row--over {
126
+ border-color: var(--color-primary);
127
+ }
128
+
129
+ .mint-story-handle {
130
+ padding: 0;
131
+ border: 0;
132
+ background: none;
133
+ color: var(--text-muted);
134
+ cursor: grab;
135
+ font-size: 1rem;
136
+ }
137
+
138
+ .mint-story-hint {
139
+ margin: 0 0 0.75rem;
140
+ color: var(--text-secondary);
141
+ font-size: 0.8125rem;
142
+ }
143
+ </style>
@@ -0,0 +1,242 @@
1
+ import { nextTick, ref, type Ref } from 'vue'
2
+
3
+ /** Inline style for a visually hidden `aria-live` region (no global utility class in the SDK). */
4
+ export const REORDER_ANNOUNCER_STYLE = Object.freeze({
5
+ position: 'absolute',
6
+ width: '1px',
7
+ height: '1px',
8
+ padding: '0',
9
+ margin: '-1px',
10
+ overflow: 'hidden',
11
+ clip: 'rect(0, 0, 0, 0)',
12
+ whiteSpace: 'nowrap',
13
+ border: '0',
14
+ })
15
+
16
+ export interface UseReorderAnnouncerReturn {
17
+ /** Bind to a polite live region's text content. */
18
+ announcement: Ref<string>
19
+ /** Publishes a message to the live region. */
20
+ announce: (message: string) => void
21
+ /** Bind to the live region's `style` so it stays visually hidden. */
22
+ announcerStyle: typeof REORDER_ANNOUNCER_STYLE
23
+ }
24
+
25
+ /**
26
+ * Screen-reader announcements for drag/keyboard move interactions. Bind
27
+ * `announcement` to a `role="status" aria-live="polite"` element styled with
28
+ * `announcerStyle`.
29
+ */
30
+ export function useReorderAnnouncer(): UseReorderAnnouncerReturn {
31
+ const announcement = ref('')
32
+
33
+ function announce(message: string) {
34
+ // Re-announce identical consecutive messages by clearing first.
35
+ if (announcement.value === message) announcement.value = ''
36
+ void nextTick(() => {
37
+ announcement.value = message
38
+ })
39
+ }
40
+
41
+ return { announcement, announce, announcerStyle: REORDER_ANNOUNCER_STYLE }
42
+ }
43
+
44
+ export interface ListReorderMove<T> {
45
+ /** Index the item moved from, within `items()`. */
46
+ fromIndex: number
47
+ /** Index the item moved to, within `items()`. */
48
+ toIndex: number
49
+ /** The reordered array (a copy — `items()` is never mutated). */
50
+ items: T[]
51
+ /** Whether the move came from the keyboard or a pointer drag. */
52
+ source: 'keyboard' | 'drag'
53
+ }
54
+
55
+ export interface UseListReorderOptions<T> {
56
+ /** The list being reordered, in its canonical (not merely rendered) order. */
57
+ items: () => T[]
58
+ /** Stable id for an item; falls back to the render index when ids repeat. */
59
+ getId: (item: T, index: number) => string
60
+ /** Applies the move. The composable never mutates the source array. */
61
+ onReorder: (move: ListReorderMove<T>) => void
62
+ /** Human-readable name used in announcements. Defaults to the item's id. */
63
+ getLabel?: (item: T, index: number) => string
64
+ /** Blocks every reorder while it returns `true` (readonly / non-sortable lists). */
65
+ disabled?: () => boolean
66
+ /** Resolves the focusable element for an id so keyboard moves keep focus on the item. */
67
+ getElement?: (id: string) => HTMLElement | null | undefined
68
+ /** Noun used in announcements, e.g. `'Reagent'`. Default `'Item'`. */
69
+ itemNoun?: string
70
+ }
71
+
72
+ export interface UseListReorderReturn<T> {
73
+ /** Id of the item currently being dragged, or `null`. */
74
+ draggingId: Ref<string | null>
75
+ /** Id of the item the pointer is hovering as a drop target, or `null`. */
76
+ dragOverId: Ref<string | null>
77
+ isDragging: (id: string) => boolean
78
+ isDragOver: (id: string) => boolean
79
+ announcement: Ref<string>
80
+ announcerStyle: typeof REORDER_ANNOUNCER_STYLE
81
+ announce: (message: string) => void
82
+ /** Bind to the draggable element's `@dragstart`. */
83
+ onDragStart: (event: DragEvent, item: T, index: number) => void
84
+ /** Bind to `@dragover` — calls `preventDefault()` so the drop is accepted. */
85
+ onDragOver: (event: DragEvent, item: T, index: number) => void
86
+ /** Bind to `@dragleave`. */
87
+ onDragLeave: () => void
88
+ /** Bind to `@drop`. */
89
+ onDrop: (event: DragEvent, item: T, index: number) => void
90
+ /** Bind to `@dragend`. */
91
+ onDragEnd: () => void
92
+ /**
93
+ * Bind to the item's (or its drag handle's) `@keydown`: Alt+ArrowUp / Alt+ArrowLeft
94
+ * moves the item one position earlier, Alt+ArrowDown / Alt+ArrowRight one later.
95
+ */
96
+ onKeydown: (event: KeyboardEvent, item: T, index: number) => void
97
+ /** Moves an item between two indices of `items()` programmatically. */
98
+ move: (fromIndex: number, toIndex: number, source?: 'keyboard' | 'drag') => void
99
+ }
100
+
101
+ /** Pure helper: a copy of `items` with the entry at `from` moved to `to`. */
102
+ export function reorderList<T>(items: T[], from: number, to: number): T[] {
103
+ const next = [...items]
104
+ const [moved] = next.splice(from, 1)
105
+ next.splice(to, 0, moved)
106
+ return next
107
+ }
108
+
109
+ /**
110
+ * Keyboard-accessible list reordering shared by the SDK's drag-to-reorder lists.
111
+ * It owns the HTML5 drag state, the drop-index maths, and an Alt+Arrow keyboard
112
+ * alternative that announces every move through a polite live region — so a
113
+ * reorder never depends on a pointer.
114
+ */
115
+ export function useListReorder<T>(options: UseListReorderOptions<T>): UseListReorderReturn<T> {
116
+ const draggingId = ref<string | null>(null)
117
+ const dragOverId = ref<string | null>(null)
118
+ const { announcement, announce, announcerStyle } = useReorderAnnouncer()
119
+ const noun = options.itemNoun ?? 'Item'
120
+
121
+ function isDisabled(): boolean {
122
+ return options.disabled?.() === true
123
+ }
124
+
125
+ function idFor(item: T, index: number): string {
126
+ return options.getId(item, index)
127
+ }
128
+
129
+ /** Index within `items()`, resolved by id so filtered/sorted renders still map back. */
130
+ function indexOf(item: T, renderIndex: number): number {
131
+ const id = idFor(item, renderIndex)
132
+ const found = options.items().findIndex((candidate, index) => idFor(candidate, index) === id)
133
+ return found === -1 ? renderIndex : found
134
+ }
135
+
136
+ function labelFor(index: number): string {
137
+ const item = options.items()[index]
138
+ if (item === undefined) return noun
139
+ return options.getLabel?.(item, index) ?? idFor(item, index)
140
+ }
141
+
142
+ function clearDragState() {
143
+ draggingId.value = null
144
+ dragOverId.value = null
145
+ }
146
+
147
+ function move(fromIndex: number, toIndex: number, source: 'keyboard' | 'drag' = 'keyboard') {
148
+ if (isDisabled()) return
149
+ const items = options.items()
150
+ if (
151
+ fromIndex === toIndex ||
152
+ fromIndex < 0 ||
153
+ toIndex < 0 ||
154
+ fromIndex >= items.length ||
155
+ toIndex >= items.length
156
+ ) return
157
+
158
+ const movedId = idFor(items[fromIndex], fromIndex)
159
+ const label = labelFor(fromIndex)
160
+ options.onReorder({
161
+ fromIndex,
162
+ toIndex,
163
+ items: reorderList(items, fromIndex, toIndex),
164
+ source,
165
+ })
166
+ announce(`${label} moved to position ${toIndex + 1} of ${items.length}.`)
167
+ void nextTick(() => options.getElement?.(movedId)?.focus())
168
+ }
169
+
170
+ function onDragStart(event: DragEvent, item: T, index: number) {
171
+ if (isDisabled()) return
172
+ const id = idFor(item, index)
173
+ draggingId.value = id
174
+ if (event.dataTransfer) {
175
+ event.dataTransfer.effectAllowed = 'move'
176
+ event.dataTransfer.setData('text/plain', id)
177
+ }
178
+ }
179
+
180
+ function onDragOver(event: DragEvent, item: T, index: number) {
181
+ if (isDisabled() || draggingId.value === null) return
182
+ event.preventDefault()
183
+ if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'
184
+ const id = idFor(item, index)
185
+ dragOverId.value = id === draggingId.value ? null : id
186
+ }
187
+
188
+ function onDragLeave() {
189
+ dragOverId.value = null
190
+ }
191
+
192
+ function onDrop(event: DragEvent, item: T, index: number) {
193
+ if (isDisabled() || draggingId.value === null) {
194
+ clearDragState()
195
+ return
196
+ }
197
+ event.preventDefault()
198
+
199
+ const items = options.items()
200
+ const fromIndex = items.findIndex((candidate, i) => idFor(candidate, i) === draggingId.value)
201
+ const toIndex = indexOf(item, index)
202
+ clearDragState()
203
+ if (fromIndex === -1) return
204
+ move(fromIndex, toIndex, 'drag')
205
+ }
206
+
207
+ function onDragEnd() {
208
+ clearDragState()
209
+ }
210
+
211
+ function onKeydown(event: KeyboardEvent, item: T, index: number) {
212
+ if (isDisabled() || !event.altKey) return
213
+ const direction =
214
+ event.key === 'ArrowUp' || event.key === 'ArrowLeft'
215
+ ? -1
216
+ : event.key === 'ArrowDown' || event.key === 'ArrowRight'
217
+ ? 1
218
+ : 0
219
+ if (direction === 0) return
220
+
221
+ event.preventDefault()
222
+ const fromIndex = indexOf(item, index)
223
+ move(fromIndex, fromIndex + direction, 'keyboard')
224
+ }
225
+
226
+ return {
227
+ draggingId,
228
+ dragOverId,
229
+ isDragging: (id: string) => draggingId.value === id,
230
+ isDragOver: (id: string) => dragOverId.value === id,
231
+ announcement,
232
+ announcerStyle,
233
+ announce,
234
+ onDragStart,
235
+ onDragOver,
236
+ onDragLeave,
237
+ onDrop,
238
+ onDragEnd,
239
+ onKeydown,
240
+ move,
241
+ }
242
+ }
@@ -27,8 +27,8 @@ export interface UseMenuKeyboardReturn {
27
27
 
28
28
  /**
29
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.
30
+ * WAI-ARIA menu button pattern). Used by ActionMenu, AppAvatarMenu and DropdownButton,
31
+ * each pairing it with `useDropdownState` for the disclosure state itself.
32
32
  */
33
33
  export function useMenuKeyboard(options: UseMenuKeyboardOptions): UseMenuKeyboardReturn {
34
34
  function focusItem(position: 'first' | 'last') {
@@ -1,4 +1,5 @@
1
1
  import { computed, ref, shallowRef, type ComputedRef, type Ref, type ShallowRef } from 'vue'
2
+ import { readErrorMessage } from '../utils/errors'
2
3
 
3
4
  export interface OptimisticMutationContext<TVariables, TSnapshot> {
4
5
  variables: TVariables
@@ -94,18 +95,3 @@ export function useOptimisticMutation<TVariables, TData, TSnapshot = unknown>(
94
95
  reset,
95
96
  }
96
97
  }
97
-
98
- function readErrorMessage(value: unknown): string {
99
- if (value instanceof Error && value.message) return value.message
100
- if (typeof value === 'string' && value.trim()) return value
101
- if (
102
- typeof value === 'object'
103
- && value !== null
104
- && 'message' in value
105
- && typeof (value as { message?: unknown }).message === 'string'
106
- && (value as { message: string }).message.trim()
107
- ) {
108
- return (value as { message: string }).message
109
- }
110
- return 'Request failed.'
111
- }
@@ -75,7 +75,9 @@ export interface UsePluginConfigReturn {
75
75
  reset: () => void
76
76
  }
77
77
 
78
- /** @deprecated Use `usePluginSettings()` for typed plugin settings. */
78
+ /**
79
+ * @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `usePluginSettings()` for typed plugin settings.
80
+ */
79
81
  export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
80
82
  const injectedContext = getInjectedPlatformContext()
81
83
  const { plugin } = usePlatformContext()