@morscherlab/mint-sdk 1.0.0-beta.1 → 1.0.0-beta.3

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 (421) hide show
  1. package/README.md +218 -6
  2. package/dist/__tests__/components/ActionItem.test.d.ts +1 -0
  3. package/dist/__tests__/components/AppAvatarMenu.test.d.ts +1 -0
  4. package/dist/__tests__/components/AppPageSelector.test.d.ts +1 -0
  5. package/dist/__tests__/components/AppPillNav.test.d.ts +1 -0
  6. package/dist/__tests__/components/AppPluginSwitcher.test.d.ts +1 -0
  7. package/dist/__tests__/components/AppToastContainer.test.d.ts +1 -0
  8. package/dist/__tests__/components/BaseRadioGroup.test.d.ts +1 -0
  9. package/dist/__tests__/components/BaseSelect.test.d.ts +1 -0
  10. package/dist/__tests__/components/BaseTabs.test.d.ts +1 -0
  11. package/dist/__tests__/components/BatchProgressList.test.d.ts +1 -0
  12. package/dist/__tests__/components/BioTemplateExperimentWorkspaceView.test.d.ts +1 -0
  13. package/dist/__tests__/components/BioTemplatePackWorkspaceView.test.d.ts +1 -0
  14. package/dist/__tests__/components/BioTemplatePresetWorkspaceView.test.d.ts +1 -0
  15. package/dist/__tests__/components/BioTemplateRenderer.test.d.ts +1 -0
  16. package/dist/__tests__/components/Breadcrumb.test.d.ts +1 -0
  17. package/dist/__tests__/components/CalendarGridPanel.test.d.ts +1 -0
  18. package/dist/__tests__/components/ConcentrationInput.test.d.ts +1 -0
  19. package/dist/__tests__/components/ControlWorkspaceView.test.d.ts +1 -0
  20. package/dist/__tests__/components/DatePicker.test.d.ts +1 -0
  21. package/dist/__tests__/components/DateTimePicker.test.d.ts +1 -0
  22. package/dist/__tests__/components/EmptyState.test.d.ts +1 -0
  23. package/dist/__tests__/components/ExperimentPopover.test.d.ts +1 -0
  24. package/dist/__tests__/components/FormBuilder.test.d.ts +1 -0
  25. package/dist/__tests__/components/FormCompatibility.test.d.ts +1 -0
  26. package/dist/__tests__/components/GroupAssigner.test.d.ts +1 -0
  27. package/dist/__tests__/components/GroupingModal.test.d.ts +1 -0
  28. package/dist/__tests__/components/MultiSelect.test.d.ts +1 -0
  29. package/dist/__tests__/components/PluginIcon.test.d.ts +1 -0
  30. package/dist/__tests__/components/ProtocolStepEditor.test.d.ts +1 -0
  31. package/dist/__tests__/components/ReagentList.test.d.ts +1 -0
  32. package/dist/__tests__/components/SampleHierarchyTree.test.d.ts +1 -0
  33. package/dist/__tests__/components/SampleSelector.test.d.ts +1 -0
  34. package/dist/__tests__/components/SegmentedControl.test.d.ts +1 -0
  35. package/dist/__tests__/components/SettingsButton.test.d.ts +1 -0
  36. package/dist/__tests__/components/SettingsModal.test.d.ts +1 -0
  37. package/dist/__tests__/components/TagsInput.test.d.ts +1 -0
  38. package/dist/__tests__/components/ThemeToggle.test.d.ts +1 -0
  39. package/dist/__tests__/components/TimePicker.test.d.ts +1 -0
  40. package/dist/__tests__/composables/useBioTemplatePackWorkspace.test.d.ts +1 -0
  41. package/dist/__tests__/composables/useBioTemplatePresetWorkspace.test.d.ts +1 -0
  42. package/dist/__tests__/composables/useBioTemplateWorkspace.test.d.ts +1 -0
  43. package/dist/__tests__/composables/useCalendarGrid.test.d.ts +1 -0
  44. package/dist/__tests__/composables/useControlSchema.test.d.ts +1 -0
  45. package/dist/__tests__/composables/useDebouncedWatch.test.d.ts +1 -0
  46. package/dist/__tests__/composables/useDropdownState.test.d.ts +1 -0
  47. package/dist/__tests__/composables/useEventListener.test.d.ts +1 -0
  48. package/dist/__tests__/composables/useExpansionSet.test.d.ts +1 -0
  49. package/dist/__tests__/composables/useExperimentData.test.d.ts +1 -0
  50. package/dist/__tests__/composables/useExperimentSelector.test.d.ts +1 -0
  51. package/dist/__tests__/composables/useGroupAssignment.test.d.ts +1 -0
  52. package/dist/__tests__/composables/useListSelection.test.d.ts +1 -0
  53. package/dist/__tests__/composables/usePluginClient.test.d.ts +1 -0
  54. package/dist/__tests__/composables/usePluginConfig.test.d.ts +1 -0
  55. package/dist/__tests__/composables/useRequestSyncState.test.d.ts +1 -0
  56. package/dist/__tests__/composables/useSampleGroups.test.d.ts +1 -0
  57. package/dist/__tests__/composables/useSelectionLimit.test.d.ts +1 -0
  58. package/dist/__tests__/composables/useSortedItems.test.d.ts +1 -0
  59. package/dist/__tests__/composables/useTemplateCollection.test.d.ts +1 -0
  60. package/dist/__tests__/composables/useTextSearch.test.d.ts +1 -0
  61. package/dist/__tests__/composables/useTheme.test.d.ts +1 -0
  62. package/dist/__tests__/composables/useTimeUtils.test.d.ts +1 -0
  63. package/dist/__tests__/docs/frontendDocsCatalog.test.d.ts +1 -0
  64. package/dist/__tests__/templates/templates.test.d.ts +1 -0
  65. package/dist/{auth-DsI0rQ7_.js → auth-QQj2kkze.js} +12 -5
  66. package/dist/auth-QQj2kkze.js.map +1 -0
  67. package/dist/components/ActionItem.vue.d.ts +32 -0
  68. package/dist/components/AppAvatarMenu.vue.d.ts +2 -7
  69. package/dist/components/AppPageSelector.vue.d.ts +3 -6
  70. package/dist/components/AppPillNav.vue.d.ts +2 -2
  71. package/dist/components/AppSidebar.vue.d.ts +56 -3
  72. package/dist/components/AppToastContainer.vue.d.ts +2 -0
  73. package/dist/components/AppTopBar.vue.d.ts +43 -10
  74. package/dist/components/BaseButton.vue.d.ts +2 -2
  75. package/dist/components/BaseCheckbox.vue.d.ts +1 -1
  76. package/dist/components/BaseInput.vue.d.ts +2 -2
  77. package/dist/components/BasePill.vue.d.ts +3 -3
  78. package/dist/components/BaseRadioGroup.vue.d.ts +4 -4
  79. package/dist/components/BaseSelect.vue.d.ts +4 -4
  80. package/dist/components/BaseSlider.vue.d.ts +1 -1
  81. package/dist/components/BaseTabs.vue.d.ts +2 -2
  82. package/dist/components/BaseTextarea.vue.d.ts +2 -2
  83. package/dist/components/BaseToggle.vue.d.ts +1 -1
  84. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +117 -0
  85. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +92 -0
  86. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +82 -0
  87. package/dist/components/BioTemplateRenderer.vue.d.ts +29 -0
  88. package/dist/components/Breadcrumb.vue.d.ts +2 -2
  89. package/dist/components/Calendar.vue.d.ts +1 -1
  90. package/dist/components/CalendarGridPanel.vue.d.ts +25 -0
  91. package/dist/components/CollapsibleCard.vue.d.ts +1 -1
  92. package/dist/components/ColorSlider.vue.d.ts +1 -1
  93. package/dist/components/ConcentrationInput.vue.d.ts +2 -2
  94. package/dist/components/ConfirmDialog.vue.d.ts +2 -2
  95. package/dist/components/ControlWorkspaceView.vue.d.ts +130 -0
  96. package/dist/components/DatePicker.vue.d.ts +2 -2
  97. package/dist/components/DateTimePicker.vue.d.ts +3 -3
  98. package/dist/components/DropdownButton.vue.d.ts +4 -4
  99. package/dist/components/EmptyState.vue.d.ts +1 -2
  100. package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
  101. package/dist/components/ExperimentTimeline.vue.d.ts +2 -2
  102. package/dist/components/FileUploader.vue.d.ts +2 -2
  103. package/dist/components/FitPanel.vue.d.ts +1 -1
  104. package/dist/components/FormActions.vue.d.ts +4 -4
  105. package/dist/components/FormBuilder.vue.d.ts +22 -8
  106. package/dist/components/FormFieldRenderer.vue.d.ts +7 -10
  107. package/dist/components/FormSection.vue.d.ts +11 -24
  108. package/dist/components/FormulaInput.vue.d.ts +2 -2
  109. package/dist/components/IconButton.vue.d.ts +1 -1
  110. package/dist/components/LoadingSpinner.vue.d.ts +1 -1
  111. package/dist/components/MoleculeInput.vue.d.ts +2 -2
  112. package/dist/components/MultiSelect.vue.d.ts +3 -3
  113. package/dist/components/NumberInput.vue.d.ts +2 -2
  114. package/dist/components/PluginIcon.vue.d.ts +11 -0
  115. package/dist/components/ProgressBar.vue.d.ts +2 -2
  116. package/dist/components/ProtocolStepEditor.vue.d.ts +3 -1
  117. package/dist/components/RackEditor.vue.d.ts +2 -2
  118. package/dist/components/ReagentEditor.vue.d.ts +1 -1
  119. package/dist/components/ResourceCard.vue.d.ts +1 -1
  120. package/dist/components/SampleLegend.vue.d.ts +2 -2
  121. package/dist/components/SampleSelector.vue.d.ts +1 -1
  122. package/dist/components/ScheduleCalendar.vue.d.ts +2 -2
  123. package/dist/components/ScientificNumber.vue.d.ts +1 -1
  124. package/dist/components/SegmentedControl.vue.d.ts +3 -3
  125. package/dist/components/SequenceInput.vue.d.ts +3 -3
  126. package/dist/components/SettingsButton.vue.d.ts +2 -2
  127. package/dist/components/SettingsModal.vue.d.ts +32 -4
  128. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  129. package/dist/components/TagsInput.vue.d.ts +3 -2
  130. package/dist/components/TimePicker.vue.d.ts +3 -3
  131. package/dist/components/TimeRangeInput.vue.d.ts +2 -2
  132. package/dist/components/UnitInput.vue.d.ts +2 -2
  133. package/dist/components/WellPlate.vue.d.ts +8 -8
  134. package/dist/components/index.d.ts +12 -1
  135. package/dist/components/index.js +3 -3
  136. package/dist/components/internal/FormFieldRendererInternal.vue.d.ts +31 -0
  137. package/dist/components/internal/FormSectionRenderer.vue.d.ts +43 -0
  138. package/dist/{components-CzbQQPCb.js → components-D_Sr0adg.js} +9629 -8647
  139. package/dist/components-D_Sr0adg.js.map +1 -0
  140. package/dist/composables/index.d.ts +21 -2
  141. package/dist/composables/index.js +4 -3
  142. package/dist/composables/platformContextHelpers.d.ts +14 -0
  143. package/dist/composables/useBioTemplateComponents.d.ts +20 -0
  144. package/dist/composables/useBioTemplateControls.d.ts +6 -0
  145. package/dist/composables/useBioTemplatePackWorkspace.d.ts +45 -0
  146. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +74 -0
  147. package/dist/composables/useBioTemplateWorkspace.d.ts +50 -0
  148. package/dist/composables/useCalendarGrid.d.ts +26 -0
  149. package/dist/composables/useControlSchema.d.ts +321 -0
  150. package/dist/composables/useDebouncedWatch.d.ts +20 -0
  151. package/dist/composables/useDropdownState.d.ts +19 -0
  152. package/dist/composables/useEventListener.d.ts +13 -0
  153. package/dist/composables/useExpansionSet.d.ts +21 -0
  154. package/dist/composables/useExperimentData.d.ts +10 -0
  155. package/dist/composables/useExperimentSave.d.ts +31 -2
  156. package/dist/composables/useExperimentSelector.d.ts +20 -0
  157. package/dist/composables/useForm.d.ts +2 -0
  158. package/dist/composables/useGroupAssignment.d.ts +31 -0
  159. package/dist/composables/useListSelection.d.ts +35 -0
  160. package/dist/composables/usePlatformContext.d.ts +24 -3
  161. package/dist/composables/usePluginApi.d.ts +7 -14
  162. package/dist/composables/usePluginClient.d.ts +109 -0
  163. package/dist/composables/usePluginConfig.d.ts +12 -0
  164. package/dist/composables/useRequestSyncState.d.ts +34 -0
  165. package/dist/composables/useSampleGroups.d.ts +32 -0
  166. package/dist/composables/useSelectionLimit.d.ts +17 -0
  167. package/dist/composables/useSortedItems.d.ts +32 -0
  168. package/dist/composables/useTemplateCollection.d.ts +58 -0
  169. package/dist/composables/useTextSearch.d.ts +18 -0
  170. package/dist/composables/useTimeUtils.d.ts +8 -0
  171. package/dist/{composables-BXklV5ii.js → composables-C3dpXQN5.js} +228 -146
  172. package/dist/composables-C3dpXQN5.js.map +1 -0
  173. package/dist/index.d.ts +12 -3
  174. package/dist/index.js +6 -5
  175. package/dist/install.d.ts +7 -2
  176. package/dist/install.js +2 -2
  177. package/dist/install.js.map +1 -1
  178. package/dist/stores/auth.d.ts +1 -1
  179. package/dist/stores/index.js +1 -1
  180. package/dist/stores/settings.d.ts +4 -1
  181. package/dist/styles.css +5255 -5654
  182. package/dist/templates/adapters.d.ts +43 -0
  183. package/dist/templates/builders.d.ts +63 -0
  184. package/dist/templates/catalog.d.ts +188 -0
  185. package/dist/templates/componentBindings.d.ts +58 -0
  186. package/dist/templates/controlSchemas.d.ts +25 -0
  187. package/dist/templates/index.d.ts +15 -0
  188. package/dist/templates/index.js +2 -0
  189. package/dist/templates/lookup.d.ts +4 -0
  190. package/dist/templates/packs.d.ts +18 -0
  191. package/dist/templates/presets.d.ts +90 -0
  192. package/dist/templates/types.d.ts +531 -0
  193. package/dist/templates-50NPjaxL.js +9333 -0
  194. package/dist/templates-50NPjaxL.js.map +1 -0
  195. package/dist/types/components.d.ts +62 -1
  196. package/dist/types/form-builder.d.ts +6 -8
  197. package/dist/types/index.d.ts +2 -2
  198. package/dist/types/platform.d.ts +8 -1
  199. package/dist/useScheduleDrag-D4oWdh41.js +4371 -0
  200. package/dist/useScheduleDrag-D4oWdh41.js.map +1 -0
  201. package/dist/utils/formModelSync.d.ts +5 -0
  202. package/dist/utils/items.d.ts +8 -0
  203. package/dist/utils/options.d.ts +6 -0
  204. package/dist/utils/pluginIcon.d.ts +9 -0
  205. package/package.json +7 -2
  206. package/src/__tests__/components/ActionItem.test.ts +99 -0
  207. package/src/__tests__/components/AppAvatarMenu.test.ts +27 -0
  208. package/src/__tests__/components/AppPageSelector.test.ts +134 -0
  209. package/src/__tests__/components/AppPillNav.test.ts +78 -0
  210. package/src/__tests__/components/AppPluginSwitcher.test.ts +44 -0
  211. package/src/__tests__/components/AppSidebar.test.ts +370 -0
  212. package/src/__tests__/components/AppToastContainer.test.ts +48 -0
  213. package/src/__tests__/components/AppTopBar.test.ts +414 -13
  214. package/src/__tests__/components/BaseRadioGroup.test.ts +25 -0
  215. package/src/__tests__/components/BaseSelect.test.ts +21 -0
  216. package/src/__tests__/components/BaseTabs.test.ts +25 -0
  217. package/src/__tests__/components/BatchProgressList.test.ts +52 -0
  218. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +153 -0
  219. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +161 -0
  220. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +281 -0
  221. package/src/__tests__/components/BioTemplateRenderer.test.ts +71 -0
  222. package/src/__tests__/components/Breadcrumb.test.ts +23 -0
  223. package/src/__tests__/components/CalendarGridPanel.test.ts +36 -0
  224. package/src/__tests__/components/ConcentrationInput.test.ts +45 -0
  225. package/src/__tests__/components/ControlWorkspaceView.test.ts +1031 -0
  226. package/src/__tests__/components/DataFrame.test.ts +11 -0
  227. package/src/__tests__/components/DatePicker.test.ts +45 -0
  228. package/src/__tests__/components/DateTimePicker.test.ts +48 -0
  229. package/src/__tests__/components/DropdownButton.test.ts +23 -0
  230. package/src/__tests__/components/EmptyState.test.ts +23 -0
  231. package/src/__tests__/components/ExperimentPopover.test.ts +56 -0
  232. package/src/__tests__/components/FormBuilder.test.ts +296 -0
  233. package/src/__tests__/components/FormCompatibility.test.ts +94 -0
  234. package/src/__tests__/components/GroupAssigner.test.ts +30 -0
  235. package/src/__tests__/components/GroupingModal.test.ts +73 -0
  236. package/src/__tests__/components/MultiSelect.test.ts +48 -0
  237. package/src/__tests__/components/PluginIcon.test.ts +119 -0
  238. package/src/__tests__/components/ProtocolStepEditor.test.ts +33 -0
  239. package/src/__tests__/components/ReagentList.test.ts +82 -0
  240. package/src/__tests__/components/SampleHierarchyTree.test.ts +53 -0
  241. package/src/__tests__/components/SampleSelector.test.ts +60 -0
  242. package/src/__tests__/components/SegmentedControl.test.ts +24 -0
  243. package/src/__tests__/components/SettingsButton.test.ts +44 -0
  244. package/src/__tests__/components/SettingsModal.test.ts +296 -0
  245. package/src/__tests__/components/TagsInput.test.ts +75 -0
  246. package/src/__tests__/components/ThemeToggle.test.ts +47 -0
  247. package/src/__tests__/components/TimePicker.test.ts +38 -0
  248. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +122 -0
  249. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +199 -0
  250. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +99 -0
  251. package/src/__tests__/composables/useCalendarGrid.test.ts +38 -0
  252. package/src/__tests__/composables/useControlSchema.test.ts +919 -0
  253. package/src/__tests__/composables/useDebouncedWatch.test.ts +93 -0
  254. package/src/__tests__/composables/useDropdownState.test.ts +95 -0
  255. package/src/__tests__/composables/useEventListener.test.ts +116 -0
  256. package/src/__tests__/composables/useExpansionSet.test.ts +62 -0
  257. package/src/__tests__/composables/useExperimentData.test.ts +4 -0
  258. package/src/__tests__/composables/useExperimentSave.test.ts +203 -8
  259. package/src/__tests__/composables/useExperimentSelector.test.ts +164 -0
  260. package/src/__tests__/composables/useForm.test.ts +58 -0
  261. package/src/__tests__/composables/useFormBuilder.test.ts +77 -0
  262. package/src/__tests__/composables/useGroupAssignment.test.ts +73 -0
  263. package/src/__tests__/composables/useListSelection.test.ts +66 -0
  264. package/src/__tests__/composables/usePluginClient.test.ts +444 -0
  265. package/src/__tests__/composables/usePluginConfig.test.ts +5 -0
  266. package/src/__tests__/composables/useRequestSyncState.test.ts +92 -0
  267. package/src/__tests__/composables/useSampleGroups.test.ts +66 -0
  268. package/src/__tests__/composables/useSelectionLimit.test.ts +41 -0
  269. package/src/__tests__/composables/useSortedItems.test.ts +87 -0
  270. package/src/__tests__/composables/useTemplateCollection.test.ts +147 -0
  271. package/src/__tests__/composables/useTextSearch.test.ts +55 -0
  272. package/src/__tests__/composables/useTheme.test.ts +91 -0
  273. package/src/__tests__/composables/useTimeUtils.test.ts +35 -0
  274. package/src/__tests__/docs/frontendDocsCatalog.test.ts +229 -0
  275. package/src/__tests__/fixtures/templates/dose-response.json +81 -0
  276. package/src/__tests__/fixtures/templates/plate-map.json +54 -0
  277. package/src/__tests__/fixtures/templates/qpcr-plate.json +96 -0
  278. package/src/__tests__/fixtures/templates/sample-sheet.json +71 -0
  279. package/src/__tests__/templates/templates.test.ts +1043 -0
  280. package/src/components/ActionItem.vue +82 -0
  281. package/src/components/AppAvatarMenu.vue +15 -69
  282. package/src/components/AppLayout.story.vue +25 -25
  283. package/src/components/AppPageSelector.vue +63 -94
  284. package/src/components/AppPillNav.vue +44 -39
  285. package/src/components/AppPluginSwitcher.vue +41 -145
  286. package/src/components/AppSidebar.story.vue +94 -0
  287. package/src/components/AppSidebar.vue +187 -12
  288. package/src/components/{ToastNotification.story.vue → AppToastContainer.story.vue} +6 -6
  289. package/src/components/AppToastContainer.vue +62 -0
  290. package/src/components/AppTopBar.story.vue +7 -30
  291. package/src/components/AppTopBar.vue +283 -84
  292. package/src/components/BaseModal.vue +3 -5
  293. package/src/components/BaseRadioGroup.vue +7 -3
  294. package/src/components/BaseSelect.vue +11 -7
  295. package/src/components/BaseTabs.vue +6 -4
  296. package/src/components/BatchProgressList.vue +5 -8
  297. package/src/components/BioTemplateExperimentWorkspaceView.story.vue +123 -0
  298. package/src/components/BioTemplateExperimentWorkspaceView.vue +337 -0
  299. package/src/components/BioTemplatePackWorkspaceView.story.vue +107 -0
  300. package/src/components/BioTemplatePackWorkspaceView.vue +176 -0
  301. package/src/components/BioTemplatePresetWorkspaceView.story.vue +151 -0
  302. package/src/components/BioTemplatePresetWorkspaceView.vue +392 -0
  303. package/src/components/BioTemplateRenderer.story.vue +57 -0
  304. package/src/components/BioTemplateRenderer.vue +269 -0
  305. package/src/components/Breadcrumb.vue +14 -8
  306. package/src/components/CalendarGridPanel.vue +120 -0
  307. package/src/components/ConcentrationInput.vue +27 -64
  308. package/src/components/ControlWorkspaceView.story.vue +336 -0
  309. package/src/components/ControlWorkspaceView.vue +347 -0
  310. package/src/components/DataFrame.vue +34 -50
  311. package/src/components/DatePicker.vue +59 -192
  312. package/src/components/DateTimePicker.vue +50 -171
  313. package/src/components/DropdownButton.vue +14 -32
  314. package/src/components/EmptyState.vue +4 -2
  315. package/src/components/ExperimentPopover.vue +5 -22
  316. package/src/components/FormBuilder.vue +124 -27
  317. package/src/components/FormFieldRenderer.vue +15 -38
  318. package/src/components/FormSection.vue +20 -73
  319. package/src/components/GroupAssigner.vue +24 -56
  320. package/src/components/GroupingModal.story.vue +3 -3
  321. package/src/components/GroupingModal.vue +30 -391
  322. package/src/components/MultiSelect.vue +17 -12
  323. package/src/components/PlateMapEditor.vue +3 -8
  324. package/src/components/PluginIcon.story.vue +71 -0
  325. package/src/components/PluginIcon.vue +68 -0
  326. package/src/components/ProtocolStepEditor.vue +13 -22
  327. package/src/components/ReagentList.vue +25 -33
  328. package/src/components/SampleHierarchyTree.vue +12 -23
  329. package/src/components/SampleSelector.vue +42 -122
  330. package/src/components/SegmentedControl.vue +7 -3
  331. package/src/components/SettingsButton.story.vue +1 -1
  332. package/src/components/SettingsButton.vue +15 -27
  333. package/src/components/SettingsModal.story.vue +337 -45
  334. package/src/components/SettingsModal.vue +344 -66
  335. package/src/components/TagsInput.vue +29 -14
  336. package/src/components/ThemeToggle.vue +9 -7
  337. package/src/components/TimePicker.vue +19 -41
  338. package/src/components/ToastNotification.vue +4 -57
  339. package/src/components/Tooltip.vue +7 -12
  340. package/src/components/WellEditPopup.vue +3 -8
  341. package/src/components/WellPlate.vue +4 -10
  342. package/src/components/index.ts +12 -1
  343. package/src/components/internal/FormFieldRendererInternal.vue +50 -0
  344. package/src/components/internal/FormSectionRenderer.vue +78 -0
  345. package/src/composables/index.ts +212 -0
  346. package/src/composables/platformContextHelpers.ts +74 -0
  347. package/src/composables/useBioTemplateComponents.ts +93 -0
  348. package/src/composables/useBioTemplateControls.ts +41 -0
  349. package/src/composables/useBioTemplatePackWorkspace.ts +181 -0
  350. package/src/composables/useBioTemplatePresetWorkspace.ts +337 -0
  351. package/src/composables/useBioTemplateWorkspace.ts +139 -0
  352. package/src/composables/useCalendarGrid.ts +140 -0
  353. package/src/composables/useControlSchema.ts +1274 -0
  354. package/src/composables/useDebouncedWatch.ts +119 -0
  355. package/src/composables/useDropdownState.ts +83 -0
  356. package/src/composables/useEventListener.ts +111 -0
  357. package/src/composables/useExpansionSet.ts +117 -0
  358. package/src/composables/useExperimentData.ts +20 -11
  359. package/src/composables/useExperimentSave.ts +202 -50
  360. package/src/composables/useExperimentSelector.ts +86 -72
  361. package/src/composables/useForm.ts +49 -4
  362. package/src/composables/useFormBuilder.ts +93 -42
  363. package/src/composables/useGroupAssignment.ts +148 -0
  364. package/src/composables/useListSelection.ts +158 -0
  365. package/src/composables/usePluginApi.ts +7 -14
  366. package/src/composables/usePluginClient.ts +425 -0
  367. package/src/composables/usePluginConfig.ts +34 -13
  368. package/src/composables/useRequestSyncState.ts +126 -0
  369. package/src/composables/useSampleGroups.ts +126 -0
  370. package/src/composables/useSelectionLimit.ts +57 -0
  371. package/src/composables/useSortedItems.ts +118 -0
  372. package/src/composables/useTemplateCollection.ts +229 -0
  373. package/src/composables/useTextSearch.ts +60 -0
  374. package/src/composables/useTheme.ts +2 -28
  375. package/src/composables/useTimeUtils.ts +26 -2
  376. package/src/composables/useWellPlateEditor.ts +13 -9
  377. package/src/index.ts +228 -4
  378. package/src/install.ts +11 -4
  379. package/src/stores/settings.ts +13 -9
  380. package/src/styles/components/app-page-selector.css +23 -0
  381. package/src/styles/components/app-pill-nav.css +8 -2
  382. package/src/styles/components/app-top-bar.css +35 -2
  383. package/src/styles/components/button.css +3 -7
  384. package/src/styles/components/concentration-input.css +3 -142
  385. package/src/styles/components/dropdown-button.css +4 -4
  386. package/src/styles/components/empty-state.css +0 -16
  387. package/src/styles/components/input.css +4 -5
  388. package/src/styles/components/number-input.css +3 -3
  389. package/src/styles/components/plugin-icon.css +38 -0
  390. package/src/styles/components/segmented-control.css +4 -7
  391. package/src/styles/components/settings-button.css +3 -66
  392. package/src/styles/components/settings-modal.css +184 -0
  393. package/src/styles/components/tabs.css +1 -2
  394. package/src/styles/components/textarea.css +4 -5
  395. package/src/styles/components/theme-toggle.css +3 -66
  396. package/src/styles/components/unit-input.css +3 -3
  397. package/src/styles/index.css +0 -1
  398. package/src/templates/adapters.ts +785 -0
  399. package/src/templates/builders.ts +2149 -0
  400. package/src/templates/catalog.ts +245 -0
  401. package/src/templates/componentBindings.ts +615 -0
  402. package/src/templates/controlSchemas.ts +718 -0
  403. package/src/templates/index.ts +314 -0
  404. package/src/templates/lookup.ts +18 -0
  405. package/src/templates/packs.ts +156 -0
  406. package/src/templates/presets.ts +146 -0
  407. package/src/templates/types.ts +668 -0
  408. package/src/types/components.ts +80 -1
  409. package/src/types/form-builder.ts +7 -2
  410. package/src/types/index.ts +17 -0
  411. package/src/types/platform.ts +8 -1
  412. package/src/utils/formModelSync.ts +52 -0
  413. package/src/utils/items.ts +28 -0
  414. package/src/utils/options.ts +23 -0
  415. package/src/utils/pluginIcon.ts +30 -0
  416. package/dist/auth-DsI0rQ7_.js.map +0 -1
  417. package/dist/components-CzbQQPCb.js.map +0 -1
  418. package/dist/composables-BXklV5ii.js.map +0 -1
  419. package/dist/useScheduleDrag-CxBeqYcu.js +0 -7181
  420. package/dist/useScheduleDrag-CxBeqYcu.js.map +0 -1
  421. package/src/styles/components/grouping-modal.css +0 -323
