@morscherlab/mint-sdk 1.1.25 → 1.2.0-beta.10
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 +44 -12
- package/dist/{ExperimentPopover-tLwJ7XF4.js → ExperimentPopover-oL0fg_A0.js} +6 -4
- package/dist/ExperimentPopover-oL0fg_A0.js.map +1 -0
- package/dist/{ExperimentSelectorModal-B6P2msT4.js → ExperimentSelectorModal-BeEOomw_.js} +5 -5
- package/dist/ExperimentSelectorModal-BeEOomw_.js.map +1 -0
- package/dist/{SettingsModal-BW3Ar-uy.js → SettingsModal-Dphh3RrI.js} +5 -8
- package/dist/SettingsModal-Dphh3RrI.js.map +1 -0
- package/dist/{EmptyState-p90quq8Z.js → Skeleton-DxtwJ5gt.js} +111 -93
- package/dist/Skeleton-DxtwJ5gt.js.map +1 -0
- package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
- package/dist/__tests__/components/PluginWorkspaceView.internal.test.d.ts +1 -0
- package/dist/__tests__/components/SecretInput.test.d.ts +1 -0
- package/dist/__tests__/components/WellEditPopupInternal.test.d.ts +1 -0
- package/dist/__tests__/composables/useEventStream.test.d.ts +1 -0
- package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
- package/dist/__tests__/styles/cssTokens.test.d.ts +1 -0
- package/dist/components/ActionMenu.vue.d.ts +1 -1
- package/dist/components/AppAvatarMenu.vue.d.ts +4 -0
- package/dist/components/AppPluginSwitcher.vue.d.ts +2 -0
- package/dist/components/AppSidebar.vue.d.ts +1 -1
- package/dist/components/AuditTrail.vue.d.ts +13 -0
- package/dist/components/BatchProgressList.vue.d.ts +17 -0
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
- package/dist/components/DataFrame.vue.d.ts +14 -2
- package/dist/components/DatePicker.vue.d.ts +1 -1
- package/dist/components/DateTimePicker.vue.d.ts +2 -0
- package/dist/components/DropdownButton.vue.d.ts +2 -0
- package/dist/components/ExperimentDataViewer.vue.d.ts +26 -1
- package/dist/components/ExperimentPopover.vue.d.ts +1 -0
- package/dist/components/ExperimentTimeline.vue.d.ts +29 -1
- package/dist/components/FitPanel.vue.d.ts +1 -1
- package/dist/components/LcmsSequenceTable.vue.d.ts +29 -1
- package/dist/components/PluginWorkspaceView.props.d.ts +29 -1
- package/dist/components/PluginWorkspaceView.vue.d.ts +5 -5
- package/dist/components/ProgressBar.vue.d.ts +1 -1
- package/dist/components/ProtocolStepEditor.vue.d.ts +1 -0
- package/dist/components/ReagentList.vue.d.ts +29 -1
- package/dist/components/SampleHierarchyTree.vue.d.ts +29 -1
- package/dist/components/SampleSelector.drag.d.ts +13 -0
- package/dist/components/SampleSelector.tree.d.ts +2 -0
- package/dist/components/SampleSelector.vue.d.ts +34 -1
- package/dist/components/ScheduleCalendar.vue.d.ts +20 -0
- package/dist/components/SecretInput.vue.d.ts +28 -0
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -1
- package/dist/components/TimePicker.vue.d.ts +1 -0
- package/dist/components/WellPlate.geometry.d.ts +15 -0
- package/dist/components/WellPlate.interaction.d.ts +13 -0
- package/dist/components/WellPlate.vue.d.ts +29 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.js +8 -8
- package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -0
- package/dist/components/internal/DataStateInternal.vue.d.ts +43 -0
- package/dist/components/internal/WellEditPopupInternal.vue.d.ts +3 -1
- package/dist/components/internal/WorkspaceControlsInternal.vue.d.ts +23 -0
- package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +70 -0
- package/dist/components/internal/WorkspaceSidebarInternal.vue.d.ts +43 -0
- package/dist/{components-DQt0tcpY.js → components-CgAUzF8F.js} +2491 -2235
- package/dist/components-CgAUzF8F.js.map +1 -0
- package/dist/composables/controlSchemaTypes.d.ts +34 -0
- package/dist/composables/flattenWorkspaceBindings.d.ts +61 -0
- package/dist/composables/index.d.ts +8 -2
- package/dist/composables/index.js +10 -10
- package/dist/composables/pluginWorkspaceModel.d.ts +164 -0
- package/dist/composables/useAsync.d.ts +5 -1
- package/dist/composables/useBioTemplateWorkspace.d.ts +4 -20
- package/dist/composables/useCommandHistory.d.ts +5 -1
- package/dist/composables/useDropdownState.d.ts +18 -2
- package/dist/composables/useEventStream.d.ts +52 -0
- package/dist/composables/useFocusTrap.d.ts +21 -0
- package/dist/composables/useJobsStatusTray.d.ts +1 -1
- package/dist/composables/useListReorder.d.ts +90 -0
- package/dist/composables/useMenuKeyboard.d.ts +29 -0
- package/dist/composables/useOptimisticMutation.d.ts +5 -1
- package/dist/composables/usePluginClient.d.ts +4 -17
- package/dist/composables/usePluginConfig.d.ts +3 -1
- package/dist/composables/usePluginWorkspace.d.ts +51 -0
- package/dist/composables/useRequestSyncState.d.ts +42 -2
- package/dist/composables/useResourceCrud.d.ts +5 -1
- package/dist/composables/useRovingFocus.d.ts +24 -0
- package/dist/composables/useScheduleDrag.d.ts +1 -0
- package/dist/composables/useWellPainting.d.ts +5 -1
- package/dist/composables/useWellPlateAdapter.d.ts +5 -1
- package/dist/composables/useWellPlateValidation.d.ts +5 -1
- package/dist/composables/workspaceRecipes.d.ts +188 -0
- package/dist/{composables-CkXDe7nm.js → composables-B_pia3hO.js} +105 -254
- package/dist/composables-B_pia3hO.js.map +1 -0
- package/dist/{useApi-CDBq6z23.js → errors-_W0-LbA6.js} +102 -3
- package/dist/errors-_W0-LbA6.js.map +1 -0
- package/dist/{experiment-Y9Iz7KYi.js → experiment-Cwl9AYxw.js} +3 -8
- package/dist/experiment-Cwl9AYxw.js.map +1 -0
- package/dist/{experiment-utils-CLnzPPq5.js → experiment-utils-DV3DrvE_.js} +70 -51
- package/dist/experiment-utils-DV3DrvE_.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -15
- package/dist/install.js +2 -7
- package/dist/install.js.map +1 -1
- package/dist/permissions.d.ts +42 -1
- package/dist/permissions.js +20 -10
- package/dist/permissions.js.map +1 -1
- package/dist/stores/index.d.ts +1 -1
- package/dist/stores/index.js +3 -3
- package/dist/stores/settings.d.ts +31 -1
- package/dist/styles.css +11247 -29501
- package/dist/templates/builderReadUtils.d.ts +1 -1
- package/dist/templates/index.js +2 -4
- package/dist/{templates-CovvRZ-9.js → templates-Qq113pyZ.js} +303 -106
- package/dist/templates-Qq113pyZ.js.map +1 -0
- package/dist/types/form-builder.d.ts +1 -1
- package/dist/{useControlSchema-BhoJzWzx.js → useControlSchema-aMZlb4Sc.js} +684 -1216
- package/dist/useControlSchema-aMZlb4Sc.js.map +1 -0
- package/dist/{useDropdownState-DSb3BC3N.js → useDropdownState-DitCtrTT.js} +22 -6
- package/dist/useDropdownState-DitCtrTT.js.map +1 -0
- package/dist/{useExperimentSelector-D0pjj9WH.js → useExperimentSelector-I8HC8hUu.js} +4 -4
- package/dist/{useExperimentSelector-D0pjj9WH.js.map → useExperimentSelector-I8HC8hUu.js.map} +1 -1
- package/dist/{useFormBuilder-DiEcDis2.js → useFormBuilder-TCxNnqeb.js} +2 -2
- package/dist/{useFormBuilder-DiEcDis2.js.map → useFormBuilder-TCxNnqeb.js.map} +1 -1
- package/dist/{useProtocolTemplates-DHwSveE6.js → useProtocolTemplates-BmX_iVBR.js} +660 -197
- package/dist/useProtocolTemplates-BmX_iVBR.js.map +1 -0
- package/dist/utils/color.d.ts +13 -0
- package/dist/utils/errors.d.ts +9 -0
- package/dist/utils/pluginSecrets.d.ts +53 -0
- package/dist/utils/records.d.ts +4 -0
- package/package.json +1 -1
- package/src/__tests__/components/ActionMenu.test.ts +94 -0
- package/src/__tests__/components/AppAvatarMenu.test.ts +123 -0
- package/src/__tests__/components/BaseTabs.test.ts +67 -0
- package/src/__tests__/components/DataFrame.test.ts +183 -11
- package/src/__tests__/components/DataStates.test.ts +176 -0
- package/src/__tests__/components/DatePicker.test.ts +76 -0
- package/src/__tests__/components/DateTimePicker.test.ts +36 -0
- package/src/__tests__/components/DropdownButton.test.ts +134 -0
- package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
- package/src/__tests__/components/GeneratedPluginView.test.ts +0 -5
- package/src/__tests__/components/GroupAssigner.test.ts +53 -0
- package/src/__tests__/components/JobsStatusTray.test.ts +20 -18
- package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
- package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +230 -0
- package/src/__tests__/components/RackEditor.test.ts +35 -0
- package/src/__tests__/components/ReagentList.test.ts +64 -2
- package/src/__tests__/components/RetiredStyles.test.ts +31 -0
- package/src/__tests__/components/SampleSelector.drag.test.ts +51 -1
- package/src/__tests__/components/ScheduleCalendar.test.ts +63 -0
- package/src/__tests__/components/SecretInput.test.ts +87 -0
- package/src/__tests__/components/SegmentedControl.test.ts +88 -0
- package/src/__tests__/components/TimePicker.test.ts +61 -0
- package/src/__tests__/components/WellEditPopupInternal.test.ts +108 -0
- package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
- package/src/__tests__/composables/useAuth.test.ts +25 -0
- package/src/__tests__/composables/useDropdownState.test.ts +58 -1
- package/src/__tests__/composables/useEventStream.test.ts +140 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +103 -0
- package/src/__tests__/composables/useJobsStatusTray.test.ts +25 -161
- package/src/__tests__/composables/useListReorder.test.ts +145 -0
- package/src/__tests__/composables/usePluginClient.test.ts +148 -13
- package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +55 -2
- package/src/__tests__/stores/settings.test.ts +71 -1
- package/src/__tests__/styles/cssTokens.test.ts +131 -0
- package/src/__tests__/utils/permissions.test.ts +68 -0
- package/src/components/ActionMenu.vue +45 -88
- package/src/components/AlertBox.vue +0 -4
- package/src/components/AppAvatarMenu.vue +26 -7
- package/src/components/AppLayout.vue +0 -4
- package/src/components/AppPluginSwitcher.story.vue +87 -0
- package/src/components/AppPluginSwitcher.vue +2 -5
- package/src/components/AppSidebar.vue +0 -4
- package/src/components/AppToastContainer.vue +6 -8
- package/src/components/AppTopBar.vue +0 -4
- package/src/components/ArtifactSaveDialog.vue +0 -4
- package/src/components/ArtifactSelectorModal.vue +0 -4
- package/src/components/AuditTrail.story.vue +18 -0
- package/src/components/AuditTrail.vue +27 -7
- package/src/components/AutoGroupModal.vue +0 -4
- package/src/components/Avatar.vue +0 -4
- package/src/components/BaseButton.vue +0 -4
- package/src/components/BaseCheckbox.vue +0 -4
- package/src/components/BaseInput.vue +0 -4
- package/src/components/BaseModal.vue +8 -60
- package/src/components/BasePill.vue +0 -4
- package/src/components/BaseRadioGroup.vue +0 -4
- package/src/components/BaseSelect.vue +0 -4
- package/src/components/BaseSlider.vue +0 -4
- package/src/components/BaseTabs.vue +26 -5
- package/src/components/BaseTextarea.vue +0 -4
- package/src/components/BaseToggle.vue +0 -4
- package/src/components/BatchProgressList.story.vue +18 -0
- package/src/components/BatchProgressList.vue +32 -4
- package/src/components/BioTemplateExperimentWorkspaceView.vue +10 -21
- package/src/components/BioTemplatePackWorkspaceView.vue +11 -15
- package/src/components/BioTemplatePresetWorkspaceView.vue +8 -13
- package/src/components/Breadcrumb.vue +0 -4
- package/src/components/Calendar.vue +0 -4
- package/src/components/ChartContainer.story.vue +1 -1
- package/src/components/ChartContainer.vue +0 -4
- package/src/components/ChemicalFormula.vue +0 -4
- package/src/components/CollapsibleCard.vue +0 -4
- package/src/components/ColorSlider.vue +0 -4
- package/src/components/ComponentBindingRenderer.vue +1 -3
- package/src/components/ConcentrationInput.vue +0 -4
- package/src/components/ConfirmDialog.vue +0 -4
- package/src/components/ControlWorkspaceView.vue +11 -22
- package/src/components/DataFrame.story.vue +14 -0
- package/src/components/DataFrame.vue +97 -23
- package/src/components/DatePicker.vue +73 -27
- package/src/components/DateTimePicker.vue +45 -17
- package/src/components/Divider.vue +0 -4
- package/src/components/DoseCalculator.vue +0 -4
- package/src/components/DoseDesignWorkspaceView.vue +6 -25
- package/src/components/DropdownButton.vue +24 -6
- package/src/components/EmptyState.vue +0 -4
- package/src/components/ExperimentCodeBadge.vue +0 -4
- package/src/components/ExperimentDataViewer.story.vue +3 -0
- package/src/components/ExperimentDataViewer.vue +33 -13
- package/src/components/ExperimentPopover.vue +2 -5
- package/src/components/ExperimentSelectorModal.vue +0 -4
- package/src/components/ExperimentTimeline.story.vue +18 -0
- package/src/components/ExperimentTimeline.vue +81 -75
- package/src/components/FileUploader.vue +0 -4
- package/src/components/FitPanel.vue +0 -4
- package/src/components/FormActions.story.vue +96 -0
- package/src/components/FormActions.vue +0 -4
- package/src/components/FormBuilder.vue +0 -4
- package/src/components/FormField.vue +0 -4
- package/src/components/FormulaInput.vue +0 -4
- package/src/components/GeneratedPathInput.story.vue +68 -0
- package/src/components/GeneratedPlotlyResult.story.vue +96 -0
- package/src/components/GeneratedPlotlyResult.vue +3 -3
- package/src/components/GeneratedPluginView.story.vue +145 -0
- package/src/components/GeneratedPluginView.vue +10 -10
- package/src/components/GeneratedResultRenderer.story.vue +117 -0
- package/src/components/GeneratedResultRenderer.vue +4 -4
- package/src/components/GroupAssigner.vue +67 -5
- package/src/components/IconButton.vue +0 -4
- package/src/components/InstrumentAlertLog.vue +0 -4
- package/src/components/InstrumentStateBadge.vue +0 -4
- package/src/components/InstrumentStatusCard.vue +0 -4
- package/src/components/JobsStatusTray.story.vue +6 -10
- package/src/components/JobsStatusTray.vue +0 -4
- package/src/components/LcmsSequenceTable.vue +81 -50
- package/src/components/LoadingSpinner.vue +0 -4
- package/src/components/MobileSupportGate.vue +0 -4
- package/src/components/MoleculeInput.vue +0 -4
- package/src/components/MultiSelect.vue +0 -4
- package/src/components/NumberInput.vue +0 -4
- package/src/components/PlateMapEditor.vue +3 -14
- package/src/components/PluginIcon.vue +0 -4
- package/src/components/PluginWorkspaceView.props.ts +38 -0
- package/src/components/PluginWorkspaceView.vue +53 -224
- package/src/components/ProgressBar.vue +0 -4
- package/src/components/ProtocolStepEditor.vue +2 -4
- package/src/components/RackEditor.vue +51 -70
- package/src/components/ReagentEditor.vue +0 -4
- package/src/components/ReagentList.story.vue +18 -0
- package/src/components/ReagentList.vue +127 -82
- package/src/components/ResourceCard.vue +0 -4
- package/src/components/RuntimeUpdateNotice.story.vue +49 -0
- package/src/components/RuntimeUpdateNotice.vue +4 -4
- package/src/components/SampleHierarchyTree.story.vue +18 -0
- package/src/components/SampleHierarchyTree.vue +32 -9
- package/src/components/SampleLegend.vue +2 -8
- package/src/components/SampleSelector.drag.ts +51 -0
- package/src/components/SampleSelector.story.vue +6 -0
- package/src/components/SampleSelector.tree.ts +2 -0
- package/src/components/SampleSelector.vue +49 -10
- package/src/components/SampleSelectorGroupNode.vue +19 -0
- package/src/components/ScheduleCalendar.story.vue +6 -0
- package/src/components/ScheduleCalendar.vue +82 -7
- package/src/components/ScientificNumber.vue +0 -4
- package/src/components/SecretInput.story.vue +79 -0
- package/src/components/SecretInput.vue +150 -0
- package/src/components/SectionCard.vue +0 -4
- package/src/components/SegmentedControl.vue +48 -5
- package/src/components/SequenceInput.vue +0 -4
- package/src/components/SequenceProgressBar.story.vue +110 -0
- package/src/components/SequenceProgressBar.vue +0 -4
- package/src/components/SettingsModal.vue +0 -4
- package/src/components/Skeleton.vue +0 -4
- package/src/components/SmartGroupFieldRecipe.vue +8 -6
- package/src/components/SmartGroupManual.vue +0 -4
- package/src/components/StatusIndicator.vue +0 -4
- package/src/components/StepWizard.vue +0 -4
- package/src/components/TagsInput.vue +4 -6
- package/src/components/ThemeToggle.vue +0 -4
- package/src/components/TimePicker.vue +36 -7
- package/src/components/TimeRangeInput.vue +0 -4
- package/src/components/Tooltip.story.vue +3 -3
- package/src/components/Tooltip.vue +0 -4
- package/src/components/UnitInput.vue +0 -4
- package/src/components/WellPlate.geometry.ts +60 -1
- package/src/components/WellPlate.interaction.ts +44 -0
- package/src/components/WellPlate.story.vue +8 -0
- package/src/components/WellPlate.vue +53 -7
- package/src/components/index.ts +9 -0
- package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -5
- package/src/components/internal/AppTopBarPillNavInternal.vue +0 -4
- package/src/components/internal/DataStateInternal.vue +74 -0
- package/src/components/internal/FormSectionRenderer.vue +0 -4
- package/src/components/internal/WellEditPopupInternal.vue +20 -5
- package/src/components/internal/WorkspaceControlsInternal.vue +20 -0
- package/src/components/internal/WorkspaceShellInternal.vue +125 -0
- package/src/components/internal/WorkspaceSidebarInternal.vue +82 -0
- package/src/composables/controlSchemaTypes.ts +34 -0
- package/src/composables/experimentDesignData.ts +1 -3
- package/src/composables/flattenWorkspaceBindings.ts +104 -0
- package/src/composables/formBuilderRegistry.ts +5 -0
- package/src/composables/index.ts +41 -5
- package/src/composables/pluginWorkspaceModel.ts +182 -0
- package/src/composables/useAsync.ts +5 -1
- package/src/composables/useBioTemplatePackWorkspace.ts +15 -9
- package/src/composables/useBioTemplatePresetWorkspace.ts +14 -15
- package/src/composables/useBioTemplateWorkspace.ts +18 -43
- package/src/composables/useCommandHistory.ts +5 -1
- package/src/composables/useDropdownState.ts +35 -5
- package/src/composables/useEventStream.ts +326 -0
- package/src/composables/useFileImport.ts +2 -7
- package/src/composables/useFocusTrap.ts +91 -0
- package/src/composables/useGeneratedAnalysis.ts +46 -103
- package/src/composables/useJobsStatusTray.ts +1 -1
- package/src/composables/useListReorder.story.vue +143 -0
- package/src/composables/useListReorder.ts +242 -0
- package/src/composables/useMenuKeyboard.ts +90 -0
- package/src/composables/useOptimisticMutation.ts +6 -16
- package/src/composables/usePluginClient.ts +91 -252
- package/src/composables/usePluginConfig.ts +3 -1
- package/src/composables/usePluginWorkspace.ts +208 -0
- package/src/composables/useRackEditor.ts +2 -11
- package/src/composables/useRequestSyncState.ts +98 -28
- package/src/composables/useResourceCrud.ts +6 -16
- package/src/composables/useRovingFocus.ts +66 -0
- package/src/composables/useScheduleDrag.ts +37 -0
- package/src/composables/useWellPainting.ts +10 -30
- package/src/composables/useWellPlateAdapter.ts +5 -1
- package/src/composables/useWellPlateEditor.ts +1 -11
- package/src/composables/useWellPlateValidation.ts +5 -1
- package/src/composables/workspaceRecipes.ts +267 -0
- package/src/index.ts +16 -0
- package/src/permissions.ts +48 -9
- package/src/stores/experiment.ts +2 -7
- package/src/stores/index.ts +9 -1
- package/src/stores/settings.ts +93 -1
- package/src/styles/components/audit-trail.css +5 -5
- package/src/styles/components/batch-progress-list.css +7 -0
- package/src/styles/components/color-slider.css +2 -2
- package/src/styles/components/data-state.css +30 -0
- package/src/styles/components/dataframe.css +29 -35
- package/src/styles/components/experiment-data-viewer.css +4 -4
- package/src/styles/components/experiment-timeline.css +3 -30
- package/src/styles/components/form-builder.css +5 -5
- package/src/styles/components/lcms-sequence-table.css +16 -1
- package/src/styles/components/multi-select.css +1 -1
- package/src/styles/components/reagent-list.css +38 -31
- package/src/styles/components/sample-hierarchy-tree.css +5 -19
- package/src/styles/components/sample-selector.css +9 -2
- package/src/styles/components/schedule-calendar.css +8 -0
- package/src/styles/components/scientific-number.css +1 -1
- package/src/styles/components/secret-input.css +60 -0
- package/src/styles/components/well-plate.css +8 -0
- package/src/styles/index.css +9 -0
- package/src/styles/variables.css +29 -0
- package/src/templates/builderPresetControls.ts +1 -1
- package/src/templates/builderReadUtils.ts +1 -3
- package/src/templates/componentPlateHelpers.ts +3 -1
- package/src/templates/componentTargetResolvers.ts +1 -3
- package/src/templates/controlSchemaTargets.ts +1 -3
- package/src/templates/doseResponseAdapters.ts +2 -13
- package/src/templates/elisaAssayCollectionBuilder.ts +2 -2
- package/src/templates/plateMapBuilder.ts +2 -2
- package/src/templates/qpcrPlateBuilder.ts +1 -1
- package/src/templates/templateValidators.ts +1 -1
- package/src/templates/timeCourseBuilder.ts +2 -2
- package/src/templates/wellPlateScreenCollectionBuilder.ts +2 -2
- package/src/types/form-builder.ts +1 -0
- package/src/utils/color.ts +51 -0
- package/src/utils/errors.ts +29 -0
- package/src/utils/formModelSync.ts +1 -3
- package/src/utils/pluginIcon.ts +1 -3
- package/src/utils/pluginSecrets.ts +102 -0
- package/src/utils/records.ts +9 -0
- package/dist/EmptyState-p90quq8Z.js.map +0 -1
- package/dist/ExperimentPopover-tLwJ7XF4.js.map +0 -1
- package/dist/ExperimentSelectorModal-B6P2msT4.js.map +0 -1
- package/dist/SettingsModal-BW3Ar-uy.js.map +0 -1
- package/dist/components-DQt0tcpY.js.map +0 -1
- package/dist/composables/autoGroupConstants.d.ts +0 -1
- package/dist/composables/autoGroupGrouping.d.ts +0 -3
- package/dist/composables/useAutoGroupInputSources.d.ts +0 -32
- package/dist/composables-CkXDe7nm.js.map +0 -1
- package/dist/experiment-Y9Iz7KYi.js.map +0 -1
- package/dist/experiment-utils-CLnzPPq5.js.map +0 -1
- package/dist/templates/builderDefaults.d.ts +0 -1
- package/dist/templates/plateGeometry.d.ts +0 -4
- package/dist/templates-CovvRZ-9.js.map +0 -1
- package/dist/useApi-CDBq6z23.js.map +0 -1
- package/dist/useControlSchema-BhoJzWzx.js.map +0 -1
- package/dist/useDropdownState-DSb3BC3N.js.map +0 -1
- package/dist/useProtocolTemplates-DHwSveE6.js.map +0 -1
- package/src/__tests__/composables/useAutoGroupInputSources.test.ts +0 -127
- package/src/composables/autoGroupConstants.ts +0 -4
- package/src/composables/autoGroupGrouping.ts +0 -148
- package/src/composables/useAutoGroupInputSources.ts +0 -147
- package/src/templates/builderDefaults.ts +0 -11
- package/src/templates/plateGeometry.ts +0 -62
- /package/dist/__tests__/{composables/useAutoGroupInputSources.test.d.ts → components/DataStates.test.d.ts} +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Renders a segmented button group for single-option selection, with simple or solid variants. */
|
|
3
|
-
import { computed } from 'vue'
|
|
3
|
+
import { computed, type ComponentPublicInstance } from 'vue'
|
|
4
4
|
import type { SegmentedOption, SegmentedOptionInput, SegmentedControlVariant, SegmentedControlSize } from '../types'
|
|
5
5
|
import { normalizeOptionInput } from '../utils/options'
|
|
6
|
+
import { useRovingFocus } from '../composables/useRovingFocus'
|
|
6
7
|
|
|
7
8
|
interface Props {
|
|
8
9
|
modelValue: string | number
|
|
@@ -42,11 +43,55 @@ function handleSelect(option: SegmentedOption) {
|
|
|
42
43
|
emit('update:modelValue', option.value)
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// Roving tabindex over the radio group: only the checked option is tabbable and
|
|
47
|
+
// Left/Right (plus Home/End) move between the selectable options, matching BaseTabs.
|
|
48
|
+
const optionRefs = new Map<string, HTMLElement>()
|
|
49
|
+
|
|
50
|
+
function optionId(option: SegmentedOption): string {
|
|
51
|
+
return String(option.value)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function setOptionRef(id: string, el: Element | ComponentPublicInstance | null) {
|
|
55
|
+
if (el instanceof HTMLElement) {
|
|
56
|
+
optionRefs.set(id, el)
|
|
57
|
+
} else {
|
|
58
|
+
optionRefs.delete(id)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const selectableOptions = computed(() =>
|
|
63
|
+
normalizedOptions.value.filter(option => !isSelectionDisabled(option)),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
/** The one option carrying `tabindex="0"`: the checked one, else the first selectable. */
|
|
67
|
+
const tabbableId = computed(() => {
|
|
68
|
+
const checked = selectableOptions.value.find(option => option.value === props.modelValue)
|
|
69
|
+
const target = checked ?? selectableOptions.value[0]
|
|
70
|
+
return target ? optionId(target) : ''
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
function isTabbable(option: SegmentedOption): boolean {
|
|
74
|
+
return optionId(option) === tabbableId.value
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const { handleKeydown: handleArrowKeydown } = useRovingFocus({
|
|
78
|
+
ids: () => selectableOptions.value.map(optionId),
|
|
79
|
+
activeId: () => tabbableId.value,
|
|
80
|
+
activate: (id) => {
|
|
81
|
+
const option = selectableOptions.value.find(candidate => optionId(candidate) === id)
|
|
82
|
+
if (option) handleSelect(option)
|
|
83
|
+
},
|
|
84
|
+
getElement: (id) => optionRefs.get(id),
|
|
85
|
+
orientation: 'horizontal',
|
|
86
|
+
})
|
|
87
|
+
|
|
45
88
|
function handleKeydown(event: KeyboardEvent, option: SegmentedOption) {
|
|
46
89
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
47
90
|
event.preventDefault()
|
|
48
91
|
handleSelect(option)
|
|
92
|
+
return
|
|
49
93
|
}
|
|
94
|
+
handleArrowKeydown(event)
|
|
50
95
|
}
|
|
51
96
|
</script>
|
|
52
97
|
|
|
@@ -64,9 +109,11 @@ function handleKeydown(event: KeyboardEvent, option: SegmentedOption) {
|
|
|
64
109
|
<button
|
|
65
110
|
v-for="option in normalizedOptions"
|
|
66
111
|
:key="String(option.value)"
|
|
112
|
+
:ref="(el) => setOptionRef(String(option.value), el)"
|
|
67
113
|
type="button"
|
|
68
114
|
role="radio"
|
|
69
115
|
:aria-checked="modelValue === option.value"
|
|
116
|
+
:tabindex="isTabbable(option) ? 0 : -1"
|
|
70
117
|
:disabled="isSelectionDisabled(option)"
|
|
71
118
|
:class="[
|
|
72
119
|
'mint-segmented-control__option',
|
|
@@ -88,7 +135,3 @@ function handleKeydown(event: KeyboardEvent, option: SegmentedOption) {
|
|
|
88
135
|
</button>
|
|
89
136
|
</div>
|
|
90
137
|
</template>
|
|
91
|
-
|
|
92
|
-
<style>
|
|
93
|
-
@import '../styles/components/segmented-control.css';
|
|
94
|
-
</style>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import SequenceProgressBar from './SequenceProgressBar.vue'
|
|
3
|
+
import type { SequenceProgress } from '../types'
|
|
4
|
+
|
|
5
|
+
const running: SequenceProgress = {
|
|
6
|
+
sequence_name: 'HILIC-neg-2026-09-05',
|
|
7
|
+
current_sample: 37,
|
|
8
|
+
total_samples: 96,
|
|
9
|
+
elapsed_seconds: 4_260,
|
|
10
|
+
estimated_remaining_seconds: 6_780,
|
|
11
|
+
avg_sample_seconds: 115,
|
|
12
|
+
current_sample_name: 'PLASMA_QC_04',
|
|
13
|
+
current_method: 'HILIC_neg_12min',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const almostDone: SequenceProgress = {
|
|
17
|
+
...running,
|
|
18
|
+
current_sample: 94,
|
|
19
|
+
estimated_remaining_seconds: 230,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const finished: SequenceProgress = {
|
|
23
|
+
...running,
|
|
24
|
+
current_sample: 96,
|
|
25
|
+
estimated_remaining_seconds: 0,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const justStarted: SequenceProgress = {
|
|
29
|
+
sequence_name: 'RP-pos-batch-7',
|
|
30
|
+
current_sample: 1,
|
|
31
|
+
total_samples: 384,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function initState() {
|
|
35
|
+
return {
|
|
36
|
+
current: 37,
|
|
37
|
+
total: 96,
|
|
38
|
+
remaining: 6780,
|
|
39
|
+
compact: false,
|
|
40
|
+
showEta: true,
|
|
41
|
+
showRemaining: true,
|
|
42
|
+
label: 'samples',
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<Story title="Workflow/SequenceProgressBar">
|
|
49
|
+
<Variant title="Playground" :init-state="initState">
|
|
50
|
+
<template #default="{ state }">
|
|
51
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
52
|
+
<SequenceProgressBar
|
|
53
|
+
:progress="{
|
|
54
|
+
sequence_name: 'HILIC-neg-2026-09-05',
|
|
55
|
+
current_sample: state.current,
|
|
56
|
+
total_samples: state.total,
|
|
57
|
+
estimated_remaining_seconds: state.remaining,
|
|
58
|
+
}"
|
|
59
|
+
:compact="state.compact"
|
|
60
|
+
:label="state.label"
|
|
61
|
+
:show-eta="state.showEta"
|
|
62
|
+
:show-remaining="state.showRemaining"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<template #controls="{ state }">
|
|
68
|
+
<HstNumber v-model="state.current" title="Current sample" />
|
|
69
|
+
<HstNumber v-model="state.total" title="Total samples" />
|
|
70
|
+
<HstNumber v-model="state.remaining" title="Remaining (s)" />
|
|
71
|
+
<HstText v-model="state.label" title="Unit label" />
|
|
72
|
+
<HstCheckbox v-model="state.compact" title="Compact" />
|
|
73
|
+
<HstCheckbox v-model="state.showEta" title="Show ETA" />
|
|
74
|
+
<HstCheckbox v-model="state.showRemaining" title="Show remaining" />
|
|
75
|
+
</template>
|
|
76
|
+
</Variant>
|
|
77
|
+
|
|
78
|
+
<Variant title="Mid-run">
|
|
79
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
80
|
+
<SequenceProgressBar :progress="running" />
|
|
81
|
+
</div>
|
|
82
|
+
</Variant>
|
|
83
|
+
|
|
84
|
+
<Variant title="Compact (no footer)">
|
|
85
|
+
<!-- Compact drops the remaining/ETA row for dense instrument tables. -->
|
|
86
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
87
|
+
<SequenceProgressBar :progress="running" compact />
|
|
88
|
+
</div>
|
|
89
|
+
</Variant>
|
|
90
|
+
|
|
91
|
+
<Variant title="Nearly finished">
|
|
92
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
93
|
+
<SequenceProgressBar :progress="almostDone" />
|
|
94
|
+
</div>
|
|
95
|
+
</Variant>
|
|
96
|
+
|
|
97
|
+
<Variant title="Complete">
|
|
98
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
99
|
+
<SequenceProgressBar :progress="finished" />
|
|
100
|
+
</div>
|
|
101
|
+
</Variant>
|
|
102
|
+
|
|
103
|
+
<Variant title="Queued — no timing yet">
|
|
104
|
+
<!-- Without timing fields the footer collapses on its own. -->
|
|
105
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
106
|
+
<SequenceProgressBar :progress="justStarted" label="injections" />
|
|
107
|
+
</div>
|
|
108
|
+
</Variant>
|
|
109
|
+
</Story>
|
|
110
|
+
</template>
|
|
@@ -144,8 +144,13 @@ function onDrop(event: DragEvent) {
|
|
|
144
144
|
// CSV action menu — reuses the SDK's dropdown state (outside-click / Escape
|
|
145
145
|
// close) but renders a compact action menu in the .fr-* styling, since the
|
|
146
146
|
// items are actions (upload / download), not a persistent value selection.
|
|
147
|
-
const {
|
|
148
|
-
|
|
147
|
+
const {
|
|
148
|
+
isOpen: csvMenuOpen,
|
|
149
|
+
rootRef: csvMenuRoot,
|
|
150
|
+
triggerRef: csvMenuTrigger,
|
|
151
|
+
close: closeCsvMenu,
|
|
152
|
+
toggle: toggleCsvMenu,
|
|
153
|
+
} = useDropdownState()
|
|
149
154
|
|
|
150
155
|
function chooseUpload() {
|
|
151
156
|
closeCsvMenu()
|
|
@@ -294,6 +299,7 @@ function onApply() {
|
|
|
294
299
|
<div class="sg-head__spacer" />
|
|
295
300
|
<div ref="csvMenuRoot" class="fr-csv">
|
|
296
301
|
<button
|
|
302
|
+
ref="csvMenuTrigger"
|
|
297
303
|
type="button"
|
|
298
304
|
class="fr-upload"
|
|
299
305
|
aria-haspopup="menu"
|
|
@@ -493,7 +499,3 @@ function onApply() {
|
|
|
493
499
|
</div>
|
|
494
500
|
</div>
|
|
495
501
|
</template>
|
|
496
|
-
|
|
497
|
-
<style>
|
|
498
|
-
@import '../styles/components/smart-group.css';
|
|
499
|
-
</style>
|
|
@@ -44,11 +44,13 @@ const emit = defineEmits<{
|
|
|
44
44
|
}>()
|
|
45
45
|
|
|
46
46
|
const inputValue = ref('')
|
|
47
|
-
const inputRef = ref<HTMLInputElement>()
|
|
48
47
|
const highlightedIndex = ref(-1)
|
|
48
|
+
// The text input doubles as the dropdown trigger, so focus restoration is the
|
|
49
|
+
// composable's `triggerRef` rather than a component-local refocus helper.
|
|
49
50
|
const {
|
|
50
51
|
isOpen: dropdownOpen,
|
|
51
52
|
rootRef,
|
|
53
|
+
triggerRef: inputRef,
|
|
52
54
|
open: openDropdown,
|
|
53
55
|
close: closeDropdown,
|
|
54
56
|
} = useDropdownState({
|
|
@@ -129,7 +131,7 @@ function handleKeydown(event: KeyboardEvent) {
|
|
|
129
131
|
return
|
|
130
132
|
}
|
|
131
133
|
if (event.key === 'Escape') {
|
|
132
|
-
closeDropdown()
|
|
134
|
+
closeDropdown(true)
|
|
133
135
|
return
|
|
134
136
|
}
|
|
135
137
|
if (event.key === 'Backspace' && !inputValue.value && props.modelValue.length > 0) {
|
|
@@ -274,7 +276,3 @@ function onInputEvent() {
|
|
|
274
276
|
</div>
|
|
275
277
|
</div>
|
|
276
278
|
</template>
|
|
277
|
-
|
|
278
|
-
<style>
|
|
279
|
-
@import '../styles/components/tags-input.css';
|
|
280
|
-
</style>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Input that opens a scrollable time-slot dropdown in 12h or 24h format, with min/max range. */
|
|
3
|
-
import { ref, computed, watch, nextTick } from 'vue'
|
|
3
|
+
import { ref, computed, watch, nextTick, useId } from 'vue'
|
|
4
4
|
import { useDropdownState } from '../composables/useDropdownState'
|
|
5
5
|
import {
|
|
6
6
|
compareTime,
|
|
@@ -38,7 +38,15 @@ const emit = defineEmits<{
|
|
|
38
38
|
|
|
39
39
|
const listRef = ref<HTMLUListElement | null>(null)
|
|
40
40
|
const highlightedIndex = ref(-1)
|
|
41
|
-
const
|
|
41
|
+
const listboxId = `mint-time-picker-listbox-${useId().replace(/:/g, '')}`
|
|
42
|
+
const { isOpen, rootRef, triggerRef, open, close, toggle } = useDropdownState()
|
|
43
|
+
|
|
44
|
+
/** Id of the highlighted slot, published to the input as `aria-activedescendant`. */
|
|
45
|
+
const activeSlotId = computed(() =>
|
|
46
|
+
isOpen.value && highlightedIndex.value >= 0
|
|
47
|
+
? `${listboxId}-option-${highlightedIndex.value}`
|
|
48
|
+
: undefined,
|
|
49
|
+
)
|
|
42
50
|
|
|
43
51
|
const slots = computed(() => {
|
|
44
52
|
const start = props.min ?? '00:00'
|
|
@@ -134,9 +142,29 @@ function handleKeydown(event: KeyboardEvent) {
|
|
|
134
142
|
}
|
|
135
143
|
break
|
|
136
144
|
}
|
|
145
|
+
case 'Home': {
|
|
146
|
+
event.preventDefault()
|
|
147
|
+
const first = slots.value.findIndex(slot => !isSlotDisabled(slot))
|
|
148
|
+
if (first >= 0) {
|
|
149
|
+
highlightedIndex.value = first
|
|
150
|
+
scrollToHighlighted()
|
|
151
|
+
}
|
|
152
|
+
break
|
|
153
|
+
}
|
|
154
|
+
case 'End': {
|
|
155
|
+
event.preventDefault()
|
|
156
|
+
for (let index = slots.value.length - 1; index >= 0; index--) {
|
|
157
|
+
if (!isSlotDisabled(slots.value[index])) {
|
|
158
|
+
highlightedIndex.value = index
|
|
159
|
+
scrollToHighlighted()
|
|
160
|
+
break
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
break
|
|
164
|
+
}
|
|
137
165
|
case 'Escape': {
|
|
138
166
|
event.preventDefault()
|
|
139
|
-
close()
|
|
167
|
+
close(true)
|
|
140
168
|
break
|
|
141
169
|
}
|
|
142
170
|
}
|
|
@@ -172,6 +200,7 @@ watch(isOpen, (open) => {
|
|
|
172
200
|
<div ref="rootRef" class="mint-time-picker" @keydown="handleKeydown">
|
|
173
201
|
<div class="mint-time-picker__input-wrapper">
|
|
174
202
|
<input
|
|
203
|
+
ref="triggerRef"
|
|
175
204
|
type="text"
|
|
176
205
|
readonly
|
|
177
206
|
:value="displayValue"
|
|
@@ -186,6 +215,8 @@ watch(isOpen, (open) => {
|
|
|
186
215
|
role="combobox"
|
|
187
216
|
aria-haspopup="listbox"
|
|
188
217
|
:aria-expanded="isOpen"
|
|
218
|
+
:aria-controls="isOpen ? listboxId : undefined"
|
|
219
|
+
:aria-activedescendant="activeSlotId"
|
|
189
220
|
@click="toggleDropdown"
|
|
190
221
|
/>
|
|
191
222
|
<button
|
|
@@ -219,6 +250,7 @@ watch(isOpen, (open) => {
|
|
|
219
250
|
class="mint-time-picker__dropdown"
|
|
220
251
|
>
|
|
221
252
|
<ul
|
|
253
|
+
:id="listboxId"
|
|
222
254
|
ref="listRef"
|
|
223
255
|
class="mint-time-picker__list"
|
|
224
256
|
role="listbox"
|
|
@@ -226,6 +258,7 @@ watch(isOpen, (open) => {
|
|
|
226
258
|
>
|
|
227
259
|
<li
|
|
228
260
|
v-for="(slot, index) in slots"
|
|
261
|
+
:id="`${listboxId}-option-${index}`"
|
|
229
262
|
:key="slot"
|
|
230
263
|
role="option"
|
|
231
264
|
:aria-selected="slot === modelValue"
|
|
@@ -245,7 +278,3 @@ watch(isOpen, (open) => {
|
|
|
245
278
|
</Transition>
|
|
246
279
|
</div>
|
|
247
280
|
</template>
|
|
248
|
-
|
|
249
|
-
<style>
|
|
250
|
-
@import '../styles/components/time-picker.css';
|
|
251
|
-
</style>
|
|
@@ -55,15 +55,15 @@ const positions: ('top' | 'bottom' | 'left' | 'right')[] = ['top', 'bottom', 'le
|
|
|
55
55
|
<p style="margin: 0;">
|
|
56
56
|
The experiment used
|
|
57
57
|
<Tooltip text="Dimethyl sulfoxide - a polar aprotic solvent" position="top" :delay="100">
|
|
58
|
-
<span style="text-decoration: underline dotted; cursor: help; color: var(--
|
|
58
|
+
<span style="text-decoration: underline dotted; cursor: help; color: var(--color-primary);">DMSO</span>
|
|
59
59
|
</Tooltip>
|
|
60
60
|
as the vehicle control. Samples were incubated at
|
|
61
61
|
<Tooltip text="Standard mammalian cell culture temperature" position="top" :delay="100">
|
|
62
|
-
<span style="text-decoration: underline dotted; cursor: help; color: var(--
|
|
62
|
+
<span style="text-decoration: underline dotted; cursor: help; color: var(--color-primary);">37 C</span>
|
|
63
63
|
</Tooltip>
|
|
64
64
|
for 24 hours before
|
|
65
65
|
<Tooltip text="3-(4,5-dimethylthiazol-2-yl)-2,5-diphenyltetrazolium bromide" position="top" :delay="100">
|
|
66
|
-
<span style="text-decoration: underline dotted; cursor: help; color: var(--
|
|
66
|
+
<span style="text-decoration: underline dotted; cursor: help; color: var(--color-primary);">MTT</span>
|
|
67
67
|
</Tooltip>
|
|
68
68
|
assay readout.
|
|
69
69
|
</p>
|
|
@@ -37,8 +37,67 @@ export const WELL_PLATE_SIZE_CONFIGS: Record<WellPlateSize, WellPlateSizeConfig>
|
|
|
37
37
|
fill: { cellWidth: '100%', cellHeight: '32px', headerWidth: '32px', headerHeight: '24px', fontSize: '0.75rem', gap: '2px' },
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Spreadsheet-style row label for a zero-based row index: A…Z, then AA, AB, …
|
|
42
|
+
*
|
|
43
|
+
* This is the single row-label generator for the SDK — plate grids, rack
|
|
44
|
+
* grids, well-id parsing and the template builders all go through it.
|
|
45
|
+
*/
|
|
46
|
+
export function rowLabel(index: number): string {
|
|
47
|
+
let label = ''
|
|
48
|
+
let current = index
|
|
49
|
+
for (;;) {
|
|
50
|
+
label = String.fromCharCode(65 + (current % 26)) + label
|
|
51
|
+
current = Math.floor(current / 26) - 1
|
|
52
|
+
if (current < 0) return label
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Inverse of {@link rowLabel}: 'A' → 0, 'Z' → 25, 'AA' → 26. */
|
|
57
|
+
export function rowIndex(label: string): number {
|
|
58
|
+
return [...label].reduce((value, char) => value * 26 + char.charCodeAt(0) - 64, 0) - 1
|
|
59
|
+
}
|
|
60
|
+
|
|
40
61
|
export function createPlateRowLabels(config: WellPlateConfig): string[] {
|
|
41
|
-
return Array.from({ length: config.rows }, (_, index) =>
|
|
62
|
+
return Array.from({ length: config.rows }, (_, index) => rowLabel(index))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function plateConfigOrThrow(format: number): WellPlateConfig {
|
|
66
|
+
const config = PLATE_CONFIGS[format as WellPlateFormat]
|
|
67
|
+
if (!config) throw new Error(`Unsupported plate format '${format}'.`)
|
|
68
|
+
return config
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Every well id of a plate, row-major (A1, A2, … B1, …). */
|
|
72
|
+
export function wellIdsForFormat(format: number): string[] {
|
|
73
|
+
const { rows, cols } = plateConfigOrThrow(format)
|
|
74
|
+
return Array.from({ length: rows }, (_, row) =>
|
|
75
|
+
Array.from({ length: cols }, (_col, col) => `${rowLabel(row)}${col + 1}`)
|
|
76
|
+
).flat()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Throw unless `wellId` names a well that exists on a `format`-well plate. */
|
|
80
|
+
export function assertWellInFormat(wellId: string, format: number): void {
|
|
81
|
+
const { rows, cols } = plateConfigOrThrow(format)
|
|
82
|
+
const match = /^([A-Z]+)([1-9][0-9]*)$/.exec(wellId.toUpperCase())
|
|
83
|
+
if (!match) {
|
|
84
|
+
throw new Error(`Invalid well id '${wellId}'.`)
|
|
85
|
+
}
|
|
86
|
+
const row = rowIndex(match[1])
|
|
87
|
+
const col = Number(match[2]) - 1
|
|
88
|
+
if (row < 0 || col < 0 || row >= rows || col >= cols) {
|
|
89
|
+
throw new Error(`Well '${wellId}' is outside a ${format}-well plate.`)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Coerce an unknown value to a supported plate format, else `fallback`. */
|
|
94
|
+
export function readPlateFormat(value: unknown, fallback: WellPlateFormat): WellPlateFormat {
|
|
95
|
+
const parsed = typeof value === 'number'
|
|
96
|
+
? value
|
|
97
|
+
: typeof value === 'string'
|
|
98
|
+
? Number(value)
|
|
99
|
+
: Number.NaN
|
|
100
|
+
return parsed in PLATE_CONFIGS ? (parsed as WellPlateFormat) : fallback
|
|
42
101
|
}
|
|
43
102
|
|
|
44
103
|
export function createPlateColumnLabels(config: WellPlateConfig): number[] {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type WellPlateCoordinate,
|
|
12
12
|
} from './WellPlate.geometry'
|
|
13
13
|
import { parseDroppedSampleTransfer } from './WellPlate.sampleDrop'
|
|
14
|
+
import { useReorderAnnouncer } from '../composables/useListReorder'
|
|
14
15
|
|
|
15
16
|
export interface WellPlateInteractionOptions {
|
|
16
17
|
modelValue: () => string[]
|
|
@@ -64,6 +65,7 @@ export function useWellPlateInteraction(options: WellPlateInteractionOptions) {
|
|
|
64
65
|
const hoveredWell = ref<string | null>(null)
|
|
65
66
|
const editingWellId = ref<string | null>(null)
|
|
66
67
|
const editPopupPosition = ref({ x: 0, y: 0 })
|
|
68
|
+
const { announcement, announce, announcerStyle } = useReorderAnnouncer()
|
|
67
69
|
|
|
68
70
|
const selectedWellSet = computed(() => new Set(options.modelValue()))
|
|
69
71
|
|
|
@@ -215,6 +217,45 @@ export function useWellPlateInteraction(options: WellPlateInteractionOptions) {
|
|
|
215
217
|
dragTargetWell.value = null
|
|
216
218
|
}
|
|
217
219
|
|
|
220
|
+
// Keyboard alternative to the well-to-well drag. The plate is a 2D grid, not a
|
|
221
|
+
// list, so it cannot use `useListReorder`'s index maths: instead Alt+Enter (or
|
|
222
|
+
// Alt+Space) picks a filled well up, the same chord on another well drops it
|
|
223
|
+
// there, and Escape cancels. Every step is announced politely.
|
|
224
|
+
function handleWellMoveKeydown(well: Well, event: KeyboardEvent) {
|
|
225
|
+
if (options.disabled() || options.readonly() || options.selectionMode() !== 'drag') return
|
|
226
|
+
|
|
227
|
+
if (event.key === 'Escape' && dragSourceWell.value) {
|
|
228
|
+
event.preventDefault()
|
|
229
|
+
announce(`Move cancelled. ${dragSourceWell.value} stays in place.`)
|
|
230
|
+
dragSourceWell.value = null
|
|
231
|
+
dragTargetWell.value = null
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!event.altKey || (event.key !== 'Enter' && event.key !== ' ')) return
|
|
236
|
+
event.preventDefault()
|
|
237
|
+
|
|
238
|
+
const source = dragSourceWell.value
|
|
239
|
+
if (!source) {
|
|
240
|
+
if (!well.sampleType) {
|
|
241
|
+
announce(`Well ${well.id} is empty and cannot be moved.`)
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
dragSourceWell.value = well.id
|
|
245
|
+
announce(`Well ${well.id} picked up. Move to another well and press Alt and Enter to drop it.`)
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (source !== well.id) {
|
|
250
|
+
options.emitWellMove(source, well.id)
|
|
251
|
+
announce(`Well ${source} moved to ${well.id}.`)
|
|
252
|
+
} else {
|
|
253
|
+
announce(`Move cancelled. ${source} stays in place.`)
|
|
254
|
+
}
|
|
255
|
+
dragSourceWell.value = null
|
|
256
|
+
dragTargetWell.value = null
|
|
257
|
+
}
|
|
258
|
+
|
|
218
259
|
function parseDroppedSample(event: DragEvent, wellId: string): WellSampleDropData | null {
|
|
219
260
|
const customParser = options.sampleDropParser()
|
|
220
261
|
if (customParser) return customParser(event, { wellId, event }) ?? null
|
|
@@ -267,6 +308,9 @@ export function useWellPlateInteraction(options: WellPlateInteractionOptions) {
|
|
|
267
308
|
handleDragLeave,
|
|
268
309
|
handleDrop,
|
|
269
310
|
handleDragEnd,
|
|
311
|
+
handleWellMoveKeydown,
|
|
270
312
|
handleKeyDown,
|
|
313
|
+
moveAnnouncement: announcement,
|
|
314
|
+
moveAnnouncerStyle: announcerStyle,
|
|
271
315
|
}
|
|
272
316
|
}
|
|
@@ -121,6 +121,8 @@ const doseWells = generateDoseWells()
|
|
|
121
121
|
|
|
122
122
|
function initState() {
|
|
123
123
|
return {
|
|
124
|
+
loading: false,
|
|
125
|
+
error: '',
|
|
124
126
|
format: 96 as WellPlateFormat,
|
|
125
127
|
selectionMode: 'multiple' as WellPlateSelectionMode,
|
|
126
128
|
size: 'md' as WellPlateSize,
|
|
@@ -141,6 +143,8 @@ function initState() {
|
|
|
141
143
|
<template #default="{ state }">
|
|
142
144
|
<div style="padding: 2rem;">
|
|
143
145
|
<WellPlate
|
|
146
|
+
:loading="state.loading"
|
|
147
|
+
:error="state.error || null"
|
|
144
148
|
v-model="selectedWells"
|
|
145
149
|
:format="state.format"
|
|
146
150
|
:wells="generateMockWells(state.format)"
|
|
@@ -161,6 +165,10 @@ function initState() {
|
|
|
161
165
|
</template>
|
|
162
166
|
|
|
163
167
|
<template #controls="{ state }">
|
|
168
|
+
|
|
169
|
+
<HstCheckbox v-model="state.loading" title="Loading" />
|
|
170
|
+
|
|
171
|
+
<HstText v-model="state.error" title="Error Message" />
|
|
164
172
|
<HstSelect
|
|
165
173
|
v-model="state.format"
|
|
166
174
|
title="Format"
|