@morscherlab/mint-sdk 1.2.0-beta.9 → 1.2.1
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 +30 -0
- package/dist/{ExperimentPopover-oL0fg_A0.js → ExperimentPopover-CJdNlCZb.js} +21 -14
- package/dist/ExperimentPopover-CJdNlCZb.js.map +1 -0
- package/dist/{ExperimentSelectorModal-BeEOomw_.js → ExperimentSelectorModal-DHE9k8TP.js} +5 -5
- package/dist/{ExperimentSelectorModal-BeEOomw_.js.map → ExperimentSelectorModal-DHE9k8TP.js.map} +1 -1
- package/dist/{SettingsModal-Dphh3RrI.js → SettingsModal-DOcCf0Vo.js} +41 -18
- package/dist/SettingsModal-DOcCf0Vo.js.map +1 -0
- package/dist/{Skeleton-DxtwJ5gt.js → Skeleton-jhF9wUkU.js} +19 -9
- package/dist/Skeleton-jhF9wUkU.js.map +1 -0
- package/dist/__tests__/apiErrors.test.d.ts +1 -0
- package/dist/__tests__/components/FileBrowserModal.test.d.ts +1 -0
- package/dist/__tests__/components/FilePicker.state.test.d.ts +1 -0
- package/dist/__tests__/components/FilePicker.test.d.ts +1 -0
- package/dist/__tests__/components/FormField.test.d.ts +1 -0
- package/dist/__tests__/components/NumberInput.test.d.ts +1 -0
- package/dist/__tests__/components/PlotlyChart.test.d.ts +1 -0
- package/dist/__tests__/components/iconButtonIconSizing.test.d.ts +1 -0
- package/dist/__tests__/composables/localFiles.test.d.ts +1 -0
- package/dist/__tests__/composables/useFileBrowser.test.d.ts +1 -0
- package/dist/__tests__/composables/usePickerSelection.test.d.ts +1 -0
- package/dist/__tests__/composables/usePlatformFilePickerAdapter.test.d.ts +1 -0
- package/dist/__tests__/styles/tokenOnlyColors.test.d.ts +1 -0
- package/dist/apiErrors.d.ts +26 -0
- package/dist/components/AppSidebar.vue.d.ts +7 -7
- package/dist/components/BaseInput.vue.d.ts +1 -1
- package/dist/components/BasePill.vue.d.ts +3 -0
- package/dist/components/BaseSelect.vue.d.ts +1 -1
- package/dist/components/BaseTabs.vue.d.ts +1 -4
- package/dist/components/BaseTextarea.vue.d.ts +1 -1
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +2 -2
- package/dist/components/ConcentrationInput.vue.d.ts +1 -1
- package/dist/components/DatePicker.vue.d.ts +1 -1
- package/dist/components/DateTimePicker.vue.d.ts +1 -1
- package/dist/components/FileBrowserModal.vue.d.ts +99 -0
- package/dist/components/FilePicker.vue.d.ts +32 -0
- package/dist/components/FileUploader.vue.d.ts +8 -2
- package/dist/components/FormField.vue.d.ts +8 -1
- package/dist/components/FormulaInput.vue.d.ts +1 -1
- package/dist/components/GeneratedPlotlyResult.vue.d.ts +1 -3
- package/dist/components/MobileSupportGate.vue.d.ts +1 -1
- package/dist/components/MoleculeInput.vue.d.ts +1 -1
- package/dist/components/NumberInput.vue.d.ts +1 -1
- package/dist/components/PlotlyChart.vue.d.ts +59 -0
- package/dist/components/PluginWorkspaceView.props.d.ts +3 -3
- package/dist/components/ResourceCard.vue.d.ts +1 -1
- package/dist/components/SecretInput.vue.d.ts +1 -1
- package/dist/components/SequenceInput.vue.d.ts +1 -1
- package/dist/components/StatusIndicator.vue.d.ts +1 -1
- package/dist/components/TagsInput.vue.d.ts +1 -1
- package/dist/components/TimePicker.vue.d.ts +1 -1
- package/dist/components/TimeRangeInput.vue.d.ts +1 -1
- package/dist/components/UnitInput.vue.d.ts +1 -1
- package/dist/components/WellPlate.vue.d.ts +1 -1
- package/dist/components/fileBrowserPresentation.d.ts +10 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +8 -8
- package/dist/components/internal/FileBrowserDialogInternal.vue.d.ts +34 -0
- package/dist/components/internal/FileBrowserListingInternal.vue.d.ts +25 -0
- package/dist/components/internal/FileBrowserToolbarInternal.vue.d.ts +26 -0
- package/dist/components/internal/FilePickerPreviewInternal.vue.d.ts +18 -0
- package/dist/components/internal/FilePickerTreeInternal.vue.d.ts +31 -0
- package/dist/{components-CgAUzF8F.js → components-Cp8DcNrU.js} +4363 -1730
- package/dist/components-Cp8DcNrU.js.map +1 -0
- package/dist/composables/filePicker/localFiles.d.ts +3 -0
- package/dist/composables/filePicker/usePickerNavigation.d.ts +96 -0
- package/dist/composables/filePicker/usePickerSelection.d.ts +31 -0
- package/dist/composables/filePicker/validation.d.ts +5 -0
- package/dist/composables/index.d.ts +2 -0
- package/dist/composables/index.js +10 -10
- package/dist/composables/pluginWorkspaceModel.d.ts +0 -3
- package/dist/composables/useApi.d.ts +14 -7
- package/dist/composables/useFileBrowser.d.ts +55 -0
- package/dist/composables/useFilePicker.d.ts +189 -0
- package/dist/composables/usePlatformFilePickerAdapter.d.ts +11 -0
- package/dist/composables/useToast.d.ts +22 -1
- package/dist/{composables-B_pia3hO.js → composables-D_4qqOlp.js} +300 -11
- package/dist/composables-D_4qqOlp.js.map +1 -0
- package/dist/{errors-_W0-LbA6.js → errors-BLuqMee5.js} +78 -15
- package/dist/errors-BLuqMee5.js.map +1 -0
- package/dist/{experiment-Cwl9AYxw.js → experiment-CVDRAnqp.js} +2 -2
- package/dist/{experiment-Cwl9AYxw.js.map → experiment-CVDRAnqp.js.map} +1 -1
- package/dist/{experiment-utils-DV3DrvE_.js → experiment-utils-CV2Wza3z.js} +1 -1
- package/dist/{experiment-utils-DV3DrvE_.js.map → experiment-utils-CV2Wza3z.js.map} +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -15
- package/dist/install.js +2 -2
- package/dist/stores/index.js +2 -2
- package/dist/styles.css +2957 -1708
- package/dist/templates/index.js +2 -2
- package/dist/{templates-Qq113pyZ.js → templates-Dd9bBSs6.js} +2 -2
- package/dist/{templates-Qq113pyZ.js.map → templates-Dd9bBSs6.js.map} +1 -1
- package/dist/types/components.d.ts +21 -0
- package/dist/types/fileBrowserTypes.d.ts +77 -0
- package/dist/types/filePicker.d.ts +77 -0
- package/dist/types/form-builder.d.ts +2 -0
- package/dist/types/generated-ui.d.ts +2 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/{useControlSchema-aMZlb4Sc.js → useControlSchema-BB2GRPhW.js} +190 -105
- package/dist/useControlSchema-BB2GRPhW.js.map +1 -0
- package/dist/{useDropdownState-DitCtrTT.js → useDropdownState-C7hRiVQf.js} +2 -2
- package/dist/{useDropdownState-DitCtrTT.js.map → useDropdownState-C7hRiVQf.js.map} +1 -1
- package/dist/{useExperimentSelector-I8HC8hUu.js → useExperimentSelector-G_CAOFTX.js} +4 -4
- package/dist/{useExperimentSelector-I8HC8hUu.js.map → useExperimentSelector-G_CAOFTX.js.map} +1 -1
- package/dist/{useFormBuilder-TCxNnqeb.js → useFormBuilder-BnGSdSRG.js} +2 -2
- package/dist/{useFormBuilder-TCxNnqeb.js.map → useFormBuilder-BnGSdSRG.js.map} +1 -1
- package/dist/{useProtocolTemplates-BmX_iVBR.js → validation-CTxuzLIX.js} +70 -13
- package/dist/validation-CTxuzLIX.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/apiErrors.test.ts +81 -0
- package/src/__tests__/components/AppSidebar.test.ts +84 -94
- package/src/__tests__/components/AppToastContainer.test.ts +55 -1
- package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +0 -21
- package/src/__tests__/components/ControlWorkspaceView.test.ts +0 -2
- package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +0 -2
- package/src/__tests__/components/ExperimentPopover.test.ts +30 -0
- package/src/__tests__/components/FileBrowserModal.test.ts +460 -0
- package/src/__tests__/components/FilePicker.state.test.ts +287 -0
- package/src/__tests__/components/FilePicker.test.ts +487 -0
- package/src/__tests__/components/FormBuilder.test.ts +26 -0
- package/src/__tests__/components/FormField.test.ts +26 -0
- package/src/__tests__/components/GeneratedPathInput.test.ts +18 -0
- package/src/__tests__/components/GeneratedPlotlyResult.test.ts +52 -5
- package/src/__tests__/components/NumberInput.test.ts +105 -0
- package/src/__tests__/components/PlotlyChart.test.ts +305 -0
- package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +1 -2
- package/src/__tests__/components/PluginWorkspaceView.test.ts +1 -2
- package/src/__tests__/components/SegmentedControl.test.ts +19 -0
- package/src/__tests__/components/iconButtonIconSizing.test.ts +18 -0
- package/src/__tests__/composables/localFiles.test.ts +57 -0
- package/src/__tests__/composables/useApi.test.ts +92 -9
- package/src/__tests__/composables/useFileBrowser.test.ts +305 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +7 -2
- package/src/__tests__/composables/usePickerSelection.test.ts +98 -0
- package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +67 -0
- package/src/__tests__/composables/usePluginClient.test.ts +3 -0
- package/src/__tests__/generatedUi.test.ts +111 -0
- package/src/__tests__/styles/tokenOnlyColors.test.ts +16 -0
- package/src/apiErrors.ts +85 -0
- package/src/components/AppLayout.story.vue +1 -1
- package/src/components/AppSidebar.story.vue +7 -10
- package/src/components/AppSidebar.vue +178 -58
- package/src/components/AppToastContainer.vue +65 -23
- package/src/components/BasePill.story.vue +2 -0
- package/src/components/BasePill.vue +4 -0
- package/src/components/BaseTabs.story.vue +0 -42
- package/src/components/BaseTabs.vue +3 -6
- package/src/components/ExperimentPopover.vue +10 -2
- package/src/components/FileBrowserModal.story.vue +203 -0
- package/src/components/FileBrowserModal.vue +384 -0
- package/src/components/FilePicker.story.vue +44 -0
- package/src/components/FilePicker.vue +284 -0
- package/src/components/FileUploader.vue +42 -15
- package/src/components/FormField.vue +10 -3
- package/src/components/GeneratedPathInput.vue +11 -7
- package/src/components/GeneratedPlotlyResult.vue +34 -69
- package/src/components/GeneratedResultRenderer.vue +0 -5
- package/src/components/NumberInput.story.vue +32 -13
- package/src/components/NumberInput.vue +116 -55
- package/src/components/PlotlyChart.story.vue +61 -0
- package/src/components/PlotlyChart.vue +233 -0
- package/src/components/PluginWorkspaceView.props.ts +3 -3
- package/src/components/SegmentedControl.vue +2 -2
- package/src/components/fileBrowserPresentation.ts +39 -0
- package/src/components/index.ts +5 -0
- package/src/components/internal/FileBrowserDialogInternal.vue +75 -0
- package/src/components/internal/FileBrowserListingInternal.vue +197 -0
- package/src/components/internal/FileBrowserToolbarInternal.vue +136 -0
- package/src/components/internal/FilePickerPreviewInternal.vue +100 -0
- package/src/components/internal/FilePickerTreeInternal.vue +238 -0
- package/src/components/internal/FormFieldRendererInternal.vue +17 -0
- package/src/components/internal/WorkspaceSidebarInternal.vue +1 -2
- package/src/composables/filePicker/localFiles.ts +65 -0
- package/src/composables/filePicker/usePickerNavigation.ts +220 -0
- package/src/composables/filePicker/usePickerSelection.ts +270 -0
- package/src/composables/filePicker/validation.ts +41 -0
- package/src/composables/index.ts +7 -0
- package/src/composables/pluginWorkspaceModel.ts +0 -3
- package/src/composables/useApi.ts +57 -13
- package/src/composables/useFileBrowser.ts +314 -0
- package/src/composables/useFilePicker.ts +366 -0
- package/src/composables/useGeneratedAnalysis.ts +10 -17
- package/src/composables/usePlatformFilePickerAdapter.ts +104 -0
- package/src/composables/usePluginClient.ts +3 -0
- package/src/composables/usePluginWorkspace.ts +1 -2
- package/src/composables/useToast.ts +21 -5
- package/src/generated/schema.ts +58 -18
- package/src/index.ts +3 -0
- package/src/styles/components/app-sidebar.css +282 -121
- package/src/styles/components/file-browser-modal.css +489 -0
- package/src/styles/components/file-picker.css +502 -0
- package/src/styles/components/form-builder.css +1 -1
- package/src/styles/components/form-field.css +28 -0
- package/src/styles/components/icon-button.css +9 -0
- package/src/styles/components/number-input.css +73 -91
- package/src/styles/components/pill.css +49 -51
- package/src/styles/components/segmented-control.css +16 -11
- package/src/styles/components/tabs.css +18 -34
- package/src/styles/components/toast.css +136 -27
- package/src/styles/index.css +3 -0
- package/src/styles/variables.css +25 -5
- package/src/types/components.ts +23 -0
- package/src/types/fileBrowserTypes.ts +85 -0
- package/src/types/filePicker.ts +72 -0
- package/src/types/form-builder.ts +2 -0
- package/src/types/generated-ui.ts +2 -0
- package/src/types/index.ts +16 -0
- package/dist/ExperimentPopover-oL0fg_A0.js.map +0 -1
- package/dist/SettingsModal-Dphh3RrI.js.map +0 -1
- package/dist/Skeleton-DxtwJ5gt.js.map +0 -1
- package/dist/components-CgAUzF8F.js.map +0 -1
- package/dist/composables-B_pia3hO.js.map +0 -1
- package/dist/errors-_W0-LbA6.js.map +0 -1
- package/dist/useControlSchema-aMZlb4Sc.js.map +0 -1
- package/dist/useProtocolTemplates-BmX_iVBR.js.map +0 -1
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
2
3
|
import NumberInput from './NumberInput.vue'
|
|
3
4
|
|
|
4
5
|
const sizes = ['sm', 'md', 'lg'] as const
|
|
6
|
+
|
|
7
|
+
const sizeValues = ref<Record<string, number | undefined>>({ sm: 42, md: 42, lg: 42 })
|
|
8
|
+
const samples = ref<number | undefined>(96)
|
|
9
|
+
const temperature = ref<number | undefined>(37)
|
|
10
|
+
const volume = ref<number | undefined>(100)
|
|
11
|
+
const overMax = ref<number | undefined>(999)
|
|
12
|
+
const empty = ref<number | undefined>(undefined)
|
|
13
|
+
const mass = ref<number | undefined>(12.5)
|
|
14
|
+
const ph = ref<number | undefined>(7.4)
|
|
15
|
+
const wells = ref<number | undefined>(96)
|
|
16
|
+
const sliderPh = ref<number | undefined>(7.4)
|
|
17
|
+
const percent = ref<number | undefined>(45)
|
|
5
18
|
</script>
|
|
6
19
|
|
|
7
20
|
<template>
|
|
@@ -39,6 +52,9 @@ const sizes = ['sm', 'md', 'lg'] as const
|
|
|
39
52
|
<HstCheckbox v-model="state.disabled" title="Disabled" />
|
|
40
53
|
<HstCheckbox v-model="state.error" title="Error" />
|
|
41
54
|
</template>
|
|
55
|
+
<template #initState>
|
|
56
|
+
{{ { value: 42, min: 0, max: 100, step: 1, size: 'md', placeholder: '', disabled: false, error: false } }}
|
|
57
|
+
</template>
|
|
42
58
|
</Variant>
|
|
43
59
|
|
|
44
60
|
<Variant title="Sizes">
|
|
@@ -47,7 +63,7 @@ const sizes = ['sm', 'md', 'lg'] as const
|
|
|
47
63
|
<p style="margin: 0 0 0.5rem; font-size: 0.75rem; color: var(--text-muted, #94a3b8); text-transform: uppercase; letter-spacing: 0.05em;">
|
|
48
64
|
{{ size }}
|
|
49
65
|
</p>
|
|
50
|
-
<NumberInput
|
|
66
|
+
<NumberInput v-model="sizeValues[size]" :size="size" />
|
|
51
67
|
</div>
|
|
52
68
|
</div>
|
|
53
69
|
</Variant>
|
|
@@ -56,22 +72,22 @@ const sizes = ['sm', 'md', 'lg'] as const
|
|
|
56
72
|
<div style="padding: 2rem; max-width: 300px; margin: 0 auto; display: flex; flex-direction: column; gap: 1.5rem;">
|
|
57
73
|
<div>
|
|
58
74
|
<p style="margin: 0 0 0.5rem; font-size: 0.875rem; color: var(--text-primary, #e2e8f0);">Sample count (0-384)</p>
|
|
59
|
-
<NumberInput
|
|
75
|
+
<NumberInput v-model="samples" :min="0" :max="384" :step="1" placeholder="Samples" />
|
|
60
76
|
</div>
|
|
61
77
|
<div>
|
|
62
78
|
<p style="margin: 0 0 0.5rem; font-size: 0.875rem; color: var(--text-primary, #e2e8f0);">Temperature (step 0.5)</p>
|
|
63
|
-
<NumberInput
|
|
79
|
+
<NumberInput v-model="temperature" :min="0" :max="100" :step="0.5" />
|
|
64
80
|
</div>
|
|
65
81
|
<div>
|
|
66
82
|
<p style="margin: 0 0 0.5rem; font-size: 0.875rem; color: var(--text-primary, #e2e8f0);">Volume in uL (step 10)</p>
|
|
67
|
-
<NumberInput
|
|
83
|
+
<NumberInput v-model="volume" :min="0" :max="1000" :step="10" />
|
|
68
84
|
</div>
|
|
69
85
|
</div>
|
|
70
86
|
</Variant>
|
|
71
87
|
|
|
72
88
|
<Variant title="Error State">
|
|
73
89
|
<div style="padding: 2rem; max-width: 300px; margin: 0 auto;">
|
|
74
|
-
<NumberInput
|
|
90
|
+
<NumberInput v-model="overMax" :max="100" error />
|
|
75
91
|
</div>
|
|
76
92
|
</Variant>
|
|
77
93
|
|
|
@@ -83,16 +99,16 @@ const sizes = ['sm', 'md', 'lg'] as const
|
|
|
83
99
|
|
|
84
100
|
<Variant title="Empty with Placeholder">
|
|
85
101
|
<div style="padding: 2rem; max-width: 300px; margin: 0 auto;">
|
|
86
|
-
<NumberInput placeholder="Enter value..." />
|
|
102
|
+
<NumberInput v-model="empty" placeholder="Enter value..." />
|
|
87
103
|
</div>
|
|
88
104
|
</Variant>
|
|
89
105
|
|
|
90
|
-
<Variant title="
|
|
106
|
+
<Variant title="Steppers (always visible)">
|
|
91
107
|
<template #default="{ state }">
|
|
92
108
|
<div style="padding: 2rem; max-width: 320px; display: flex; flex-direction: column; gap: 1rem;">
|
|
93
109
|
<NumberInput v-model="state.value" :step="0.5" />
|
|
94
110
|
<p style="margin: 0; font-size: 0.8125rem; color: var(--text-muted);">
|
|
95
|
-
|
|
111
|
+
Two horizontal steppers, hairline-divided. Arrow keys, PageUp/PageDown also step.
|
|
96
112
|
</p>
|
|
97
113
|
</div>
|
|
98
114
|
</template>
|
|
@@ -106,16 +122,19 @@ const sizes = ['sm', 'md', 'lg'] as const
|
|
|
106
122
|
|
|
107
123
|
<Variant title="With unit suffix">
|
|
108
124
|
<div style="padding: 2rem; max-width: 320px; display: flex; flex-direction: column; gap: 0.75rem;">
|
|
109
|
-
<NumberInput
|
|
110
|
-
<NumberInput
|
|
111
|
-
<NumberInput
|
|
125
|
+
<NumberInput v-model="mass" unit="mg" :step="0.1" />
|
|
126
|
+
<NumberInput v-model="ph" unit="pH" :step="0.1" :min="0" :max="14" />
|
|
127
|
+
<NumberInput v-model="wells" unit="wells" :step="1" />
|
|
112
128
|
</div>
|
|
113
129
|
</Variant>
|
|
114
130
|
|
|
115
131
|
<Variant title="Slider mode (min + max provided)">
|
|
116
132
|
<div style="padding: 2rem; max-width: 320px; display: flex; flex-direction: column; gap: 1rem;">
|
|
117
|
-
<NumberInput
|
|
118
|
-
<NumberInput
|
|
133
|
+
<NumberInput v-model="sliderPh" :min="0" :max="14" :step="0.1" unit="pH" />
|
|
134
|
+
<NumberInput v-model="percent" :min="0" :max="100" :step="1" unit="%" />
|
|
135
|
+
<p style="margin: 0; font-size: 0.8125rem; color: var(--text-muted);">
|
|
136
|
+
Drag anywhere in the field (outside the text) to scrub. Home/End jump to min/max.
|
|
137
|
+
</p>
|
|
119
138
|
</div>
|
|
120
139
|
</Variant>
|
|
121
140
|
</Story>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
/** Numeric input with
|
|
2
|
+
/** Numeric input with always-visible steppers, optional unit label, and (with min+max) an in-field drag-to-scrub range fill. */
|
|
3
3
|
import { computed } from 'vue'
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
@@ -33,7 +33,7 @@ const sliderPercent = computed(() => {
|
|
|
33
33
|
if (min === undefined || max === undefined || props.modelValue === undefined) return 0
|
|
34
34
|
const range = max - min
|
|
35
35
|
if (range === 0) return 0
|
|
36
|
-
return ((props.modelValue - min) / range) * 100
|
|
36
|
+
return Math.min(100, Math.max(0, ((props.modelValue - min) / range) * 100))
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
const canDecrement = computed(() => {
|
|
@@ -55,6 +55,17 @@ function clamp(value: number): number {
|
|
|
55
55
|
return result
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/** Clamp and trim float noise (0.1 + 0.2 → 0.3) without losing real precision. */
|
|
59
|
+
function settle(value: number): number {
|
|
60
|
+
return Number(clamp(value).toFixed(10))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Snap to the step grid anchored at `min`, for pointer scrubbing. */
|
|
64
|
+
function snap(value: number): number {
|
|
65
|
+
const origin = props.min ?? 0
|
|
66
|
+
return settle(origin + Math.round((value - origin) / props.step) * props.step)
|
|
67
|
+
}
|
|
68
|
+
|
|
58
69
|
function handleInput(event: Event) {
|
|
59
70
|
const target = event.target as HTMLInputElement
|
|
60
71
|
const value = target.value === '' ? undefined : Number(target.value)
|
|
@@ -65,24 +76,70 @@ function handleInput(event: Event) {
|
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
78
|
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (!
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
function nudge(delta: number) {
|
|
80
|
+
if (props.disabled) return
|
|
81
|
+
if (delta < 0 && !canDecrement.value) return
|
|
82
|
+
if (delta > 0 && !canIncrement.value) return
|
|
83
|
+
const current = props.modelValue ?? (delta < 0 ? props.max ?? 0 : props.min ?? 0)
|
|
84
|
+
emit('update:modelValue', settle(current + delta))
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
function decrement() {
|
|
77
|
-
|
|
78
|
-
const current = props.modelValue ?? (props.max ?? 0)
|
|
79
|
-
emit('update:modelValue', clamp(current - props.step))
|
|
88
|
+
nudge(-props.step)
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
function increment() {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
nudge(props.step)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Home/End only apply on the slider surface: inside the text input they must
|
|
96
|
+
// keep moving the caret.
|
|
97
|
+
function handleKeydown(event: KeyboardEvent, allowJump = false) {
|
|
98
|
+
if (props.disabled) return
|
|
99
|
+
switch (event.key) {
|
|
100
|
+
case 'ArrowUp': increment(); break
|
|
101
|
+
case 'ArrowDown': decrement(); break
|
|
102
|
+
case 'PageUp': nudge(props.step * 10); break
|
|
103
|
+
case 'PageDown': nudge(-props.step * 10); break
|
|
104
|
+
case 'Home':
|
|
105
|
+
if (!allowJump || props.min === undefined) return
|
|
106
|
+
emit('update:modelValue', props.min)
|
|
107
|
+
break
|
|
108
|
+
case 'End':
|
|
109
|
+
if (!allowJump || props.max === undefined) return
|
|
110
|
+
emit('update:modelValue', props.max)
|
|
111
|
+
break
|
|
112
|
+
default:
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
event.preventDefault()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Drag-to-scrub across the field. Pointerdown on the <input> itself is left
|
|
119
|
+
// alone so click-to-place-caret and double-click-to-select keep working.
|
|
120
|
+
function scrubTo(clientX: number, area: HTMLElement) {
|
|
121
|
+
const rect = area.getBoundingClientRect()
|
|
122
|
+
if (rect.width === 0 || props.min === undefined || props.max === undefined) return
|
|
123
|
+
const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width))
|
|
124
|
+
emit('update:modelValue', snap(props.min + ratio * (props.max - props.min)))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function handleScrubStart(event: PointerEvent) {
|
|
128
|
+
if (props.disabled || !isSliderMode.value) return
|
|
129
|
+
if ((event.target as HTMLElement).tagName === 'INPUT') return
|
|
130
|
+
const area = event.currentTarget as HTMLElement
|
|
131
|
+
event.preventDefault()
|
|
132
|
+
area.setPointerCapture?.(event.pointerId)
|
|
133
|
+
scrubTo(event.clientX, area)
|
|
134
|
+
const move = (e: PointerEvent) => scrubTo(e.clientX, area)
|
|
135
|
+
const stop = () => {
|
|
136
|
+
area.removeEventListener('pointermove', move)
|
|
137
|
+
area.removeEventListener('pointerup', stop)
|
|
138
|
+
area.removeEventListener('pointercancel', stop)
|
|
139
|
+
}
|
|
140
|
+
area.addEventListener('pointermove', move)
|
|
141
|
+
area.addEventListener('pointerup', stop)
|
|
142
|
+
area.addEventListener('pointercancel', stop)
|
|
86
143
|
}
|
|
87
144
|
</script>
|
|
88
145
|
|
|
@@ -97,62 +154,66 @@ function increment() {
|
|
|
97
154
|
]"
|
|
98
155
|
>
|
|
99
156
|
<div class="mint-number-input__field">
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
:
|
|
103
|
-
:
|
|
104
|
-
:
|
|
105
|
-
:
|
|
106
|
-
:
|
|
107
|
-
:
|
|
108
|
-
:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
157
|
+
<div
|
|
158
|
+
class="mint-number-input__scrub"
|
|
159
|
+
:style="isSliderMode ? { '--mint-number-input-fill': `${sliderPercent}%` } : undefined"
|
|
160
|
+
:role="isSliderMode ? 'slider' : undefined"
|
|
161
|
+
:tabindex="isSliderMode && !disabled ? 0 : undefined"
|
|
162
|
+
:aria-valuenow="isSliderMode ? modelValue : undefined"
|
|
163
|
+
:aria-valuemin="isSliderMode ? min : undefined"
|
|
164
|
+
:aria-valuemax="isSliderMode ? max : undefined"
|
|
165
|
+
:aria-orientation="isSliderMode ? 'horizontal' : undefined"
|
|
166
|
+
@pointerdown="handleScrubStart"
|
|
167
|
+
@keydown.self="isSliderMode && handleKeydown($event, true)"
|
|
168
|
+
>
|
|
169
|
+
<template v-if="isSliderMode">
|
|
170
|
+
<span class="mint-number-input__wash" aria-hidden="true" />
|
|
171
|
+
<span class="mint-number-input__rule" aria-hidden="true" />
|
|
172
|
+
</template>
|
|
173
|
+
<input
|
|
174
|
+
type="number"
|
|
175
|
+
:value="modelValue"
|
|
176
|
+
:min="min"
|
|
177
|
+
:max="max"
|
|
178
|
+
:step="step"
|
|
179
|
+
:disabled="disabled"
|
|
180
|
+
:placeholder="placeholder"
|
|
181
|
+
:class="[
|
|
182
|
+
'mint-number-input__input',
|
|
183
|
+
`mint-number-input__input--${size}`,
|
|
184
|
+
]"
|
|
185
|
+
@input="handleInput"
|
|
186
|
+
@keydown="handleKeydown"
|
|
187
|
+
/>
|
|
188
|
+
<span v-if="unit" class="mint-number-input__unit">{{ unit }}</span>
|
|
189
|
+
</div>
|
|
116
190
|
|
|
117
191
|
<div class="mint-number-input__steppers" aria-hidden="true">
|
|
118
192
|
<button
|
|
119
193
|
type="button"
|
|
120
194
|
tabindex="-1"
|
|
121
|
-
aria-label="
|
|
122
|
-
:disabled="disabled || !
|
|
123
|
-
class="mint-number-input__stepper mint-number-input__stepper--
|
|
124
|
-
@click="
|
|
195
|
+
aria-label="Decrease value"
|
|
196
|
+
:disabled="disabled || !canDecrement"
|
|
197
|
+
class="mint-number-input__stepper mint-number-input__stepper--down"
|
|
198
|
+
@click="decrement"
|
|
125
199
|
>
|
|
126
|
-
<svg viewBox="0 0 10
|
|
127
|
-
<path d="
|
|
200
|
+
<svg viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
|
|
201
|
+
<path d="M2 5h6" />
|
|
128
202
|
</svg>
|
|
129
203
|
</button>
|
|
130
204
|
<button
|
|
131
205
|
type="button"
|
|
132
206
|
tabindex="-1"
|
|
133
|
-
aria-label="
|
|
134
|
-
:disabled="disabled || !
|
|
135
|
-
class="mint-number-input__stepper mint-number-input__stepper--
|
|
136
|
-
@click="
|
|
207
|
+
aria-label="Increase value"
|
|
208
|
+
:disabled="disabled || !canIncrement"
|
|
209
|
+
class="mint-number-input__stepper mint-number-input__stepper--up"
|
|
210
|
+
@click="increment"
|
|
137
211
|
>
|
|
138
|
-
<svg viewBox="0 0 10
|
|
139
|
-
<path d="
|
|
212
|
+
<svg viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
|
|
213
|
+
<path d="M5 2v6" /><path d="M2 5h6" />
|
|
140
214
|
</svg>
|
|
141
215
|
</button>
|
|
142
216
|
</div>
|
|
143
217
|
</div>
|
|
144
|
-
|
|
145
|
-
<input
|
|
146
|
-
v-if="isSliderMode"
|
|
147
|
-
type="range"
|
|
148
|
-
:value="modelValue ?? min"
|
|
149
|
-
:min="min"
|
|
150
|
-
:max="max"
|
|
151
|
-
:step="step"
|
|
152
|
-
:disabled="disabled"
|
|
153
|
-
:style="{ '--mint-slider-fill': `${sliderPercent}%` }"
|
|
154
|
-
class="mint-number-input__slider"
|
|
155
|
-
@input="handleSliderInput"
|
|
156
|
-
/>
|
|
157
218
|
</div>
|
|
158
219
|
</template>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type * as Plotly from 'plotly.js'
|
|
3
|
+
|
|
4
|
+
import PlotlyChart from './PlotlyChart.vue'
|
|
5
|
+
|
|
6
|
+
const data: Plotly.Data[] = [
|
|
7
|
+
{
|
|
8
|
+
type: 'scatter',
|
|
9
|
+
mode: 'lines+markers',
|
|
10
|
+
name: 'Control',
|
|
11
|
+
x: ['0 h', '6 h', '12 h', '24 h'],
|
|
12
|
+
y: [1, 1.3, 1.8, 2.1],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
type: 'scatter',
|
|
16
|
+
mode: 'lines+markers',
|
|
17
|
+
name: 'Treatment',
|
|
18
|
+
x: ['0 h', '6 h', '12 h', '24 h'],
|
|
19
|
+
y: [1, 1.8, 3.1, 4.4],
|
|
20
|
+
},
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const layout: Partial<Plotly.Layout> = {
|
|
24
|
+
margin: { t: 24, r: 24, b: 56, l: 56 },
|
|
25
|
+
xaxis: { title: { text: 'Time' } },
|
|
26
|
+
yaxis: { title: { text: 'Relative abundance' } },
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<Story title="Data Display/PlotlyChart">
|
|
32
|
+
<Variant title="Native Plotly">
|
|
33
|
+
<div style="max-width: 760px; margin: 0 auto; padding: 2rem;">
|
|
34
|
+
<PlotlyChart
|
|
35
|
+
title="Metabolite response"
|
|
36
|
+
description="Native Plotly traces with MINT theme and resize handling"
|
|
37
|
+
:data="data"
|
|
38
|
+
:layout="layout"
|
|
39
|
+
:config="{ toImageButtonOptions: { filename: 'metabolite-response' } }"
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
</Variant>
|
|
43
|
+
|
|
44
|
+
<Variant title="Empty">
|
|
45
|
+
<div style="max-width: 760px; margin: 0 auto; padding: 2rem;">
|
|
46
|
+
<PlotlyChart
|
|
47
|
+
title="Metabolite response"
|
|
48
|
+
:data="[]"
|
|
49
|
+
empty
|
|
50
|
+
empty-message="No measurements are available."
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
</Variant>
|
|
54
|
+
|
|
55
|
+
<Variant title="Loading">
|
|
56
|
+
<div style="max-width: 760px; margin: 0 auto; padding: 2rem;">
|
|
57
|
+
<PlotlyChart title="Loading measurements" :data="[]" loading />
|
|
58
|
+
</div>
|
|
59
|
+
</Variant>
|
|
60
|
+
</Story>
|
|
61
|
+
</template>
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Render native Plotly data with MINT loading, empty, theme, and error states.
|
|
4
|
+
*
|
|
5
|
+
* Plotly is loaded only when the component is mounted, so importing the SDK does
|
|
6
|
+
* not add Plotly to the application's initial bundle.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```vue
|
|
10
|
+
* <PlotlyChart
|
|
11
|
+
* title="QC overview"
|
|
12
|
+
* :data="traces"
|
|
13
|
+
* :layout="{ xaxis: { title: { text: 'Sample' } } }"
|
|
14
|
+
* :config="{ toImageButtonOptions: { filename: 'qc-overview' } }"
|
|
15
|
+
* />
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import type * as PlotlyType from 'plotly.js'
|
|
19
|
+
import { nextTick, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue'
|
|
20
|
+
|
|
21
|
+
import ChartContainer from './ChartContainer.vue'
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
data: PlotlyType.Data[]
|
|
25
|
+
layout?: Partial<PlotlyType.Layout>
|
|
26
|
+
config?: Partial<PlotlyType.Config>
|
|
27
|
+
title?: string
|
|
28
|
+
description?: string
|
|
29
|
+
loading?: boolean
|
|
30
|
+
empty?: boolean
|
|
31
|
+
emptyMessage?: string
|
|
32
|
+
ariaLabel?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
36
|
+
layout: () => ({}),
|
|
37
|
+
config: () => ({}),
|
|
38
|
+
loading: false,
|
|
39
|
+
empty: false,
|
|
40
|
+
emptyMessage: 'No data available',
|
|
41
|
+
ariaLabel: 'Interactive Plotly chart',
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const chartElement = ref<HTMLDivElement | null>(null)
|
|
45
|
+
const failed = ref(false)
|
|
46
|
+
let plotly: typeof PlotlyType | null = null
|
|
47
|
+
let resizeObserver: ResizeObserver | null = null
|
|
48
|
+
let themeObserver: MutationObserver | null = null
|
|
49
|
+
let renderedElement: HTMLDivElement | null = null
|
|
50
|
+
let rendering = false
|
|
51
|
+
let disposed = false
|
|
52
|
+
let revision = 0
|
|
53
|
+
|
|
54
|
+
async function loadPlotly(): Promise<typeof PlotlyType> {
|
|
55
|
+
const module = await import('plotly.js-dist-min')
|
|
56
|
+
return ('default' in module ? module.default : module) as unknown as typeof PlotlyType
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function cssValue(name: string, fallback: string): string {
|
|
60
|
+
const value = getComputedStyle(chartElement.value ?? document.documentElement).getPropertyValue(name).trim()
|
|
61
|
+
return value || getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Unwrap nested Vue proxies while copying Plotly's mutable input tree. Keep
|
|
65
|
+
// native dates/typed arrays and callbacks instead of round-tripping through JSON.
|
|
66
|
+
function clonePlotlyInput<T>(value: T): T {
|
|
67
|
+
const raw = toRaw(value)
|
|
68
|
+
if (Array.isArray(raw)) return raw.map(clonePlotlyInput) as T
|
|
69
|
+
if (raw !== null && typeof raw === 'object') {
|
|
70
|
+
if (Object.getPrototypeOf(raw) === Object.prototype) {
|
|
71
|
+
return Object.fromEntries(Object.entries(raw).map(([key, item]) => [key, clonePlotlyInput(item)])) as T
|
|
72
|
+
}
|
|
73
|
+
return structuredClone(raw)
|
|
74
|
+
}
|
|
75
|
+
return raw
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function themedLayout(): Partial<PlotlyType.Layout> {
|
|
79
|
+
const textColor = cssValue('--text-primary', '#1E293B')
|
|
80
|
+
const gridColor = cssValue('--border-color', '#E5E7EB')
|
|
81
|
+
const layout = clonePlotlyInput(props.layout)
|
|
82
|
+
return {
|
|
83
|
+
autosize: true,
|
|
84
|
+
paper_bgcolor: 'rgba(0,0,0,0)',
|
|
85
|
+
plot_bgcolor: 'rgba(0,0,0,0)',
|
|
86
|
+
...layout,
|
|
87
|
+
font: { color: textColor, family: cssValue('--font-sans', 'Arial, sans-serif'), ...layout.font },
|
|
88
|
+
xaxis: { gridcolor: gridColor, ...layout.xaxis },
|
|
89
|
+
yaxis: { gridcolor: gridColor, ...layout.yaxis },
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function purgeChart(): void {
|
|
94
|
+
resizeObserver?.disconnect()
|
|
95
|
+
if (renderedElement) plotly?.purge(renderedElement)
|
|
96
|
+
renderedElement = null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function renderChart(): Promise<void> {
|
|
100
|
+
revision++
|
|
101
|
+
if (rendering || disposed || props.loading || props.empty) return
|
|
102
|
+
rendering = true
|
|
103
|
+
try {
|
|
104
|
+
while (!disposed && !props.loading && !props.empty) {
|
|
105
|
+
const currentRevision = revision
|
|
106
|
+
failed.value = false
|
|
107
|
+
await nextTick()
|
|
108
|
+
if (disposed || props.loading || props.empty) break
|
|
109
|
+
try {
|
|
110
|
+
plotly ??= await loadPlotly()
|
|
111
|
+
if (disposed || props.loading || props.empty) break
|
|
112
|
+
if (currentRevision !== revision) continue
|
|
113
|
+
const element = chartElement.value
|
|
114
|
+
if (!element) break
|
|
115
|
+
renderedElement = element
|
|
116
|
+
// Plotly mutates traces and nested layout fields; keep caller-owned values intact.
|
|
117
|
+
await plotly.react(
|
|
118
|
+
element,
|
|
119
|
+
clonePlotlyInput(props.data),
|
|
120
|
+
themedLayout(),
|
|
121
|
+
{ responsive: true, displaylogo: false, ...props.config },
|
|
122
|
+
)
|
|
123
|
+
if (disposed || props.loading || props.empty || element !== chartElement.value) {
|
|
124
|
+
purgeChart()
|
|
125
|
+
} else {
|
|
126
|
+
resizeObserver?.observe(element)
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
purgeChart()
|
|
130
|
+
if (!disposed && currentRevision === revision) failed.value = true
|
|
131
|
+
}
|
|
132
|
+
if (currentRevision === revision) break
|
|
133
|
+
}
|
|
134
|
+
} finally {
|
|
135
|
+
rendering = false
|
|
136
|
+
if (disposed || props.loading || props.empty) purgeChart()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
onMounted(() => {
|
|
141
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
142
|
+
resizeObserver = new ResizeObserver(() => {
|
|
143
|
+
if (rendering || !plotly || !renderedElement) return
|
|
144
|
+
// A tab can become hidden or unmount before Plotly's async resize finishes.
|
|
145
|
+
void Promise.resolve(plotly.Plots.resize(renderedElement)).catch(() => {})
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
150
|
+
themeObserver = new MutationObserver(() => void renderChart())
|
|
151
|
+
themeObserver.observe(document.documentElement, {
|
|
152
|
+
attributes: true,
|
|
153
|
+
attributeFilter: ['class', 'style'],
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
void renderChart()
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
watch(
|
|
160
|
+
[() => props.data, () => props.layout, () => props.config, () => props.loading, () => props.empty],
|
|
161
|
+
() => {
|
|
162
|
+
if (props.loading || props.empty) {
|
|
163
|
+
resizeObserver?.disconnect()
|
|
164
|
+
if (!rendering) purgeChart()
|
|
165
|
+
}
|
|
166
|
+
void renderChart()
|
|
167
|
+
},
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
onBeforeUnmount(() => {
|
|
171
|
+
disposed = true
|
|
172
|
+
resizeObserver?.disconnect()
|
|
173
|
+
themeObserver?.disconnect()
|
|
174
|
+
// An in-flight react must finish before its resources can be purged completely.
|
|
175
|
+
if (!rendering) purgeChart()
|
|
176
|
+
})
|
|
177
|
+
</script>
|
|
178
|
+
|
|
179
|
+
<template>
|
|
180
|
+
<ChartContainer
|
|
181
|
+
:title="title"
|
|
182
|
+
:description="description"
|
|
183
|
+
:loading="loading"
|
|
184
|
+
:empty="empty"
|
|
185
|
+
:empty-message="emptyMessage"
|
|
186
|
+
>
|
|
187
|
+
<template v-if="$slots.toolbar" #toolbar>
|
|
188
|
+
<slot name="toolbar" />
|
|
189
|
+
</template>
|
|
190
|
+
<template v-if="$slots.empty" #empty>
|
|
191
|
+
<slot name="empty" />
|
|
192
|
+
</template>
|
|
193
|
+
|
|
194
|
+
<template v-if="failed">
|
|
195
|
+
<slot name="error">
|
|
196
|
+
<div class="mint-plotly-chart__error">
|
|
197
|
+
<p role="alert">Unable to display this chart.</p>
|
|
198
|
+
</div>
|
|
199
|
+
</slot>
|
|
200
|
+
</template>
|
|
201
|
+
<div
|
|
202
|
+
ref="chartElement"
|
|
203
|
+
v-show="!failed"
|
|
204
|
+
class="mint-plotly-chart__plot"
|
|
205
|
+
role="group"
|
|
206
|
+
:aria-label="ariaLabel"
|
|
207
|
+
/>
|
|
208
|
+
|
|
209
|
+
<template v-if="$slots.legend" #legend>
|
|
210
|
+
<slot name="legend" />
|
|
211
|
+
</template>
|
|
212
|
+
</ChartContainer>
|
|
213
|
+
</template>
|
|
214
|
+
|
|
215
|
+
<style scoped>
|
|
216
|
+
.mint-plotly-chart__plot {
|
|
217
|
+
width: 100%;
|
|
218
|
+
min-height: 24rem;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.mint-plotly-chart__error {
|
|
222
|
+
margin: 0;
|
|
223
|
+
padding: 1rem;
|
|
224
|
+
border: 1px solid var(--mint-error-border);
|
|
225
|
+
border-radius: 0.75rem;
|
|
226
|
+
background: var(--mint-error-bg);
|
|
227
|
+
color: var(--mint-error-hover);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.mint-plotly-chart__error :deep(p) {
|
|
231
|
+
margin: 0;
|
|
232
|
+
}
|
|
233
|
+
</style>
|
|
@@ -97,9 +97,9 @@ export interface PluginWorkspaceViewProps {
|
|
|
97
97
|
sidebarSubtitle?: string
|
|
98
98
|
/** Optional compact badge/count rendered in the sidebar header. */
|
|
99
99
|
sidebarBadge?: string | number
|
|
100
|
-
/** AppSidebar
|
|
100
|
+
/** @deprecated No-op. AppSidebar has one look now; the former `analysis` preset is the default. */
|
|
101
101
|
sidebarVariant?: PluginWorkspaceSidebarVariant
|
|
102
|
-
/** Sidebar CSS width. Defaults to AppSidebar's
|
|
102
|
+
/** Sidebar CSS width. Defaults to AppSidebar's width. */
|
|
103
103
|
sidebarWidth?: string
|
|
104
104
|
/** Sidebar position in AppLayout. */
|
|
105
105
|
sidebarPosition?: 'left' | 'right'
|
|
@@ -113,7 +113,7 @@ export interface PluginWorkspaceViewProps {
|
|
|
113
113
|
sidebarContentId?: string
|
|
114
114
|
/** Compact AppSidebar density. */
|
|
115
115
|
dense?: boolean
|
|
116
|
-
/** Show a built-in collapse/expand button in the sidebar chrome. Defaults to
|
|
116
|
+
/** Show a built-in collapse/expand button in the sidebar chrome. Defaults to true. */
|
|
117
117
|
sidebarCollapsible?: boolean
|
|
118
118
|
/** Sidebar width when collapsed. */
|
|
119
119
|
sidebarCollapsedWidth?: string
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
/** Renders a segmented button group for single-option selection,
|
|
2
|
+
/** Renders a segmented button group for single-option selection, defaulting to the card variant. */
|
|
3
3
|
import { computed, type ComponentPublicInstance } from 'vue'
|
|
4
4
|
import type { SegmentedOption, SegmentedOptionInput, SegmentedControlVariant, SegmentedControlSize } from '../types'
|
|
5
5
|
import { normalizeOptionInput } from '../utils/options'
|
|
@@ -16,7 +16,7 @@ interface Props {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const props = withDefaults(defineProps<Props>(), {
|
|
19
|
-
variant: '
|
|
19
|
+
variant: 'card',
|
|
20
20
|
size: 'md',
|
|
21
21
|
fullWidth: false,
|
|
22
22
|
disabled: false,
|