@@ -1,16 +1,91 @@
1
1
  <script setup lang="ts">
2
- /** Tabbed settings modal with a built-in appearance tab (theme, color palette, table density) and extensible custom tabs. */
3
- import { ref, computed } from 'vue'
2
+ /**
3
+ * Tabbed settings modal with three usage modes:
4
+ *
5
+ * 1. Schema-driven (recommended) — pass `schema`, compact `controls`, or a
6
+ * complete `defineControlModel()` result plus `v-model:values`. Each
7
+ * group/section becomes a tab; fields auto-render via the SDK form field
8
+ * registry (text, select, number, toggle, molecule, concentration, …).
9
+ * Conditional visibility, validation, and dynamic options come for free.
10
+ *
11
+ * 2. Manual tabs + slots — pass `tabs` and a `<template #tab-{id}>` slot
12
+ * per tab. Use this when you need bespoke widgets the form-builder
13
+ * registry doesn't cover (or for legacy plugins).
14
+ *
15
+ * 3. Appearance only — `showAppearance` (default true) renders the built-in
16
+ * theme / palette / table-density tab. Works alongside both modes above.
17
+ *
18
+ * Layout: `horizontal` (underline tabs, default) or `vertical` (sidebar rail
19
+ * with optional icons + descriptions, recommended for 5+ groups).
20
+ */
21
+ import { ref, computed, watch } from 'vue'
4
22
  import BaseModal from './BaseModal.vue'
