@morscherlab/mint-sdk 1.0.0-beta.2 → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +225 -6
- package/dist/__tests__/components/ActionItem.test.d.ts +1 -0
- package/dist/__tests__/components/AppAvatarMenu.test.d.ts +1 -0
- package/dist/__tests__/components/AppPageSelector.test.d.ts +1 -0
- package/dist/__tests__/components/AppPillNav.test.d.ts +1 -0
- package/dist/__tests__/components/AppPluginSwitcher.test.d.ts +1 -0
- package/dist/__tests__/components/AppToastContainer.test.d.ts +1 -0
- package/dist/__tests__/components/BaseRadioGroup.test.d.ts +1 -0
- package/dist/__tests__/components/BaseSelect.test.d.ts +1 -0
- package/dist/__tests__/components/BaseTabs.test.d.ts +1 -0
- package/dist/__tests__/components/BatchProgressList.test.d.ts +1 -0
- package/dist/__tests__/components/BioTemplateExperimentWorkspaceView.test.d.ts +1 -0
- package/dist/__tests__/components/BioTemplatePackWorkspaceView.test.d.ts +1 -0
- package/dist/__tests__/components/BioTemplatePresetWorkspaceView.test.d.ts +1 -0
- package/dist/__tests__/components/BioTemplateRenderer.test.d.ts +1 -0
- package/dist/__tests__/components/Breadcrumb.test.d.ts +1 -0
- package/dist/__tests__/components/CalendarGridPanel.test.d.ts +1 -0
- package/dist/__tests__/components/ComponentBindingRenderer.test.d.ts +1 -0
- package/dist/__tests__/components/ConcentrationInput.test.d.ts +1 -0
- package/dist/__tests__/components/ControlWorkspaceView.test.d.ts +1 -0
- package/dist/__tests__/components/DatePicker.test.d.ts +1 -0
- package/dist/__tests__/components/DateTimePicker.test.d.ts +1 -0
- package/dist/__tests__/components/DoseDesignWorkspaceView.test.d.ts +1 -0
- package/dist/__tests__/components/EmptyState.test.d.ts +1 -0
- package/dist/__tests__/components/ExperimentPopover.test.d.ts +1 -0
- package/dist/__tests__/components/FormBuilder.test.d.ts +1 -0
- package/dist/__tests__/components/GroupAssigner.test.d.ts +1 -0
- package/dist/__tests__/components/MultiSelect.test.d.ts +1 -0
- package/dist/__tests__/components/PluginWorkspaceView.test.d.ts +1 -0
- package/dist/__tests__/components/ProtocolStepEditor.test.d.ts +1 -0
- package/dist/__tests__/components/ReagentList.test.d.ts +1 -0
- package/dist/__tests__/components/SampleHierarchyTree.test.d.ts +1 -0
- package/dist/__tests__/components/SampleSelector.test.d.ts +1 -0
- package/dist/__tests__/components/SegmentedControl.test.d.ts +1 -0
- package/dist/__tests__/components/SettingsModal.test.d.ts +1 -0
- package/dist/__tests__/components/TagsInput.test.d.ts +1 -0
- package/dist/__tests__/components/ThemeToggle.test.d.ts +1 -0
- package/dist/__tests__/components/TimePicker.test.d.ts +1 -0
- package/dist/__tests__/composables/experiment-utils.test.d.ts +1 -0
- package/dist/__tests__/composables/useApi.test.d.ts +1 -0
- package/dist/__tests__/composables/useBioTemplatePackWorkspace.test.d.ts +1 -0
- package/dist/__tests__/composables/useBioTemplatePresetWorkspace.test.d.ts +1 -0
- package/dist/__tests__/composables/useBioTemplateWorkspace.test.d.ts +1 -0
- package/dist/__tests__/composables/useCalendarGrid.test.d.ts +1 -0
- package/dist/__tests__/composables/useControlSchema.test.d.ts +1 -0
- package/dist/__tests__/composables/useDebouncedWatch.test.d.ts +1 -0
- package/dist/__tests__/composables/useDropdownState.test.d.ts +1 -0
- package/dist/__tests__/composables/useEventListener.test.d.ts +1 -0
- package/dist/__tests__/composables/useExpansionSet.test.d.ts +1 -0
- package/dist/__tests__/composables/useExperimentData.test.d.ts +1 -0
- package/dist/__tests__/composables/useExperimentSelector.test.d.ts +1 -0
- package/dist/__tests__/composables/useGroupAssignment.test.d.ts +1 -0
- package/dist/__tests__/composables/useListSelection.test.d.ts +1 -0
- package/dist/__tests__/composables/usePluginClient.test.d.ts +1 -0
- package/dist/__tests__/composables/usePluginConfig.test.d.ts +1 -0
- package/dist/__tests__/composables/useRequestSyncState.test.d.ts +1 -0
- package/dist/__tests__/composables/useSampleGroups.test.d.ts +1 -0
- package/dist/__tests__/composables/useSelectionLimit.test.d.ts +1 -0
- package/dist/__tests__/composables/useSortedItems.test.d.ts +1 -0
- package/dist/__tests__/composables/useTemplateCollection.test.d.ts +1 -0
- package/dist/__tests__/composables/useTextSearch.test.d.ts +1 -0
- package/dist/__tests__/composables/useTheme.test.d.ts +1 -0
- package/dist/__tests__/composables/useTimeUtils.test.d.ts +1 -0
- package/dist/__tests__/docs/frontendDocsCatalog.test.d.ts +1 -0
- package/dist/__tests__/templates/templates.test.d.ts +1 -0
- package/dist/{auth-DsI0rQ7_.js → auth-QQj2kkze.js} +12 -5
- package/dist/auth-QQj2kkze.js.map +1 -0
- package/dist/components/AppAvatarMenu.vue.d.ts +2 -7
- package/dist/components/AppContainer.vue.d.ts +1 -1
- package/dist/components/AppLayout.vue.d.ts +20 -1
- package/dist/components/AppSidebar.vue.d.ts +111 -6
- package/dist/components/AppTopBar.vue.d.ts +35 -22
- package/dist/components/BaseButton.vue.d.ts +1 -1
- package/dist/components/BaseCheckbox.vue.d.ts +1 -1
- package/dist/components/BaseInput.vue.d.ts +2 -2
- package/dist/components/BasePill.vue.d.ts +2 -2
- package/dist/components/BaseRadioGroup.vue.d.ts +3 -3
- package/dist/components/BaseSelect.vue.d.ts +3 -3
- package/dist/components/BaseTabs.vue.d.ts +2 -2
- package/dist/components/BaseTextarea.vue.d.ts +1 -1
- package/dist/components/BaseToggle.vue.d.ts +1 -1
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +119 -0
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +93 -0
- package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +87 -0
- package/dist/components/BioTemplateRenderer.vue.d.ts +29 -0
- package/dist/components/Breadcrumb.vue.d.ts +2 -2
- package/dist/components/Calendar.vue.d.ts +1 -1
- package/dist/components/CollapsibleCard.vue.d.ts +1 -1
- package/dist/components/ComponentBindingRenderer.vue.d.ts +44 -0
- package/dist/components/ConcentrationInput.vue.d.ts +2 -2
- package/dist/components/ConfirmDialog.vue.d.ts +2 -2
- package/dist/components/ControlWorkspaceView.vue.d.ts +147 -0
- package/dist/components/DatePicker.vue.d.ts +1 -1
- package/dist/components/DateTimePicker.vue.d.ts +3 -3
- package/dist/components/Divider.vue.d.ts +1 -1
- package/dist/components/DoseDesignWorkspaceView.vue.d.ts +149 -0
- package/dist/components/DropdownButton.vue.d.ts +3 -3
- package/dist/components/EmptyState.vue.d.ts +1 -2
- package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
- package/dist/components/ExperimentTimeline.vue.d.ts +2 -2
- package/dist/components/FileUploader.vue.d.ts +1 -1
- package/dist/components/FitPanel.vue.d.ts +1 -1
- package/dist/components/FormActions.vue.d.ts +4 -4
- package/dist/components/FormBuilder.vue.d.ts +31 -17
- package/dist/components/FormulaInput.vue.d.ts +2 -2
- package/dist/components/MoleculeInput.vue.d.ts +2 -2
- package/dist/components/MultiSelect.vue.d.ts +3 -3
- package/dist/components/NumberInput.vue.d.ts +1 -1
- package/dist/components/PlateMapEditor.vue.d.ts +1 -1
- package/dist/components/PluginWorkspaceView.vue.d.ts +310 -0
- package/dist/components/ProgressBar.vue.d.ts +1 -1
- package/dist/components/ProtocolStepEditor.vue.d.ts +3 -1
- package/dist/components/RackEditor.vue.d.ts +2 -2
- package/dist/components/SampleLegend.vue.d.ts +2 -2
- package/dist/components/ScheduleCalendar.vue.d.ts +2 -2
- package/dist/components/SegmentedControl.vue.d.ts +2 -2
- package/dist/components/SequenceInput.vue.d.ts +3 -3
- package/dist/components/SettingsModal.vue.d.ts +14 -6
- package/dist/components/StatusIndicator.vue.d.ts +1 -1
- package/dist/components/TagsInput.vue.d.ts +3 -2
- package/dist/components/TimePicker.vue.d.ts +3 -3
- package/dist/components/TimeRangeInput.vue.d.ts +1 -1
- package/dist/components/UnitInput.vue.d.ts +2 -2
- package/dist/components/WellPlate.vue.d.ts +6 -6
- package/dist/components/index.d.ts +9 -8
- package/dist/components/index.js +3 -3
- package/dist/components/{SettingsButton.vue.d.ts → internal/ActionItemInternal.vue.d.ts} +11 -9
- package/dist/components/{AppPageSelector.vue.d.ts → internal/AppPageSelectorInternal.vue.d.ts} +3 -6
- package/dist/components/{AppPillNav.vue.d.ts → internal/AppPillNavInternal.vue.d.ts} +4 -2
- package/dist/components/internal/CalendarGridPanelInternal.vue.d.ts +25 -0
- package/dist/components/{FormFieldRenderer.vue.d.ts → internal/FormFieldRendererInternal.vue.d.ts} +2 -2
- package/dist/components/{FormSection.vue.d.ts → internal/FormSectionRenderer.vue.d.ts} +7 -7
- package/dist/components/{WellEditPopup.vue.d.ts → internal/WellEditPopupInternal.vue.d.ts} +1 -1
- package/dist/{components-_XqPEhP9.js → components-BkGF4B4y.js} +9760 -8471
- package/dist/components-BkGF4B4y.js.map +1 -0
- package/dist/composables/experiment-utils.d.ts +8 -0
- package/dist/composables/index.d.ts +22 -5
- package/dist/composables/index.js +4 -3
- package/dist/composables/platformContextHelpers.d.ts +14 -0
- package/dist/composables/useAppExperiment.d.ts +31 -2
- package/dist/composables/useBioTemplateComponents.d.ts +22 -0
- package/dist/composables/useBioTemplateControls.d.ts +6 -0
- package/dist/composables/useBioTemplatePackWorkspace.d.ts +46 -0
- package/dist/composables/useBioTemplatePresetWorkspace.d.ts +75 -0
- package/dist/composables/useBioTemplateWorkspace.d.ts +51 -0
- package/dist/composables/useCalendarGrid.d.ts +26 -0
- package/dist/composables/useControlSchema.d.ts +343 -0
- package/dist/composables/useDebouncedWatch.d.ts +20 -0
- package/dist/composables/useDropdownState.d.ts +19 -0
- package/dist/composables/useEventListener.d.ts +13 -0
- package/dist/composables/useExpansionSet.d.ts +21 -0
- package/dist/composables/useExperimentData.d.ts +10 -0
- package/dist/composables/useExperimentSave.d.ts +31 -2
- package/dist/composables/useExperimentSelector.d.ts +20 -0
- package/dist/composables/useForm.d.ts +2 -0
- package/dist/composables/useGroupAssignment.d.ts +31 -0
- package/dist/composables/useListSelection.d.ts +35 -0
- package/dist/composables/usePlatformContext.d.ts +21 -3
- package/dist/composables/usePluginClient.d.ts +112 -0
- package/dist/composables/usePluginConfig.d.ts +12 -0
- package/dist/composables/useRequestSyncState.d.ts +34 -0
- package/dist/composables/useSampleGroups.d.ts +32 -0
- package/dist/composables/useSelectionLimit.d.ts +17 -0
- package/dist/composables/useSortedItems.d.ts +32 -0
- package/dist/composables/useTemplateCollection.d.ts +58 -0
- package/dist/composables/useTextSearch.d.ts +18 -0
- package/dist/composables/useTimeUtils.d.ts +8 -0
- package/dist/{composables-tiZqLu1M.js → composables-CHsME9H1.js} +240 -146
- package/dist/composables-CHsME9H1.js.map +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +6 -5
- package/dist/install.d.ts +7 -2
- package/dist/install.js +2 -2
- package/dist/install.js.map +1 -1
- package/dist/stores/index.js +1 -1
- package/dist/stores/settings.d.ts +4 -1
- package/dist/styles.css +4746 -5514
- package/dist/templates/adapters.d.ts +43 -0
- package/dist/templates/builders.d.ts +63 -0
- package/dist/templates/catalog.d.ts +188 -0
- package/dist/templates/componentBindings.d.ts +71 -0
- package/dist/templates/controlSchemas.d.ts +25 -0
- package/dist/templates/index.d.ts +15 -0
- package/dist/templates/index.js +2 -0
- package/dist/templates/lookup.d.ts +4 -0
- package/dist/templates/packs.d.ts +18 -0
- package/dist/templates/presets.d.ts +90 -0
- package/dist/templates/types.d.ts +531 -0
- package/dist/templates-B5jmTWuk.js +9388 -0
- package/dist/templates-B5jmTWuk.js.map +1 -0
- package/dist/types/components.d.ts +26 -23
- package/dist/types/form-builder.d.ts +6 -8
- package/dist/types/index.d.ts +2 -2
- package/dist/types/platform.d.ts +7 -1
- package/dist/useScheduleDrag-BgzpQT53.js +4414 -0
- package/dist/useScheduleDrag-BgzpQT53.js.map +1 -0
- package/dist/utils/formModelSync.d.ts +5 -0
- package/dist/utils/items.d.ts +8 -0
- package/dist/utils/options.d.ts +6 -0
- package/dist/utils/pluginIcon.d.ts +9 -0
- package/package.json +7 -2
- package/src/__tests__/components/ActionItem.test.ts +99 -0
- package/src/__tests__/components/AppAvatarMenu.test.ts +27 -0
- package/src/__tests__/components/AppLayout.test.ts +44 -0
- package/src/__tests__/components/AppPageSelector.test.ts +134 -0
- package/src/__tests__/components/AppPillNav.test.ts +125 -0
- package/src/__tests__/components/AppPluginSwitcher.test.ts +44 -0
- package/src/__tests__/components/AppSidebar.test.ts +496 -0
- package/src/__tests__/components/AppToastContainer.test.ts +37 -0
- package/src/__tests__/components/AppTopBar.test.ts +455 -9
- package/src/__tests__/components/BaseRadioGroup.test.ts +25 -0
- package/src/__tests__/components/BaseSelect.test.ts +21 -0
- package/src/__tests__/components/BaseTabs.test.ts +25 -0
- package/src/__tests__/components/BatchProgressList.test.ts +52 -0
- package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +159 -0
- package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +175 -0
- package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +306 -0
- package/src/__tests__/components/BioTemplateRenderer.test.ts +71 -0
- package/src/__tests__/components/Breadcrumb.test.ts +23 -0
- package/src/__tests__/components/CalendarGridPanel.test.ts +36 -0
- package/src/__tests__/components/ComponentBindingRenderer.test.ts +161 -0
- package/src/__tests__/components/ConcentrationInput.test.ts +45 -0
- package/src/__tests__/components/ControlWorkspaceView.test.ts +1102 -0
- package/src/__tests__/components/DataFrame.test.ts +11 -0
- package/src/__tests__/components/DatePicker.test.ts +45 -0
- package/src/__tests__/components/DateTimePicker.test.ts +48 -0
- package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +185 -0
- package/src/__tests__/components/DropdownButton.test.ts +23 -0
- package/src/__tests__/components/EmptyState.test.ts +23 -0
- package/src/__tests__/components/ExperimentPopover.test.ts +56 -0
- package/src/__tests__/components/FormBuilder.test.ts +296 -0
- package/src/__tests__/components/GroupAssigner.test.ts +30 -0
- package/src/__tests__/components/MultiSelect.test.ts +48 -0
- package/src/__tests__/components/PluginWorkspaceView.test.ts +548 -0
- package/src/__tests__/components/ProtocolStepEditor.test.ts +33 -0
- package/src/__tests__/components/ReagentList.test.ts +82 -0
- package/src/__tests__/components/SampleHierarchyTree.test.ts +53 -0
- package/src/__tests__/components/SampleSelector.test.ts +60 -0
- package/src/__tests__/components/SegmentedControl.test.ts +24 -0
- package/src/__tests__/components/SettingsModal.test.ts +296 -0
- package/src/__tests__/components/TagsInput.test.ts +75 -0
- package/src/__tests__/components/ThemeToggle.test.ts +47 -0
- package/src/__tests__/components/TimePicker.test.ts +38 -0
- package/src/__tests__/composables/experiment-utils.test.ts +30 -0
- package/src/__tests__/composables/useApi.test.ts +30 -0
- package/src/__tests__/composables/useAppExperiment.test.ts +100 -1
- package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +125 -0
- package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +199 -0
- package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +104 -0
- package/src/__tests__/composables/useCalendarGrid.test.ts +38 -0
- package/src/__tests__/composables/useControlSchema.test.ts +1033 -0
- package/src/__tests__/composables/useDebouncedWatch.test.ts +93 -0
- package/src/__tests__/composables/useDropdownState.test.ts +95 -0
- package/src/__tests__/composables/useEventListener.test.ts +116 -0
- package/src/__tests__/composables/useExpansionSet.test.ts +62 -0
- package/src/__tests__/composables/useExperimentData.test.ts +4 -0
- package/src/__tests__/composables/useExperimentSave.test.ts +203 -8
- package/src/__tests__/composables/useExperimentSelector.test.ts +164 -0
- package/src/__tests__/composables/useForm.test.ts +58 -0
- package/src/__tests__/composables/useFormBuilder.test.ts +77 -0
- package/src/__tests__/composables/useGroupAssignment.test.ts +73 -0
- package/src/__tests__/composables/useListSelection.test.ts +66 -0
- package/src/__tests__/composables/usePluginClient.test.ts +541 -0
- package/src/__tests__/composables/usePluginConfig.test.ts +5 -0
- package/src/__tests__/composables/useRequestSyncState.test.ts +92 -0
- package/src/__tests__/composables/useSampleGroups.test.ts +66 -0
- package/src/__tests__/composables/useSelectionLimit.test.ts +41 -0
- package/src/__tests__/composables/useSortedItems.test.ts +87 -0
- package/src/__tests__/composables/useTemplateCollection.test.ts +147 -0
- package/src/__tests__/composables/useTextSearch.test.ts +55 -0
- package/src/__tests__/composables/useTheme.test.ts +91 -0
- package/src/__tests__/composables/useTimeUtils.test.ts +35 -0
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +324 -0
- package/src/__tests__/fixtures/templates/dose-response.json +81 -0
- package/src/__tests__/fixtures/templates/plate-map.json +54 -0
- package/src/__tests__/fixtures/templates/qpcr-plate.json +96 -0
- package/src/__tests__/fixtures/templates/sample-sheet.json +71 -0
- package/src/__tests__/templates/templates.test.ts +1055 -0
- package/src/components/AppAvatarMenu.vue +15 -69
- package/src/components/AppLayout.story.vue +64 -25
- package/src/components/AppLayout.vue +83 -2
- package/src/components/AppPluginSwitcher.vue +41 -145
- package/src/components/AppSidebar.story.vue +203 -1
- package/src/components/AppSidebar.vue +320 -25
- package/src/components/{ToastNotification.story.vue → AppToastContainer.story.vue} +6 -6
- package/src/components/{ToastNotification.vue → AppToastContainer.vue} +1 -1
- package/src/components/AppTopBar.story.vue +7 -33
- package/src/components/AppTopBar.vue +104 -300
- package/src/components/BaseModal.vue +3 -5
- package/src/components/BaseRadioGroup.vue +7 -3
- package/src/components/BaseSelect.vue +11 -7
- package/src/components/BaseTabs.vue +6 -4
- package/src/components/BatchProgressList.vue +5 -8
- package/src/components/BioTemplateExperimentWorkspaceView.story.vue +123 -0
- package/src/components/BioTemplateExperimentWorkspaceView.vue +343 -0
- package/src/components/BioTemplatePackWorkspaceView.story.vue +107 -0
- package/src/components/BioTemplatePackWorkspaceView.vue +177 -0
- package/src/components/BioTemplatePresetWorkspaceView.story.vue +163 -0
- package/src/components/BioTemplatePresetWorkspaceView.vue +401 -0
- package/src/components/BioTemplateRenderer.story.vue +57 -0
- package/src/components/BioTemplateRenderer.vue +57 -0
- package/src/components/Breadcrumb.vue +14 -8
- package/src/components/ComponentBindingRenderer.story.vue +57 -0
- package/src/components/ComponentBindingRenderer.vue +308 -0
- package/src/components/ConcentrationInput.vue +27 -64
- package/src/components/ControlWorkspaceView.story.vue +347 -0
- package/src/components/ControlWorkspaceView.vue +378 -0
- package/src/components/DataFrame.vue +34 -50
- package/src/components/DatePicker.vue +59 -192
- package/src/components/DateTimePicker.vue +50 -171
- package/src/components/DoseDesignWorkspaceView.story.vue +77 -0
- package/src/components/DoseDesignWorkspaceView.vue +255 -0
- package/src/components/DropdownButton.vue +14 -32
- package/src/components/EmptyState.vue +4 -2
- package/src/components/ExperimentPopover.vue +7 -28
- package/src/components/ExperimentSelectorModal.vue +6 -5
- package/src/components/FormBuilder.story.vue +190 -0
- package/src/components/FormBuilder.vue +124 -27
- package/src/components/GroupAssigner.vue +24 -56
- package/src/components/MultiSelect.vue +17 -12
- package/src/components/PlateMapEditor.vue +3 -8
- package/src/components/PluginIcon.vue +2 -22
- package/src/components/PluginWorkspaceView.story.vue +334 -0
- package/src/components/PluginWorkspaceView.vue +708 -0
- package/src/components/ProtocolStepEditor.vue +13 -22
- package/src/components/ReagentList.vue +25 -33
- package/src/components/SampleHierarchyTree.vue +12 -23
- package/src/components/SampleSelector.vue +42 -122
- package/src/components/SegmentedControl.vue +7 -3
- package/src/components/SettingsModal.story.vue +88 -1
- package/src/components/SettingsModal.vue +120 -29
- package/src/components/TagsInput.vue +29 -14
- package/src/components/ThemeToggle.vue +9 -7
- package/src/components/TimePicker.vue +19 -41
- package/src/components/Tooltip.vue +7 -12
- package/src/components/WellPlate.vue +6 -12
- package/src/components/index.ts +9 -8
- package/src/components/internal/ActionItemInternal.vue +82 -0
- package/src/components/internal/AppPageSelectorInternal.vue +128 -0
- package/src/components/internal/AppPillNavInternal.vue +194 -0
- package/src/components/internal/CalendarGridPanelInternal.vue +120 -0
- package/src/components/{FormFieldRenderer.vue → internal/FormFieldRendererInternal.vue} +4 -12
- package/src/components/{FormSection.vue → internal/FormSectionRenderer.vue} +6 -18
- package/src/components/{WellEditPopup.vue → internal/WellEditPopupInternal.vue} +5 -10
- package/src/composables/experiment-utils.ts +26 -0
- package/src/composables/index.ts +229 -3
- package/src/composables/platformContextHelpers.ts +74 -0
- package/src/composables/useApi.ts +9 -2
- package/src/composables/useAppExperiment.ts +85 -13
- package/src/composables/useBioTemplateComponents.ts +105 -0
- package/src/composables/useBioTemplateControls.ts +41 -0
- package/src/composables/useBioTemplatePackWorkspace.ts +185 -0
- package/src/composables/useBioTemplatePresetWorkspace.ts +326 -0
- package/src/composables/useBioTemplateWorkspace.ts +141 -0
- package/src/composables/useCalendarGrid.ts +140 -0
- package/src/composables/useControlSchema.ts +1362 -0
- package/src/composables/useDebouncedWatch.ts +119 -0
- package/src/composables/useDropdownState.ts +83 -0
- package/src/composables/useEventListener.ts +111 -0
- package/src/composables/useExpansionSet.ts +117 -0
- package/src/composables/useExperimentData.ts +20 -11
- package/src/composables/useExperimentSave.ts +202 -50
- package/src/composables/useExperimentSelector.ts +86 -72
- package/src/composables/useForm.ts +49 -4
- package/src/composables/useFormBuilder.ts +93 -42
- package/src/composables/useGroupAssignment.ts +148 -0
- package/src/composables/useListSelection.ts +158 -0
- package/src/composables/usePluginClient.ts +466 -0
- package/src/composables/usePluginConfig.ts +34 -13
- package/src/composables/useRequestSyncState.ts +126 -0
- package/src/composables/useSampleGroups.ts +126 -0
- package/src/composables/useSelectionLimit.ts +57 -0
- package/src/composables/useSortedItems.ts +118 -0
- package/src/composables/useTemplateCollection.ts +229 -0
- package/src/composables/useTextSearch.ts +60 -0
- package/src/composables/useTheme.ts +2 -28
- package/src/composables/useTimeUtils.ts +26 -2
- package/src/composables/useWellPlateEditor.ts +13 -9
- package/src/index.ts +11 -348
- package/src/install.ts +11 -4
- package/src/stores/settings.ts +13 -9
- package/src/styles/components/app-layout.css +82 -0
- package/src/styles/components/app-page-selector.css +23 -0
- package/src/styles/components/app-pill-nav.css +77 -0
- package/src/styles/components/app-sidebar.css +119 -0
- package/src/styles/components/app-top-bar.css +0 -201
- package/src/styles/components/concentration-input.css +3 -142
- package/src/styles/components/empty-state.css +0 -16
- package/src/styles/components/theme-toggle.css +3 -66
- package/src/styles/index.css +0 -2
- package/src/templates/adapters.ts +785 -0
- package/src/templates/builders.ts +2149 -0
- package/src/templates/catalog.ts +245 -0
- package/src/templates/componentBindings.ts +653 -0
- package/src/templates/controlSchemas.ts +718 -0
- package/src/templates/index.ts +318 -0
- package/src/templates/lookup.ts +18 -0
- package/src/templates/packs.ts +156 -0
- package/src/templates/presets.ts +146 -0
- package/src/templates/types.ts +668 -0
- package/src/types/components.ts +39 -27
- package/src/types/form-builder.ts +7 -2
- package/src/types/index.ts +13 -3
- package/src/types/platform.ts +7 -1
- package/src/utils/formModelSync.ts +52 -0
- package/src/utils/items.ts +28 -0
- package/src/utils/options.ts +23 -0
- package/src/utils/pluginIcon.ts +30 -0
- package/dist/__tests__/composables/usePluginApi.test.d.ts +0 -13
- package/dist/auth-DsI0rQ7_.js.map +0 -1
- package/dist/components/GroupingModal.vue.d.ts +0 -12
- package/dist/components-_XqPEhP9.js.map +0 -1
- package/dist/composables/usePluginApi.d.ts +0 -29
- package/dist/composables-tiZqLu1M.js.map +0 -1
- package/dist/useScheduleDrag-CA9sGNJG.js +0 -7181
- package/dist/useScheduleDrag-CA9sGNJG.js.map +0 -1
- package/src/__tests__/composables/usePluginApi.test.ts +0 -81
- package/src/components/AppPageSelector.vue +0 -159
- package/src/components/AppPillNav.vue +0 -66
- package/src/components/GroupingModal.story.vue +0 -52
- package/src/components/GroupingModal.vue +0 -422
- package/src/components/SettingsButton.story.vue +0 -58
- package/src/components/SettingsButton.vue +0 -76
- package/src/composables/usePluginApi.ts +0 -39
- package/src/styles/components/grouping-modal.css +0 -323
- package/src/styles/components/settings-button.css +0 -94
- /package/dist/components/{ToastNotification.vue.d.ts → AppToastContainer.vue.d.ts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ref, reactive, computed, watch, type Ref } from 'vue'
|
|
1
|
+
import { ref, reactive, computed, toRaw, watch, type Ref } from 'vue'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Validation rule function type.
|
|
@@ -58,6 +58,8 @@ export interface UseFormReturn<T extends Record<string, unknown>> {
|
|
|
58
58
|
validateField: (field: string) => boolean
|
|
59
59
|
validate: () => boolean
|
|
60
60
|
reset: (values?: Partial<T>) => void
|
|
61
|
+
/** Replace the full form state and treat the provided values as the new clean baseline. */
|
|
62
|
+
replaceState: (values: T) => void
|
|
61
63
|
handleSubmit: (onSubmit: (data: T) => Promise<void> | void) => (e?: Event) => Promise<void>
|
|
62
64
|
getFieldProps: <K extends keyof T>(field: K) => {
|
|
63
65
|
modelValue: T[K]
|
|
@@ -156,11 +158,13 @@ export function useForm<T extends Record<string, unknown>>(
|
|
|
156
158
|
initialValues: T,
|
|
157
159
|
rules: Partial<Record<keyof T, FieldRules>> = {}
|
|
158
160
|
): UseFormReturn<T> {
|
|
161
|
+
const cloneableInitialValues = deepToRaw(initialValues) as T
|
|
162
|
+
|
|
159
163
|
// Deep copy initial values so nested objects are not shared
|
|
160
|
-
|
|
164
|
+
let _initialValues = structuredClone(cloneableInitialValues)
|
|
161
165
|
|
|
162
166
|
// Reactive form data
|
|
163
|
-
const data = reactive(structuredClone(
|
|
167
|
+
const data = reactive(structuredClone(cloneableInitialValues)) as T
|
|
164
168
|
|
|
165
169
|
// Field state - use simple Record types for better TS compatibility
|
|
166
170
|
const errors = reactive<Record<string, string | null>>(
|
|
@@ -355,7 +359,27 @@ export function useForm<T extends Record<string, unknown>>(
|
|
|
355
359
|
function reset(values?: Partial<T>): void {
|
|
356
360
|
const resetValues = values ? { ..._initialValues, ...values } : _initialValues
|
|
357
361
|
for (const key of Object.keys(data)) {
|
|
358
|
-
;(data as Record<string, unknown>)[key] = structuredClone(resetValues[key as keyof T])
|
|
362
|
+
;(data as Record<string, unknown>)[key] = structuredClone(deepToRaw(resetValues[key as keyof T]))
|
|
363
|
+
errors[key] = null
|
|
364
|
+
touched[key] = false
|
|
365
|
+
dirty[key] = false
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function replaceState(values: T): void {
|
|
370
|
+
const nextValues = structuredClone(deepToRaw(values)) as T
|
|
371
|
+
_initialValues = structuredClone(nextValues)
|
|
372
|
+
|
|
373
|
+
for (const key of Object.keys(data)) {
|
|
374
|
+
if (key in nextValues) continue
|
|
375
|
+
delete (data as Record<string, unknown>)[key]
|
|
376
|
+
delete errors[key]
|
|
377
|
+
delete touched[key]
|
|
378
|
+
delete dirty[key]
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
for (const key of Object.keys(nextValues)) {
|
|
382
|
+
;(data as Record<string, unknown>)[key] = structuredClone(deepToRaw(nextValues[key as keyof T]))
|
|
359
383
|
errors[key] = null
|
|
360
384
|
touched[key] = false
|
|
361
385
|
dirty[key] = false
|
|
@@ -410,7 +434,28 @@ export function useForm<T extends Record<string, unknown>>(
|
|
|
410
434
|
validateField,
|
|
411
435
|
validate,
|
|
412
436
|
reset,
|
|
437
|
+
replaceState,
|
|
413
438
|
handleSubmit,
|
|
414
439
|
getFieldProps,
|
|
415
440
|
}
|
|
416
441
|
}
|
|
442
|
+
|
|
443
|
+
function deepToRaw(value: unknown): unknown {
|
|
444
|
+
const raw = toRaw(value)
|
|
445
|
+
|
|
446
|
+
if (Array.isArray(raw)) {
|
|
447
|
+
return raw.map(item => deepToRaw(item))
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (isPlainRecord(raw)) {
|
|
451
|
+
return Object.fromEntries(
|
|
452
|
+
Object.entries(raw).map(([key, item]) => [key, deepToRaw(item)])
|
|
453
|
+
)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return raw
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
460
|
+
return Object.prototype.toString.call(value) === '[object Object]'
|
|
461
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ref, computed, watch } from 'vue'
|
|
1
|
+
import { reactive, ref, computed, shallowRef, watch } from 'vue'
|
|
2
2
|
import { useForm, type FieldRules, type UseFormReturn } from './useForm'
|
|
3
3
|
import { getFieldRegistryEntry, getTypeDefault } from './formBuilderRegistry'
|
|
4
4
|
import type {
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
FormSectionSchema,
|
|
8
8
|
FieldCondition,
|
|
9
9
|
FieldValidation,
|
|
10
|
+
FormOptionInput,
|
|
10
11
|
FormEnhancements,
|
|
11
12
|
UseFormBuilderReturn,
|
|
12
13
|
} from '../types/form-builder'
|
|
@@ -92,37 +93,15 @@ function convertValidation(v: FieldValidation): FieldRules {
|
|
|
92
93
|
return rules
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// ---------------------------------------------------------------------------
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Drive a `FormSchema` as reactive form state.
|
|
101
|
-
*
|
|
102
|
-
* Builds initial values from schema defaults and `initialData`, derives
|
|
103
|
-
* validation rules from `FieldValidation` descriptors and enhancement
|
|
104
|
-
* validators, evaluates `FieldCondition` expressions for field/section
|
|
105
|
-
* visibility, and wires wizard step navigation when `schema.steps` is set.
|
|
106
|
-
*
|
|
107
|
-
* @param schema - Declarative form or wizard schema.
|
|
108
|
-
* @param initialData - Values that override schema defaults.
|
|
109
|
-
* @param enhancements - TypeScript-only callbacks (dynamic options, validators,
|
|
110
|
-
* submit handler, transform, field-change watcher).
|
|
111
|
-
*/
|
|
112
|
-
/** Drives a declarative FormSchema as reactive state with conditional fields, wizard steps, and validation. */
|
|
113
|
-
export function useFormBuilder<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
114
|
-
schema: FormSchema,
|
|
96
|
+
function buildInitialValues<T extends Record<string, unknown>>(
|
|
97
|
+
fields: readonly FormFieldSchema[],
|
|
115
98
|
initialData?: Partial<T>,
|
|
116
|
-
|
|
117
|
-
): UseFormBuilderReturn<T> {
|
|
118
|
-
const fields = flattenFields(schema)
|
|
119
|
-
const sections = collectSections(schema)
|
|
120
|
-
|
|
121
|
-
// -- Build initial values --------------------------------------------------
|
|
99
|
+
): Record<string, unknown> {
|
|
122
100
|
const initialValues = {} as Record<string, unknown>
|
|
101
|
+
|
|
123
102
|
for (const field of fields) {
|
|
124
103
|
const key = field.name
|
|
125
|
-
if (initialData && key
|
|
104
|
+
if (initialData && hasOwnKey(initialData, key)) {
|
|
126
105
|
initialValues[key] = (initialData as Record<string, unknown>)[key]
|
|
127
106
|
} else if (field.defaultValue !== undefined) {
|
|
128
107
|
initialValues[key] = field.defaultValue
|
|
@@ -131,14 +110,23 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
|
|
|
131
110
|
}
|
|
132
111
|
}
|
|
133
112
|
|
|
134
|
-
|
|
113
|
+
return initialValues
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function buildRules<T extends Record<string, unknown>>(
|
|
117
|
+
fields: readonly FormFieldSchema[],
|
|
118
|
+
enhancements?: FormEnhancements<T>,
|
|
119
|
+
): Partial<Record<string, FieldRules>> {
|
|
135
120
|
const rules: Partial<Record<string, FieldRules>> = {}
|
|
121
|
+
|
|
136
122
|
for (const field of fields) {
|
|
137
123
|
const base: FieldRules = field.validation ? convertValidation(field.validation) : {}
|
|
138
124
|
const enhancement = enhancements?.fields?.[field.name as keyof T]
|
|
139
125
|
|
|
140
|
-
|
|
141
|
-
|
|
126
|
+
const customValidators: Array<(
|
|
127
|
+
value: unknown,
|
|
128
|
+
formData: Record<string, unknown>,
|
|
129
|
+
) => string | undefined | null> = []
|
|
142
130
|
|
|
143
131
|
if (enhancement?.validate) {
|
|
144
132
|
const fn = enhancement.validate
|
|
@@ -149,14 +137,65 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
|
|
|
149
137
|
base.custom = customValidators
|
|
150
138
|
}
|
|
151
139
|
|
|
152
|
-
// Wrap all rules to skip hidden fields
|
|
153
140
|
if (Object.keys(base).length > 0) {
|
|
154
141
|
rules[field.name] = base
|
|
155
142
|
}
|
|
156
143
|
}
|
|
157
144
|
|
|
145
|
+
return rules
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function replaceArray<T>(target: T[], values: readonly T[]): void {
|
|
149
|
+
target.splice(0, target.length, ...values)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function replaceRecord<TValue>(
|
|
153
|
+
target: Partial<Record<string, TValue>>,
|
|
154
|
+
source: Partial<Record<string, TValue>>,
|
|
155
|
+
): void {
|
|
156
|
+
for (const key of Object.keys(target)) {
|
|
157
|
+
delete target[key]
|
|
158
|
+
}
|
|
159
|
+
Object.assign(target, source)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function hasOwnKey(source: object, key: string): boolean {
|
|
163
|
+
return Object.prototype.hasOwnProperty.call(source, key)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// useFormBuilder
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Drive a `FormSchema` as reactive form state.
|
|
172
|
+
*
|
|
173
|
+
* Builds initial values from schema defaults and `initialData`, derives
|
|
174
|
+
* validation rules from `FieldValidation` descriptors and enhancement
|
|
175
|
+
* validators, evaluates `FieldCondition` expressions for field/section
|
|
176
|
+
* visibility, and wires wizard step navigation when `schema.steps` is set.
|
|
177
|
+
*
|
|
178
|
+
* @param schema - Declarative form or wizard schema.
|
|
179
|
+
* @param initialData - Values that override schema defaults.
|
|
180
|
+
* @param enhancements - TypeScript-only callbacks (dynamic options, validators,
|
|
181
|
+
* submit handler, transform, field-change watcher).
|
|
182
|
+
*/
|
|
183
|
+
/** Drives a declarative FormSchema as reactive state with conditional fields, wizard steps, and validation. */
|
|
184
|
+
export function useFormBuilder<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
185
|
+
schema: FormSchema,
|
|
186
|
+
initialData?: Partial<T>,
|
|
187
|
+
enhancements?: FormEnhancements<T>,
|
|
188
|
+
): UseFormBuilderReturn<T> {
|
|
189
|
+
const currentSchema = shallowRef(schema)
|
|
190
|
+
const fields = reactive<FormFieldSchema[]>(flattenFields(schema))
|
|
191
|
+
const sections = reactive<FormSectionSchema[]>(collectSections(schema))
|
|
192
|
+
const rules = reactive<Partial<Record<string, FieldRules>>>(buildRules(fields, enhancements))
|
|
193
|
+
|
|
158
194
|
// -- Create form -----------------------------------------------------------
|
|
159
|
-
const form = useForm(
|
|
195
|
+
const form = useForm(
|
|
196
|
+
buildInitialValues<T>(fields, initialData) as T,
|
|
197
|
+
rules as Partial<Record<keyof T, FieldRules>>,
|
|
198
|
+
)
|
|
160
199
|
|
|
161
200
|
// -- Visibility ------------------------------------------------------------
|
|
162
201
|
function isFieldVisible(name: string): boolean {
|
|
@@ -230,7 +269,7 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
|
|
|
230
269
|
return merged
|
|
231
270
|
}
|
|
232
271
|
|
|
233
|
-
function getFieldOptions(name: string):
|
|
272
|
+
function getFieldOptions(name: string): FormOptionInput[] | undefined {
|
|
234
273
|
const enhancement = enhancements?.fields?.[name as keyof T]
|
|
235
274
|
if (enhancement?.options) {
|
|
236
275
|
return enhancement.options(form.data as T)
|
|
@@ -238,7 +277,7 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
|
|
|
238
277
|
|
|
239
278
|
const field = fields.find((f) => f.name === name)
|
|
240
279
|
if (field?.props?.options) {
|
|
241
|
-
return field.props.options as
|
|
280
|
+
return field.props.options as FormOptionInput[]
|
|
242
281
|
}
|
|
243
282
|
|
|
244
283
|
return undefined
|
|
@@ -248,9 +287,9 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
|
|
|
248
287
|
const currentStep = ref(0)
|
|
249
288
|
|
|
250
289
|
const isCurrentStepValid = computed(() => {
|
|
251
|
-
if (!
|
|
290
|
+
if (!currentSchema.value.steps) return form.isValid.value
|
|
252
291
|
|
|
253
|
-
const step =
|
|
292
|
+
const step = currentSchema.value.steps[currentStep.value]
|
|
254
293
|
if (!step) return true
|
|
255
294
|
|
|
256
295
|
for (const section of step.sections) {
|
|
@@ -264,10 +303,10 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
|
|
|
264
303
|
})
|
|
265
304
|
|
|
266
305
|
function goNext(): boolean {
|
|
267
|
-
if (!
|
|
306
|
+
if (!currentSchema.value.steps) return false
|
|
268
307
|
|
|
269
308
|
// Touch and validate all visible fields in current step
|
|
270
|
-
const step =
|
|
309
|
+
const step = currentSchema.value.steps[currentStep.value]
|
|
271
310
|
if (!step) return false
|
|
272
311
|
|
|
273
312
|
let valid = true
|
|
@@ -282,7 +321,7 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
|
|
|
282
321
|
|
|
283
322
|
if (!valid) return false
|
|
284
323
|
|
|
285
|
-
if (currentStep.value <
|
|
324
|
+
if (currentStep.value < currentSchema.value.steps.length - 1) {
|
|
286
325
|
currentStep.value++
|
|
287
326
|
}
|
|
288
327
|
return true
|
|
@@ -295,8 +334,8 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
|
|
|
295
334
|
}
|
|
296
335
|
|
|
297
336
|
function goToStep(index: number): void {
|
|
298
|
-
if (!
|
|
299
|
-
if (index >= 0 && index <
|
|
337
|
+
if (!currentSchema.value.steps) return
|
|
338
|
+
if (index >= 0 && index < currentSchema.value.steps.length) {
|
|
300
339
|
currentStep.value = index
|
|
301
340
|
}
|
|
302
341
|
}
|
|
@@ -346,6 +385,17 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
|
|
|
346
385
|
currentStep.value = 0
|
|
347
386
|
}
|
|
348
387
|
|
|
388
|
+
function updateSchema(nextSchema: FormSchema, nextData?: Partial<T>): void {
|
|
389
|
+
currentSchema.value = nextSchema
|
|
390
|
+
|
|
391
|
+
const nextFields = flattenFields(nextSchema)
|
|
392
|
+
replaceArray(fields, nextFields)
|
|
393
|
+
replaceArray(sections, collectSections(nextSchema))
|
|
394
|
+
replaceRecord(rules, buildRules(nextFields, enhancements))
|
|
395
|
+
form.replaceState(buildInitialValues<T>(nextFields, nextData) as T)
|
|
396
|
+
currentStep.value = 0
|
|
397
|
+
}
|
|
398
|
+
|
|
349
399
|
// -- onFieldChange wiring --------------------------------------------------
|
|
350
400
|
if (enhancements?.onFieldChange) {
|
|
351
401
|
const callback = enhancements.onFieldChange
|
|
@@ -379,5 +429,6 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
|
|
|
379
429
|
validate,
|
|
380
430
|
reset,
|
|
381
431
|
submit,
|
|
432
|
+
updateSchema,
|
|
382
433
|
}
|
|
383
434
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { computed, toValue, type ComputedRef, type Ref } from 'vue'
|
|
2
|
+
import type { GroupItem } from '../types'
|
|
3
|
+
|
|
4
|
+
export type GroupAssignmentZone = 'zone1' | 'zone2'
|
|
5
|
+
|
|
6
|
+
export type GroupAssignmentSource<T> =
|
|
7
|
+
| T
|
|
8
|
+
| Ref<T>
|
|
9
|
+
| ComputedRef<T>
|
|
10
|
+
| (() => T)
|
|
11
|
+
|
|
12
|
+
export interface GroupAssignmentState {
|
|
13
|
+
group1: string[]
|
|
14
|
+
group2: string[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UseGroupAssignmentOptions {
|
|
18
|
+
groups: GroupAssignmentSource<readonly GroupItem[]>
|
|
19
|
+
group1: GroupAssignmentSource<readonly string[]>
|
|
20
|
+
group2: GroupAssignmentSource<readonly string[]>
|
|
21
|
+
label1?: GroupAssignmentSource<string | null | undefined>
|
|
22
|
+
label2?: GroupAssignmentSource<string | null | undefined>
|
|
23
|
+
minPerGroup?: GroupAssignmentSource<number | null | undefined>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UseGroupAssignmentReturn {
|
|
27
|
+
unassignedGroups: ComputedRef<GroupItem[]>
|
|
28
|
+
zone1Groups: ComputedRef<GroupItem[]>
|
|
29
|
+
zone2Groups: ComputedRef<GroupItem[]>
|
|
30
|
+
zone1Count: ComputedRef<number>
|
|
31
|
+
zone2Count: ComputedRef<number>
|
|
32
|
+
isValid: ComputedRef<boolean>
|
|
33
|
+
validationMessage: ComputedRef<string | null>
|
|
34
|
+
getZoneForGroup: (groupName: string) => GroupAssignmentZone | null
|
|
35
|
+
assignToZone: (groupName: string, zone: GroupAssignmentZone) => GroupAssignmentState
|
|
36
|
+
removeFromZone: (groupName: string, zone: GroupAssignmentZone) => GroupAssignmentState
|
|
37
|
+
clearAll: () => GroupAssignmentState
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Shared two-zone group assignment model for control/treatment style workflows. */
|
|
41
|
+
export function useGroupAssignment(options: UseGroupAssignmentOptions): UseGroupAssignmentReturn {
|
|
42
|
+
const groups = computed(() => [...toValue(options.groups)])
|
|
43
|
+
const group1 = computed(() => [...toValue(options.group1)])
|
|
44
|
+
const group2 = computed(() => [...toValue(options.group2)])
|
|
45
|
+
const label1 = computed(() => toValue(options.label1) || 'Control')
|
|
46
|
+
const label2 = computed(() => toValue(options.label2) || 'Treatment')
|
|
47
|
+
const minPerGroup = computed(() => normalizeMinimum(toValue(options.minPerGroup)))
|
|
48
|
+
const group1Set = computed(() => new Set(group1.value))
|
|
49
|
+
const group2Set = computed(() => new Set(group2.value))
|
|
50
|
+
|
|
51
|
+
const unassignedGroups = computed(() =>
|
|
52
|
+
groups.value.filter(group => !group1Set.value.has(group.name) && !group2Set.value.has(group.name))
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
const zone1Groups = computed(() =>
|
|
56
|
+
groups.value.filter(group => group1Set.value.has(group.name))
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const zone2Groups = computed(() =>
|
|
60
|
+
groups.value.filter(group => group2Set.value.has(group.name))
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const zone1Count = computed(() =>
|
|
64
|
+
zone1Groups.value.reduce((sum, group) => sum + group.count, 0)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const zone2Count = computed(() =>
|
|
68
|
+
zone2Groups.value.reduce((sum, group) => sum + group.count, 0)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
const isValid = computed(() =>
|
|
72
|
+
group1.value.length >= minPerGroup.value &&
|
|
73
|
+
group2.value.length >= minPerGroup.value
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const validationMessage = computed(() => {
|
|
77
|
+
if (isValid.value) return null
|
|
78
|
+
const missing1 = Math.max(0, minPerGroup.value - group1.value.length)
|
|
79
|
+
const missing2 = Math.max(0, minPerGroup.value - group2.value.length)
|
|
80
|
+
const parts: string[] = []
|
|
81
|
+
if (missing1 > 0) parts.push(`${missing1} more to ${label1.value}`)
|
|
82
|
+
if (missing2 > 0) parts.push(`${missing2} more to ${label2.value}`)
|
|
83
|
+
return `Add ${parts.join(' and ')}`
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
function getZoneForGroup(groupName: string): GroupAssignmentZone | null {
|
|
87
|
+
if (group1Set.value.has(groupName)) return 'zone1'
|
|
88
|
+
if (group2Set.value.has(groupName)) return 'zone2'
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function assignToZone(groupName: string, zone: GroupAssignmentZone): GroupAssignmentState {
|
|
93
|
+
const next = removeFromBoth(groupName)
|
|
94
|
+
if (zone === 'zone1') {
|
|
95
|
+
return {
|
|
96
|
+
group1: [...next.group1, groupName],
|
|
97
|
+
group2: next.group2,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
group1: next.group1,
|
|
102
|
+
group2: [...next.group2, groupName],
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function removeFromZone(groupName: string, zone: GroupAssignmentZone): GroupAssignmentState {
|
|
107
|
+
if (zone === 'zone1') {
|
|
108
|
+
return {
|
|
109
|
+
group1: group1.value.filter(name => name !== groupName),
|
|
110
|
+
group2: group2.value,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
group1: group1.value,
|
|
115
|
+
group2: group2.value.filter(name => name !== groupName),
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function clearAll(): GroupAssignmentState {
|
|
120
|
+
return { group1: [], group2: [] }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function removeFromBoth(groupName: string): GroupAssignmentState {
|
|
124
|
+
return {
|
|
125
|
+
group1: group1.value.filter(name => name !== groupName),
|
|
126
|
+
group2: group2.value.filter(name => name !== groupName),
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
unassignedGroups,
|
|
132
|
+
zone1Groups,
|
|
133
|
+
zone2Groups,
|
|
134
|
+
zone1Count,
|
|
135
|
+
zone2Count,
|
|
136
|
+
isValid,
|
|
137
|
+
validationMessage,
|
|
138
|
+
getZoneForGroup,
|
|
139
|
+
assignToZone,
|
|
140
|
+
removeFromZone,
|
|
141
|
+
clearAll,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function normalizeMinimum(value: number | null | undefined): number {
|
|
146
|
+
if (value === null || value === undefined || !Number.isFinite(value)) return 1
|
|
147
|
+
return Math.max(0, Math.floor(value))
|
|
148
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { computed, toValue, type ComputedRef, type Ref } from 'vue'
|
|
2
|
+
import { useSelectionLimit, type SelectionLimitSource } from './useSelectionLimit'
|
|
3
|
+
|
|
4
|
+
export type ListSelectionValue = string | number
|
|
5
|
+
|
|
6
|
+
export type ListSelectionSource<T> =
|
|
7
|
+
| T
|
|
8
|
+
| Ref<T>
|
|
9
|
+
| ComputedRef<T>
|
|
10
|
+
| (() => T)
|
|
11
|
+
|
|
12
|
+
export type WidenListSelectionValue<TValue extends ListSelectionValue> =
|
|
13
|
+
TValue extends string ? string : TValue extends number ? number : TValue
|
|
14
|
+
|
|
15
|
+
export interface UseListSelectionOptions<
|
|
16
|
+
TItem,
|
|
17
|
+
TValue extends ListSelectionValue = ListSelectionValue,
|
|
18
|
+
> {
|
|
19
|
+
selected: ListSelectionSource<readonly TValue[]>
|
|
20
|
+
items?: ListSelectionSource<readonly TItem[]>
|
|
21
|
+
getValue?: (item: TItem) => TValue
|
|
22
|
+
disabled?: ListSelectionSource<boolean | null | undefined>
|
|
23
|
+
max?: SelectionLimitSource<number | null | undefined>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UseListSelectionReturn<TValue extends ListSelectionValue = ListSelectionValue> {
|
|
27
|
+
selectedValues: ComputedRef<TValue[]>
|
|
28
|
+
selectedSet: ComputedRef<Set<TValue>>
|
|
29
|
+
itemValues: ComputedRef<TValue[]>
|
|
30
|
+
selectedCount: ComputedRef<number>
|
|
31
|
+
itemCount: ComputedRef<number>
|
|
32
|
+
isAllSelected: ComputedRef<boolean>
|
|
33
|
+
isLimited: ComputedRef<boolean>
|
|
34
|
+
isAtLimit: ComputedRef<boolean>
|
|
35
|
+
canAddMore: ComputedRef<boolean>
|
|
36
|
+
remaining: ComputedRef<number | undefined>
|
|
37
|
+
isSelected: (value: TValue) => boolean
|
|
38
|
+
isFullySelected: (values: readonly TValue[]) => boolean
|
|
39
|
+
isPartiallySelected: (values: readonly TValue[]) => boolean
|
|
40
|
+
selectValues: (values: readonly TValue[]) => TValue[]
|
|
41
|
+
toggleValue: (value: TValue) => TValue[]
|
|
42
|
+
toggleValues: (values: readonly TValue[]) => TValue[]
|
|
43
|
+
selectAll: () => TValue[]
|
|
44
|
+
toggleAll: () => TValue[]
|
|
45
|
+
clear: () => TValue[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Shared list-selection state for checkbox lists, sample selectors, and chip selectors. */
|
|
49
|
+
export function useListSelection<
|
|
50
|
+
TItem = ListSelectionValue,
|
|
51
|
+
TValue extends ListSelectionValue = TItem extends ListSelectionValue ? TItem : ListSelectionValue,
|
|
52
|
+
>(options: UseListSelectionOptions<TItem, TValue>): UseListSelectionReturn<WidenListSelectionValue<TValue>> {
|
|
53
|
+
type ResolvedValue = WidenListSelectionValue<TValue>
|
|
54
|
+
|
|
55
|
+
const disabled = computed(() => toValue(options.disabled) ?? false)
|
|
56
|
+
const selectedValues = computed<ResolvedValue[]>(() => [...toValue(options.selected)] as ResolvedValue[])
|
|
57
|
+
const selectedSet = computed(() => new Set(selectedValues.value))
|
|
58
|
+
const itemValues = computed<ResolvedValue[]>(() => {
|
|
59
|
+
const items = toValue(options.items) ?? []
|
|
60
|
+
return items.map(item => getItemValue(item, options.getValue) as ResolvedValue)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const selectedCount = computed(() => selectedValues.value.length)
|
|
64
|
+
const itemCount = computed(() => itemValues.value.length)
|
|
65
|
+
const isAllSelected = computed(() => (
|
|
66
|
+
itemValues.value.length > 0 && itemValues.value.every(value => selectedSet.value.has(value))
|
|
67
|
+
))
|
|
68
|
+
const selectionLimit = useSelectionLimit({
|
|
69
|
+
count: selectedCount,
|
|
70
|
+
max: options.max,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
function isSelected(value: ResolvedValue): boolean {
|
|
74
|
+
return selectedSet.value.has(value)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isFullySelected(values: readonly ResolvedValue[]): boolean {
|
|
78
|
+
return values.length > 0 && values.every(value => selectedSet.value.has(value))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isPartiallySelected(values: readonly ResolvedValue[]): boolean {
|
|
82
|
+
if (values.length === 0) return false
|
|
83
|
+
const selected = values.filter(value => selectedSet.value.has(value)).length
|
|
84
|
+
return selected > 0 && selected < values.length
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function selectValues(values: readonly ResolvedValue[]): ResolvedValue[] {
|
|
88
|
+
if (disabled.value) return selectedValues.value
|
|
89
|
+
const missing = uniqueValues(values).filter(value => !selectedSet.value.has(value))
|
|
90
|
+
if (!selectionLimit.canAdd(missing.length)) return selectedValues.value
|
|
91
|
+
return [...selectedValues.value, ...missing]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function toggleValue(value: ResolvedValue): ResolvedValue[] {
|
|
95
|
+
if (disabled.value) return selectedValues.value
|
|
96
|
+
if (selectedSet.value.has(value)) {
|
|
97
|
+
return selectedValues.value.filter(selected => selected !== value)
|
|
98
|
+
}
|
|
99
|
+
if (!selectionLimit.canAddMore.value) return selectedValues.value
|
|
100
|
+
return [...selectedValues.value, value]
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function toggleValues(values: readonly ResolvedValue[]): ResolvedValue[] {
|
|
104
|
+
if (disabled.value) return selectedValues.value
|
|
105
|
+
const unique = uniqueValues(values)
|
|
106
|
+
if (isFullySelected(unique)) {
|
|
107
|
+
const remove = new Set(unique)
|
|
108
|
+
return selectedValues.value.filter(value => !remove.has(value))
|
|
109
|
+
}
|
|
110
|
+
return selectValues(unique)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function selectAll(): ResolvedValue[] {
|
|
114
|
+
return selectValues(itemValues.value)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function toggleAll(): ResolvedValue[] {
|
|
118
|
+
if (isAllSelected.value) return clear()
|
|
119
|
+
return selectAll()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function clear(): ResolvedValue[] {
|
|
123
|
+
return disabled.value ? selectedValues.value : []
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
selectedValues,
|
|
128
|
+
selectedSet,
|
|
129
|
+
itemValues,
|
|
130
|
+
selectedCount,
|
|
131
|
+
itemCount,
|
|
132
|
+
isAllSelected,
|
|
133
|
+
isLimited: selectionLimit.isLimited,
|
|
134
|
+
isAtLimit: selectionLimit.isAtLimit,
|
|
135
|
+
canAddMore: selectionLimit.canAddMore,
|
|
136
|
+
remaining: selectionLimit.remaining,
|
|
137
|
+
isSelected,
|
|
138
|
+
isFullySelected,
|
|
139
|
+
isPartiallySelected,
|
|
140
|
+
selectValues,
|
|
141
|
+
toggleValue,
|
|
142
|
+
toggleValues,
|
|
143
|
+
selectAll,
|
|
144
|
+
toggleAll,
|
|
145
|
+
clear,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getItemValue<TItem, TValue extends ListSelectionValue>(
|
|
150
|
+
item: TItem,
|
|
151
|
+
getValue: ((item: TItem) => TValue) | undefined,
|
|
152
|
+
): TValue {
|
|
153
|
+
return getValue ? getValue(item) : item as unknown as TValue
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function uniqueValues<TValue extends ListSelectionValue>(values: readonly TValue[]): TValue[] {
|
|
157
|
+
return [...new Set(values)]
|
|
158
|
+
}
|