@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
|
@@ -12,7 +12,8 @@ import type {
|
|
|
12
12
|
JobsStatusTrayAdapter,
|
|
13
13
|
PluginJobCenterSource,
|
|
14
14
|
} from './useJobsStatusTray'
|
|
15
|
-
import {
|
|
15
|
+
import { useEventStream, type EventStreamMessage } from './useEventStream'
|
|
16
|
+
import { isActiveJobStatus, normalizeJobState } from '../jobs'
|
|
16
17
|
import type {
|
|
17
18
|
GeneratedAnalysisJob,
|
|
18
19
|
GeneratedAnalysisJobInput,
|
|
@@ -110,7 +111,6 @@ export interface UsePluginJobsReturn extends PluginJobCenterSource {
|
|
|
110
111
|
|
|
111
112
|
export type UseGeneratedAnalysisReturn = UsePluginJobsReturn
|
|
112
113
|
|
|
113
|
-
const ACTIVE_STATUSES = new Set(['awaiting_input', 'queued', 'running'])
|
|
114
114
|
const AUTH_TOKEN_KEY = 'mint-auth-token'
|
|
115
115
|
const SESSION_NOT_FOUND_DETAIL = 'Session not found'
|
|
116
116
|
const MAX_RESULT_CACHE_ENTRIES = 32
|
|
@@ -125,6 +125,10 @@ interface PluginJobWaiter {
|
|
|
125
125
|
abort?: () => void
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
type PluginJobStreamData = GeneratedAnalysisJobInput | {
|
|
129
|
+
jobs: GeneratedAnalysisJobInput[]
|
|
130
|
+
}
|
|
131
|
+
|
|
128
132
|
class GeneratedAnalysisRequestError extends Error {
|
|
129
133
|
constructor(
|
|
130
134
|
readonly status: number,
|
|
@@ -200,10 +204,6 @@ function createPluginJobs(
|
|
|
200
204
|
const error = shallowRef<string | null>(null)
|
|
201
205
|
const status = shallowRef<'idle' | 'initializing' | 'ready' | 'paused' | 'error' | 'disposed'>('idle')
|
|
202
206
|
let pollTimer: ReturnType<typeof setTimeout> | undefined
|
|
203
|
-
let streamReconnectTimer: ReturnType<typeof setTimeout> | undefined
|
|
204
|
-
let streamController: AbortController | undefined
|
|
205
|
-
let streamConnected = false
|
|
206
|
-
let streamConnecting = false
|
|
207
207
|
let disposed = false
|
|
208
208
|
let paused = false
|
|
209
209
|
let initialized = false
|
|
@@ -217,6 +217,17 @@ function createPluginJobs(
|
|
|
217
217
|
const resultRetryAttempts = new Map<string, number>()
|
|
218
218
|
const resultRetryTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
219
219
|
const downloadRefreshTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
220
|
+
const jobStream = useEventStream<PluginJobStreamData>(`${baseUrl}/jobs/events`, {
|
|
221
|
+
transport,
|
|
222
|
+
immediate: false,
|
|
223
|
+
reconnectDelayMs: streamReconnectDelay,
|
|
224
|
+
parseJson: true,
|
|
225
|
+
errorLabel: 'Job stream',
|
|
226
|
+
onOpen: clearPoll,
|
|
227
|
+
onMessage: applyJobStreamMessage,
|
|
228
|
+
onError: schedulePollForActiveJobs,
|
|
229
|
+
onClose: schedulePollForActiveJobs,
|
|
230
|
+
})
|
|
220
231
|
|
|
221
232
|
const latestResult = computed<GeneratedAnalysisResult | null>(() => {
|
|
222
233
|
const job = currentCompletedJob()
|
|
@@ -242,7 +253,7 @@ function createPluginJobs(
|
|
|
242
253
|
if (disposed) return
|
|
243
254
|
initialized = true
|
|
244
255
|
status.value = paused ? 'paused' : 'ready'
|
|
245
|
-
if (streamEnabled && !paused)
|
|
256
|
+
if (streamEnabled && !paused) jobStream.start()
|
|
246
257
|
})().catch((cause: unknown) => {
|
|
247
258
|
if (!disposed) {
|
|
248
259
|
error.value = errorMessage(cause, 'Failed to initialize generated analysis')
|
|
@@ -303,7 +314,7 @@ function createPluginJobs(
|
|
|
303
314
|
)
|
|
304
315
|
}
|
|
305
316
|
const job = upsertJob(jobInput, definitionId)
|
|
306
|
-
if (!
|
|
317
|
+
if (!jobStream.isConnected.value) schedulePoll()
|
|
307
318
|
return job
|
|
308
319
|
} catch (cause) {
|
|
309
320
|
error.value = errorMessage(cause, 'Failed to submit analysis')
|
|
@@ -461,12 +472,12 @@ function createPluginJobs(
|
|
|
461
472
|
.then((payload) => {
|
|
462
473
|
if (disposed) return jobs.value
|
|
463
474
|
if (revision !== jobsRevision) {
|
|
464
|
-
if (jobs.value.some(job =>
|
|
475
|
+
if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
|
|
465
476
|
return jobs.value
|
|
466
477
|
}
|
|
467
478
|
replaceJobs(payload.jobs)
|
|
468
479
|
error.value = null
|
|
469
|
-
if (!paused && !
|
|
480
|
+
if (!paused && !jobStream.isConnected.value && jobs.value.some(job => isActiveJobStatus(job.status))) {
|
|
470
481
|
schedulePoll()
|
|
471
482
|
}
|
|
472
483
|
return jobs.value
|
|
@@ -518,7 +529,7 @@ function createPluginJobs(
|
|
|
518
529
|
const current = typeof job === 'string'
|
|
519
530
|
? jobs.value.find(candidate => candidate.job_id === jobId)
|
|
520
531
|
: jobs.value.find(candidate => candidate.job_id === jobId) ?? job
|
|
521
|
-
if (current && !
|
|
532
|
+
if (current && !isActiveJobStatus(current.status)) {
|
|
522
533
|
return terminalWaitResult(current, options.rejectOnFailure === true)
|
|
523
534
|
}
|
|
524
535
|
if (paused) return Promise.reject(new Error('Plugin jobs runtime is paused'))
|
|
@@ -540,12 +551,12 @@ function createPluginJobs(
|
|
|
540
551
|
waiting.add(waiter)
|
|
541
552
|
jobWaiters.set(jobId, waiting)
|
|
542
553
|
if (!paused) {
|
|
543
|
-
if (streamEnabled)
|
|
544
|
-
if (!
|
|
554
|
+
if (streamEnabled) jobStream.start()
|
|
555
|
+
if (!jobStream.isConnected.value) schedulePoll()
|
|
545
556
|
}
|
|
546
557
|
void getJob(jobId, { signal: options.signal }).catch((cause: unknown) => {
|
|
547
558
|
if (current && isRetryableJobLookupError(cause)) {
|
|
548
|
-
if (!
|
|
559
|
+
if (!jobStream.isConnected.value) schedulePoll()
|
|
549
560
|
return
|
|
550
561
|
}
|
|
551
562
|
removeJobWaiter(jobId, waiter)
|
|
@@ -555,7 +566,7 @@ function createPluginJobs(
|
|
|
555
566
|
}
|
|
556
567
|
|
|
557
568
|
function settleJobWaiters(job: GeneratedAnalysisJob): void {
|
|
558
|
-
if (
|
|
569
|
+
if (isActiveJobStatus(job.status)) return
|
|
559
570
|
const waiting = jobWaiters.get(job.job_id)
|
|
560
571
|
if (!waiting) return
|
|
561
572
|
jobWaiters.delete(job.job_id)
|
|
@@ -895,7 +906,7 @@ function createPluginJobs(
|
|
|
895
906
|
}
|
|
896
907
|
|
|
897
908
|
function schedulePoll(): void {
|
|
898
|
-
if (disposed || paused ||
|
|
909
|
+
if (disposed || paused || jobStream.isConnected.value || pollTimer !== undefined) return
|
|
899
910
|
pollTimer = setTimeout(async () => {
|
|
900
911
|
pollTimer = undefined
|
|
901
912
|
if (disposed) return
|
|
@@ -903,90 +914,31 @@ function createPluginJobs(
|
|
|
903
914
|
await refreshJobs()
|
|
904
915
|
} catch (cause) {
|
|
905
916
|
error.value = errorMessage(cause, 'Failed to refresh analysis jobs')
|
|
906
|
-
if (jobs.value.some(job =>
|
|
917
|
+
if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
|
|
907
918
|
}
|
|
908
919
|
}, pollInterval)
|
|
909
920
|
}
|
|
910
921
|
|
|
911
|
-
|
|
912
|
-
if (
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
try {
|
|
916
|
-
const response = await transport(`${baseUrl}/jobs/events`, {
|
|
917
|
-
method: 'GET',
|
|
918
|
-
headers: { Accept: 'text/event-stream' },
|
|
919
|
-
signal: streamController.signal,
|
|
920
|
-
})
|
|
921
|
-
if (!response.ok) throw new Error(`Job stream failed (${response.status})`)
|
|
922
|
-
if (!response.body) throw new Error('Job stream response has no readable body')
|
|
923
|
-
streamConnected = true
|
|
924
|
-
if (pollTimer !== undefined) {
|
|
925
|
-
clearTimeout(pollTimer)
|
|
926
|
-
pollTimer = undefined
|
|
927
|
-
}
|
|
928
|
-
await consumeJobStream(response.body)
|
|
929
|
-
} catch (cause) {
|
|
930
|
-
if (!isAbortError(cause) && !disposed) {
|
|
931
|
-
if (jobs.value.some(job => ACTIVE_STATUSES.has(job.status))) schedulePoll()
|
|
932
|
-
}
|
|
933
|
-
} finally {
|
|
934
|
-
streamConnected = false
|
|
935
|
-
streamConnecting = false
|
|
936
|
-
streamController = undefined
|
|
937
|
-
if (!disposed && !paused) scheduleStreamReconnect()
|
|
938
|
-
}
|
|
922
|
+
function clearPoll(): void {
|
|
923
|
+
if (pollTimer === undefined) return
|
|
924
|
+
clearTimeout(pollTimer)
|
|
925
|
+
pollTimer = undefined
|
|
939
926
|
}
|
|
940
927
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
const decoder = new TextDecoder()
|
|
944
|
-
let buffer = ''
|
|
945
|
-
try {
|
|
946
|
-
while (!disposed) {
|
|
947
|
-
const { done, value } = await reader.read()
|
|
948
|
-
buffer += decoder.decode(value, { stream: !done })
|
|
949
|
-
const blocks = buffer.split(/\r?\n\r?\n/)
|
|
950
|
-
buffer = blocks.pop() ?? ''
|
|
951
|
-
for (const block of blocks) applyJobStreamEvent(block)
|
|
952
|
-
if (done) {
|
|
953
|
-
if (buffer.trim()) applyJobStreamEvent(buffer)
|
|
954
|
-
return
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
} finally {
|
|
958
|
-
reader.releaseLock()
|
|
959
|
-
}
|
|
928
|
+
function schedulePollForActiveJobs(): void {
|
|
929
|
+
if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
|
|
960
930
|
}
|
|
961
931
|
|
|
962
|
-
function
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
for (const line of block.split(/\r?\n/)) {
|
|
966
|
-
if (!line || line.startsWith(':')) continue
|
|
967
|
-
if (line.startsWith('event:')) event = line.slice('event:'.length).trim()
|
|
968
|
-
if (line.startsWith('data:')) data.push(line.slice('data:'.length).trimStart())
|
|
969
|
-
}
|
|
970
|
-
if (!data.length) return
|
|
971
|
-
const payload = JSON.parse(data.join('\n')) as (
|
|
972
|
-
GeneratedAnalysisJobInput | { jobs: GeneratedAnalysisJobInput[] }
|
|
973
|
-
)
|
|
974
|
-
if (event === 'snapshot' && 'jobs' in payload) {
|
|
932
|
+
function applyJobStreamMessage(message: EventStreamMessage<PluginJobStreamData>): void {
|
|
933
|
+
if (message.event === 'snapshot' && 'jobs' in message.data) {
|
|
934
|
+
const payload = message.data
|
|
975
935
|
replaceJobs(payload.jobs)
|
|
976
|
-
} else if (event === 'job' || event === 'terminal') {
|
|
977
|
-
upsertJob(
|
|
936
|
+
} else if (message.event === 'job' || message.event === 'terminal') {
|
|
937
|
+
upsertJob(message.data as GeneratedAnalysisJobInput)
|
|
978
938
|
}
|
|
979
939
|
error.value = null
|
|
980
940
|
}
|
|
981
941
|
|
|
982
|
-
function scheduleStreamReconnect(): void {
|
|
983
|
-
if (disposed || paused || streamReconnectTimer !== undefined) return
|
|
984
|
-
streamReconnectTimer = setTimeout(() => {
|
|
985
|
-
streamReconnectTimer = undefined
|
|
986
|
-
void connectJobStream()
|
|
987
|
-
}, streamReconnectDelay)
|
|
988
|
-
}
|
|
989
|
-
|
|
990
942
|
async function cancelJob(job: GeneratedAnalysisJob | string): Promise<GeneratedAnalysisJob> {
|
|
991
943
|
const jobId = typeof job === 'string' ? job : job.job_id
|
|
992
944
|
const revision = beginJobOperation(jobId)
|
|
@@ -1030,19 +982,17 @@ function createPluginJobs(
|
|
|
1030
982
|
deleteJob: async job => deleteJob(job as GeneratedAnalysisJob),
|
|
1031
983
|
clearFinishedJobs: async () => clearFinishedJobs(),
|
|
1032
984
|
}
|
|
985
|
+
|
|
1033
986
|
function pause(): void {
|
|
1034
987
|
if (disposed || paused) return
|
|
1035
988
|
paused = true
|
|
1036
989
|
status.value = 'paused'
|
|
1037
|
-
|
|
1038
|
-
if (streamReconnectTimer !== undefined) clearTimeout(streamReconnectTimer)
|
|
1039
|
-
pollTimer = undefined
|
|
1040
|
-
streamReconnectTimer = undefined
|
|
990
|
+
clearPoll()
|
|
1041
991
|
for (const jobId of resultRetryTimers.keys()) clearResultRetry(jobId)
|
|
1042
992
|
for (const jobId of downloadRefreshTimers.keys()) {
|
|
1043
993
|
clearManagedDownloadRefresh(jobId)
|
|
1044
994
|
}
|
|
1045
|
-
|
|
995
|
+
jobStream.stop()
|
|
1046
996
|
}
|
|
1047
997
|
|
|
1048
998
|
function resume(): void {
|
|
@@ -1053,8 +1003,8 @@ function createPluginJobs(
|
|
|
1053
1003
|
for (const [jobId, result] of Object.entries(resultCache.value)) {
|
|
1054
1004
|
scheduleManagedDownloadRefresh(jobId, managedDownloadRefreshDelay(result))
|
|
1055
1005
|
}
|
|
1056
|
-
if (streamEnabled)
|
|
1057
|
-
else if (jobs.value.some(job =>
|
|
1006
|
+
if (streamEnabled) jobStream.start()
|
|
1007
|
+
else if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
|
|
1058
1008
|
}
|
|
1059
1009
|
|
|
1060
1010
|
function dispose(): void {
|
|
@@ -1062,10 +1012,7 @@ function createPluginJobs(
|
|
|
1062
1012
|
disposed = true
|
|
1063
1013
|
paused = true
|
|
1064
1014
|
status.value = 'disposed'
|
|
1065
|
-
|
|
1066
|
-
if (streamReconnectTimer !== undefined) clearTimeout(streamReconnectTimer)
|
|
1067
|
-
pollTimer = undefined
|
|
1068
|
-
streamReconnectTimer = undefined
|
|
1015
|
+
clearPoll()
|
|
1069
1016
|
for (const timer of resultRetryTimers.values()) clearTimeout(timer)
|
|
1070
1017
|
resultRetryTimers.clear()
|
|
1071
1018
|
resultRetryAttempts.clear()
|
|
@@ -1075,7 +1022,7 @@ function createPluginJobs(
|
|
|
1075
1022
|
for (const jobId of jobWaiters.keys()) rejectJobWaiters(jobId, cause)
|
|
1076
1023
|
jobRevisions.clear()
|
|
1077
1024
|
pendingJobOperations.clear()
|
|
1078
|
-
|
|
1025
|
+
jobStream.stop()
|
|
1079
1026
|
}
|
|
1080
1027
|
|
|
1081
1028
|
async function request<T>(path: string, init: RequestInit): Promise<T> {
|
|
@@ -1229,10 +1176,6 @@ function createGeneratedSessionStorage(candidate?: Storage): GeneratedSessionSto
|
|
|
1229
1176
|
}
|
|
1230
1177
|
}
|
|
1231
1178
|
|
|
1232
|
-
function isAbortError(error: unknown): boolean {
|
|
1233
|
-
return error instanceof DOMException && error.name === 'AbortError'
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
1179
|
function isSessionNotFoundError(error: unknown): boolean {
|
|
1237
1180
|
return error instanceof GeneratedAnalysisRequestError
|
|
1238
1181
|
&& error.status === 404
|
|
@@ -485,7 +485,7 @@ export function usePluginJobCenter(
|
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
-
/**
|
|
488
|
+
/** @deprecated Use `usePluginJobCenter()` for the plugin job-center controller. */
|
|
489
489
|
export function useJobsStatusTray(
|
|
490
490
|
options: UseJobsStatusTrayOptions = {},
|
|
491
491
|
): UseJobsStatusTrayReturn {
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Demonstrates `useListReorder`: drag a row, or focus a handle and press Alt with the arrow keys. */
|
|
3
|
+
import { ref } from 'vue'
|
|
4
|
+
import { useListReorder } from './useListReorder'
|
|
5
|
+
|
|
6
|
+
interface Step { id: string; name: string }
|
|
7
|
+
|
|
8
|
+
const steps = ref<Step[]>([
|
|
9
|
+
{ id: 's1', name: 'Thaw samples' },
|
|
10
|
+
{ id: 's2', name: 'Quench metabolism' },
|
|
11
|
+
{ id: 's3', name: 'Extract metabolites' },
|
|
12
|
+
{ id: 's4', name: 'Dry down' },
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
const handles = new Map<string, HTMLElement>()
|
|
16
|
+
|
|
17
|
+
function setHandle(id: string, el: Element | null) {
|
|
18
|
+
if (el instanceof HTMLElement) handles.set(id, el)
|
|
19
|
+
else handles.delete(id)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
draggingId,
|
|
24
|
+
dragOverId,
|
|
25
|
+
announcement,
|
|
26
|
+
announcerStyle,
|
|
27
|
+
onDragStart,
|
|
28
|
+
onDragOver,
|
|
29
|
+
onDragLeave,
|
|
30
|
+
onDrop,
|
|
31
|
+
onDragEnd,
|
|
32
|
+
onKeydown,
|
|
33
|
+
} = useListReorder<Step>({
|
|
34
|
+
items: () => steps.value,
|
|
35
|
+
getId: (step) => step.id,
|
|
36
|
+
getLabel: (step) => step.name,
|
|
37
|
+
getElement: (id) => handles.get(id),
|
|
38
|
+
itemNoun: 'Step',
|
|
39
|
+
onReorder: ({ items }) => {
|
|
40
|
+
steps.value = items
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
function initState() {
|
|
45
|
+
return { showAnnouncement: true }
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<Story title="Composables/useListReorder">
|
|
51
|
+
<Variant title="Playground" :init-state="initState">
|
|
52
|
+
<template #default="{ state }">
|
|
53
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
54
|
+
<p class="mint-story-hint">
|
|
55
|
+
Drag a row, or focus its handle and press Alt with the up/down arrow keys.
|
|
56
|
+
</p>
|
|
57
|
+
<ul class="mint-story-list">
|
|
58
|
+
<li
|
|
59
|
+
v-for="(step, index) in steps"
|
|
60
|
+
:key="step.id"
|
|
61
|
+
class="mint-story-row"
|
|
62
|
+
:class="{
|
|
63
|
+
'mint-story-row--dragging': draggingId === step.id,
|
|
64
|
+
'mint-story-row--over': dragOverId === step.id,
|
|
65
|
+
}"
|
|
66
|
+
draggable="true"
|
|
67
|
+
@dragstart="onDragStart($event, step, index)"
|
|
68
|
+
@dragover="onDragOver($event, step, index)"
|
|
69
|
+
@dragleave="onDragLeave"
|
|
70
|
+
@drop="onDrop($event, step, index)"
|
|
71
|
+
@dragend="onDragEnd"
|
|
72
|
+
>
|
|
73
|
+
<button
|
|
74
|
+
:ref="(el) => setHandle(step.id, el as Element | null)"
|
|
75
|
+
type="button"
|
|
76
|
+
class="mint-story-handle"
|
|
77
|
+
:aria-label="`Reorder ${step.name}. Press Alt with the arrow keys to move it.`"
|
|
78
|
+
@keydown="onKeydown($event, step, index)"
|
|
79
|
+
>
|
|
80
|
+
⠿
|
|
81
|
+
</button>
|
|
82
|
+
<span>{{ index + 1 }}. {{ step.name }}</span>
|
|
83
|
+
</li>
|
|
84
|
+
</ul>
|
|
85
|
+
|
|
86
|
+
<p v-if="state.showAnnouncement" class="mint-story-hint">
|
|
87
|
+
Live region: <em>{{ announcement || '—' }}</em>
|
|
88
|
+
</p>
|
|
89
|
+
<div role="status" aria-live="polite" :style="announcerStyle">{{ announcement }}</div>
|
|
90
|
+
</div>
|
|
91
|
+
</template>
|
|
92
|
+
|
|
93
|
+
<template #controls="{ state }">
|
|
94
|
+
<HstCheckbox v-model="state.showAnnouncement" title="Show live-region text" />
|
|
95
|
+
</template>
|
|
96
|
+
</Variant>
|
|
97
|
+
</Story>
|
|
98
|
+
</template>
|
|
99
|
+
|
|
100
|
+
<style scoped>
|
|
101
|
+
.mint-story-list {
|
|
102
|
+
list-style: none;
|
|
103
|
+
margin: 0;
|
|
104
|
+
padding: 0;
|
|
105
|
+
display: flex;
|
|
106
|
+
flex-direction: column;
|
|
107
|
+
gap: 0.25rem;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.mint-story-row {
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
gap: 0.5rem;
|
|
114
|
+
padding: 0.5rem 0.75rem;
|
|
115
|
+
border: 1px solid var(--border-color);
|
|
116
|
+
border-radius: 0.5rem;
|
|
117
|
+
background: var(--bg-primary);
|
|
118
|
+
color: var(--text-primary);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.mint-story-row--dragging {
|
|
122
|
+
opacity: 0.5;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.mint-story-row--over {
|
|
126
|
+
border-color: var(--color-primary);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.mint-story-handle {
|
|
130
|
+
padding: 0;
|
|
131
|
+
border: 0;
|
|
132
|
+
background: none;
|
|
133
|
+
color: var(--text-muted);
|
|
134
|
+
cursor: grab;
|
|
135
|
+
font-size: 1rem;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.mint-story-hint {
|
|
139
|
+
margin: 0 0 0.75rem;
|
|
140
|
+
color: var(--text-secondary);
|
|
141
|
+
font-size: 0.8125rem;
|
|
142
|
+
}
|
|
143
|
+
</style>
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { nextTick, ref, type Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
/** Inline style for a visually hidden `aria-live` region (no global utility class in the SDK). */
|
|
4
|
+
export const REORDER_ANNOUNCER_STYLE = Object.freeze({
|
|
5
|
+
position: 'absolute',
|
|
6
|
+
width: '1px',
|
|
7
|
+
height: '1px',
|
|
8
|
+
padding: '0',
|
|
9
|
+
margin: '-1px',
|
|
10
|
+
overflow: 'hidden',
|
|
11
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
12
|
+
whiteSpace: 'nowrap',
|
|
13
|
+
border: '0',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export interface UseReorderAnnouncerReturn {
|
|
17
|
+
/** Bind to a polite live region's text content. */
|
|
18
|
+
announcement: Ref<string>
|
|
19
|
+
/** Publishes a message to the live region. */
|
|
20
|
+
announce: (message: string) => void
|
|
21
|
+
/** Bind to the live region's `style` so it stays visually hidden. */
|
|
22
|
+
announcerStyle: typeof REORDER_ANNOUNCER_STYLE
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Screen-reader announcements for drag/keyboard move interactions. Bind
|
|
27
|
+
* `announcement` to a `role="status" aria-live="polite"` element styled with
|
|
28
|
+
* `announcerStyle`.
|
|
29
|
+
*/
|
|
30
|
+
export function useReorderAnnouncer(): UseReorderAnnouncerReturn {
|
|
31
|
+
const announcement = ref('')
|
|
32
|
+
|
|
33
|
+
function announce(message: string) {
|
|
34
|
+
// Re-announce identical consecutive messages by clearing first.
|
|
35
|
+
if (announcement.value === message) announcement.value = ''
|
|
36
|
+
void nextTick(() => {
|
|
37
|
+
announcement.value = message
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { announcement, announce, announcerStyle: REORDER_ANNOUNCER_STYLE }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ListReorderMove<T> {
|
|
45
|
+
/** Index the item moved from, within `items()`. */
|
|
46
|
+
fromIndex: number
|
|
47
|
+
/** Index the item moved to, within `items()`. */
|
|
48
|
+
toIndex: number
|
|
49
|
+
/** The reordered array (a copy — `items()` is never mutated). */
|
|
50
|
+
items: T[]
|
|
51
|
+
/** Whether the move came from the keyboard or a pointer drag. */
|
|
52
|
+
source: 'keyboard' | 'drag'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface UseListReorderOptions<T> {
|
|
56
|
+
/** The list being reordered, in its canonical (not merely rendered) order. */
|
|
57
|
+
items: () => T[]
|
|
58
|
+
/** Stable id for an item; falls back to the render index when ids repeat. */
|
|
59
|
+
getId: (item: T, index: number) => string
|
|
60
|
+
/** Applies the move. The composable never mutates the source array. */
|
|
61
|
+
onReorder: (move: ListReorderMove<T>) => void
|
|
62
|
+
/** Human-readable name used in announcements. Defaults to the item's id. */
|
|
63
|
+
getLabel?: (item: T, index: number) => string
|
|
64
|
+
/** Blocks every reorder while it returns `true` (readonly / non-sortable lists). */
|
|
65
|
+
disabled?: () => boolean
|
|
66
|
+
/** Resolves the focusable element for an id so keyboard moves keep focus on the item. */
|
|
67
|
+
getElement?: (id: string) => HTMLElement | null | undefined
|
|
68
|
+
/** Noun used in announcements, e.g. `'Reagent'`. Default `'Item'`. */
|
|
69
|
+
itemNoun?: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface UseListReorderReturn<T> {
|
|
73
|
+
/** Id of the item currently being dragged, or `null`. */
|
|
74
|
+
draggingId: Ref<string | null>
|
|
75
|
+
/** Id of the item the pointer is hovering as a drop target, or `null`. */
|
|
76
|
+
dragOverId: Ref<string | null>
|
|
77
|
+
isDragging: (id: string) => boolean
|
|
78
|
+
isDragOver: (id: string) => boolean
|
|
79
|
+
announcement: Ref<string>
|
|
80
|
+
announcerStyle: typeof REORDER_ANNOUNCER_STYLE
|
|
81
|
+
announce: (message: string) => void
|
|
82
|
+
/** Bind to the draggable element's `@dragstart`. */
|
|
83
|
+
onDragStart: (event: DragEvent, item: T, index: number) => void
|
|
84
|
+
/** Bind to `@dragover` — calls `preventDefault()` so the drop is accepted. */
|
|
85
|
+
onDragOver: (event: DragEvent, item: T, index: number) => void
|
|
86
|
+
/** Bind to `@dragleave`. */
|
|
87
|
+
onDragLeave: () => void
|
|
88
|
+
/** Bind to `@drop`. */
|
|
89
|
+
onDrop: (event: DragEvent, item: T, index: number) => void
|
|
90
|
+
/** Bind to `@dragend`. */
|
|
91
|
+
onDragEnd: () => void
|
|
92
|
+
/**
|
|
93
|
+
* Bind to the item's (or its drag handle's) `@keydown`: Alt+ArrowUp / Alt+ArrowLeft
|
|
94
|
+
* moves the item one position earlier, Alt+ArrowDown / Alt+ArrowRight one later.
|
|
95
|
+
*/
|
|
96
|
+
onKeydown: (event: KeyboardEvent, item: T, index: number) => void
|
|
97
|
+
/** Moves an item between two indices of `items()` programmatically. */
|
|
98
|
+
move: (fromIndex: number, toIndex: number, source?: 'keyboard' | 'drag') => void
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Pure helper: a copy of `items` with the entry at `from` moved to `to`. */
|
|
102
|
+
export function reorderList<T>(items: T[], from: number, to: number): T[] {
|
|
103
|
+
const next = [...items]
|
|
104
|
+
const [moved] = next.splice(from, 1)
|
|
105
|
+
next.splice(to, 0, moved)
|
|
106
|
+
return next
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Keyboard-accessible list reordering shared by the SDK's drag-to-reorder lists.
|
|
111
|
+
* It owns the HTML5 drag state, the drop-index maths, and an Alt+Arrow keyboard
|
|
112
|
+
* alternative that announces every move through a polite live region — so a
|
|
113
|
+
* reorder never depends on a pointer.
|
|
114
|
+
*/
|
|
115
|
+
export function useListReorder<T>(options: UseListReorderOptions<T>): UseListReorderReturn<T> {
|
|
116
|
+
const draggingId = ref<string | null>(null)
|
|
117
|
+
const dragOverId = ref<string | null>(null)
|
|
118
|
+
const { announcement, announce, announcerStyle } = useReorderAnnouncer()
|
|
119
|
+
const noun = options.itemNoun ?? 'Item'
|
|
120
|
+
|
|
121
|
+
function isDisabled(): boolean {
|
|
122
|
+
return options.disabled?.() === true
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function idFor(item: T, index: number): string {
|
|
126
|
+
return options.getId(item, index)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Index within `items()`, resolved by id so filtered/sorted renders still map back. */
|
|
130
|
+
function indexOf(item: T, renderIndex: number): number {
|
|
131
|
+
const id = idFor(item, renderIndex)
|
|
132
|
+
const found = options.items().findIndex((candidate, index) => idFor(candidate, index) === id)
|
|
133
|
+
return found === -1 ? renderIndex : found
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function labelFor(index: number): string {
|
|
137
|
+
const item = options.items()[index]
|
|
138
|
+
if (item === undefined) return noun
|
|
139
|
+
return options.getLabel?.(item, index) ?? idFor(item, index)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function clearDragState() {
|
|
143
|
+
draggingId.value = null
|
|
144
|
+
dragOverId.value = null
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function move(fromIndex: number, toIndex: number, source: 'keyboard' | 'drag' = 'keyboard') {
|
|
148
|
+
if (isDisabled()) return
|
|
149
|
+
const items = options.items()
|
|
150
|
+
if (
|
|
151
|
+
fromIndex === toIndex ||
|
|
152
|
+
fromIndex < 0 ||
|
|
153
|
+
toIndex < 0 ||
|
|
154
|
+
fromIndex >= items.length ||
|
|
155
|
+
toIndex >= items.length
|
|
156
|
+
) return
|
|
157
|
+
|
|
158
|
+
const movedId = idFor(items[fromIndex], fromIndex)
|
|
159
|
+
const label = labelFor(fromIndex)
|
|
160
|
+
options.onReorder({
|
|
161
|
+
fromIndex,
|
|
162
|
+
toIndex,
|
|
163
|
+
items: reorderList(items, fromIndex, toIndex),
|
|
164
|
+
source,
|
|
165
|
+
})
|
|
166
|
+
announce(`${label} moved to position ${toIndex + 1} of ${items.length}.`)
|
|
167
|
+
void nextTick(() => options.getElement?.(movedId)?.focus())
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function onDragStart(event: DragEvent, item: T, index: number) {
|
|
171
|
+
if (isDisabled()) return
|
|
172
|
+
const id = idFor(item, index)
|
|
173
|
+
draggingId.value = id
|
|
174
|
+
if (event.dataTransfer) {
|
|
175
|
+
event.dataTransfer.effectAllowed = 'move'
|
|
176
|
+
event.dataTransfer.setData('text/plain', id)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function onDragOver(event: DragEvent, item: T, index: number) {
|
|
181
|
+
if (isDisabled() || draggingId.value === null) return
|
|
182
|
+
event.preventDefault()
|
|
183
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'
|
|
184
|
+
const id = idFor(item, index)
|
|
185
|
+
dragOverId.value = id === draggingId.value ? null : id
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function onDragLeave() {
|
|
189
|
+
dragOverId.value = null
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function onDrop(event: DragEvent, item: T, index: number) {
|
|
193
|
+
if (isDisabled() || draggingId.value === null) {
|
|
194
|
+
clearDragState()
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
event.preventDefault()
|
|
198
|
+
|
|
199
|
+
const items = options.items()
|
|
200
|
+
const fromIndex = items.findIndex((candidate, i) => idFor(candidate, i) === draggingId.value)
|
|
201
|
+
const toIndex = indexOf(item, index)
|
|
202
|
+
clearDragState()
|
|
203
|
+
if (fromIndex === -1) return
|
|
204
|
+
move(fromIndex, toIndex, 'drag')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function onDragEnd() {
|
|
208
|
+
clearDragState()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function onKeydown(event: KeyboardEvent, item: T, index: number) {
|
|
212
|
+
if (isDisabled() || !event.altKey) return
|
|
213
|
+
const direction =
|
|
214
|
+
event.key === 'ArrowUp' || event.key === 'ArrowLeft'
|
|
215
|
+
? -1
|
|
216
|
+
: event.key === 'ArrowDown' || event.key === 'ArrowRight'
|
|
217
|
+
? 1
|
|
218
|
+
: 0
|
|
219
|
+
if (direction === 0) return
|
|
220
|
+
|
|
221
|
+
event.preventDefault()
|
|
222
|
+
const fromIndex = indexOf(item, index)
|
|
223
|
+
move(fromIndex, fromIndex + direction, 'keyboard')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
draggingId,
|
|
228
|
+
dragOverId,
|
|
229
|
+
isDragging: (id: string) => draggingId.value === id,
|
|
230
|
+
isDragOver: (id: string) => dragOverId.value === id,
|
|
231
|
+
announcement,
|
|
232
|
+
announcerStyle,
|
|
233
|
+
announce,
|
|
234
|
+
onDragStart,
|
|
235
|
+
onDragOver,
|
|
236
|
+
onDragLeave,
|
|
237
|
+
onDrop,
|
|
238
|
+
onDragEnd,
|
|
239
|
+
onKeydown,
|
|
240
|
+
move,
|
|
241
|
+
}
|
|
242
|
+
}
|