23
+ import FormFieldRendererInternal from './internal/FormFieldRendererInternal.vue'
24
+ import { useFormBuilder } from '../composables/useFormBuilder'
25
+ import {
26
+ controlsToSettingsSchema,
27
+ defineControlModel,
28
+ mergeControlWorkspaceOptions,
29
+ type ControlModel,
30
+ type ControlModelBinding,
31
+ type ControlSchema,
32
+ type ControlWorkspaceOptions,
33
+ } from '../composables/useControlSchema'
5
34
  import { useSettingsStore, colorPalettes } from '../stores/settings'
6
- import type { ThemeMode, ColorPalette, TableDensity, SettingsTab } from '../types'
35
+ import {
36
+ formSchemaFieldNames,
37
+ pickExistingRecordKeys,
38
+ pickRecordKeys,
39
+ recordValuesEqualForKeys,
40
+ } from '../utils/formModelSync'
41
+ import type {
42
+ ThemeMode,
43
+ ColorPalette,
44
+ TableDensity,
45
+ SettingsTab,
46
+ SettingsTabInput,
47
+ SettingsModalLayout,
48
+ SettingsModalSchema,
49
+ FormSchema,
50
+ FormSectionSchema,
51
+ FormEnhancements,
52
+ } from '../types'
53
+ import { normalizeItemInput } from '../utils/items'
54
+
55
+ // Map our settings groups onto the form-builder's flat-section shape.
56
+ // `title: ''` because the rail/pane header already shows the group name.
57
+ function buildFlatSchema(schema: SettingsModalSchema): FormSchema {
58
+ return {
59
+ sections: schema.groups.map<FormSectionSchema>((g) => ({
60
+ id: g.id,
61
+ title: '',
62
+ fields: g.fields,
63
+ columns: g.columns,
64
+ condition: g.condition,
65
+ })),
66
+ }
67
+ }
7
68
 
