@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
|
@@ -5,6 +5,14 @@ import type { DataFrameColumn, SortState, PaginationState } from '../types'
|
|
|
5
5
|
import { useSortedItems } from '../composables/useSortedItems'
|
|
6
6
|
import { useTextSearch } from '../composables/useTextSearch'
|
|
7
7
|
import { useListSelection } from '../composables/useListSelection'
|
|
8
|
+
import DataStateInternal from './internal/DataStateInternal.vue'
|
|
9
|
+
import LoadingSpinner from './LoadingSpinner.vue'
|
|
10
|
+
import { tableDensityToSize, tryUseSettingsStore } from '../stores/settings'
|
|
11
|
+
|
|
12
|
+
/** Inbox glyph kept from the pre-1.2 hand-rolled empty state. */
|
|
13
|
+
const EMPTY_ICON_PATH
|
|
14
|
+
= 'M22 12L16 12L14 15L10 15L8 12L2 12'
|
|
15
|
+
+ 'M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z'
|
|
8
16
|
|
|
9
17
|
/**
|
|
10
18
|
* DataFrame - Data table with sorting, searching, pagination, and row selection.
|
|
@@ -34,7 +42,11 @@ interface Props {
|
|
|
34
42
|
striped?: boolean
|
|
35
43
|
/** Show table borders */
|
|
36
44
|
bordered?: boolean
|
|
37
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* Table size. Left unset, it follows the platform's `tableDensity` setting
|
|
47
|
+
* (compact → `sm`, normal → `md`, comfortable → `lg`) and falls back to
|
|
48
|
+
* `md` when no settings store is installed.
|
|
49
|
+
*/
|
|
38
50
|
size?: 'sm' | 'md' | 'lg'
|
|
39
51
|
/** Make header sticky on scroll */
|
|
40
52
|
stickyHeader?: boolean
|
|
@@ -42,6 +54,11 @@ interface Props {
|
|
|
42
54
|
maxHeight?: string | number
|
|
43
55
|
/** Show loading overlay */
|
|
44
56
|
loading?: boolean
|
|
57
|
+
/**
|
|
58
|
+
* Error message. When set, the table body is replaced by an error state
|
|
59
|
+
* instead of the empty state. `null`/`undefined` renders normally.
|
|
60
|
+
*/
|
|
61
|
+
error?: string | null
|
|
45
62
|
/** Empty state message */
|
|
46
63
|
emptyText?: string
|
|
47
64
|
/** Enable column sorting */
|
|
@@ -75,9 +92,9 @@ interface Props {
|
|
|
75
92
|
const props = withDefaults(defineProps<Props>(), {
|
|
76
93
|
striped: true,
|
|
77
94
|
bordered: true,
|
|
78
|
-
size: 'md',
|
|
79
95
|
stickyHeader: false,
|
|
80
96
|
loading: false,
|
|
97
|
+
error: null,
|
|
81
98
|
emptyText: 'No data',
|
|
82
99
|
sortable: false,
|
|
83
100
|
pagination: false,
|
|
@@ -108,6 +125,13 @@ const emit = defineEmits<{
|
|
|
108
125
|
'delete-row': [row: Record<string, unknown>, index: number]
|
|
109
126
|
}>()
|
|
110
127
|
|
|
128
|
+
const settings = tryUseSettingsStore()
|
|
129
|
+
|
|
130
|
+
/** Explicit `size` wins; otherwise the user's table density, else `md`. */
|
|
131
|
+
const size = computed<'sm' | 'md' | 'lg'>(() =>
|
|
132
|
+
props.size ?? (settings ? tableDensityToSize(settings.tableDensity) : 'md'),
|
|
133
|
+
)
|
|
134
|
+
|
|
111
135
|
const searchQuery = ref('')
|
|
112
136
|
const internalSort = ref<SortState | null>(null)
|
|
113
137
|
|
|
@@ -219,6 +243,21 @@ function isColumnSortable(col: DataFrameColumn): boolean {
|
|
|
219
243
|
return col.sortable === true || (props.sortable && col.sortable !== false)
|
|
220
244
|
}
|
|
221
245
|
|
|
246
|
+
/** `aria-sort` for the header cell, omitted on columns that cannot be sorted. */
|
|
247
|
+
function columnAriaSort(col: DataFrameColumn): 'ascending' | 'descending' | 'none' | undefined {
|
|
248
|
+
if (!isColumnSortable(col)) return undefined
|
|
249
|
+
if (activeSort.value?.key !== col.key || !activeSort.value.direction) return 'none'
|
|
250
|
+
return activeSort.value.direction === 'asc' ? 'ascending' : 'descending'
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Native buttons activate on Enter/Space, but the default is prevented here so
|
|
254
|
+
// the header sorts exactly once per key press in every engine.
|
|
255
|
+
function handleSortKeydown(event: KeyboardEvent, col: DataFrameColumn) {
|
|
256
|
+
if (event.key !== 'Enter' && event.key !== ' ') return
|
|
257
|
+
event.preventDefault()
|
|
258
|
+
handleSort(col)
|
|
259
|
+
}
|
|
260
|
+
|
|
222
261
|
// Selection
|
|
223
262
|
const pageRowKeys = computed(() =>
|
|
224
263
|
paginatedData.value.map((row, i) => getRowKey(row, i))
|
|
@@ -264,6 +303,35 @@ function columnStyle(col: DataFrameColumn) {
|
|
|
264
303
|
}
|
|
265
304
|
}
|
|
266
305
|
|
|
306
|
+
const RESIZE_KEY_STEP = 16
|
|
307
|
+
|
|
308
|
+
function columnMinWidth(col: DataFrameColumn): number {
|
|
309
|
+
return Math.max(MIN_COL_WIDTH, typeof col.minWidth === 'number' ? col.minWidth : 0)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Current px width of a column when one is known (resized, or a numeric `width`). */
|
|
313
|
+
function columnWidthValue(col: DataFrameColumn): number | undefined {
|
|
314
|
+
const width = activeWidths.value[col.key] ?? col.width
|
|
315
|
+
return typeof width === 'number' ? width : undefined
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function applyColumnWidth(key: string, width: number) {
|
|
319
|
+
const widths = { ...activeWidths.value, [key]: width }
|
|
320
|
+
if (props.columnWidths === undefined) internalWidths.value = widths
|
|
321
|
+
emit('update:columnWidths', widths)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Left/Right resize the column by a fixed step; the handle is a `role="slider"`. */
|
|
325
|
+
function handleResizeKeydown(event: KeyboardEvent, col: DataFrameColumn) {
|
|
326
|
+
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
|
|
327
|
+
event.preventDefault()
|
|
328
|
+
const th = (event.target as HTMLElement).closest('th')
|
|
329
|
+
const base =
|
|
330
|
+
columnWidthValue(col) ?? (th instanceof HTMLElement ? th.offsetWidth : MIN_COL_WIDTH)
|
|
331
|
+
const delta = event.key === 'ArrowRight' ? RESIZE_KEY_STEP : -RESIZE_KEY_STEP
|
|
332
|
+
applyColumnWidth(col.key, Math.max(columnMinWidth(col), base + delta))
|
|
333
|
+
}
|
|
334
|
+
|
|
267
335
|
let resizeState: { key: string; startX: number; startWidth: number; min: number } | null = null
|
|
268
336
|
|
|
269
337
|
function startResize(col: DataFrameColumn, event: MouseEvent) {
|
|
@@ -272,8 +340,7 @@ function startResize(col: DataFrameColumn, event: MouseEvent) {
|
|
|
272
340
|
const th = (event.target as HTMLElement).closest('th')
|
|
273
341
|
const startWidth =
|
|
274
342
|
th instanceof HTMLElement ? th.offsetWidth : (typeof col.width === 'number' ? col.width : MIN_COL_WIDTH)
|
|
275
|
-
|
|
276
|
-
resizeState = { key: col.key, startX: event.clientX, startWidth, min }
|
|
343
|
+
resizeState = { key: col.key, startX: event.clientX, startWidth, min: columnMinWidth(col) }
|
|
277
344
|
window.addEventListener('mousemove', onResizeMove)
|
|
278
345
|
window.addEventListener('mouseup', stopResize)
|
|
279
346
|
}
|
|
@@ -281,9 +348,7 @@ function startResize(col: DataFrameColumn, event: MouseEvent) {
|
|
|
281
348
|
function onResizeMove(event: MouseEvent) {
|
|
282
349
|
if (!resizeState) return
|
|
283
350
|
const next = Math.max(resizeState.min, resizeState.startWidth + (event.clientX - resizeState.startX))
|
|
284
|
-
|
|
285
|
-
if (props.columnWidths === undefined) internalWidths.value = widths
|
|
286
|
-
emit('update:columnWidths', widths)
|
|
351
|
+
applyColumnWidth(resizeState.key, next)
|
|
287
352
|
}
|
|
288
353
|
|
|
289
354
|
function stopResize() {
|
|
@@ -355,8 +420,15 @@ const wrapperStyle = computed(() => {
|
|
|
355
420
|
},
|
|
356
421
|
]"
|
|
357
422
|
:style="columnStyle(col)"
|
|
358
|
-
|
|
423
|
+
:aria-sort="columnAriaSort(col)"
|
|
359
424
|
>
|
|
425
|
+
<component
|
|
426
|
+
:is="isColumnSortable(col) ? 'button' : 'div'"
|
|
427
|
+
:type="isColumnSortable(col) ? 'button' : undefined"
|
|
428
|
+
class="mint-dataframe__th-button"
|
|
429
|
+
@click="isColumnSortable(col) ? handleSort(col) : undefined"
|
|
430
|
+
@keydown="isColumnSortable(col) ? handleSortKeydown($event, col) : undefined"
|
|
431
|
+
>
|
|
360
432
|
<slot :name="`header-${col.key}`" :column="col">
|
|
361
433
|
<div class="mint-dataframe__th-content">
|
|
362
434
|
<span>{{ col.label }}</span>
|
|
@@ -380,13 +452,18 @@ const wrapperStyle = computed(() => {
|
|
|
380
452
|
</svg>
|
|
381
453
|
</div>
|
|
382
454
|
</slot>
|
|
455
|
+
</component>
|
|
383
456
|
<span
|
|
384
457
|
v-if="resizable"
|
|
385
458
|
class="mint-dataframe__resize-handle"
|
|
386
|
-
role="
|
|
459
|
+
role="slider"
|
|
460
|
+
tabindex="0"
|
|
387
461
|
aria-orientation="vertical"
|
|
388
|
-
aria-label="Resize column"
|
|
462
|
+
:aria-label="`Resize ${col.label} column`"
|
|
463
|
+
:aria-valuemin="columnMinWidth(col)"
|
|
464
|
+
:aria-valuenow="columnWidthValue(col)"
|
|
389
465
|
@mousedown="startResize(col, $event)"
|
|
466
|
+
@keydown="handleResizeKeydown($event, col)"
|
|
390
467
|
@click.stop
|
|
391
468
|
/>
|
|
392
469
|
</th>
|
|
@@ -459,23 +536,24 @@ const wrapperStyle = computed(() => {
|
|
|
459
536
|
</tbody>
|
|
460
537
|
</table>
|
|
461
538
|
|
|
539
|
+
<!-- Error state -->
|
|
540
|
+
<div v-if="error" class="mint-dataframe__state">
|
|
541
|
+
<slot name="error" :error="error">
|
|
542
|
+
<DataStateInternal state="error" :title="error" />
|
|
543
|
+
</slot>
|
|
544
|
+
</div>
|
|
545
|
+
|
|
462
546
|
<!-- Empty state -->
|
|
463
|
-
<div v-if="paginatedData.length === 0 && !loading" class="mint-
|
|
547
|
+
<div v-else-if="paginatedData.length === 0 && !loading" class="mint-dataframe__state">
|
|
464
548
|
<slot name="empty">
|
|
465
|
-
<
|
|
466
|
-
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12" /><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
|
|
467
|
-
</svg>
|
|
468
|
-
<p class="mint-dataframe__empty-text">{{ emptyText }}</p>
|
|
549
|
+
<DataStateInternal state="empty" :title="emptyText" :icon-path="EMPTY_ICON_PATH" />
|
|
469
550
|
</slot>
|
|
470
551
|
</div>
|
|
471
552
|
|
|
472
553
|
<!-- Loading overlay -->
|
|
473
554
|
<div v-if="loading" class="mint-dataframe__loading">
|
|
474
555
|
<slot name="loading">
|
|
475
|
-
<
|
|
476
|
-
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" opacity="0.25" />
|
|
477
|
-
<path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" stroke-width="3" stroke-linecap="round" />
|
|
478
|
-
</svg>
|
|
556
|
+
<LoadingSpinner size="lg" />
|
|
479
557
|
</slot>
|
|
480
558
|
</div>
|
|
481
559
|
</div>
|
|
@@ -514,7 +592,3 @@ const wrapperStyle = computed(() => {
|
|
|
514
592
|
</div>
|
|
515
593
|
</div>
|
|
516
594
|
</template>
|
|
517
|
-
|
|
518
|
-
<style>
|
|
519
|
-
@import '../styles/components/dataframe.css';
|
|
520
|
-
</style>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Input that opens a calendar dropdown for selecting a single date, with clearable support. */
|
|
3
|
-
import {
|
|
3
|
+
import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom'
|
|
4
|
+
import { ref, computed, nextTick, onBeforeUnmount, watch } from 'vue'
|
|
4
5
|
import { useCalendarGrid, type CalendarGridDay } from '../composables/useCalendarGrid'
|
|
5
6
|
import { useDropdownState } from '../composables/useDropdownState'
|
|
6
|
-
import {
|
|
7
|
+
import { useFocusTrap } from '../composables/useFocusTrap'
|
|
7
8
|
import CalendarGridPanelInternal from './internal/CalendarGridPanelInternal.vue'
|
|
8
9
|
|
|
9
10
|
interface Props {
|
|
@@ -28,12 +29,14 @@ const emit = defineEmits<{
|
|
|
28
29
|
'update:modelValue': [value: string | undefined]
|
|
29
30
|
}>()
|
|
30
31
|
|
|
31
|
-
const
|
|
32
|
-
const dropdownRef = ref<HTMLDivElement | null>(null)
|
|
32
|
+
const dropdownRef = ref<HTMLElement | null>(null)
|
|
33
33
|
const dropdownStyle = ref<Record<string, string>>({})
|
|
34
|
-
const { isOpen, rootRef: containerRef, close, toggle } = useDropdownState({
|
|
34
|
+
const { isOpen, rootRef: containerRef, triggerRef, open, close, toggle } = useDropdownState({
|
|
35
35
|
insideRefs: [dropdownRef],
|
|
36
36
|
})
|
|
37
|
+
// The panel is a non-modal dialog: Tab cycles inside it while it is open, and
|
|
38
|
+
// focus returns to the input when it closes.
|
|
39
|
+
useFocusTrap({ containerRef: dropdownRef, active: isOpen })
|
|
37
40
|
const {
|
|
38
41
|
weekDays,
|
|
39
42
|
monthYear,
|
|
@@ -88,41 +91,88 @@ function toggleCalendar() {
|
|
|
88
91
|
toggle()
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
function
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
+
function handleTriggerKeydown(event: KeyboardEvent) {
|
|
95
|
+
if (props.disabled || isOpen.value) return
|
|
96
|
+
if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {
|
|
97
|
+
event.preventDefault()
|
|
98
|
+
open()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// floating-ui keeps the panel anchored (flip/shift) while the page scrolls,
|
|
103
|
+
// instead of the panel closing on every scroll or resize.
|
|
104
|
+
let stopAutoUpdate: (() => void) | null = null
|
|
105
|
+
|
|
106
|
+
async function updateDropdownPosition() {
|
|
107
|
+
const trigger = triggerRef.value
|
|
108
|
+
const panel = dropdownRef.value
|
|
109
|
+
if (!trigger || !panel) return
|
|
110
|
+
|
|
111
|
+
const { x, y } = await computePosition(trigger, panel, {
|
|
112
|
+
placement: 'bottom-start',
|
|
113
|
+
strategy: 'fixed',
|
|
114
|
+
middleware: [offset(4), flip(), shift({ padding: 8 })],
|
|
115
|
+
})
|
|
94
116
|
dropdownStyle.value = {
|
|
95
117
|
position: 'fixed',
|
|
96
|
-
top: `${
|
|
97
|
-
left: `${
|
|
98
|
-
width: `${Math.max(
|
|
118
|
+
top: `${y}px`,
|
|
119
|
+
left: `${x}px`,
|
|
120
|
+
width: `${Math.max(trigger.getBoundingClientRect().width, 288)}px`,
|
|
99
121
|
zIndex: '9999',
|
|
100
122
|
}
|
|
101
123
|
}
|
|
102
124
|
|
|
103
|
-
function
|
|
104
|
-
|
|
125
|
+
function stopPositioning() {
|
|
126
|
+
stopAutoUpdate?.()
|
|
127
|
+
stopAutoUpdate = null
|
|
105
128
|
}
|
|
106
129
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
130
|
+
function startPositioning() {
|
|
131
|
+
const trigger = triggerRef.value
|
|
132
|
+
const panel = dropdownRef.value
|
|
133
|
+
if (!trigger || !panel) return
|
|
134
|
+
stopPositioning()
|
|
135
|
+
stopAutoUpdate = autoUpdate(trigger, panel, () => void updateDropdownPosition())
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Moves focus into the calendar grid: selected day, else today, else the first day. */
|
|
139
|
+
function focusCalendar() {
|
|
140
|
+
const panel = dropdownRef.value
|
|
141
|
+
if (!panel) return
|
|
142
|
+
const target =
|
|
143
|
+
panel.querySelector<HTMLElement>('.mint-date-picker__day--selected') ??
|
|
144
|
+
panel.querySelector<HTMLElement>('.mint-date-picker__day--today') ??
|
|
145
|
+
panel.querySelector<HTMLElement>('.mint-date-picker__day:not([disabled])')
|
|
146
|
+
target?.focus()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
watch(isOpen, async (opened) => {
|
|
150
|
+
if (!opened) {
|
|
151
|
+
stopPositioning()
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
if (selectedDate.value) {
|
|
155
|
+
setCurrentMonthFromDate(selectedDate.value)
|
|
113
156
|
}
|
|
157
|
+
await nextTick()
|
|
158
|
+
startPositioning()
|
|
159
|
+
// A second tick so this focus lands after the focus trap's initial focus.
|
|
160
|
+
await nextTick()
|
|
161
|
+
focusCalendar()
|
|
114
162
|
})
|
|
115
163
|
|
|
116
|
-
|
|
117
|
-
useEventListener(() => window, 'resize', handleScrollOrResize)
|
|
164
|
+
onBeforeUnmount(stopPositioning)
|
|
118
165
|
</script>
|
|
119
166
|
|
|
120
167
|
<template>
|
|
121
168
|
<div ref="containerRef" class="mint-date-picker">
|
|
122
|
-
<div
|
|
169
|
+
<div class="mint-date-picker__input-wrapper">
|
|
123
170
|
<input
|
|
171
|
+
ref="triggerRef"
|
|
124
172
|
type="text"
|
|
125
173
|
readonly
|
|
174
|
+
aria-haspopup="dialog"
|
|
175
|
+
:aria-expanded="isOpen"
|
|
126
176
|
:value="displayValue"
|
|
127
177
|
:placeholder="placeholder || 'Select date'"
|
|
128
178
|
:disabled="disabled"
|
|
@@ -133,6 +183,7 @@ useEventListener(() => window, 'resize', handleScrollOrResize)
|
|
|
133
183
|
disabled ? 'mint-date-picker__input--disabled' : '',
|
|
134
184
|
]"
|
|
135
185
|
@click="toggleCalendar"
|
|
186
|
+
@keydown="handleTriggerKeydown"
|
|
136
187
|
/>
|
|
137
188
|
<div class="mint-date-picker__icon">
|
|
138
189
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -156,7 +207,6 @@ useEventListener(() => window, 'resize', handleScrollOrResize)
|
|
|
156
207
|
class="mint-date-picker__dropdown"
|
|
157
208
|
:style="dropdownStyle"
|
|
158
209
|
role="dialog"
|
|
159
|
-
aria-modal="true"
|
|
160
210
|
aria-label="Date picker"
|
|
161
211
|
>
|
|
162
212
|
<CalendarGridPanelInternal
|
|
@@ -191,7 +241,3 @@ useEventListener(() => window, 'resize', handleScrollOrResize)
|
|
|
191
241
|
</Teleport>
|
|
192
242
|
</div>
|
|
193
243
|
</template>
|
|
194
|
-
|
|
195
|
-
<style>
|
|
196
|
-
@import '../styles/components/date-picker.css';
|
|
197
|
-
</style>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Combined date and time picker with a dropdown calendar, time slots, and 12/24 h format support. */
|
|
3
|
-
import { computed, watch, nextTick } from 'vue'
|
|
3
|
+
import { computed, ref, watch, nextTick } from 'vue'
|
|
4
4
|
import { useCalendarGrid, type CalendarGridDay } from '../composables/useCalendarGrid'
|
|
5
5
|
import { useDropdownState } from '../composables/useDropdownState'
|
|
6
|
+
import { useFocusTrap } from '../composables/useFocusTrap'
|
|
6
7
|
import { formatTime, formatTimeSlot, generateTimeSlots, toMinutes } from '../composables/useTimeUtils'
|
|
7
8
|
import CalendarGridPanelInternal from './internal/CalendarGridPanelInternal.vue'
|
|
8
9
|
|
|
@@ -35,7 +36,10 @@ const emit = defineEmits<{
|
|
|
35
36
|
'update:modelValue': [value: string | undefined]
|
|
36
37
|
}>()
|
|
37
38
|
|
|
38
|
-
const
|
|
39
|
+
const dropdownRef = ref<HTMLElement | null>(null)
|
|
40
|
+
const { isOpen, rootRef, triggerRef, open, close, toggle } = useDropdownState()
|
|
41
|
+
// Non-modal dialog: Tab stays inside the panel, focus returns to the input on close.
|
|
42
|
+
useFocusTrap({ containerRef: dropdownRef, active: isOpen })
|
|
39
43
|
const {
|
|
40
44
|
weekDays,
|
|
41
45
|
monthYear,
|
|
@@ -148,18 +152,42 @@ function toggleDropdown() {
|
|
|
148
152
|
toggle()
|
|
149
153
|
}
|
|
150
154
|
|
|
151
|
-
|
|
152
|
-
if (
|
|
155
|
+
function handleTriggerKeydown(event: KeyboardEvent) {
|
|
156
|
+
if (props.disabled || isOpen.value) return
|
|
157
|
+
if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {
|
|
158
|
+
event.preventDefault()
|
|
159
|
+
open()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Moves focus into the calendar grid: selected day, else today, else the first day. */
|
|
164
|
+
function focusCalendar() {
|
|
165
|
+
const panel = dropdownRef.value
|
|
166
|
+
if (!panel) return
|
|
167
|
+
const target =
|
|
168
|
+
panel.querySelector<HTMLElement>('.mint-date-picker__day--selected') ??
|
|
169
|
+
panel.querySelector<HTMLElement>('.mint-date-picker__day--today') ??
|
|
170
|
+
panel.querySelector<HTMLElement>('.mint-date-picker__day:not([disabled])')
|
|
171
|
+
target?.focus()
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
watch(isOpen, async (opened) => {
|
|
175
|
+
if (!opened) return
|
|
176
|
+
if (selectedDate.value) {
|
|
153
177
|
setCurrentMonthFromDate(selectedDate.value)
|
|
154
|
-
nextTick(() => {
|
|
155
|
-
// Scroll time grid to selected time
|
|
156
|
-
const grid = rootRef.value?.querySelector('.mint-datetime-picker__time-grid')
|
|
157
|
-
const active = rootRef.value?.querySelector('.mint-datetime-picker__time-chip--active')
|
|
158
|
-
if (grid && active) {
|
|
159
|
-
active.scrollIntoView({ block: 'center' })
|
|
160
|
-
}
|
|
161
|
-
})
|
|
162
178
|
}
|
|
179
|
+
await nextTick()
|
|
180
|
+
if (selectedDate.value) {
|
|
181
|
+
// Scroll time grid to selected time
|
|
182
|
+
const grid = dropdownRef.value?.querySelector('.mint-datetime-picker__time-grid')
|
|
183
|
+
const active = dropdownRef.value?.querySelector('.mint-datetime-picker__time-chip--active')
|
|
184
|
+
if (grid && active) {
|
|
185
|
+
active.scrollIntoView({ block: 'center' })
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// A second tick so this focus lands after the focus trap's initial focus.
|
|
189
|
+
await nextTick()
|
|
190
|
+
focusCalendar()
|
|
163
191
|
})
|
|
164
192
|
</script>
|
|
165
193
|
|
|
@@ -172,8 +200,11 @@ watch(isOpen, (open) => {
|
|
|
172
200
|
</svg>
|
|
173
201
|
</div>
|
|
174
202
|
<input
|
|
203
|
+
ref="triggerRef"
|
|
175
204
|
type="text"
|
|
176
205
|
readonly
|
|
206
|
+
aria-haspopup="dialog"
|
|
207
|
+
:aria-expanded="isOpen"
|
|
177
208
|
:value="displayValue"
|
|
178
209
|
:placeholder="placeholder"
|
|
179
210
|
:disabled="disabled"
|
|
@@ -185,6 +216,7 @@ watch(isOpen, (open) => {
|
|
|
185
216
|
]"
|
|
186
217
|
aria-label="Select date and time"
|
|
187
218
|
@click="toggleDropdown"
|
|
219
|
+
@keydown="handleTriggerKeydown"
|
|
188
220
|
/>
|
|
189
221
|
<div class="mint-datetime-picker__icon-clock">
|
|
190
222
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -203,9 +235,9 @@ watch(isOpen, (open) => {
|
|
|
203
235
|
>
|
|
204
236
|
<div
|
|
205
237
|
v-if="isOpen"
|
|
238
|
+
ref="dropdownRef"
|
|
206
239
|
class="mint-datetime-picker__dropdown"
|
|
207
240
|
role="dialog"
|
|
208
|
-
aria-modal="true"
|
|
209
241
|
aria-label="Date and time picker"
|
|
210
242
|
>
|
|
211
243
|
<!-- Calendar section -->
|
|
@@ -265,7 +297,3 @@ watch(isOpen, (open) => {
|
|
|
265
297
|
</Transition>
|
|
266
298
|
</div>
|
|
267
299
|
</template>
|
|
268
|
-
|
|
269
|
-
<style>
|
|
270
|
-
@import '../styles/components/datetime-picker.css';
|
|
271
|
-
</style>
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
UseControlWorkspaceReturn,
|
|
20
20
|
} from '../composables/useControlSchema'
|
|
21
21
|
import { defineDoseDesignControlModel } from '../composables/useControlSchema'
|
|
22
|
+
import { doseDesignWorkspaceProps } from '../composables/workspaceRecipes'
|
|
22
23
|
import ControlWorkspaceView from './ControlWorkspaceView.vue'
|
|
23
24
|
import DoseCalculator from './DoseCalculator.vue'
|
|
24
25
|
import WellPlate from './WellPlate.vue'
|
|
@@ -158,6 +159,10 @@ const resolvedModel = computed<ControlModel | ControlModelBinding>(() =>
|
|
|
158
159
|
props.model ?? defineDoseDesignControlModel(props.doseDesignOptions)
|
|
159
160
|
)
|
|
160
161
|
|
|
162
|
+
// Thin recipe: forwards the ~25 props this view shares verbatim with ControlWorkspaceView
|
|
163
|
+
// (see workspaceRecipes.ts) instead of spelling each one out in the template.
|
|
164
|
+
const controlWorkspaceRecipe = computed(() => doseDesignWorkspaceProps(props, resolvedModel.value))
|
|
165
|
+
|
|
161
166
|
function resolvedPlateId(): string {
|
|
162
167
|
return props.plateId ?? props.doseDesignOptions.componentProps?.plateId ?? 'plate'
|
|
163
168
|
}
|
|
@@ -200,31 +205,7 @@ function slotProps(slotProps: {
|
|
|
200
205
|
|
|
201
206
|
<template>
|
|
202
207
|
<ControlWorkspaceView
|
|
203
|
-
|
|
204
|
-
:workspace="workspace"
|
|
205
|
-
:control-options="controlOptions"
|
|
206
|
-
:initial-values="initialValues"
|
|
207
|
-
:model-value="modelValue"
|
|
208
|
-
:values="values"
|
|
209
|
-
:title="title"
|
|
210
|
-
:subtitle="subtitle"
|
|
211
|
-
:top-bar-variant="topBarVariant"
|
|
212
|
-
:sidebar-width="sidebarWidth"
|
|
213
|
-
:sidebar-variant="sidebarVariant"
|
|
214
|
-
:responsive-sidebar="responsiveSidebar"
|
|
215
|
-
:sidebar-position="sidebarPosition"
|
|
216
|
-
:sidebar-title="sidebarTitle"
|
|
217
|
-
:sidebar-subtitle="sidebarSubtitle"
|
|
218
|
-
:sidebar-badge="sidebarBadge"
|
|
219
|
-
:floating="floating"
|
|
220
|
-
:dense="dense"
|
|
221
|
-
:show-settings="showSettings"
|
|
222
|
-
:show-form-actions="showFormActions"
|
|
223
|
-
:form-enhancements="formEnhancements"
|
|
224
|
-
:form-loading="formLoading"
|
|
225
|
-
:form-disabled="formDisabled"
|
|
226
|
-
:form-readonly="formReadonly"
|
|
227
|
-
:form-size="formSize"
|
|
208
|
+
v-bind="controlWorkspaceRecipe"
|
|
228
209
|
@update:model-value="emit('update:modelValue', $event)"
|
|
229
210
|
@update:values="emit('update:values', $event)"
|
|
230
211
|
@submit="emit('submit', $event)"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Button that opens a dropdown menu for selecting one option from a list. */
|
|
3
|
-
import { computed } from 'vue'
|
|
3
|
+
import { computed, ref } from 'vue'
|
|
4
4
|
import { useDropdownState } from '../composables/useDropdownState'
|
|
5
|
+
import { useMenuKeyboard } from '../composables/useMenuKeyboard'
|
|
5
6
|
import type { SelectOption, SelectOptionInput, ButtonVariant, ButtonSize } from '../types'
|
|
6
7
|
import { normalizeOptionInput } from '../utils/options'
|
|
7
8
|
|
|
@@ -53,11 +54,25 @@ const emit = defineEmits<{
|
|
|
53
54
|
select: [option: SelectOption<string | number>]
|
|
54
55
|
}>()
|
|
55
56
|
|
|
56
|
-
const { isOpen, rootRef, close, toggle: toggleDropdown } = useDropdownState()
|
|
57
|
+
const { isOpen, rootRef, triggerRef, open, close, toggle: toggleDropdown } = useDropdownState()
|
|
57
58
|
const normalizedOptions = computed<SelectOption<string | number>[]>(() =>
|
|
58
59
|
props.options.map(normalizeOptionInput)
|
|
59
60
|
)
|
|
60
61
|
|
|
62
|
+
const panelRef = ref<HTMLElement | null>(null)
|
|
63
|
+
|
|
64
|
+
function optionItems(): HTMLElement[] {
|
|
65
|
+
if (!panelRef.value) return []
|
|
66
|
+
return Array.from(panelRef.value.querySelectorAll<HTMLElement>('[role="option"]:not([aria-disabled="true"])'))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { handleTriggerKeydown, handlePanelKeydown } = useMenuKeyboard({
|
|
70
|
+
items: optionItems,
|
|
71
|
+
isOpen: () => isOpen.value,
|
|
72
|
+
open,
|
|
73
|
+
close,
|
|
74
|
+
})
|
|
75
|
+
|
|
61
76
|
const selectedLabel = computed(() => {
|
|
62
77
|
if (props.modelValue === undefined) return props.placeholder ?? 'Select...'
|
|
63
78
|
const found = normalizedOptions.value.find(o => o.value === props.modelValue)
|
|
@@ -80,6 +95,7 @@ function selectOption(option: SelectOption<string | number>) {
|
|
|
80
95
|
<template>
|
|
81
96
|
<div ref="rootRef" class="mint-dropdown-button">
|
|
82
97
|
<button
|
|
98
|
+
ref="triggerRef"
|
|
83
99
|
type="button"
|
|
84
100
|
:disabled="disabled || loading"
|
|
85
101
|
:class="[
|
|
@@ -94,6 +110,7 @@ function selectOption(option: SelectOption<string | number>) {
|
|
|
94
110
|
:aria-expanded="isOpen"
|
|
95
111
|
aria-haspopup="listbox"
|
|
96
112
|
@click="toggle"
|
|
113
|
+
@keydown="handleTriggerKeydown"
|
|
97
114
|
>
|
|
98
115
|
<svg v-if="loading" class="mint-dropdown-button__spinner" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
99
116
|
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" opacity="0.25" />
|
|
@@ -116,15 +133,18 @@ function selectOption(option: SelectOption<string | number>) {
|
|
|
116
133
|
<Transition name="mint-dropdown-button-menu">
|
|
117
134
|
<ul
|
|
118
135
|
v-if="isOpen"
|
|
136
|
+
ref="panelRef"
|
|
119
137
|
class="mint-dropdown-button__menu"
|
|
120
138
|
role="listbox"
|
|
121
139
|
:aria-activedescendant="modelValue !== undefined ? `mint-dropdown-option-${modelValue}` : undefined"
|
|
140
|
+
@keydown="handlePanelKeydown"
|
|
122
141
|
>
|
|
123
142
|
<li
|
|
124
143
|
v-for="option in normalizedOptions"
|
|
125
144
|
:id="`mint-dropdown-option-${option.value}`"
|
|
126
145
|
:key="String(option.value)"
|
|
127
146
|
role="option"
|
|
147
|
+
:tabindex="-1"
|
|
128
148
|
:aria-selected="option.value === modelValue"
|
|
129
149
|
:aria-disabled="option.disabled"
|
|
130
150
|
:class="[
|
|
@@ -135,6 +155,8 @@ function selectOption(option: SelectOption<string | number>) {
|
|
|
135
155
|
},
|
|
136
156
|
]"
|
|
137
157
|
@click="selectOption(option)"
|
|
158
|
+
@keydown.enter.prevent="selectOption(option)"
|
|
159
|
+
@keydown.space.prevent="selectOption(option)"
|
|
138
160
|
>
|
|
139
161
|
<span class="mint-dropdown-button__option-label">{{ option.label }}</span>
|
|
140
162
|
<span v-if="option.description" class="mint-dropdown-button__option-description">
|
|
@@ -157,7 +179,3 @@ function selectOption(option: SelectOption<string | number>) {
|
|
|
157
179
|
</Transition>
|
|
158
180
|
</div>
|
|
159
181
|
</template>
|
|
160
|
-
|
|
161
|
-
<style>
|
|
162
|
-
@import '../styles/components/dropdown-button.css';
|
|
163
|
-
</style>
|