@morscherlab/mint-sdk 1.1.25 → 1.2.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -12
- package/dist/{ExperimentPopover-tLwJ7XF4.js → ExperimentPopover-oL0fg_A0.js} +6 -4
- package/dist/ExperimentPopover-oL0fg_A0.js.map +1 -0
- package/dist/{ExperimentSelectorModal-B6P2msT4.js → ExperimentSelectorModal-BeEOomw_.js} +5 -5
- package/dist/ExperimentSelectorModal-BeEOomw_.js.map +1 -0
- package/dist/{SettingsModal-BW3Ar-uy.js → SettingsModal-Dphh3RrI.js} +5 -8
- package/dist/SettingsModal-Dphh3RrI.js.map +1 -0
- package/dist/{EmptyState-p90quq8Z.js → Skeleton-DxtwJ5gt.js} +111 -93
- package/dist/Skeleton-DxtwJ5gt.js.map +1 -0
- package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
- package/dist/__tests__/components/PluginWorkspaceView.internal.test.d.ts +1 -0
- package/dist/__tests__/components/SecretInput.test.d.ts +1 -0
- package/dist/__tests__/components/WellEditPopupInternal.test.d.ts +1 -0
- package/dist/__tests__/composables/useEventStream.test.d.ts +1 -0
- package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
- package/dist/__tests__/styles/cssTokens.test.d.ts +1 -0
- package/dist/components/ActionMenu.vue.d.ts +1 -1
- package/dist/components/AppAvatarMenu.vue.d.ts +4 -0
- package/dist/components/AppPluginSwitcher.vue.d.ts +2 -0
- package/dist/components/AppSidebar.vue.d.ts +1 -1
- package/dist/components/AuditTrail.vue.d.ts +13 -0
- package/dist/components/BatchProgressList.vue.d.ts +17 -0
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
- package/dist/components/DataFrame.vue.d.ts +14 -2
- package/dist/components/DatePicker.vue.d.ts +1 -1
- package/dist/components/DateTimePicker.vue.d.ts +2 -0
- package/dist/components/DropdownButton.vue.d.ts +2 -0
- package/dist/components/ExperimentDataViewer.vue.d.ts +26 -1
- package/dist/components/ExperimentPopover.vue.d.ts +1 -0
- package/dist/components/ExperimentTimeline.vue.d.ts +29 -1
- package/dist/components/FitPanel.vue.d.ts +1 -1
- package/dist/components/LcmsSequenceTable.vue.d.ts +29 -1
- package/dist/components/PluginWorkspaceView.props.d.ts +29 -1
- package/dist/components/PluginWorkspaceView.vue.d.ts +5 -5
- package/dist/components/ProgressBar.vue.d.ts +1 -1
- package/dist/components/ProtocolStepEditor.vue.d.ts +1 -0
- package/dist/components/ReagentList.vue.d.ts +29 -1
- package/dist/components/SampleHierarchyTree.vue.d.ts +29 -1
- package/dist/components/SampleSelector.drag.d.ts +13 -0
- package/dist/components/SampleSelector.tree.d.ts +2 -0
- package/dist/components/SampleSelector.vue.d.ts +34 -1
- package/dist/components/ScheduleCalendar.vue.d.ts +20 -0
- package/dist/components/SecretInput.vue.d.ts +28 -0
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -1
- package/dist/components/TimePicker.vue.d.ts +1 -0
- package/dist/components/WellPlate.geometry.d.ts +15 -0
- package/dist/components/WellPlate.interaction.d.ts +13 -0
- package/dist/components/WellPlate.vue.d.ts +29 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.js +8 -8
- package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -0
- package/dist/components/internal/DataStateInternal.vue.d.ts +43 -0
- package/dist/components/internal/WellEditPopupInternal.vue.d.ts +3 -1
- package/dist/components/internal/WorkspaceControlsInternal.vue.d.ts +23 -0
- package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +70 -0
- package/dist/components/internal/WorkspaceSidebarInternal.vue.d.ts +43 -0
- package/dist/{components-DQt0tcpY.js → components-CgAUzF8F.js} +2491 -2235
- package/dist/components-CgAUzF8F.js.map +1 -0
- package/dist/composables/controlSchemaTypes.d.ts +34 -0
- package/dist/composables/flattenWorkspaceBindings.d.ts +61 -0
- package/dist/composables/index.d.ts +8 -2
- package/dist/composables/index.js +10 -10
- package/dist/composables/pluginWorkspaceModel.d.ts +164 -0
- package/dist/composables/useAsync.d.ts +5 -1
- package/dist/composables/useBioTemplateWorkspace.d.ts +4 -20
- package/dist/composables/useCommandHistory.d.ts +5 -1
- package/dist/composables/useDropdownState.d.ts +18 -2
- package/dist/composables/useEventStream.d.ts +52 -0
- package/dist/composables/useFocusTrap.d.ts +21 -0
- package/dist/composables/useJobsStatusTray.d.ts +1 -1
- package/dist/composables/useListReorder.d.ts +90 -0
- package/dist/composables/useMenuKeyboard.d.ts +29 -0
- package/dist/composables/useOptimisticMutation.d.ts +5 -1
- package/dist/composables/usePluginClient.d.ts +4 -17
- package/dist/composables/usePluginConfig.d.ts +3 -1
- package/dist/composables/usePluginWorkspace.d.ts +51 -0
- package/dist/composables/useRequestSyncState.d.ts +42 -2
- package/dist/composables/useResourceCrud.d.ts +5 -1
- package/dist/composables/useRovingFocus.d.ts +24 -0
- package/dist/composables/useScheduleDrag.d.ts +1 -0
- package/dist/composables/useWellPainting.d.ts +5 -1
- package/dist/composables/useWellPlateAdapter.d.ts +5 -1
- package/dist/composables/useWellPlateValidation.d.ts +5 -1
- package/dist/composables/workspaceRecipes.d.ts +188 -0
- package/dist/{composables-CkXDe7nm.js → composables-B_pia3hO.js} +105 -254
- package/dist/composables-B_pia3hO.js.map +1 -0
- package/dist/{useApi-CDBq6z23.js → errors-_W0-LbA6.js} +102 -3
- package/dist/errors-_W0-LbA6.js.map +1 -0
- package/dist/{experiment-Y9Iz7KYi.js → experiment-Cwl9AYxw.js} +3 -8
- package/dist/experiment-Cwl9AYxw.js.map +1 -0
- package/dist/{experiment-utils-CLnzPPq5.js → experiment-utils-DV3DrvE_.js} +70 -51
- package/dist/experiment-utils-DV3DrvE_.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -15
- package/dist/install.js +2 -7
- package/dist/install.js.map +1 -1
- package/dist/permissions.d.ts +42 -1
- package/dist/permissions.js +20 -10
- package/dist/permissions.js.map +1 -1
- package/dist/stores/index.d.ts +1 -1
- package/dist/stores/index.js +3 -3
- package/dist/stores/settings.d.ts +31 -1
- package/dist/styles.css +11247 -29501
- package/dist/templates/builderReadUtils.d.ts +1 -1
- package/dist/templates/index.js +2 -4
- package/dist/{templates-CovvRZ-9.js → templates-Qq113pyZ.js} +303 -106
- package/dist/templates-Qq113pyZ.js.map +1 -0
- package/dist/types/form-builder.d.ts +1 -1
- package/dist/{useControlSchema-BhoJzWzx.js → useControlSchema-aMZlb4Sc.js} +684 -1216
- package/dist/useControlSchema-aMZlb4Sc.js.map +1 -0
- package/dist/{useDropdownState-DSb3BC3N.js → useDropdownState-DitCtrTT.js} +22 -6
- package/dist/useDropdownState-DitCtrTT.js.map +1 -0
- package/dist/{useExperimentSelector-D0pjj9WH.js → useExperimentSelector-I8HC8hUu.js} +4 -4
- package/dist/{useExperimentSelector-D0pjj9WH.js.map → useExperimentSelector-I8HC8hUu.js.map} +1 -1
- package/dist/{useFormBuilder-DiEcDis2.js → useFormBuilder-TCxNnqeb.js} +2 -2
- package/dist/{useFormBuilder-DiEcDis2.js.map → useFormBuilder-TCxNnqeb.js.map} +1 -1
- package/dist/{useProtocolTemplates-DHwSveE6.js → useProtocolTemplates-BmX_iVBR.js} +660 -197
- package/dist/useProtocolTemplates-BmX_iVBR.js.map +1 -0
- package/dist/utils/color.d.ts +13 -0
- package/dist/utils/errors.d.ts +9 -0
- package/dist/utils/pluginSecrets.d.ts +53 -0
- package/dist/utils/records.d.ts +4 -0
- package/package.json +1 -1
- package/src/__tests__/components/ActionMenu.test.ts +94 -0
- package/src/__tests__/components/AppAvatarMenu.test.ts +123 -0
- package/src/__tests__/components/BaseTabs.test.ts +67 -0
- package/src/__tests__/components/DataFrame.test.ts +183 -11
- package/src/__tests__/components/DataStates.test.ts +176 -0
- package/src/__tests__/components/DatePicker.test.ts +76 -0
- package/src/__tests__/components/DateTimePicker.test.ts +36 -0
- package/src/__tests__/components/DropdownButton.test.ts +134 -0
- package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
- package/src/__tests__/components/GeneratedPluginView.test.ts +0 -5
- package/src/__tests__/components/GroupAssigner.test.ts +53 -0
- package/src/__tests__/components/JobsStatusTray.test.ts +20 -18
- package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
- package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +230 -0
- package/src/__tests__/components/RackEditor.test.ts +35 -0
- package/src/__tests__/components/ReagentList.test.ts +64 -2
- package/src/__tests__/components/RetiredStyles.test.ts +31 -0
- package/src/__tests__/components/SampleSelector.drag.test.ts +51 -1
- package/src/__tests__/components/ScheduleCalendar.test.ts +63 -0
- package/src/__tests__/components/SecretInput.test.ts +87 -0
- package/src/__tests__/components/SegmentedControl.test.ts +88 -0
- package/src/__tests__/components/TimePicker.test.ts +61 -0
- package/src/__tests__/components/WellEditPopupInternal.test.ts +108 -0
- package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
- package/src/__tests__/composables/useAuth.test.ts +25 -0
- package/src/__tests__/composables/useDropdownState.test.ts +58 -1
- package/src/__tests__/composables/useEventStream.test.ts +140 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +103 -0
- package/src/__tests__/composables/useJobsStatusTray.test.ts +25 -161
- package/src/__tests__/composables/useListReorder.test.ts +145 -0
- package/src/__tests__/composables/usePluginClient.test.ts +148 -13
- package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +55 -2
- package/src/__tests__/stores/settings.test.ts +71 -1
- package/src/__tests__/styles/cssTokens.test.ts +131 -0
- package/src/__tests__/utils/permissions.test.ts +68 -0
- package/src/components/ActionMenu.vue +45 -88
- package/src/components/AlertBox.vue +0 -4
- package/src/components/AppAvatarMenu.vue +26 -7
- package/src/components/AppLayout.vue +0 -4
- package/src/components/AppPluginSwitcher.story.vue +87 -0
- package/src/components/AppPluginSwitcher.vue +2 -5
- package/src/components/AppSidebar.vue +0 -4
- package/src/components/AppToastContainer.vue +6 -8
- package/src/components/AppTopBar.vue +0 -4
- package/src/components/ArtifactSaveDialog.vue +0 -4
- package/src/components/ArtifactSelectorModal.vue +0 -4
- package/src/components/AuditTrail.story.vue +18 -0
- package/src/components/AuditTrail.vue +27 -7
- package/src/components/AutoGroupModal.vue +0 -4
- package/src/components/Avatar.vue +0 -4
- package/src/components/BaseButton.vue +0 -4
- package/src/components/BaseCheckbox.vue +0 -4
- package/src/components/BaseInput.vue +0 -4
- package/src/components/BaseModal.vue +8 -60
- package/src/components/BasePill.vue +0 -4
- package/src/components/BaseRadioGroup.vue +0 -4
- package/src/components/BaseSelect.vue +0 -4
- package/src/components/BaseSlider.vue +0 -4
- package/src/components/BaseTabs.vue +26 -5
- package/src/components/BaseTextarea.vue +0 -4
- package/src/components/BaseToggle.vue +0 -4
- package/src/components/BatchProgressList.story.vue +18 -0
- package/src/components/BatchProgressList.vue +32 -4
- package/src/components/BioTemplateExperimentWorkspaceView.vue +10 -21
- package/src/components/BioTemplatePackWorkspaceView.vue +11 -15
- package/src/components/BioTemplatePresetWorkspaceView.vue +8 -13
- package/src/components/Breadcrumb.vue +0 -4
- package/src/components/Calendar.vue +0 -4
- package/src/components/ChartContainer.story.vue +1 -1
- package/src/components/ChartContainer.vue +0 -4
- package/src/components/ChemicalFormula.vue +0 -4
- package/src/components/CollapsibleCard.vue +0 -4
- package/src/components/ColorSlider.vue +0 -4
- package/src/components/ComponentBindingRenderer.vue +1 -3
- package/src/components/ConcentrationInput.vue +0 -4
- package/src/components/ConfirmDialog.vue +0 -4
- package/src/components/ControlWorkspaceView.vue +11 -22
- package/src/components/DataFrame.story.vue +14 -0
- package/src/components/DataFrame.vue +97 -23
- package/src/components/DatePicker.vue +73 -27
- package/src/components/DateTimePicker.vue +45 -17
- package/src/components/Divider.vue +0 -4
- package/src/components/DoseCalculator.vue +0 -4
- package/src/components/DoseDesignWorkspaceView.vue +6 -25
- package/src/components/DropdownButton.vue +24 -6
- package/src/components/EmptyState.vue +0 -4
- package/src/components/ExperimentCodeBadge.vue +0 -4
- package/src/components/ExperimentDataViewer.story.vue +3 -0
- package/src/components/ExperimentDataViewer.vue +33 -13
- package/src/components/ExperimentPopover.vue +2 -5
- package/src/components/ExperimentSelectorModal.vue +0 -4
- package/src/components/ExperimentTimeline.story.vue +18 -0
- package/src/components/ExperimentTimeline.vue +81 -75
- package/src/components/FileUploader.vue +0 -4
- package/src/components/FitPanel.vue +0 -4
- package/src/components/FormActions.story.vue +96 -0
- package/src/components/FormActions.vue +0 -4
- package/src/components/FormBuilder.vue +0 -4
- package/src/components/FormField.vue +0 -4
- package/src/components/FormulaInput.vue +0 -4
- package/src/components/GeneratedPathInput.story.vue +68 -0
- package/src/components/GeneratedPlotlyResult.story.vue +96 -0
- package/src/components/GeneratedPlotlyResult.vue +3 -3
- package/src/components/GeneratedPluginView.story.vue +145 -0
- package/src/components/GeneratedPluginView.vue +10 -10
- package/src/components/GeneratedResultRenderer.story.vue +117 -0
- package/src/components/GeneratedResultRenderer.vue +4 -4
- package/src/components/GroupAssigner.vue +67 -5
- package/src/components/IconButton.vue +0 -4
- package/src/components/InstrumentAlertLog.vue +0 -4
- package/src/components/InstrumentStateBadge.vue +0 -4
- package/src/components/InstrumentStatusCard.vue +0 -4
- package/src/components/JobsStatusTray.story.vue +6 -10
- package/src/components/JobsStatusTray.vue +0 -4
- package/src/components/LcmsSequenceTable.vue +81 -50
- package/src/components/LoadingSpinner.vue +0 -4
- package/src/components/MobileSupportGate.vue +0 -4
- package/src/components/MoleculeInput.vue +0 -4
- package/src/components/MultiSelect.vue +0 -4
- package/src/components/NumberInput.vue +0 -4
- package/src/components/PlateMapEditor.vue +3 -14
- package/src/components/PluginIcon.vue +0 -4
- package/src/components/PluginWorkspaceView.props.ts +38 -0
- package/src/components/PluginWorkspaceView.vue +53 -224
- package/src/components/ProgressBar.vue +0 -4
- package/src/components/ProtocolStepEditor.vue +2 -4
- package/src/components/RackEditor.vue +51 -70
- package/src/components/ReagentEditor.vue +0 -4
- package/src/components/ReagentList.story.vue +18 -0
- package/src/components/ReagentList.vue +127 -82
- package/src/components/ResourceCard.vue +0 -4
- package/src/components/RuntimeUpdateNotice.story.vue +49 -0
- package/src/components/RuntimeUpdateNotice.vue +4 -4
- package/src/components/SampleHierarchyTree.story.vue +18 -0
- package/src/components/SampleHierarchyTree.vue +32 -9
- package/src/components/SampleLegend.vue +2 -8
- package/src/components/SampleSelector.drag.ts +51 -0
- package/src/components/SampleSelector.story.vue +6 -0
- package/src/components/SampleSelector.tree.ts +2 -0
- package/src/components/SampleSelector.vue +49 -10
- package/src/components/SampleSelectorGroupNode.vue +19 -0
- package/src/components/ScheduleCalendar.story.vue +6 -0
- package/src/components/ScheduleCalendar.vue +82 -7
- package/src/components/ScientificNumber.vue +0 -4
- package/src/components/SecretInput.story.vue +79 -0
- package/src/components/SecretInput.vue +150 -0
- package/src/components/SectionCard.vue +0 -4
- package/src/components/SegmentedControl.vue +48 -5
- package/src/components/SequenceInput.vue +0 -4
- package/src/components/SequenceProgressBar.story.vue +110 -0
- package/src/components/SequenceProgressBar.vue +0 -4
- package/src/components/SettingsModal.vue +0 -4
- package/src/components/Skeleton.vue +0 -4
- package/src/components/SmartGroupFieldRecipe.vue +8 -6
- package/src/components/SmartGroupManual.vue +0 -4
- package/src/components/StatusIndicator.vue +0 -4
- package/src/components/StepWizard.vue +0 -4
- package/src/components/TagsInput.vue +4 -6
- package/src/components/ThemeToggle.vue +0 -4
- package/src/components/TimePicker.vue +36 -7
- package/src/components/TimeRangeInput.vue +0 -4
- package/src/components/Tooltip.story.vue +3 -3
- package/src/components/Tooltip.vue +0 -4
- package/src/components/UnitInput.vue +0 -4
- package/src/components/WellPlate.geometry.ts +60 -1
- package/src/components/WellPlate.interaction.ts +44 -0
- package/src/components/WellPlate.story.vue +8 -0
- package/src/components/WellPlate.vue +53 -7
- package/src/components/index.ts +9 -0
- package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -5
- package/src/components/internal/AppTopBarPillNavInternal.vue +0 -4
- package/src/components/internal/DataStateInternal.vue +74 -0
- package/src/components/internal/FormSectionRenderer.vue +0 -4
- package/src/components/internal/WellEditPopupInternal.vue +20 -5
- package/src/components/internal/WorkspaceControlsInternal.vue +20 -0
- package/src/components/internal/WorkspaceShellInternal.vue +125 -0
- package/src/components/internal/WorkspaceSidebarInternal.vue +82 -0
- package/src/composables/controlSchemaTypes.ts +34 -0
- package/src/composables/experimentDesignData.ts +1 -3
- package/src/composables/flattenWorkspaceBindings.ts +104 -0
- package/src/composables/formBuilderRegistry.ts +5 -0
- package/src/composables/index.ts +41 -5
- package/src/composables/pluginWorkspaceModel.ts +182 -0
- package/src/composables/useAsync.ts +5 -1
- package/src/composables/useBioTemplatePackWorkspace.ts +15 -9
- package/src/composables/useBioTemplatePresetWorkspace.ts +14 -15
- package/src/composables/useBioTemplateWorkspace.ts +18 -43
- package/src/composables/useCommandHistory.ts +5 -1
- package/src/composables/useDropdownState.ts +35 -5
- package/src/composables/useEventStream.ts +326 -0
- package/src/composables/useFileImport.ts +2 -7
- package/src/composables/useFocusTrap.ts +91 -0
- package/src/composables/useGeneratedAnalysis.ts +46 -103
- package/src/composables/useJobsStatusTray.ts +1 -1
- package/src/composables/useListReorder.story.vue +143 -0
- package/src/composables/useListReorder.ts +242 -0
- package/src/composables/useMenuKeyboard.ts +90 -0
- package/src/composables/useOptimisticMutation.ts +6 -16
- package/src/composables/usePluginClient.ts +91 -252
- package/src/composables/usePluginConfig.ts +3 -1
- package/src/composables/usePluginWorkspace.ts +208 -0
- package/src/composables/useRackEditor.ts +2 -11
- package/src/composables/useRequestSyncState.ts +98 -28
- package/src/composables/useResourceCrud.ts +6 -16
- package/src/composables/useRovingFocus.ts +66 -0
- package/src/composables/useScheduleDrag.ts +37 -0
- package/src/composables/useWellPainting.ts +10 -30
- package/src/composables/useWellPlateAdapter.ts +5 -1
- package/src/composables/useWellPlateEditor.ts +1 -11
- package/src/composables/useWellPlateValidation.ts +5 -1
- package/src/composables/workspaceRecipes.ts +267 -0
- package/src/index.ts +16 -0
- package/src/permissions.ts +48 -9
- package/src/stores/experiment.ts +2 -7
- package/src/stores/index.ts +9 -1
- package/src/stores/settings.ts +93 -1
- package/src/styles/components/audit-trail.css +5 -5
- package/src/styles/components/batch-progress-list.css +7 -0
- package/src/styles/components/color-slider.css +2 -2
- package/src/styles/components/data-state.css +30 -0
- package/src/styles/components/dataframe.css +29 -35
- package/src/styles/components/experiment-data-viewer.css +4 -4
- package/src/styles/components/experiment-timeline.css +3 -30
- package/src/styles/components/form-builder.css +5 -5
- package/src/styles/components/lcms-sequence-table.css +16 -1
- package/src/styles/components/multi-select.css +1 -1
- package/src/styles/components/reagent-list.css +38 -31
- package/src/styles/components/sample-hierarchy-tree.css +5 -19
- package/src/styles/components/sample-selector.css +9 -2
- package/src/styles/components/schedule-calendar.css +8 -0
- package/src/styles/components/scientific-number.css +1 -1
- package/src/styles/components/secret-input.css +60 -0
- package/src/styles/components/well-plate.css +8 -0
- package/src/styles/index.css +9 -0
- package/src/styles/variables.css +29 -0
- package/src/templates/builderPresetControls.ts +1 -1
- package/src/templates/builderReadUtils.ts +1 -3
- package/src/templates/componentPlateHelpers.ts +3 -1
- package/src/templates/componentTargetResolvers.ts +1 -3
- package/src/templates/controlSchemaTargets.ts +1 -3
- package/src/templates/doseResponseAdapters.ts +2 -13
- package/src/templates/elisaAssayCollectionBuilder.ts +2 -2
- package/src/templates/plateMapBuilder.ts +2 -2
- package/src/templates/qpcrPlateBuilder.ts +1 -1
- package/src/templates/templateValidators.ts +1 -1
- package/src/templates/timeCourseBuilder.ts +2 -2
- package/src/templates/wellPlateScreenCollectionBuilder.ts +2 -2
- package/src/types/form-builder.ts +1 -0
- package/src/utils/color.ts +51 -0
- package/src/utils/errors.ts +29 -0
- package/src/utils/formModelSync.ts +1 -3
- package/src/utils/pluginIcon.ts +1 -3
- package/src/utils/pluginSecrets.ts +102 -0
- package/src/utils/records.ts +9 -0
- package/dist/EmptyState-p90quq8Z.js.map +0 -1
- package/dist/ExperimentPopover-tLwJ7XF4.js.map +0 -1
- package/dist/ExperimentSelectorModal-B6P2msT4.js.map +0 -1
- package/dist/SettingsModal-BW3Ar-uy.js.map +0 -1
- package/dist/components-DQt0tcpY.js.map +0 -1
- package/dist/composables/autoGroupConstants.d.ts +0 -1
- package/dist/composables/autoGroupGrouping.d.ts +0 -3
- package/dist/composables/useAutoGroupInputSources.d.ts +0 -32
- package/dist/composables-CkXDe7nm.js.map +0 -1
- package/dist/experiment-Y9Iz7KYi.js.map +0 -1
- package/dist/experiment-utils-CLnzPPq5.js.map +0 -1
- package/dist/templates/builderDefaults.d.ts +0 -1
- package/dist/templates/plateGeometry.d.ts +0 -4
- package/dist/templates-CovvRZ-9.js.map +0 -1
- package/dist/useApi-CDBq6z23.js.map +0 -1
- package/dist/useControlSchema-BhoJzWzx.js.map +0 -1
- package/dist/useDropdownState-DSb3BC3N.js.map +0 -1
- package/dist/useProtocolTemplates-DHwSveE6.js.map +0 -1
- package/src/__tests__/composables/useAutoGroupInputSources.test.ts +0 -127
- package/src/composables/autoGroupConstants.ts +0 -4
- package/src/composables/autoGroupGrouping.ts +0 -148
- package/src/composables/useAutoGroupInputSources.ts +0 -147
- package/src/templates/builderDefaults.ts +0 -11
- package/src/templates/plateGeometry.ts +0 -62
- /package/dist/__tests__/{composables/useAutoGroupInputSources.test.d.ts → components/DataStates.test.d.ts} +0 -0
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import { ref, type Ref } from 'vue'
|
|
1
|
+
import { nextTick, ref, type Ref } from 'vue'
|
|
2
2
|
import { useEventListener } from './useEventListener'
|
|
3
3
|
|
|
4
4
|
export interface UseDropdownStateOptions {
|
|
5
5
|
closeOnOutsideClick?: boolean
|
|
6
6
|
closeOnEscape?: boolean
|
|
7
|
+
/** Close the panel when the page scrolls or the viewport resizes. Default `false`. */
|
|
8
|
+
closeOnScroll?: boolean
|
|
7
9
|
insideRefs?: Array<Ref<HTMLElement | null>>
|
|
10
|
+
/**
|
|
11
|
+
* Element focus returns to when the panel closes with `close(true)` (Escape).
|
|
12
|
+
* Pass one when the trigger lives outside `rootRef`; otherwise bind the
|
|
13
|
+
* returned `triggerRef` to the trigger element.
|
|
14
|
+
*/
|
|
15
|
+
triggerRef?: Ref<HTMLElement | null>
|
|
8
16
|
onOpen?: () => void
|
|
9
17
|
onClose?: () => void
|
|
10
18
|
}
|
|
@@ -12,19 +20,29 @@ export interface UseDropdownStateOptions {
|
|
|
12
20
|
export interface UseDropdownStateReturn {
|
|
13
21
|
isOpen: Ref<boolean>
|
|
14
22
|
rootRef: Ref<HTMLElement | null>
|
|
23
|
+
/** Bind to the trigger element so Escape can return focus to it. */
|
|
24
|
+
triggerRef: Ref<HTMLElement | null>
|
|
15
25
|
open: () => void
|
|
16
|
-
|
|
26
|
+
/** Closes the panel; `returnFocus` moves focus back to `triggerRef`. */
|
|
27
|
+
close: (returnFocus?: boolean) => void
|
|
17
28
|
toggle: () => void
|
|
18
29
|
handleClickOutside: (event: MouseEvent) => void
|
|
19
30
|
handleKeydown: (event: KeyboardEvent) => void
|
|
20
31
|
}
|
|
21
32
|
|
|
22
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Shared disclosure state for dropdown-style SDK components: open/close state,
|
|
35
|
+
* outside-click and Escape closing, optional scroll/resize closing, and focus
|
|
36
|
+
* return to the trigger. `close(returnFocus)` mirrors `useMenuKeyboard`'s
|
|
37
|
+
* contract, so the two compose directly.
|
|
38
|
+
*/
|
|
23
39
|
export function useDropdownState(options: UseDropdownStateOptions = {}): UseDropdownStateReturn {
|
|
24
40
|
const isOpen = ref(false)
|
|
25
41
|
const rootRef = ref<HTMLElement | null>(null)
|
|
42
|
+
const triggerRef = options.triggerRef ?? ref<HTMLElement | null>(null)
|
|
26
43
|
const closeOnOutsideClick = options.closeOnOutsideClick ?? true
|
|
27
44
|
const closeOnEscape = options.closeOnEscape ?? true
|
|
45
|
+
const closeOnScroll = options.closeOnScroll ?? false
|
|
28
46
|
|
|
29
47
|
function open(): void {
|
|
30
48
|
if (isOpen.value) return
|
|
@@ -32,10 +50,13 @@ export function useDropdownState(options: UseDropdownStateOptions = {}): UseDrop
|
|
|
32
50
|
options.onOpen?.()
|
|
33
51
|
}
|
|
34
52
|
|
|
35
|
-
function close(): void {
|
|
53
|
+
function close(returnFocus = false): void {
|
|
36
54
|
if (!isOpen.value) return
|
|
37
55
|
isOpen.value = false
|
|
38
56
|
options.onClose?.()
|
|
57
|
+
if (returnFocus) {
|
|
58
|
+
void nextTick(() => triggerRef.value?.focus())
|
|
59
|
+
}
|
|
39
60
|
}
|
|
40
61
|
|
|
41
62
|
function toggle(): void {
|
|
@@ -60,20 +81,29 @@ export function useDropdownState(options: UseDropdownStateOptions = {}): UseDrop
|
|
|
60
81
|
|
|
61
82
|
function handleKeydown(event: KeyboardEvent): void {
|
|
62
83
|
if (closeOnEscape && event.key === 'Escape') {
|
|
63
|
-
close()
|
|
84
|
+
close(true)
|
|
64
85
|
}
|
|
65
86
|
}
|
|
66
87
|
|
|
88
|
+
function handleViewportChange(): void {
|
|
89
|
+
if (isOpen.value) close()
|
|
90
|
+
}
|
|
91
|
+
|
|
67
92
|
if (closeOnOutsideClick) {
|
|
68
93
|
useEventListener(() => document, 'click', handleClickOutside)
|
|
69
94
|
}
|
|
70
95
|
if (closeOnEscape) {
|
|
71
96
|
useEventListener(() => document, 'keydown', handleKeydown)
|
|
72
97
|
}
|
|
98
|
+
if (closeOnScroll) {
|
|
99
|
+
useEventListener(() => window, 'scroll', handleViewportChange, true)
|
|
100
|
+
useEventListener(() => window, 'resize', handleViewportChange)
|
|
101
|
+
}
|
|
73
102
|
|
|
74
103
|
return {
|
|
75
104
|
isOpen,
|
|
76
105
|
rootRef,
|
|
106
|
+
triggerRef,
|
|
77
107
|
open,
|
|
78
108
|
close,
|
|
79
109
|
toggle,
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCurrentInstance,
|
|
3
|
+
getCurrentScope,
|
|
4
|
+
onMounted,
|
|
5
|
+
onScopeDispose,
|
|
6
|
+
ref,
|
|
7
|
+
shallowRef,
|
|
8
|
+
type Ref,
|
|
9
|
+
} from 'vue'
|
|
10
|
+
import { tryUseSettingsStore } from '../stores/settings'
|
|
11
|
+
|
|
12
|
+
export type EventStreamTransport = (
|
|
13
|
+
input: RequestInfo | URL,
|
|
14
|
+
init?: RequestInit,
|
|
15
|
+
) => Promise<Response>
|
|
16
|
+
|
|
17
|
+
export interface EventStreamMessage<TData = string> {
|
|
18
|
+
event: string
|
|
19
|
+
data: TData
|
|
20
|
+
id?: string
|
|
21
|
+
retry?: number
|
|
22
|
+
raw: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface EventStreamOptions<TData = string> {
|
|
26
|
+
transport?: EventStreamTransport
|
|
27
|
+
headers?: HeadersInit
|
|
28
|
+
credentials?: RequestCredentials
|
|
29
|
+
immediate?: boolean
|
|
30
|
+
/**
|
|
31
|
+
* Reconnect after a dropped stream. Left unset, it follows the platform's
|
|
32
|
+
* `wsAutoReconnect` setting, defaulting to `true` without a settings store.
|
|
33
|
+
*/
|
|
34
|
+
reconnect?: boolean
|
|
35
|
+
/**
|
|
36
|
+
* Delay before the first reconnect attempt. Left unset, it follows the
|
|
37
|
+
* platform's `wsReconnectInterval` setting, defaulting to 2000 ms without a
|
|
38
|
+
* settings store. A server-sent `retry:` field still overrides it.
|
|
39
|
+
*/
|
|
40
|
+
reconnectDelayMs?: number
|
|
41
|
+
parseJson?: boolean
|
|
42
|
+
shouldReconnect?: (cause?: unknown) => boolean
|
|
43
|
+
onOpen?: () => void
|
|
44
|
+
onMessage?: (message: EventStreamMessage<TData>) => void
|
|
45
|
+
onError?: (cause: unknown) => void
|
|
46
|
+
onClose?: (cause?: unknown) => void
|
|
47
|
+
errorLabel?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface UseEventStreamReturn<TData = string> {
|
|
51
|
+
isConnected: Ref<boolean>
|
|
52
|
+
isConnecting: Ref<boolean>
|
|
53
|
+
error: Ref<string | null>
|
|
54
|
+
lastMessage: Ref<EventStreamMessage<TData> | null>
|
|
55
|
+
lastEventId: Ref<string | null>
|
|
56
|
+
start: () => void
|
|
57
|
+
stop: () => void
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface ParsedEventStreamBlock<TData> {
|
|
61
|
+
message?: EventStreamMessage<TData>
|
|
62
|
+
id?: string
|
|
63
|
+
retry?: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class EventStreamResponseError extends Error {
|
|
67
|
+
constructor(readonly response: Response, label: string) {
|
|
68
|
+
super(`${label} failed with HTTP ${response.status}`)
|
|
69
|
+
this.name = 'EventStreamResponseError'
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Parse one complete SSE block without owning connection policy. */
|
|
74
|
+
export function parseEventStreamBlock<TData>(
|
|
75
|
+
block: string,
|
|
76
|
+
parseJson: boolean,
|
|
77
|
+
): ParsedEventStreamBlock<TData> | null {
|
|
78
|
+
let event = 'message'
|
|
79
|
+
let id: string | undefined
|
|
80
|
+
let retry: number | undefined
|
|
81
|
+
const dataLines: string[] = []
|
|
82
|
+
|
|
83
|
+
for (const line of block.split(/\r\n|\r|\n/)) {
|
|
84
|
+
if (!line || line.startsWith(':')) continue
|
|
85
|
+
const separator = line.indexOf(':')
|
|
86
|
+
const field = separator === -1 ? line : line.slice(0, separator)
|
|
87
|
+
const value = separator === -1
|
|
88
|
+
? ''
|
|
89
|
+
: line.slice(separator + 1).replace(/^ /, '')
|
|
90
|
+
|
|
91
|
+
if (field === 'event') event = value || 'message'
|
|
92
|
+
if (field === 'data') dataLines.push(value)
|
|
93
|
+
if (field === 'id' && !value.includes('\0')) id = value
|
|
94
|
+
if (field === 'retry' && /^\d+$/.test(value)) retry = Number(value)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!dataLines.length && id === undefined && retry === undefined) return null
|
|
98
|
+
const data = dataLines.join('\n')
|
|
99
|
+
return {
|
|
100
|
+
message: dataLines.length
|
|
101
|
+
? {
|
|
102
|
+
event,
|
|
103
|
+
data: (parseJson && data ? JSON.parse(data) : data) as TData,
|
|
104
|
+
id,
|
|
105
|
+
retry,
|
|
106
|
+
raw: block,
|
|
107
|
+
}
|
|
108
|
+
: undefined,
|
|
109
|
+
id,
|
|
110
|
+
retry,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Shared fetch-based SSE lifecycle with cancellation and reconnect safety. */
|
|
115
|
+
export function useEventStream<TData = string>(
|
|
116
|
+
url: string | (() => string),
|
|
117
|
+
options: EventStreamOptions<TData> = {},
|
|
118
|
+
): UseEventStreamReturn<TData> {
|
|
119
|
+
const isConnected = ref(false)
|
|
120
|
+
const isConnecting = ref(false)
|
|
121
|
+
const error = ref<string | null>(null)
|
|
122
|
+
const lastMessage = shallowRef<EventStreamMessage<TData> | null>(null)
|
|
123
|
+
const lastEventId = ref<string | null>(null)
|
|
124
|
+
let controller: AbortController | null = null
|
|
125
|
+
let reader: ReadableStreamDefaultReader<Uint8Array> | null = null
|
|
126
|
+
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
|
127
|
+
const settings = tryUseSettingsStore()
|
|
128
|
+
const reconnectEnabled = options.reconnect ?? settings?.wsAutoReconnect ?? true
|
|
129
|
+
let reconnectDelayMs = options.reconnectDelayMs ?? settings?.wsReconnectInterval ?? 2_000
|
|
130
|
+
let stopped = true
|
|
131
|
+
let generation = 0
|
|
132
|
+
|
|
133
|
+
function clearReconnectTimer(): void {
|
|
134
|
+
if (reconnectTimer === null) return
|
|
135
|
+
clearTimeout(reconnectTimer)
|
|
136
|
+
reconnectTimer = null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function ownsConnection(
|
|
140
|
+
connectionGeneration: number,
|
|
141
|
+
connectionController: AbortController,
|
|
142
|
+
): boolean {
|
|
143
|
+
return !stopped
|
|
144
|
+
&& generation === connectionGeneration
|
|
145
|
+
&& controller === connectionController
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function scheduleReconnect(connectionGeneration: number, cause?: unknown): void {
|
|
149
|
+
if (stopped || generation !== connectionGeneration) return
|
|
150
|
+
if (!reconnectEnabled || options.shouldReconnect?.(cause) === false) {
|
|
151
|
+
stopped = true
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
clearReconnectTimer()
|
|
155
|
+
reconnectTimer = setTimeout(() => {
|
|
156
|
+
reconnectTimer = null
|
|
157
|
+
if (stopped || generation !== connectionGeneration) return
|
|
158
|
+
void connect(connectionGeneration)
|
|
159
|
+
}, reconnectDelayMs)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function dispatchBlock(
|
|
163
|
+
block: string,
|
|
164
|
+
connectionGeneration: number,
|
|
165
|
+
connectionController: AbortController,
|
|
166
|
+
): void {
|
|
167
|
+
const parsed = parseEventStreamBlock<TData>(block, options.parseJson ?? false)
|
|
168
|
+
if (!parsed || !ownsConnection(connectionGeneration, connectionController)) return
|
|
169
|
+
if (parsed.id !== undefined) {
|
|
170
|
+
lastEventId.value = parsed.id
|
|
171
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return
|
|
172
|
+
}
|
|
173
|
+
if (parsed.retry !== undefined) reconnectDelayMs = parsed.retry
|
|
174
|
+
const message = parsed.message
|
|
175
|
+
if (!message || !ownsConnection(connectionGeneration, connectionController)) return
|
|
176
|
+
lastMessage.value = message
|
|
177
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return
|
|
178
|
+
options.onMessage?.(message)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function consume(
|
|
182
|
+
body: ReadableStream<Uint8Array>,
|
|
183
|
+
connectionGeneration: number,
|
|
184
|
+
connectionController: AbortController,
|
|
185
|
+
): Promise<void> {
|
|
186
|
+
const connectionReader = body.getReader()
|
|
187
|
+
reader = connectionReader
|
|
188
|
+
const decoder = new TextDecoder()
|
|
189
|
+
let buffer = ''
|
|
190
|
+
try {
|
|
191
|
+
for (;;) {
|
|
192
|
+
const { value, done } = await connectionReader.read()
|
|
193
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return
|
|
194
|
+
buffer += decoder.decode(value, { stream: !done })
|
|
195
|
+
|
|
196
|
+
let boundary = /(?:\r\n|\r|\n){2}/.exec(buffer)
|
|
197
|
+
while (boundary?.index !== undefined) {
|
|
198
|
+
const block = buffer.slice(0, boundary.index)
|
|
199
|
+
buffer = buffer.slice(boundary.index + boundary[0].length)
|
|
200
|
+
dispatchBlock(block, connectionGeneration, connectionController)
|
|
201
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return
|
|
202
|
+
boundary = /(?:\r\n|\r|\n){2}/.exec(buffer)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!done) continue
|
|
206
|
+
if (buffer) dispatchBlock(buffer, connectionGeneration, connectionController)
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
} finally {
|
|
210
|
+
if (reader === connectionReader) reader = null
|
|
211
|
+
connectionReader.releaseLock()
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function connect(connectionGeneration: number): Promise<void> {
|
|
216
|
+
if (stopped || generation !== connectionGeneration) return
|
|
217
|
+
controller?.abort()
|
|
218
|
+
const connectionController = new AbortController()
|
|
219
|
+
controller = connectionController
|
|
220
|
+
isConnecting.value = true
|
|
221
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return
|
|
222
|
+
error.value = null
|
|
223
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return
|
|
224
|
+
|
|
225
|
+
let cause: unknown
|
|
226
|
+
try {
|
|
227
|
+
const headers = new Headers(options.headers)
|
|
228
|
+
headers.set('Accept', 'text/event-stream')
|
|
229
|
+
if (lastEventId.value && !headers.has('Last-Event-ID')) {
|
|
230
|
+
headers.set('Last-Event-ID', lastEventId.value)
|
|
231
|
+
}
|
|
232
|
+
const transport = options.transport ?? globalThis.fetch?.bind(globalThis)
|
|
233
|
+
if (!transport) throw new Error('[MINT SDK] fetch is required for event streams.')
|
|
234
|
+
const response = await transport(typeof url === 'function' ? url() : url, {
|
|
235
|
+
method: 'GET',
|
|
236
|
+
headers,
|
|
237
|
+
credentials: options.credentials ?? 'same-origin',
|
|
238
|
+
signal: connectionController.signal,
|
|
239
|
+
})
|
|
240
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return
|
|
241
|
+
if (!response.ok) {
|
|
242
|
+
throw new EventStreamResponseError(response, options.errorLabel ?? 'Event stream')
|
|
243
|
+
}
|
|
244
|
+
if (!response.body) {
|
|
245
|
+
throw new Error(`${options.errorLabel ?? 'Event stream'} response did not include a readable body.`)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
isConnected.value = true
|
|
249
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return
|
|
250
|
+
isConnecting.value = false
|
|
251
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return
|
|
252
|
+
options.onOpen?.()
|
|
253
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return
|
|
254
|
+
await consume(response.body, connectionGeneration, connectionController)
|
|
255
|
+
} catch (errorCause) {
|
|
256
|
+
cause = errorCause
|
|
257
|
+
if (
|
|
258
|
+
ownsConnection(connectionGeneration, connectionController)
|
|
259
|
+
&& !isAbortError(errorCause)
|
|
260
|
+
) {
|
|
261
|
+
error.value = errorCause instanceof Error
|
|
262
|
+
? errorCause.message
|
|
263
|
+
: `${options.errorLabel ?? 'Event stream'} failed`
|
|
264
|
+
if (ownsConnection(connectionGeneration, connectionController)) {
|
|
265
|
+
options.onError?.(errorCause)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
} finally {
|
|
269
|
+
if (ownsConnection(connectionGeneration, connectionController)) {
|
|
270
|
+
isConnecting.value = false
|
|
271
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return
|
|
272
|
+
isConnected.value = false
|
|
273
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return
|
|
274
|
+
controller = null
|
|
275
|
+
options.onClose?.(cause)
|
|
276
|
+
if (!stopped && generation === connectionGeneration) {
|
|
277
|
+
scheduleReconnect(connectionGeneration, cause)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function start(): void {
|
|
284
|
+
if (!stopped) return
|
|
285
|
+
stopped = false
|
|
286
|
+
clearReconnectTimer()
|
|
287
|
+
generation += 1
|
|
288
|
+
void connect(generation)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function stop(): void {
|
|
292
|
+
if (stopped) return
|
|
293
|
+
stopped = true
|
|
294
|
+
generation += 1
|
|
295
|
+
clearReconnectTimer()
|
|
296
|
+
controller?.abort()
|
|
297
|
+
controller = null
|
|
298
|
+
if (reader) void reader.cancel().catch(() => undefined)
|
|
299
|
+
reader = null
|
|
300
|
+
isConnecting.value = false
|
|
301
|
+
isConnected.value = false
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const currentInstance = getCurrentInstance()
|
|
305
|
+
if (currentInstance && options.immediate !== false && typeof window === 'undefined') {
|
|
306
|
+
onMounted(start)
|
|
307
|
+
}
|
|
308
|
+
if (getCurrentScope()) onScopeDispose(stop)
|
|
309
|
+
if (options.immediate !== false && (typeof window !== 'undefined' || !currentInstance)) {
|
|
310
|
+
start()
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
isConnected,
|
|
315
|
+
isConnecting,
|
|
316
|
+
error,
|
|
317
|
+
lastMessage,
|
|
318
|
+
lastEventId,
|
|
319
|
+
start,
|
|
320
|
+
stop,
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function isAbortError(cause: unknown): boolean {
|
|
325
|
+
return cause instanceof DOMException && cause.name === 'AbortError'
|
|
326
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ref, shallowRef, type Ref, type ShallowRef } from 'vue'
|
|
2
|
+
import { readErrorMessage } from '../utils/errors'
|
|
2
3
|
|
|
3
4
|
export interface DelimitedTable {
|
|
4
5
|
columns: string[]
|
|
@@ -63,7 +64,7 @@ export function useFileImport<TResult = string>(
|
|
|
63
64
|
result.value = parsed
|
|
64
65
|
return parsed
|
|
65
66
|
} catch (err) {
|
|
66
|
-
const message = readErrorMessage(err)
|
|
67
|
+
const message = readErrorMessage(err, 'Failed to import file.')
|
|
67
68
|
error.value = message
|
|
68
69
|
throw err
|
|
69
70
|
} finally {
|
|
@@ -223,9 +224,3 @@ function formatBytes(bytes: number): string {
|
|
|
223
224
|
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`
|
|
224
225
|
return `${Math.round(bytes / (1024 * 1024))} MB`
|
|
225
226
|
}
|
|
226
|
-
|
|
227
|
-
function readErrorMessage(value: unknown): string {
|
|
228
|
-
if (value instanceof Error && value.message) return value.message
|
|
229
|
-
if (typeof value === 'string' && value.trim()) return value
|
|
230
|
-
return 'Failed to import file.'
|
|
231
|
-
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { nextTick, watch, type Ref } from 'vue'
|
|
2
|
+
import { useEventListener } from './useEventListener'
|
|
3
|
+
|
|
4
|
+
export interface UseFocusTrapOptions {
|
|
5
|
+
/** Ref to the trap's container element. Focusable elements are queried within it. */
|
|
6
|
+
containerRef: Ref<HTMLElement | null>
|
|
7
|
+
/** Reactive flag — the trap activates when this becomes `true` and releases when it becomes `false`. */
|
|
8
|
+
active: Ref<boolean>
|
|
9
|
+
/** Restore focus to the element that was focused before activation, when the trap releases. Default `true`. */
|
|
10
|
+
restoreFocus?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UseFocusTrapReturn {
|
|
14
|
+
/** Focusable descendants of the container, in DOM order, excluding roving-tabindex (`tabindex="-1"`) elements. */
|
|
15
|
+
getFocusableElements: () => HTMLElement[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const FOCUSABLE_SELECTOR = [
|
|
19
|
+
'a[href]',
|
|
20
|
+
'button:not([disabled])',
|
|
21
|
+
'input:not([disabled])',
|
|
22
|
+
'select:not([disabled])',
|
|
23
|
+
'textarea:not([disabled])',
|
|
24
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
25
|
+
].join(', ')
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Traps Tab focus within a container while `active` is true: Tab wraps from the last
|
|
29
|
+
* focusable element back to the first (and Shift+Tab from the first to the last), the
|
|
30
|
+
* first focusable element (or the container itself, if none) receives focus when the
|
|
31
|
+
* trap activates, and focus returns to whatever was focused beforehand when it releases.
|
|
32
|
+
* Used by BaseModal and WellEditPopupInternal.
|
|
33
|
+
*/
|
|
34
|
+
export function useFocusTrap(options: UseFocusTrapOptions): UseFocusTrapReturn {
|
|
35
|
+
const { containerRef, active } = options
|
|
36
|
+
const restoreFocus = options.restoreFocus ?? true
|
|
37
|
+
let previouslyFocused: HTMLElement | null = null
|
|
38
|
+
|
|
39
|
+
function getFocusableElements(): HTMLElement[] {
|
|
40
|
+
if (!containerRef.value) return []
|
|
41
|
+
// Roving tabindex (inactive tabs/rail items) stays out of the Tab cycle. Checking
|
|
42
|
+
// the attribute directly (rather than the `.tabIndex` IDL property) avoids relying
|
|
43
|
+
// on a browser/engine's default-tabindex computation for freshly-patched elements.
|
|
44
|
+
return Array.from(containerRef.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR))
|
|
45
|
+
.filter((el) => el.getAttribute('tabindex') !== '-1')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
49
|
+
if (event.key !== 'Tab' || !active.value || !containerRef.value) return
|
|
50
|
+
|
|
51
|
+
const focusable = getFocusableElements()
|
|
52
|
+
if (focusable.length === 0) {
|
|
53
|
+
event.preventDefault()
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const first = focusable[0]
|
|
58
|
+
const last = focusable[focusable.length - 1]
|
|
59
|
+
|
|
60
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
61
|
+
event.preventDefault()
|
|
62
|
+
last.focus()
|
|
63
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
64
|
+
event.preventDefault()
|
|
65
|
+
first.focus()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// `flush: 'sync'` so releasing the trap from `onBeforeUnmount` restores focus
|
|
70
|
+
// before the component's reactive scope is torn down — a batched (default
|
|
71
|
+
// 'pre') watcher would be scheduled but never flushed once that scope stops.
|
|
72
|
+
watch(active, async (isActive) => {
|
|
73
|
+
if (isActive) {
|
|
74
|
+
previouslyFocused = document.activeElement as HTMLElement | null
|
|
75
|
+
await nextTick()
|
|
76
|
+
const focusable = getFocusableElements()
|
|
77
|
+
if (focusable.length > 0) {
|
|
78
|
+
focusable[0].focus()
|
|
79
|
+
} else {
|
|
80
|
+
containerRef.value?.focus()
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
if (restoreFocus) previouslyFocused?.focus()
|
|
84
|
+
previouslyFocused = null
|
|
85
|
+
}
|
|
86
|
+
}, { flush: 'sync' })
|
|
87
|
+
|
|
88
|
+
useEventListener(() => document, 'keydown', handleKeydown)
|
|
89
|
+
|
|
90
|
+
return { getFocusableElements }
|
|
91
|
+
}
|