8
69
  interface Props {
9
70
  modelValue: boolean
10
71
  title?: string
11
- tabs?: SettingsTab[]
72
+ /** Manual tab descriptors. Ignored when `schema` is set (groups become tabs). */
73
+ tabs?: SettingsTabInput[]
12
74
  showAppearance?: boolean
13
75
  size?: 'md' | 'lg' | 'xl'
76
+ layout?: SettingsModalLayout
77
+ /** Declarative schema — fields auto-render via SDK form components. */
78
+ schema?: SettingsModalSchema
79
+ /** Model returned by defineControlModel(), or a raw nested ControlModel for one-step settings generation. */
80
+ model?: ControlModel | ControlModelBinding
81
+ /** Compact controls model — converted to `schema` when `schema` is not set. */
82
+ controls?: ControlSchema
83
+ /** Conversion options for `controls`, such as section labels, columns, and shared initialValues. */
84
+ controlOptions?: ControlWorkspaceOptions
85
+ /** Two-way bound values when `schema`, `model`, or `controls` is set. */
86
+ values?: Record<string, unknown>
87
+ /** Optional dynamic enhancements (validators, dynamic options, callbacks). */
88
+ enhancements?: FormEnhancements<Record<string, unknown>>
14
89
  }
15
90
 
16
91
  const props = withDefaults(defineProps<Props>(), {
@@ -18,22 +93,144 @@ const props = withDefaults(defineProps<Props>(), {
18
93
  tabs: () => [],
19
94
  showAppearance: true,
20
95
  size: 'lg',
96
+ layout: 'horizontal',
97
+ model: undefined,
21
98
  })
22
99
 
23
100
  const emit = defineEmits<{
24
101
  'update:modelValue': [value: boolean]
102
+ 'update:values': [data: Record<string, unknown>]
25
103
  close: []
26
104
  }>()
27
105
 
28
106
  const settings = useSettingsStore()
29
107
 
30
- const allTabs = computed<SettingsTab[]>(() =>
31
- props.showAppearance
32
- ? [...props.tabs, { id: 'appearance', label: 'Appearance' }]
33
- : props.tabs
108
+ const APPEARANCE_TAB_ID = 'appearance'
109
+
110
+ const APPEARANCE_TAB: SettingsTab = {
111
+ id: APPEARANCE_TAB_ID,
112
+ label: 'Appearance',
113
+ description: 'Theme, color palette, and table density',
114
+ }
115
+
116
+ const isVertical = computed(() => props.layout === 'vertical')
117
+
118
+ // Per-instance prefix for ARIA tab/tabpanel id wiring. Stable across renders.
119
+ const uid = `mint-settings-${Math.random().toString(36).slice(2, 9)}`
120
+ const tabId = (id: string) => `${uid}-tab-${id}`
121
+ const panelId = (id: string) => `${uid}-panel-${id}`
122
+
123
+ const resolvedModel = computed<ControlModelBinding | undefined>(() => {
124
+ if (props.model === undefined) return undefined
125
+ return isControlModelBinding(props.model) ? props.model : defineControlModel(props.model)
126
+ })
127
+ const resolvedControls = computed<ControlSchema | undefined>(() =>
128
+ props.controls ?? resolvedModel.value?.controls,
129
+ )
130
+ const resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>
131
+ mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)
132
+ )
133
+ const settingsSchema = computed<SettingsModalSchema | undefined>(() =>
134
+ props.schema ?? (
135
+ resolvedControls.value
136
+ ? controlsToSettingsSchema(resolvedControls.value, resolvedControlOptions.value)
137
+ : undefined
138
+ ),
139
+ )
140
+ const isSchemaDriven = computed(() => !!settingsSchema.value)
141
+ const resolvedValues = computed<Record<string, unknown>>(() => ({
142
+ ...(resolvedControlOptions.value.initialValues ?? {}),
143
+ ...(props.values ?? {}),
144
+ }))
145
+
146
+ // Schema-driven mode keeps one form-builder instance and syncs schema changes
147
+ // into it, so callers can swap groups/fields without remounting the modal.
148
+ const builder = useFormBuilder(
149
+ settingsSchema.value ? buildFlatSchema(settingsSchema.value) : { sections: [] },
150
+ resolvedValues.value,
151
+ props.enhancements,
152
+ )
153
+
154
+ watch(
155
+ () => settingsSchema.value,
156
+ (schema) => {
157
+ if (!schema) {
158
+ builder.updateSchema({ sections: [] }, {})
159
+ return
160
+ }
161
+
162
+ const flatSchema = buildFlatSchema(schema)
163
+ const fieldNames = formSchemaFieldNames(flatSchema)
164
+ const sourceValues = props.values === undefined
165
+ ? {
166
+ ...resolvedValues.value,
167
+ ...(builder.form.data as Record<string, unknown>),
168
+ }
169
+ : resolvedValues.value
170
+
171
+ builder.updateSchema(flatSchema, pickExistingRecordKeys(sourceValues ?? {}, fieldNames))
172
+ },
173
+ { deep: true },
174
+ )
175
+
176
+ watch(
177
+ () => ({ ...resolvedValues.value }),
178
+ (data) => {
179
+ if (!settingsSchema.value) return
180
+ const fieldNames = builderFieldNames()
181
+ if (recordValuesEqualForKeys(data, builder.form.data as Record<string, unknown>, fieldNames)) return
182
+ builder.reset(pickRecordKeys(data, fieldNames))
183
+ },
184
+ { deep: true },
185
+ )
186
+
187
+ watch(
188
+ () => ({ ...builder.form.data }),
189
+ (data) => {
190
+ if (settingsSchema.value) emit('update:values', data as Record<string, unknown>)
191
+ },
192
+ { deep: true },
193
+ )
194
+
195
+ // Schema groups whose `condition` evaluates false against the current data are
196
+ // dropped from the rail entirely — same semantics as section visibility in
197
+ // FormBuilder. Manual `tabs` have no condition mechanism, so they pass through.
198
+ const visibleSchemaGroups = computed(() =>
199
+ settingsSchema.value
200
+ ? settingsSchema.value.groups.filter((g) => builder.isSectionVisible(g.id))
201
+ : [],
34
202
  )
35
203
 
36
- const activeTab = ref(allTabs.value[0]?.id || 'appearance')
204
+ const manualTabs = computed<SettingsTab[]>(() => props.tabs.map(normalizeItemInput))
205
+
206
+ const allTabs = computed<SettingsTab[]>(() => {
207
+ const base: SettingsTab[] = settingsSchema.value
208
+ ? visibleSchemaGroups.value.map((g) => ({ id: g.id, label: g.label, icon: g.icon, description: g.description }))
209
+ : manualTabs.value
210
+ return props.showAppearance ? [...base, APPEARANCE_TAB] : base
211
+ })
212
+
213
+ const activeTab = ref(allTabs.value[0]?.id || APPEARANCE_TAB_ID)
214
+
215
+ const activeTabMeta = computed<SettingsTab | undefined>(() =>
216
+ allTabs.value.find((t) => t.id === activeTab.value),
217
+ )
218
+
219
+ // If the active tab vanishes (group hidden by condition), drop back to the first available.
220
+ watch(allTabs, (tabs) => {
221
+ if (!tabs.some((t) => t.id === activeTab.value)) {
222
+ activeTab.value = tabs[0]?.id ?? APPEARANCE_TAB_ID
223
+ }
224
+ })
225
+
226
+ const activeGroup = computed(() =>
227
+ visibleSchemaGroups.value.find((g) => g.id === activeTab.value),
228
+ )
229
+
230
+ const activeGroupVisibleFields = computed(() => {
231
+ if (!activeGroup.value) return []
232
+ return activeGroup.value.fields.filter((f) => builder.isFieldVisible(f.name))
233
+ })
37
234
 
38
235
  const themeOptions: { value: ThemeMode; label: string }[] = [
39
236
  { value: 'light', label: 'Light' },
@@ -51,6 +248,14 @@ function handleClose() {
51
248
  emit('update:modelValue', false)
52
249
  emit('close')
53
250
  }
251
+
252
+ function builderFieldNames(): string[] {
253
+ return builder.fields.map(field => field.name)
254
+ }
255
+
256
+ function isControlModelBinding(model: ControlModel | ControlModelBinding): model is ControlModelBinding {
257
+ return 'controls' in model && 'controlOptions' in model
258
+ }
54
259
  </script>
55
260
 
56
261
  <template>
@@ -61,9 +266,11 @@ function handleClose() {
61
266
  @update:model-value="emit('update:modelValue', $event)"
62
267
  @close="handleClose"
63
268
  >
64
- <div class="mint-settings-modal">
65
- <!-- Tabs -->
66
- <div v-if="allTabs.length > 1" class="mint-settings-modal__tabs">
269
+ <div :class="['mint-settings-modal', `mint-settings-modal--${layout}`]">
270
+ <div
271
+ v-if="!isVertical && allTabs.length > 1"
272
+ class="mint-settings-modal__tabs"
273
+ >
67
274
  <button
68
275
  v-for="tab in allTabs"
69
276
  :key="tab.id"
@@ -75,68 +282,139 @@ function handleClose() {
75
282
  </button>
76
283
  </div>
77
284
 
78
- <!-- Custom tab content -->
79
- <div class="mint-settings-modal__content">
80
- <template v-for="tab in tabs" :key="tab.id">
81
- <div v-show="activeTab === tab.id">
82
- <slot :name="`tab-${tab.id}`" />
285
+ <div
286
+ v-else-if="isVertical"
287
+ class="mint-settings-modal__rail"
288
+ role="tablist"
289
+ aria-orientation="vertical"
290
+ aria-label="Settings sections"
291
+ >
292
+ <button
293
+ v-for="tab in allTabs"
294
+ :id="tabId(tab.id)"
295
+ :key="tab.id"
296
+ type="button"
297
+ role="tab"
298
+ :class="['mint-settings-modal__rail-item', { 'mint-settings-modal__rail-item--active': activeTab === tab.id }]"
299
+ :aria-selected="activeTab === tab.id"
300
+ :aria-controls="panelId(tab.id)"
301
+ :tabindex="activeTab === tab.id ? 0 : -1"
302
+ @click="activeTab = tab.id"
303
+ >
304
+ <span class="mint-settings-modal__rail-item-icon" aria-hidden="true">
305
+ <span v-if="tab.icon" v-html="tab.icon" />
306
+ </span>
307
+ <span class="mint-settings-modal__rail-item-text">
308
+ <span class="mint-settings-modal__rail-item-label">{{ tab.label }}</span>
309
+ <span v-if="tab.description" class="mint-settings-modal__rail-item-description">
310
+ {{ tab.description }}
311
+ </span>
312
+ </span>
313
+ </button>
314
+ </div>
315
+
316
+ <!-- Vertical mode wraps content in a tabpanel <section> with a header bar; horizontal mode stays a plain <div>. -->
317
+ <component
318
+ :is="isVertical ? 'section' : 'div'"
319
+ :class="isVertical ? 'mint-settings-modal__pane' : 'mint-settings-modal__content'"
320
+ :id="isVertical && activeTabMeta ? panelId(activeTabMeta.id) : undefined"
321
+ :role="isVertical ? 'tabpanel' : undefined"
322
+ :aria-labelledby="isVertical && activeTabMeta ? tabId(activeTabMeta.id) : undefined"
323
+ :tabindex="isVertical ? 0 : undefined"
324
+ >
325
+ <header
326
+ v-if="isVertical && activeTabMeta?.label"
327
+ class="mint-settings-modal__pane-header"
328
+ >
329
+ <h4 class="mint-settings-modal__pane-title">{{ activeTabMeta.label }}</h4>
330
+ <p v-if="activeTabMeta.description" class="mint-settings-modal__pane-subtitle">
331
+ {{ activeTabMeta.description }}
332
+ </p>
333
+ </header>
334
+
335
+ <div :class="isVertical ? 'mint-settings-modal__pane-body' : null">
336
+ <div
337
+ v-if="isSchemaDriven && activeGroup"
338
+ class="mint-settings-modal__group-grid"
339
+ :style="{ '--mint-settings-cols': activeGroup.columns ?? 1 }"
340
+ >
341
+ <template v-for="field in activeGroupVisibleFields" :key="field.name">
342
+ <div :style="field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined">
343
+ <slot
344
+ :name="`field:${field.name}`"
345
+ :field="field"
346
+ :form="builder.form"
347
+ :field-props="builder.form.getFieldProps(field.name)"
348
+ >
349
+ <FormFieldRendererInternal
350
+ :field="field"
351
+ :resolved-props="builder.getResolvedFieldProps(field)"
352
+ :form="builder.form"
353
+ />
354
+ </slot>
355
+ </div>
356
+ </template>
83
357
  </div>
84
- </template>
85
-
86
- <!-- Built-in appearance tab -->
87
- <div v-if="showAppearance" v-show="activeTab === 'appearance'">
88
- <!-- Theme -->
89
- <div class="mint-settings-modal__section">
90
- <div class="mint-settings-modal__section-label">Theme</div>
91
- <div class="mint-settings-modal__option-group">
92
- <button
93
- v-for="opt in themeOptions"
94
- :key="opt.value"
95
- type="button"
96
- :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.theme === opt.value }]"
97
- @click="settings.theme = opt.value"
98
- >
99
- {{ opt.label }}
100
- </button>
358
+
359
+ <template v-else-if="!isSchemaDriven">
360
+ <template v-for="tab in manualTabs" :key="tab.id">
361
+ <div v-show="activeTab === tab.id">
362
+ <slot :name="`tab-${tab.id}`" />
363
+ </div>
364
+ </template>
365
+ </template>
366
+
367
+ <div v-if="showAppearance" v-show="activeTab === APPEARANCE_TAB_ID">
368
+ <div class="mint-settings-modal__section">
369
+ <div class="mint-settings-modal__section-label">Theme</div>
370
+ <div class="mint-settings-modal__option-group">
371
+ <button
372
+ v-for="opt in themeOptions"
373
+ :key="opt.value"
374
+ type="button"
375
+ :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.theme === opt.value }]"
376
+ @click="settings.theme = opt.value"
377
+ >
378
+ {{ opt.label }}
379
+ </button>
380
+ </div>
101
381
  </div>
