@morscherlab/mint-sdk 1.2.0-beta.8 → 1.2.0
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 +60 -0
- package/dist/{ExperimentPopover-CfOdh5Rm.js → ExperimentPopover-CJdNlCZb.js} +24 -15
- package/dist/ExperimentPopover-CJdNlCZb.js.map +1 -0
- package/dist/{ExperimentSelectorModal-DvIagrxY.js → ExperimentSelectorModal-DCXmwKNS.js} +5 -5
- package/dist/ExperimentSelectorModal-DCXmwKNS.js.map +1 -0
- package/dist/{SettingsModal-W3TYfnP9.js → SettingsModal-BYR20I4c.js} +5 -5
- package/dist/SettingsModal-BYR20I4c.js.map +1 -0
- package/dist/{EmptyState-NBqf2_8z.js → Skeleton-BAF3CKY7.js} +111 -93
- package/dist/Skeleton-BAF3CKY7.js.map +1 -0
- package/dist/__tests__/components/DataStates.test.d.ts +1 -0
- package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
- package/dist/__tests__/components/FileBrowserModal.test.d.ts +1 -0
- package/dist/__tests__/components/FilePicker.state.test.d.ts +1 -0
- package/dist/__tests__/components/FilePicker.test.d.ts +1 -0
- package/dist/__tests__/components/PlotlyChart.test.d.ts +1 -0
- package/dist/__tests__/components/iconButtonIconSizing.test.d.ts +1 -0
- package/dist/__tests__/composables/useFileBrowser.test.d.ts +1 -0
- package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
- package/dist/__tests__/composables/usePlatformFilePickerAdapter.test.d.ts +1 -0
- package/dist/__tests__/styles/cssTokens.test.d.ts +1 -0
- package/dist/apiErrors.d.ts +26 -0
- package/dist/components/ActionMenu.vue.d.ts +1 -1
- package/dist/components/AppPluginSwitcher.vue.d.ts +2 -0
- package/dist/components/AuditTrail.vue.d.ts +13 -0
- package/dist/components/BaseInput.vue.d.ts +1 -1
- package/dist/components/BaseSelect.vue.d.ts +1 -1
- package/dist/components/BaseTextarea.vue.d.ts +1 -1
- package/dist/components/BatchProgressList.vue.d.ts +17 -0
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +2 -2
- package/dist/components/ConcentrationInput.vue.d.ts +1 -1
- package/dist/components/DataFrame.vue.d.ts +14 -2
- package/dist/components/DatePicker.vue.d.ts +2 -2
- package/dist/components/DateTimePicker.vue.d.ts +3 -1
- 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/FileBrowserModal.vue.d.ts +99 -0
- package/dist/components/FilePicker.vue.d.ts +32 -0
- package/dist/components/FileUploader.vue.d.ts +5 -2
- package/dist/components/FormulaInput.vue.d.ts +1 -1
- package/dist/components/GeneratedPlotlyResult.vue.d.ts +1 -3
- package/dist/components/LcmsSequenceTable.vue.d.ts +29 -1
- package/dist/components/MobileSupportGate.vue.d.ts +1 -1
- package/dist/components/MoleculeInput.vue.d.ts +1 -1
- package/dist/components/NumberInput.vue.d.ts +1 -1
- package/dist/components/PlotlyChart.vue.d.ts +59 -0
- 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/ResourceCard.vue.d.ts +1 -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 +1 -1
- package/dist/components/SequenceInput.vue.d.ts +1 -1
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -0
- package/dist/components/StatusIndicator.vue.d.ts +1 -1
- package/dist/components/TagsInput.vue.d.ts +1 -1
- package/dist/components/TimePicker.vue.d.ts +2 -1
- package/dist/components/TimeRangeInput.vue.d.ts +1 -1
- package/dist/components/UnitInput.vue.d.ts +1 -1
- 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 +30 -1
- package/dist/components/fileBrowserPresentation.d.ts +10 -0
- package/dist/components/index.d.ts +4 -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/FileBrowserDialogInternal.vue.d.ts +34 -0
- package/dist/components/internal/FileBrowserListingInternal.vue.d.ts +25 -0
- package/dist/components/internal/FileBrowserToolbarInternal.vue.d.ts +26 -0
- package/dist/components/internal/FilePickerPreviewInternal.vue.d.ts +18 -0
- package/dist/components/internal/FilePickerTreeInternal.vue.d.ts +31 -0
- package/dist/{components-B80gV0P6.js → components-DIum5hkx.js} +5415 -2753
- package/dist/components-DIum5hkx.js.map +1 -0
- package/dist/composables/controlSchemaTypes.d.ts +34 -0
- package/dist/composables/filePicker/localFiles.d.ts +3 -0
- package/dist/composables/filePicker/usePickerNavigation.d.ts +96 -0
- package/dist/composables/filePicker/usePickerSelection.d.ts +31 -0
- package/dist/composables/filePicker/validation.d.ts +5 -0
- package/dist/composables/index.d.ts +3 -0
- package/dist/composables/index.js +10 -10
- package/dist/composables/useApi.d.ts +14 -7
- package/dist/composables/useDropdownState.d.ts +18 -2
- package/dist/composables/useEventStream.d.ts +9 -0
- package/dist/composables/useFileBrowser.d.ts +55 -0
- package/dist/composables/useFilePicker.d.ts +189 -0
- package/dist/composables/useListReorder.d.ts +90 -0
- package/dist/composables/useMenuKeyboard.d.ts +2 -2
- package/dist/composables/usePlatformFilePickerAdapter.d.ts +11 -0
- package/dist/composables/usePluginConfig.d.ts +3 -1
- package/dist/composables/useRequestSyncState.d.ts +42 -2
- package/dist/composables/useScheduleDrag.d.ts +1 -0
- package/dist/{composables-C8mmD5ta.js → composables-mFXsTA4g.js} +307 -74
- package/dist/composables-mFXsTA4g.js.map +1 -0
- package/dist/{useApi-CDBq6z23.js → errors-BLuqMee5.js} +178 -16
- package/dist/errors-BLuqMee5.js.map +1 -0
- package/dist/{experiment-Y9Iz7KYi.js → experiment-CVDRAnqp.js} +3 -8
- package/dist/experiment-CVDRAnqp.js.map +1 -0
- package/dist/{experiment-utils-DV3DrvE_.js → experiment-utils-CV2Wza3z.js} +1 -1
- package/dist/experiment-utils-CV2Wza3z.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 +15162 -32475
- package/dist/templates/index.js +2 -4
- package/dist/{templates-CK72aKO1.js → templates-CmGfmBRA.js} +291 -85
- package/dist/templates-CmGfmBRA.js.map +1 -0
- package/dist/types/fileBrowserTypes.d.ts +77 -0
- package/dist/types/filePicker.d.ts +77 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/{useControlSchema-_OVhavKq.js → useControlSchema-DldpxNOP.js} +234 -1121
- package/dist/useControlSchema-DldpxNOP.js.map +1 -0
- package/dist/{useDropdownState-D5G4UvWd.js → useDropdownState-C7hRiVQf.js} +22 -6
- package/dist/useDropdownState-C7hRiVQf.js.map +1 -0
- package/dist/{useExperimentSelector-DSEckf_g.js → useExperimentSelector-CEcIaueJ.js} +4 -4
- package/dist/{useExperimentSelector-DSEckf_g.js.map → useExperimentSelector-CEcIaueJ.js.map} +1 -1
- package/dist/{useFormBuilder-Bd714C7y.js → useFormBuilder-z5L_dUIy.js} +2 -2
- package/dist/{useFormBuilder-Bd714C7y.js.map → useFormBuilder-z5L_dUIy.js.map} +1 -1
- package/dist/utils/color.d.ts +13 -0
- package/dist/utils/errors.d.ts +9 -0
- package/dist/{useProtocolTemplates-Dq5Pzwyd.js → validation-uwoJGEb6.js} +231 -158
- package/dist/validation-uwoJGEb6.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/apiErrors.test.ts +81 -0
- package/src/__tests__/components/ActionMenu.test.ts +94 -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/ExperimentPopover.test.ts +30 -0
- package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
- package/src/__tests__/components/FileBrowserModal.test.ts +460 -0
- package/src/__tests__/components/FilePicker.state.test.ts +287 -0
- package/src/__tests__/components/FilePicker.test.ts +487 -0
- package/src/__tests__/components/GeneratedPlotlyResult.test.ts +52 -5
- package/src/__tests__/components/GroupAssigner.test.ts +53 -0
- package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
- package/src/__tests__/components/PlotlyChart.test.ts +305 -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/SegmentedControl.test.ts +107 -0
- package/src/__tests__/components/TimePicker.test.ts +61 -0
- package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
- package/src/__tests__/components/iconButtonIconSizing.test.ts +18 -0
- package/src/__tests__/composables/useApi.test.ts +92 -9
- package/src/__tests__/composables/useDropdownState.test.ts +58 -1
- package/src/__tests__/composables/useFileBrowser.test.ts +305 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +7 -2
- package/src/__tests__/composables/useListReorder.test.ts +145 -0
- package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +67 -0
- package/src/__tests__/composables/usePluginClient.test.ts +3 -0
- package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
- 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/apiErrors.ts +85 -0
- package/src/components/ActionMenu.vue +45 -88
- package/src/components/AlertBox.vue +0 -4
- package/src/components/AppAvatarMenu.vue +2 -14
- 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 +0 -4
- 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 +0 -4
- 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/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/ConcentrationInput.vue +0 -4
- package/src/components/ConfirmDialog.vue +0 -4
- 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/DropdownButton.vue +2 -14
- 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 +12 -7
- 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/FileBrowserModal.story.vue +203 -0
- package/src/components/FileBrowserModal.vue +384 -0
- package/src/components/FilePicker.story.vue +44 -0
- package/src/components/FilePicker.vue +284 -0
- package/src/components/FileUploader.vue +32 -18
- 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 +34 -69
- 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 -9
- 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.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/PlotlyChart.story.vue +61 -0
- package/src/components/PlotlyChart.vue +233 -0
- package/src/components/PluginIcon.vue +0 -4
- 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/SectionCard.vue +0 -4
- package/src/components/SegmentedControl.vue +50 -7
- 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/fileBrowserPresentation.ts +39 -0
- package/src/components/index.ts +5 -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/FileBrowserDialogInternal.vue +75 -0
- package/src/components/internal/FileBrowserListingInternal.vue +197 -0
- package/src/components/internal/FileBrowserToolbarInternal.vue +136 -0
- package/src/components/internal/FilePickerPreviewInternal.vue +100 -0
- package/src/components/internal/FilePickerTreeInternal.vue +238 -0
- package/src/components/internal/FormSectionRenderer.vue +0 -4
- package/src/components/internal/WellEditPopupInternal.vue +0 -4
- package/src/composables/controlSchemaTypes.ts +34 -0
- package/src/composables/filePicker/localFiles.ts +58 -0
- package/src/composables/filePicker/usePickerNavigation.ts +220 -0
- package/src/composables/filePicker/usePickerSelection.ts +259 -0
- package/src/composables/filePicker/validation.ts +41 -0
- package/src/composables/index.ts +17 -0
- package/src/composables/useApi.ts +57 -13
- package/src/composables/useDropdownState.ts +35 -5
- package/src/composables/useEventStream.ts +14 -2
- package/src/composables/useFileBrowser.ts +314 -0
- package/src/composables/useFileImport.ts +2 -7
- package/src/composables/useFilePicker.ts +366 -0
- package/src/composables/useGeneratedAnalysis.ts +18 -26
- package/src/composables/useListReorder.story.vue +143 -0
- package/src/composables/useListReorder.ts +242 -0
- package/src/composables/useMenuKeyboard.ts +2 -2
- package/src/composables/useOptimisticMutation.ts +1 -15
- package/src/composables/usePlatformFilePickerAdapter.ts +104 -0
- package/src/composables/usePluginClient.ts +3 -0
- package/src/composables/usePluginConfig.ts +3 -1
- package/src/composables/useRackEditor.ts +2 -11
- package/src/composables/useRequestSyncState.ts +98 -28
- package/src/composables/useResourceCrud.ts +1 -15
- package/src/composables/useScheduleDrag.ts +37 -0
- package/src/composables/useWellPainting.ts +5 -29
- package/src/composables/useWellPlateEditor.ts +1 -11
- package/src/index.ts +3 -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/file-browser-modal.css +489 -0
- package/src/styles/components/file-picker.css +502 -0
- package/src/styles/components/form-builder.css +5 -5
- package/src/styles/components/icon-button.css +9 -0
- 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/well-plate.css +8 -0
- package/src/styles/index.css +11 -0
- package/src/styles/variables.css +29 -0
- package/src/templates/builderPresetControls.ts +1 -1
- package/src/templates/componentPlateHelpers.ts +3 -1
- 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/fileBrowserTypes.ts +85 -0
- package/src/types/filePicker.ts +72 -0
- package/src/types/index.ts +14 -0
- package/src/utils/color.ts +51 -0
- package/src/utils/errors.ts +29 -0
- package/dist/EmptyState-NBqf2_8z.js.map +0 -1
- package/dist/ExperimentPopover-CfOdh5Rm.js.map +0 -1
- package/dist/ExperimentSelectorModal-DvIagrxY.js.map +0 -1
- package/dist/SettingsModal-W3TYfnP9.js.map +0 -1
- package/dist/components-B80gV0P6.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-C8mmD5ta.js.map +0 -1
- package/dist/experiment-Y9Iz7KYi.js.map +0 -1
- package/dist/experiment-utils-DV3DrvE_.js.map +0 -1
- package/dist/templates/builderDefaults.d.ts +0 -1
- package/dist/templates/plateGeometry.d.ts +0 -4
- package/dist/templates-CK72aKO1.js.map +0 -1
- package/dist/useApi-CDBq6z23.js.map +0 -1
- package/dist/useControlSchema-_OVhavKq.js.map +0 -1
- package/dist/useDropdownState-D5G4UvWd.js.map +0 -1
- package/dist/useProtocolTemplates-Dq5Pzwyd.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 → apiErrors.test.d.ts} +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Sortable, searchable table of experiment reagents showing lot number, expiry, storage conditions, and stock level with low-stock warnings. */
|
|
3
|
-
import { ref, computed } from 'vue'
|
|
3
|
+
import { ref, computed, type ComponentPublicInstance } from 'vue'
|
|
4
4
|
import type { ReagentColumn, Reagent } from '../types'
|
|
5
|
+
import { useListReorder } from '../composables/useListReorder'
|
|
5
6
|
import { useSortedItems } from '../composables/useSortedItems'
|
|
6
7
|
import { useTextSearch } from '../composables/useTextSearch'
|
|
7
8
|
import {
|
|
@@ -14,6 +15,13 @@ import {
|
|
|
14
15
|
isReagentExpired as isExpired,
|
|
15
16
|
isReagentExpiringSoon as isExpiringSoon,
|
|
16
17
|
} from './ReagentList.presentation'
|
|
18
|
+
import DataStateInternal from './internal/DataStateInternal.vue'
|
|
19
|
+
|
|
20
|
+
/** Beaker glyph kept from the pre-1.2 hand-rolled empty state. */
|
|
21
|
+
const EMPTY_ICON_PATH
|
|
22
|
+
= 'M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0'
|
|
23
|
+
+ ' 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26'
|
|
24
|
+
+ ' 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z'
|
|
17
25
|
|
|
18
26
|
interface Props {
|
|
19
27
|
modelValue?: Reagent[]
|
|
@@ -23,6 +31,13 @@ interface Props {
|
|
|
23
31
|
columns?: ReagentColumn[]
|
|
24
32
|
sortable?: boolean
|
|
25
33
|
searchable?: boolean
|
|
34
|
+
/** Replace the table with a loading placeholder. */
|
|
35
|
+
loading?: boolean
|
|
36
|
+
/**
|
|
37
|
+
* Error message. When set, the table is replaced by an error state.
|
|
38
|
+
* `null`/`undefined` renders normally.
|
|
39
|
+
*/
|
|
40
|
+
error?: string | null
|
|
26
41
|
}
|
|
27
42
|
|
|
28
43
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -33,6 +48,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
33
48
|
columns: () => ['name', 'lot', 'expiry', 'storage', 'stock'],
|
|
34
49
|
sortable: true,
|
|
35
50
|
searchable: true,
|
|
51
|
+
loading: false,
|
|
52
|
+
error: null,
|
|
36
53
|
})
|
|
37
54
|
|
|
38
55
|
const emit = defineEmits<{
|
|
@@ -47,8 +64,7 @@ const emit = defineEmits<{
|
|
|
47
64
|
const searchQuery = ref('')
|
|
48
65
|
const sortColumn = ref<ReagentColumn | null>(null)
|
|
49
66
|
const sortDirection = ref<'asc' | 'desc'>('asc')
|
|
50
|
-
const
|
|
51
|
-
const dragOverId = ref<string | null>(null)
|
|
67
|
+
const dragHandleRefs = new Map<string, HTMLElement>()
|
|
52
68
|
|
|
53
69
|
const reagentSearch = useTextSearch({
|
|
54
70
|
items: () => props.modelValue || [],
|
|
@@ -78,14 +94,63 @@ const sortedReagents = useSortedItems<Reagent, ReagentColumn>({
|
|
|
78
94
|
})
|
|
79
95
|
const filteredReagents = sortedReagents.sortedItems
|
|
80
96
|
|
|
97
|
+
// Drag-and-drop plus the Alt+Arrow keyboard alternative live in the shared
|
|
98
|
+
// reorder composable, which also announces every move politely.
|
|
99
|
+
const {
|
|
100
|
+
draggingId,
|
|
101
|
+
dragOverId,
|
|
102
|
+
announcement: reorderAnnouncement,
|
|
103
|
+
announcerStyle,
|
|
104
|
+
onDragStart,
|
|
105
|
+
onDragOver,
|
|
106
|
+
onDragLeave,
|
|
107
|
+
onDrop,
|
|
108
|
+
onDragEnd,
|
|
109
|
+
onKeydown: onReorderKeydown,
|
|
110
|
+
} = useListReorder<Reagent>({
|
|
111
|
+
items: () => props.modelValue || [],
|
|
112
|
+
getId: (reagent) => reagent.id,
|
|
113
|
+
getLabel: (reagent) => reagent.name,
|
|
114
|
+
disabled: () => props.readonly || !props.sortable,
|
|
115
|
+
getElement: (id) => dragHandleRefs.get(id),
|
|
116
|
+
itemNoun: 'Reagent',
|
|
117
|
+
onReorder: ({ fromIndex, toIndex, items }) => {
|
|
118
|
+
emit('reorder', items[toIndex].id, fromIndex, toIndex)
|
|
119
|
+
emit('update:modelValue', items)
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
function setDragHandleRef(id: string, el: Element | ComponentPublicInstance | null) {
|
|
124
|
+
if (el instanceof HTMLElement) {
|
|
125
|
+
dragHandleRefs.set(id, el)
|
|
126
|
+
} else {
|
|
127
|
+
dragHandleRefs.delete(id)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
81
131
|
function getRowClass(reagent: Reagent): string[] {
|
|
82
132
|
return getReagentRowClasses(reagent, {
|
|
83
133
|
lowStockThreshold: props.lowStockThreshold,
|
|
84
|
-
draggedId:
|
|
134
|
+
draggedId: draggingId.value,
|
|
85
135
|
dragOverId: dragOverId.value,
|
|
86
136
|
})
|
|
87
137
|
}
|
|
88
138
|
|
|
139
|
+
/** `aria-sort` for a column header, omitted while sorting is off. */
|
|
140
|
+
function columnAriaSort(column: ReagentColumn): 'ascending' | 'descending' | 'none' | undefined {
|
|
141
|
+
if (!props.sortable) return undefined
|
|
142
|
+
if (sortColumn.value !== column) return 'none'
|
|
143
|
+
return sortDirection.value === 'asc' ? 'ascending' : 'descending'
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Native buttons activate on Enter/Space; the default is prevented so a header
|
|
147
|
+
// sorts exactly once per key press in every engine.
|
|
148
|
+
function handleSortKeydown(event: KeyboardEvent, column: ReagentColumn) {
|
|
149
|
+
if (event.key !== 'Enter' && event.key !== ' ') return
|
|
150
|
+
event.preventDefault()
|
|
151
|
+
handleSort(column)
|
|
152
|
+
}
|
|
153
|
+
|
|
89
154
|
// Actions
|
|
90
155
|
function handleSort(column: ReagentColumn) {
|
|
91
156
|
if (!props.sortable) return
|
|
@@ -111,54 +176,7 @@ function handleAdd() {
|
|
|
111
176
|
emit('add')
|
|
112
177
|
}
|
|
113
178
|
|
|
114
|
-
// Drag and drop
|
|
115
|
-
function handleDragStart(event: DragEvent, reagent: Reagent) {
|
|
116
|
-
if (props.readonly || !props.sortable) return
|
|
117
|
-
draggedId.value = reagent.id
|
|
118
|
-
if (event.dataTransfer) {
|
|
119
|
-
event.dataTransfer.effectAllowed = 'move'
|
|
120
|
-
event.dataTransfer.setData('text/plain', reagent.id)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function handleDragOver(event: DragEvent, reagent: Reagent) {
|
|
125
|
-
if (props.readonly || !props.sortable || !draggedId.value) return
|
|
126
|
-
event.preventDefault()
|
|
127
|
-
dragOverId.value = reagent.id
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function handleDragLeave() {
|
|
131
|
-
dragOverId.value = null
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function handleDrop(event: DragEvent, targetReagent: Reagent) {
|
|
135
|
-
if (props.readonly || !props.sortable || !draggedId.value) return
|
|
136
|
-
event.preventDefault()
|
|
137
|
-
|
|
138
|
-
const reagents = [...(props.modelValue || [])]
|
|
139
|
-
const fromIndex = reagents.findIndex((r) => r.id === draggedId.value)
|
|
140
|
-
const toIndex = reagents.findIndex((r) => r.id === targetReagent.id)
|
|
141
|
-
|
|
142
|
-
if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) {
|
|
143
|
-
draggedId.value = null
|
|
144
|
-
dragOverId.value = null
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
179
|
|
|
148
|
-
const [removed] = reagents.splice(fromIndex, 1)
|
|
149
|
-
reagents.splice(toIndex, 0, removed)
|
|
150
|
-
|
|
151
|
-
emit('reorder', draggedId.value, fromIndex, toIndex)
|
|
152
|
-
emit('update:modelValue', reagents)
|
|
153
|
-
|
|
154
|
-
draggedId.value = null
|
|
155
|
-
dragOverId.value = null
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function handleDragEnd() {
|
|
159
|
-
draggedId.value = null
|
|
160
|
-
dragOverId.value = null
|
|
161
|
-
}
|
|
162
180
|
</script>
|
|
163
181
|
|
|
164
182
|
<template>
|
|
@@ -193,7 +211,10 @@ function handleDragEnd() {
|
|
|
193
211
|
</div>
|
|
194
212
|
|
|
195
213
|
<!-- Table -->
|
|
196
|
-
<table
|
|
214
|
+
<table
|
|
215
|
+
v-if="!loading && !error && filteredReagents.length > 0"
|
|
216
|
+
class="mint-reagent-list__table"
|
|
217
|
+
>
|
|
197
218
|
<thead>
|
|
198
219
|
<tr>
|
|
199
220
|
<th v-if="sortable && !readonly" style="width: 24px" />
|
|
@@ -201,8 +222,15 @@ function handleDragEnd() {
|
|
|
201
222
|
v-for="col in columns"
|
|
202
223
|
:key="col"
|
|
203
224
|
:class="[sortable ? 'mint-reagent-list__col--sortable' : '']"
|
|
204
|
-
|
|
225
|
+
:aria-sort="columnAriaSort(col)"
|
|
205
226
|
>
|
|
227
|
+
<component
|
|
228
|
+
:is="sortable ? 'button' : 'span'"
|
|
229
|
+
:type="sortable ? 'button' : undefined"
|
|
230
|
+
class="mint-reagent-list__sort-btn"
|
|
231
|
+
@click="handleSort(col)"
|
|
232
|
+
@keydown="sortable ? handleSortKeydown($event, col) : undefined"
|
|
233
|
+
>
|
|
206
234
|
{{ columnLabels[col] }}
|
|
207
235
|
<svg
|
|
208
236
|
v-if="sortable"
|
|
@@ -229,27 +257,36 @@ function handleDragEnd() {
|
|
|
229
257
|
d="M5 15l7-7 7 7"
|
|
230
258
|
/>
|
|
231
259
|
</svg>
|
|
260
|
+
</component>
|
|
232
261
|
</th>
|
|
233
262
|
<th v-if="!readonly" style="width: 80px" />
|
|
234
263
|
</tr>
|
|
235
264
|
</thead>
|
|
236
265
|
<tbody>
|
|
237
266
|
<tr
|
|
238
|
-
v-for="reagent in filteredReagents"
|
|
267
|
+
v-for="(reagent, index) in filteredReagents"
|
|
239
268
|
:key="reagent.id"
|
|
240
269
|
:class="getRowClass(reagent)"
|
|
241
270
|
:draggable="sortable && !readonly"
|
|
242
|
-
@dragstart="
|
|
243
|
-
@dragover="
|
|
244
|
-
@dragleave="
|
|
245
|
-
@drop="
|
|
246
|
-
@dragend="
|
|
271
|
+
@dragstart="onDragStart($event, reagent, index)"
|
|
272
|
+
@dragover="onDragOver($event, reagent, index)"
|
|
273
|
+
@dragleave="onDragLeave"
|
|
274
|
+
@drop="onDrop($event, reagent, index)"
|
|
275
|
+
@dragend="onDragEnd"
|
|
247
276
|
>
|
|
248
|
-
<!-- Drag handle -->
|
|
277
|
+
<!-- Drag handle: also the keyboard reorder target (Alt+Arrow keys). -->
|
|
249
278
|
<td v-if="sortable && !readonly">
|
|
250
|
-
<
|
|
251
|
-
|
|
252
|
-
|
|
279
|
+
<button
|
|
280
|
+
:ref="(el) => setDragHandleRef(reagent.id, el)"
|
|
281
|
+
type="button"
|
|
282
|
+
class="mint-reagent-list__drag-handle-btn"
|
|
283
|
+
:aria-label="`Reorder ${reagent.name}. Press Alt with the arrow keys to move it.`"
|
|
284
|
+
@keydown="onReorderKeydown($event, reagent, index)"
|
|
285
|
+
>
|
|
286
|
+
<svg class="mint-reagent-list__drag-handle" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
287
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8h16M4 16h16" />
|
|
288
|
+
</svg>
|
|
289
|
+
</button>
|
|
253
290
|
</td>
|
|
254
291
|
|
|
255
292
|
<!-- Name -->
|
|
@@ -367,26 +404,34 @@ function handleDragEnd() {
|
|
|
367
404
|
</tbody>
|
|
368
405
|
</table>
|
|
369
406
|
|
|
407
|
+
<!-- Error state -->
|
|
408
|
+
<div v-else-if="error" class="mint-reagent-list__state">
|
|
409
|
+
<slot name="error" :error="error">
|
|
410
|
+
<DataStateInternal state="error" :title="error" />
|
|
411
|
+
</slot>
|
|
412
|
+
</div>
|
|
413
|
+
|
|
414
|
+
<!-- Loading state -->
|
|
415
|
+
<div v-else-if="loading" class="mint-reagent-list__state">
|
|
416
|
+
<slot name="loading">
|
|
417
|
+
<DataStateInternal state="loading" loading-label="Loading reagents" />
|
|
418
|
+
</slot>
|
|
419
|
+
</div>
|
|
420
|
+
|
|
370
421
|
<!-- Empty state -->
|
|
371
|
-
<div v-else class="mint-reagent-
|
|
372
|
-
<
|
|
373
|
-
<
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
class="mint-reagent-list__empty-btn"
|
|
382
|
-
@click="handleAdd"
|
|
383
|
-
>
|
|
384
|
-
Add Reagent
|
|
385
|
-
</button>
|
|
422
|
+
<div v-else class="mint-reagent-list__state">
|
|
423
|
+
<slot name="empty">
|
|
424
|
+
<DataStateInternal
|
|
425
|
+
state="empty"
|
|
426
|
+
:title="searchQuery ? 'No reagents found' : 'No reagents added yet'"
|
|
427
|
+
:icon-path="EMPTY_ICON_PATH"
|
|
428
|
+
:action-label="!readonly && !searchQuery ? 'Add Reagent' : undefined"
|
|
429
|
+
@action="handleAdd"
|
|
430
|
+
/>
|
|
431
|
+
</slot>
|
|
386
432
|
</div>
|
|
433
|
+
|
|
434
|
+
<!-- Reorder announcements for screen readers -->
|
|
435
|
+
<div role="status" aria-live="polite" :style="announcerStyle">{{ reorderAnnouncement }}</div>
|
|
387
436
|
</div>
|
|
388
437
|
</template>
|
|
389
|
-
|
|
390
|
-
<style>
|
|
391
|
-
@import '../styles/components/reagent-list.css';
|
|
392
|
-
</style>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import RuntimeUpdateNotice from './RuntimeUpdateNotice.vue'
|
|
3
|
+
|
|
4
|
+
function handleReload() {
|
|
5
|
+
console.log('reload requested')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function initState() {
|
|
9
|
+
return {
|
|
10
|
+
visible: true,
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<Story title="Feedback/RuntimeUpdateNotice">
|
|
17
|
+
<Variant title="Playground" :init-state="initState">
|
|
18
|
+
<template #default="{ state }">
|
|
19
|
+
<!-- The notice is `position: fixed`, so it anchors to the preview
|
|
20
|
+
viewport rather than this wrapper. -->
|
|
21
|
+
<div style="padding: 2rem; min-height: 14rem; background: var(--bg-primary);">
|
|
22
|
+
<p style="margin: 0; font-size: 0.8125rem; color: var(--text-secondary);">
|
|
23
|
+
Shown after the platform detects a new frontend build.
|
|
24
|
+
</p>
|
|
25
|
+
<RuntimeUpdateNotice :visible="state.visible" @reload="handleReload" />
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<template #controls="{ state }">
|
|
30
|
+
<HstCheckbox v-model="state.visible" title="Visible" />
|
|
31
|
+
</template>
|
|
32
|
+
</Variant>
|
|
33
|
+
|
|
34
|
+
<Variant title="Visible">
|
|
35
|
+
<div style="padding: 2rem; min-height: 14rem; background: var(--bg-primary);">
|
|
36
|
+
<RuntimeUpdateNotice :visible="true" @reload="handleReload" />
|
|
37
|
+
</div>
|
|
38
|
+
</Variant>
|
|
39
|
+
|
|
40
|
+
<Variant title="Hidden">
|
|
41
|
+
<div style="padding: 2rem; min-height: 8rem; background: var(--bg-primary);">
|
|
42
|
+
<p style="margin: 0; font-size: 0.8125rem; color: var(--text-secondary);">
|
|
43
|
+
Nothing renders while <code>visible</code> is false.
|
|
44
|
+
</p>
|
|
45
|
+
<RuntimeUpdateNotice :visible="false" @reload="handleReload" />
|
|
46
|
+
</div>
|
|
47
|
+
</Variant>
|
|
48
|
+
</Story>
|
|
49
|
+
</template>
|
|
@@ -30,9 +30,9 @@ const emit = defineEmits<{
|
|
|
30
30
|
align-items: center;
|
|
31
31
|
gap: 10px;
|
|
32
32
|
padding: 10px 12px;
|
|
33
|
-
color: var(--
|
|
34
|
-
background: var(--
|
|
35
|
-
border: 1px solid var(--
|
|
33
|
+
color: var(--text-primary);
|
|
34
|
+
background: var(--bg-card);
|
|
35
|
+
border: 1px solid var(--border-color);
|
|
36
36
|
border-radius: 10px;
|
|
37
37
|
box-shadow: 0 10px 28px rgb(15 23 42 / 16%);
|
|
38
38
|
font-size: 13px;
|
|
@@ -42,7 +42,7 @@ const emit = defineEmits<{
|
|
|
42
42
|
.mint-runtime-update button {
|
|
43
43
|
padding: 5px 9px;
|
|
44
44
|
color: #fff;
|
|
45
|
-
background: var(--
|
|
45
|
+
background: var(--color-primary);
|
|
46
46
|
border: 0;
|
|
47
47
|
border-radius: 6px;
|
|
48
48
|
font: inherit;
|
|
@@ -84,6 +84,8 @@ const mockNodes: TreeNode[] = [
|
|
|
84
84
|
|
|
85
85
|
function initState() {
|
|
86
86
|
return {
|
|
87
|
+
loading: false,
|
|
88
|
+
error: '',
|
|
87
89
|
nodes: mockNodes,
|
|
88
90
|
expandAll: false,
|
|
89
91
|
showIcons: true,
|
|
@@ -99,6 +101,8 @@ function initState() {
|
|
|
99
101
|
<template #default="{ state }">
|
|
100
102
|
<div style="padding: 2rem; max-width: 500px;">
|
|
101
103
|
<SampleHierarchyTree
|
|
104
|
+
:loading="state.loading"
|
|
105
|
+
:error="state.error || null"
|
|
102
106
|
:nodes="state.nodes"
|
|
103
107
|
:expand-all="state.expandAll"
|
|
104
108
|
:show-icons="state.showIcons"
|
|
@@ -108,6 +112,8 @@ function initState() {
|
|
|
108
112
|
</div>
|
|
109
113
|
</template>
|
|
110
114
|
<template #controls="{ state }">
|
|
115
|
+
<HstCheckbox v-model="state.loading" title="Loading" />
|
|
116
|
+
<HstText v-model="state.error" title="Error Message" />
|
|
111
117
|
<HstCheckbox v-model="state.expandAll" title="Expand All" />
|
|
112
118
|
<HstCheckbox v-model="state.showIcons" title="Show Icons" />
|
|
113
119
|
<HstCheckbox v-model="state.showCounts" title="Show Counts" />
|
|
@@ -157,5 +163,17 @@ function initState() {
|
|
|
157
163
|
<SampleHierarchyTree :nodes="[]" />
|
|
158
164
|
</div>
|
|
159
165
|
</Variant>
|
|
166
|
+
|
|
167
|
+
<Variant title="Loading">
|
|
168
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
169
|
+
<SampleHierarchyTree :nodes="[]" loading />
|
|
170
|
+
</div>
|
|
171
|
+
</Variant>
|
|
172
|
+
|
|
173
|
+
<Variant title="Error">
|
|
174
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
175
|
+
<SampleHierarchyTree :nodes="[]" error="Could not load the sample hierarchy." />
|
|
176
|
+
</div>
|
|
177
|
+
</Variant>
|
|
160
178
|
</Story>
|
|
161
179
|
</template>
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
import { h, Transition, type VNode } from 'vue'
|
|
4
4
|
import type { TreeNodeType, BadgeVariant, TreeNode } from '../types'
|
|
5
5
|
import { useExpansionSet } from '../composables/useExpansionSet'
|
|
6
|
+
import DataStateInternal from './internal/DataStateInternal.vue'
|
|
7
|
+
|
|
8
|
+
/** Folder glyph kept from the pre-1.2 hand-rolled empty state. */
|
|
9
|
+
const EMPTY_ICON_PATH
|
|
10
|
+
= 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z'
|
|
6
11
|
|
|
7
12
|
interface Props {
|
|
8
13
|
nodes: TreeNode[]
|
|
@@ -12,6 +17,13 @@ interface Props {
|
|
|
12
17
|
showCounts?: boolean
|
|
13
18
|
maxDepth?: number
|
|
14
19
|
size?: 'sm' | 'md' | 'lg'
|
|
20
|
+
/** Replace the tree with a loading placeholder. */
|
|
21
|
+
loading?: boolean
|
|
22
|
+
/**
|
|
23
|
+
* Error message. When set, the tree is replaced by an error state.
|
|
24
|
+
* `null`/`undefined` renders normally.
|
|
25
|
+
*/
|
|
26
|
+
error?: string | null
|
|
15
27
|
}
|
|
16
28
|
|
|
17
29
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -20,6 +32,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
20
32
|
showIcons: true,
|
|
21
33
|
showCounts: true,
|
|
22
34
|
size: 'md',
|
|
35
|
+
loading: false,
|
|
36
|
+
error: null,
|
|
23
37
|
})
|
|
24
38
|
|
|
25
39
|
const emit = defineEmits<{
|
|
@@ -226,12 +240,25 @@ function renderTree(): VNode[] {
|
|
|
226
240
|
|
|
227
241
|
<template>
|
|
228
242
|
<div :class="['mint-sample-tree', `mint-sample-tree--${size}`]" role="tree">
|
|
243
|
+
<!-- Error state -->
|
|
244
|
+
<div v-if="error" class="mint-sample-tree__state">
|
|
245
|
+
<slot name="error" :error="error">
|
|
246
|
+
<DataStateInternal state="error" size="sm" :title="error" />
|
|
247
|
+
</slot>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<!-- Loading state -->
|
|
251
|
+
<div v-else-if="loading" class="mint-sample-tree__state">
|
|
252
|
+
<slot name="loading">
|
|
253
|
+
<DataStateInternal state="loading" size="sm" loading-label="Loading samples" />
|
|
254
|
+
</slot>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
229
257
|
<!-- Empty state -->
|
|
230
|
-
<div v-if="nodes.length === 0" class="mint-sample-
|
|
231
|
-
<
|
|
232
|
-
<
|
|
233
|
-
</
|
|
234
|
-
<p class="mint-sample-tree__empty-text">No items</p>
|
|
258
|
+
<div v-else-if="nodes.length === 0" class="mint-sample-tree__state">
|
|
259
|
+
<slot name="empty">
|
|
260
|
+
<DataStateInternal state="empty" size="sm" title="No items" :icon-path="EMPTY_ICON_PATH" />
|
|
261
|
+
</slot>
|
|
235
262
|
</div>
|
|
236
263
|
|
|
237
264
|
<!-- Tree nodes rendered via render function -->
|
|
@@ -240,7 +267,3 @@ function renderTree(): VNode[] {
|
|
|
240
267
|
</template>
|
|
241
268
|
</div>
|
|
242
269
|
</template>
|
|
243
|
-
|
|
244
|
-
<style>
|
|
245
|
-
@import '../styles/components/sample-hierarchy-tree.css';
|
|
246
|
-
</style>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Clickable color-swatch legend for plate sample types, supporting selection, count display, add/remove, and custom color assignment. */
|
|
3
3
|
import type { SampleType } from '../types'
|
|
4
|
+
import { SAMPLE_COLOR_SCALE } from '../utils/color'
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
7
|
modelValue?: string
|
|
@@ -17,10 +18,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
17
18
|
samples: () => [],
|
|
18
19
|
showCounts: true,
|
|
19
20
|
editable: false,
|
|
20
|
-
colorPalette: () => [
|
|
21
|
-
'#3B82F6', '#10B981', '#EF4444', '#F59E0B', '#8B5CF6',
|
|
22
|
-
'#F97316', '#06B6D4', '#14B8A6', '#6B7280',
|
|
23
|
-
],
|
|
21
|
+
colorPalette: () => [...SAMPLE_COLOR_SCALE],
|
|
24
22
|
size: 'md',
|
|
25
23
|
orientation: 'vertical',
|
|
26
24
|
})
|
|
@@ -113,7 +111,3 @@ function handleAdd() {
|
|
|
113
111
|
</button>
|
|
114
112
|
</div>
|
|
115
113
|
</template>
|
|
116
|
-
|
|
117
|
-
<style>
|
|
118
|
-
@import '../styles/components/sample-legend.css';
|
|
119
|
-
</style>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ref } from 'vue'
|
|
2
2
|
import type { SampleGroup } from '../types'
|
|
3
|
+
import { useReorderAnnouncer } from '../composables/useListReorder'
|
|
4
|
+
import { getSampleGroupMajorPrefix } from '../utils/sampleGroupPath'
|
|
3
5
|
import {
|
|
4
6
|
detectSampleGroupSeparator,
|
|
5
7
|
getSampleGroupParentPath,
|
|
@@ -23,6 +25,7 @@ export function useSampleSelectorDrag(groups: MutableSampleGroups) {
|
|
|
23
25
|
const draggingGroupKind = ref<GroupDragKind | null>(null)
|
|
24
26
|
const reorderTarget = ref<string | null>(null)
|
|
25
27
|
const reorderPosition = ref<GroupReorderPosition | null>(null)
|
|
28
|
+
const { announcement, announce, announcerStyle } = useReorderAnnouncer()
|
|
26
29
|
|
|
27
30
|
function handleDragStart(sample: string, sourceGroup: string | null, event: DragEvent) {
|
|
28
31
|
draggingSample.value = sample
|
|
@@ -143,7 +146,45 @@ export function useSampleSelectorDrag(groups: MutableSampleGroups) {
|
|
|
143
146
|
handleGroupDragEnd()
|
|
144
147
|
}
|
|
145
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Keyboard alternative to the group reorder drag. The hierarchy constrains a
|
|
151
|
+
* move to the dragged node's siblings (a leaf may not cross a parent), so this
|
|
152
|
+
* is not a flat list reorder and does not use `useListReorder`: Alt+ArrowUp /
|
|
153
|
+
* Alt+ArrowDown swap the node with its previous / next sibling instead.
|
|
154
|
+
*/
|
|
155
|
+
function handleGroupReorderKeydown(name: string, kind: GroupDragKind, event: KeyboardEvent) {
|
|
156
|
+
if (!event.altKey) return
|
|
157
|
+
const direction = event.key === 'ArrowUp' ? -1 : event.key === 'ArrowDown' ? 1 : 0
|
|
158
|
+
if (direction === 0) return
|
|
159
|
+
|
|
160
|
+
event.preventDefault()
|
|
161
|
+
const separator = detectSampleGroupSeparator(groups.value)
|
|
162
|
+
const siblings = kind === 'major'
|
|
163
|
+
? uniqueMajorPrefixes(groups.value, separator)
|
|
164
|
+
: groups.value
|
|
165
|
+
.filter(group =>
|
|
166
|
+
getSampleGroupParentPath(group.name, separator) ===
|
|
167
|
+
getSampleGroupParentPath(name, separator))
|
|
168
|
+
.map(group => group.name)
|
|
169
|
+
|
|
170
|
+
const currentIndex = siblings.indexOf(name)
|
|
171
|
+
const target = siblings[currentIndex + direction]
|
|
172
|
+
if (currentIndex === -1 || target === undefined) return
|
|
173
|
+
|
|
174
|
+
const position: GroupReorderPosition = direction === -1 ? 'before' : 'after'
|
|
175
|
+
const nextGroups = kind === 'major'
|
|
176
|
+
? reorderSampleMajorGroup(groups.value, name, target, position)
|
|
177
|
+
: reorderSampleGroup(groups.value, name, target, position)
|
|
178
|
+
|
|
179
|
+
if (nextGroups === groups.value) return
|
|
180
|
+
groups.value = nextGroups
|
|
181
|
+
announce(`${name} moved ${direction === -1 ? 'up' : 'down'} to position ${currentIndex + direction + 1} of ${siblings.length}.`)
|
|
182
|
+
}
|
|
183
|
+
|
|
146
184
|
return {
|
|
185
|
+
announcement,
|
|
186
|
+
announcerStyle,
|
|
187
|
+
handleGroupReorderKeydown,
|
|
147
188
|
draggingSample,
|
|
148
189
|
dragOverGroup,
|
|
149
190
|
draggingGroup,
|
|
@@ -162,3 +203,13 @@ export function useSampleSelectorDrag(groups: MutableSampleGroups) {
|
|
|
162
203
|
handleGroupDrop,
|
|
163
204
|
}
|
|
164
205
|
}
|
|
206
|
+
|
|
207
|
+
/** Major-group prefixes in tree order, de-duplicated. */
|
|
208
|
+
function uniqueMajorPrefixes(groups: SampleGroup[], separator: string): string[] {
|
|
209
|
+
const seen: string[] = []
|
|
210
|
+
for (const group of groups) {
|
|
211
|
+
const prefix = getSampleGroupMajorPrefix(group.name, separator)
|
|
212
|
+
if (!seen.includes(prefix)) seen.push(prefix)
|
|
213
|
+
}
|
|
214
|
+
return seen
|
|
215
|
+
}
|
|
@@ -72,6 +72,8 @@ const lcmsSamples = ['Liver', 'Kidney'].flatMap(tissue =>
|
|
|
72
72
|
|
|
73
73
|
function initState() {
|
|
74
74
|
return {
|
|
75
|
+
loading: false,
|
|
76
|
+
error: '',
|
|
75
77
|
samples: mockSamples,
|
|
76
78
|
selected: [] as string[],
|
|
77
79
|
groups: [] as SampleGroup[],
|
|
@@ -100,6 +102,8 @@ function handleSmartGroup(result: AutoGroupResult, state: { groups: SampleGroup[
|
|
|
100
102
|
<template #default="{ state }">
|
|
101
103
|
<div style="padding: 2rem; max-width: 400px;">
|
|
102
104
|
<SampleSelector
|
|
105
|
+
:loading="state.loading"
|
|
106
|
+
:error="state.error || null"
|
|
103
107
|
v-model="state.selected"
|
|
104
108
|
v-model:groups="state.groups"
|
|
105
109
|
:samples="state.samples"
|
|
@@ -110,6 +114,8 @@ function handleSmartGroup(result: AutoGroupResult, state: { groups: SampleGroup[
|
|
|
110
114
|
</div>
|
|
111
115
|
</template>
|
|
112
116
|
<template #controls="{ state }">
|
|
117
|
+
<HstCheckbox v-model="state.loading" title="Loading" />
|
|
118
|
+
<HstText v-model="state.error" title="Error Message" />
|
|
113
119
|
<HstCheckbox v-model="state.enableGrouping" title="Enable Grouping" />
|
|
114
120
|
<HstCheckbox v-model="state.enableSmartGroup" title="Enable Smart" />
|
|
115
121
|
</template>
|
|
@@ -34,6 +34,8 @@ export interface SampleGroupTreeContext {
|
|
|
34
34
|
onGroupDragOver: (name: string, kind: GroupDragKind, event: DragEvent) => void
|
|
35
35
|
onGroupDragLeave: (event: DragEvent) => void
|
|
36
36
|
onGroupDrop: (name: string, kind: GroupDragKind, event: DragEvent) => void
|
|
37
|
+
/** Alt+ArrowUp / Alt+ArrowDown reorder a group among its siblings. */
|
|
38
|
+
onGroupReorderKeydown: (name: string, kind: GroupDragKind, event: KeyboardEvent) => void
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/** Every concrete group name beneath a node, in tree order. */
|