102
- </div>
103
382
 
104
- <!-- Color palette -->
105
- <div class="mint-settings-modal__section">
106
- <div class="mint-settings-modal__section-label">Color Palette</div>
107
- <div class="mint-settings-modal__option-group">
108
- <button
109
- v-for="(palette, key) in colorPalettes"
110
- :key="key"
111
- type="button"
112
- :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.colorPalette === key }]"
113
- @click="settings.colorPalette = key as ColorPalette"
114
- >
115
- {{ palette.name }}
116
- </button>
383
+ <div class="mint-settings-modal__section">
384
+ <div class="mint-settings-modal__section-label">Color Palette</div>
385
+ <div class="mint-settings-modal__option-group">
386
+ <button
387
+ v-for="(palette, key) in colorPalettes"
388
+ :key="key"
389
+ type="button"
390
+ :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.colorPalette === key }]"
391
+ @click="settings.colorPalette = key as ColorPalette"
392
+ >
393
+ {{ palette.name }}
394
+ </button>
395
+ </div>
117
396
  </div>
118
- </div>
119
397
 
120
- <!-- Table density -->
121
- <div class="mint-settings-modal__section">
122
- <div class="mint-settings-modal__section-label">Table Density</div>
123
- <div class="mint-settings-modal__option-group">
124
- <button
125
- v-for="opt in densityOptions"
126
- :key="opt.value"
127
- type="button"
128
- :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.tableDensity === opt.value }]"
129
- @click="settings.tableDensity = opt.value"
130
- >
131
- {{ opt.label }}
132
- </button>
398
+ <div class="mint-settings-modal__section">
399
+ <div class="mint-settings-modal__section-label">Table Density</div>
400
+ <div class="mint-settings-modal__option-group">
401
+ <button
402
+ v-for="opt in densityOptions"
403
+ :key="opt.value"
404
+ type="button"
405
+ :class="['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.tableDensity === opt.value }]"
406
+ @click="settings.tableDensity = opt.value"
407
+ >
408
+ {{ opt.label }}
409
+ </button>
410
+ </div>
411
+ <p class="mint-settings-modal__note">Adjusts row height in data tables.</p>
133
412
  </div>
134
- <p class="mint-settings-modal__note">Adjusts row height in data tables.</p>
135
- </div>
136
413
 
137
- <slot name="appearance" />
414
+ <slot name="appearance" />
415
+ </div>
138
416
  </div>
139
- </div>
417
+ </component>
140
418
  </div>
141
419
  </BaseModal>
142
420
  </template>
@@ -1,6 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  /** Lets users type and add tags with autocomplete suggestions, categories, and a max-tag cap. */
3
3
  import { ref, computed } from 'vue'
4
+ import { useSelectionLimit } from '../composables/useSelectionLimit'
5
+ import { useDropdownState } from '../composables/useDropdownState'
4
6
 
5
7
  interface TagSuggestion {
6
8
  value: string
@@ -43,13 +45,23 @@ const emit = defineEmits<{
43
45
 
44
46
  const inputValue = ref('')
45
47
  const inputRef = ref<HTMLInputElement>()
46
- const dropdownOpen = ref(false)
47
48
  const highlightedIndex = ref(-1)
49
+ const {
50
+ isOpen: dropdownOpen,
51
+ rootRef,
52
+ open: openDropdown,
53
+ close: closeDropdown,
54
+ } = useDropdownState({
55
+ onClose: () => {
56
+ highlightedIndex.value = -1
57
+ },
58
+ })
48
59
 
49
- const canAddMore = computed(() => {
50
- if (props.maxTags === undefined) return true
51
- return props.modelValue.length < props.maxTags
60
+ const selectionLimit = useSelectionLimit({
61
+ count: () => props.modelValue.length,
62
+ max: () => props.maxTags,
52
63
  })
64
+ const canAddMore = selectionLimit.canAddMore
53
65
 
54
66
  const normalizedSuggestions = computed<TagSuggestion[]>(() => {
55
67
  return props.suggestions.map(s => typeof s === 'string' ? { value: s } : s)
@@ -73,7 +85,7 @@ function categoryFor(tag: string): TagCategory | undefined {
73
85
  function addTag(value: string) {
74
86
  const trimmed = value.trim()
75
87
  if (!trimmed) return
76
- if (!canAddMore.value) return
88
+ if (!selectionLimit.canAdd()) return
77
89
 
78
90
  // If a category is active and user didn't already type a prefix, prepend it
79
91
  let final = trimmed
@@ -85,8 +97,7 @@ function addTag(value: string) {
85
97
 
86
98
  emit('update:modelValue', [...props.modelValue, final])
87
99
  inputValue.value = ''
88
- dropdownOpen.value = false
89
- highlightedIndex.value = -1
100
+ closeDropdown()
90
101
  }
91
102
 
92
103
  function removeTag(index: number) {
@@ -100,7 +111,7 @@ function handleKeydown(event: KeyboardEvent) {
100
111
  if (event.key === 'ArrowDown' && filteredSuggestions.value.length) {
101
112
  event.preventDefault()
102
113
  highlightedIndex.value = Math.min(highlightedIndex.value + 1, filteredSuggestions.value.length - 1)
103
- dropdownOpen.value = true
114
+ openDropdown()
104
115
  return
105
116
  }
106
117
  if (event.key === 'ArrowUp' && filteredSuggestions.value.length) {
@@ -118,8 +129,7 @@ function handleKeydown(event: KeyboardEvent) {
118
129
  return
119
130
  }
120
131
  if (event.key === 'Escape') {
121
- dropdownOpen.value = false
122
- highlightedIndex.value = -1
132
+ closeDropdown()
123
133
  return
124
134
  }
125
135
  if (event.key === 'Backspace' && !inputValue.value && props.modelValue.length > 0) {
@@ -128,13 +138,13 @@ function handleKeydown(event: KeyboardEvent) {
128
138
  }
129
139
 
130
140
  function handleFocus() {
131
- if (filteredSuggestions.value.length) dropdownOpen.value = true
141
+ if (filteredSuggestions.value.length) openDropdown()
132
142
  }
133
143
 
134
144
  function handleBlur() {
135
145
  // Delay so a click on a suggestion fires first
136
146
  setTimeout(() => {
137
- dropdownOpen.value = false
147
+ closeDropdown()
138
148
  }, 120)
139
149
  }
140
150
 
@@ -147,7 +157,7 @@ function handlePaste(event: ClipboardEvent) {
147
157
  const newTags = [...props.modelValue]
148
158
 
149
159
  for (const tag of tags) {
150
- if (!canAddMore.value) break
160
+ if (!selectionLimit.canAdd(1, newTags.length)) break
151
161
  if (!props.allowDuplicates && newTags.includes(tag)) continue
152
162
  newTags.push(tag)
153
163
  }
@@ -164,13 +174,18 @@ function setCategory(prefix: string) {
164
174
  }
165
175
 
166
176
  function onInputEvent() {
167
- dropdownOpen.value = filteredSuggestions.value.length > 0
177
+ if (filteredSuggestions.value.length > 0) {
178
+ openDropdown()
179
+ } else {
180
+ closeDropdown()
181
+ }
168
182
  highlightedIndex.value = -1
169
183
  }
170
184
  </script>
171
185
 
172
186
  <template>
173
187
  <div
188
+ ref="rootRef"
174
189
  :class="[
175
190
  'mint-tags-input',
176
191
  `mint-tags-input--${size}`,
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  /** Icon button that toggles between light and dark mode, swapping sun/moon icons to reflect the active theme. */
3
3
  import { useTheme } from '../composables/useTheme'
4
+ import IconButton from './IconButton.vue'
4
5
 
5
6
  interface Props {
6
7
  size?: 'sm' | 'md' | 'lg'
@@ -14,16 +15,17 @@ const { isDark, toggleTheme } = useTheme()
14
15
  </script>
15
16
 
16
17
  <template>
17
- <button
18
- type="button"
19
- :class="['mint-theme-toggle', `mint-theme-toggle--${size}`]"
20
- :aria-label="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
18
+ <IconButton
19
+ class="mint-theme-toggle"
20
+ variant="ghost"
21
+ :size="size"
22
+ :label="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
21
23
  @click="toggleTheme"
22
24
  >
23
25
  <!-- Lucide sun -->
24
26
  <svg
25
27
  v-if="isDark"
26
- :class="`mint-theme-toggle__icon--${size}`"
28
+ class="mint-theme-toggle__icon"
27
29
  viewBox="0 0 24 24"
28
30
  fill="none"
29
31
  stroke="currentColor"
@@ -36,7 +38,7 @@ const { isDark, toggleTheme } = useTheme()
36
38
  <!-- Lucide moon -->
37
39
  <svg
38
40
  v-else
39
- :class="`mint-theme-toggle__icon--${size}`"
41
+ class="mint-theme-toggle__icon"
40
42
  viewBox="0 0 24 24"
41
43
  fill="none"
42
44
  stroke="currentColor"
@@ -46,7 +48,7 @@ const { isDark, toggleTheme } = useTheme()
46
48
  >
47
49
  <path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401" />
48
50
  </svg>
49
- </button>
51
+ </IconButton>
50
52
  </template>
51
53
 
52
54
  <style>