@morscherlab/mint-sdk 1.2.0-beta.7 → 1.2.0-beta.9
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-CfOdh5Rm.js → ExperimentPopover-oL0fg_A0.js} +5 -3
- package/dist/ExperimentPopover-oL0fg_A0.js.map +1 -0
- package/dist/{ExperimentSelectorModal-DvIagrxY.js → ExperimentSelectorModal-BeEOomw_.js} +4 -4
- package/dist/ExperimentSelectorModal-BeEOomw_.js.map +1 -0
- package/dist/{SettingsModal-W3TYfnP9.js → SettingsModal-Dphh3RrI.js} +4 -4
- package/dist/SettingsModal-Dphh3RrI.js.map +1 -0
- package/dist/{EmptyState-NBqf2_8z.js → Skeleton-DxtwJ5gt.js} +110 -92
- package/dist/Skeleton-DxtwJ5gt.js.map +1 -0
- package/dist/__tests__/components/ExperimentTimeline.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/AppPluginSwitcher.vue.d.ts +2 -0
- package/dist/components/AuditTrail.vue.d.ts +13 -0
- package/dist/components/BatchProgressList.vue.d.ts +17 -0
- 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/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/LcmsSequenceTable.vue.d.ts +29 -1
- 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/SmartGroupFieldRecipe.vue.d.ts +1 -0
- 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.js +6 -6
- package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -0
- package/dist/components/internal/DataStateInternal.vue.d.ts +43 -0
- package/dist/{components-B80gV0P6.js → components-CgAUzF8F.js} +1930 -1688
- package/dist/components-CgAUzF8F.js.map +1 -0
- package/dist/composables/controlSchemaTypes.d.ts +34 -0
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/index.js +9 -9
- package/dist/composables/useDropdownState.d.ts +18 -2
- package/dist/composables/useEventStream.d.ts +9 -0
- package/dist/composables/useListReorder.d.ts +90 -0
- package/dist/composables/useMenuKeyboard.d.ts +2 -2
- package/dist/composables/usePluginConfig.d.ts +3 -1
- package/dist/composables/useRequestSyncState.d.ts +42 -2
- package/dist/composables/useScheduleDrag.d.ts +1 -0
- package/dist/{composables-C8mmD5ta.js → composables-B_pia3hO.js} +13 -69
- 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-DV3DrvE_.js.map +1 -1
- package/dist/index.js +14 -14
- 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 +9572 -27877
- package/dist/templates/index.js +2 -4
- package/dist/{templates-CK72aKO1.js → templates-Qq113pyZ.js} +291 -85
- package/dist/templates-Qq113pyZ.js.map +1 -0
- package/dist/{useControlSchema-_OVhavKq.js → useControlSchema-aMZlb4Sc.js} +197 -1089
- package/dist/useControlSchema-aMZlb4Sc.js.map +1 -0
- package/dist/{useDropdownState-D5G4UvWd.js → useDropdownState-DitCtrTT.js} +21 -5
- package/dist/useDropdownState-DitCtrTT.js.map +1 -0
- package/dist/{useExperimentSelector-DSEckf_g.js → useExperimentSelector-I8HC8hUu.js} +3 -3
- package/dist/{useExperimentSelector-DSEckf_g.js.map → useExperimentSelector-I8HC8hUu.js.map} +1 -1
- package/dist/{useFormBuilder-Bd714C7y.js → useFormBuilder-TCxNnqeb.js} +2 -2
- package/dist/{useFormBuilder-Bd714C7y.js.map → useFormBuilder-TCxNnqeb.js.map} +1 -1
- package/dist/{useProtocolTemplates-Dq5Pzwyd.js → useProtocolTemplates-BmX_iVBR.js} +185 -157
- 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/package.json +1 -1
- package/src/__tests__/components/ActionMenu.test.ts +94 -0
- package/src/__tests__/components/DataFrame.test.ts +183 -11
- package/src/__tests__/components/DataStates.test.ts +176 -0
- package/src/__tests__/components/DatePicker.test.ts +76 -0
- package/src/__tests__/components/DateTimePicker.test.ts +36 -0
- package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
- package/src/__tests__/components/GroupAssigner.test.ts +53 -0
- package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
- package/src/__tests__/components/RackEditor.test.ts +35 -0
- package/src/__tests__/components/ReagentList.test.ts +64 -2
- package/src/__tests__/components/RetiredStyles.test.ts +31 -0
- package/src/__tests__/components/SampleSelector.drag.test.ts +51 -1
- package/src/__tests__/components/ScheduleCalendar.test.ts +63 -0
- package/src/__tests__/components/SegmentedControl.test.ts +88 -0
- package/src/__tests__/components/TimePicker.test.ts +61 -0
- package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
- package/src/__tests__/composables/useDropdownState.test.ts +58 -1
- package/src/__tests__/composables/useListReorder.test.ts +145 -0
- package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
- package/src/__tests__/stores/settings.test.ts +71 -1
- package/src/__tests__/styles/cssTokens.test.ts +131 -0
- package/src/__tests__/utils/permissions.test.ts +68 -0
- package/src/components/ActionMenu.vue +45 -88
- package/src/components/AlertBox.vue +0 -4
- package/src/components/AppAvatarMenu.vue +2 -14
- package/src/components/AppLayout.vue +0 -4
- package/src/components/AppPluginSwitcher.story.vue +87 -0
- package/src/components/AppPluginSwitcher.vue +2 -5
- package/src/components/AppSidebar.vue +0 -4
- package/src/components/AppToastContainer.vue +6 -8
- package/src/components/AppTopBar.vue +0 -4
- package/src/components/ArtifactSaveDialog.vue +0 -4
- package/src/components/ArtifactSelectorModal.vue +0 -4
- package/src/components/AuditTrail.story.vue +18 -0
- package/src/components/AuditTrail.vue +27 -7
- package/src/components/AutoGroupModal.vue +0 -4
- package/src/components/Avatar.vue +0 -4
- package/src/components/BaseButton.vue +0 -4
- package/src/components/BaseCheckbox.vue +0 -4
- package/src/components/BaseInput.vue +0 -4
- package/src/components/BaseModal.vue +0 -4
- package/src/components/BasePill.vue +0 -4
- package/src/components/BaseRadioGroup.vue +0 -4
- package/src/components/BaseSelect.vue +0 -4
- package/src/components/BaseSlider.vue +0 -4
- package/src/components/BaseTabs.vue +0 -4
- package/src/components/BaseTextarea.vue +0 -4
- package/src/components/BaseToggle.vue +0 -4
- package/src/components/BatchProgressList.story.vue +18 -0
- package/src/components/BatchProgressList.vue +32 -4
- package/src/components/Breadcrumb.vue +0 -4
- package/src/components/Calendar.vue +0 -4
- package/src/components/ChartContainer.story.vue +1 -1
- package/src/components/ChartContainer.vue +0 -4
- package/src/components/ChemicalFormula.vue +0 -4
- package/src/components/CollapsibleCard.vue +0 -4
- package/src/components/ColorSlider.vue +0 -4
- package/src/components/ConcentrationInput.vue +0 -4
- package/src/components/ConfirmDialog.vue +0 -4
- package/src/components/DataFrame.story.vue +14 -0
- package/src/components/DataFrame.vue +97 -23
- package/src/components/DatePicker.vue +73 -27
- package/src/components/DateTimePicker.vue +45 -17
- package/src/components/Divider.vue +0 -4
- package/src/components/DoseCalculator.vue +0 -4
- package/src/components/DropdownButton.vue +2 -14
- package/src/components/EmptyState.vue +0 -4
- package/src/components/ExperimentCodeBadge.vue +0 -4
- package/src/components/ExperimentDataViewer.story.vue +3 -0
- package/src/components/ExperimentDataViewer.vue +33 -13
- package/src/components/ExperimentPopover.vue +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.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/ProgressBar.vue +0 -4
- package/src/components/ProtocolStepEditor.vue +2 -4
- package/src/components/RackEditor.vue +51 -70
- package/src/components/ReagentEditor.vue +0 -4
- package/src/components/ReagentList.story.vue +18 -0
- package/src/components/ReagentList.vue +127 -82
- package/src/components/ResourceCard.vue +0 -4
- package/src/components/RuntimeUpdateNotice.story.vue +49 -0
- package/src/components/RuntimeUpdateNotice.vue +4 -4
- package/src/components/SampleHierarchyTree.story.vue +18 -0
- package/src/components/SampleHierarchyTree.vue +32 -9
- package/src/components/SampleLegend.vue +2 -8
- package/src/components/SampleSelector.drag.ts +51 -0
- package/src/components/SampleSelector.story.vue +6 -0
- package/src/components/SampleSelector.tree.ts +2 -0
- package/src/components/SampleSelector.vue +49 -10
- package/src/components/SampleSelectorGroupNode.vue +19 -0
- package/src/components/ScheduleCalendar.story.vue +6 -0
- package/src/components/ScheduleCalendar.vue +82 -7
- package/src/components/ScientificNumber.vue +0 -4
- package/src/components/SectionCard.vue +0 -4
- package/src/components/SegmentedControl.vue +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/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 +0 -4
- package/src/composables/controlSchemaTypes.ts +34 -0
- package/src/composables/index.ts +10 -0
- package/src/composables/useDropdownState.ts +35 -5
- package/src/composables/useEventStream.ts +14 -2
- package/src/composables/useFileImport.ts +2 -7
- package/src/composables/useGeneratedAnalysis.ts +8 -9
- package/src/composables/useListReorder.story.vue +143 -0
- package/src/composables/useListReorder.ts +242 -0
- package/src/composables/useMenuKeyboard.ts +2 -2
- package/src/composables/useOptimisticMutation.ts +1 -15
- package/src/composables/usePluginConfig.ts +3 -1
- package/src/composables/useRackEditor.ts +2 -11
- package/src/composables/useRequestSyncState.ts +98 -28
- package/src/composables/useResourceCrud.ts +1 -15
- package/src/composables/useScheduleDrag.ts +37 -0
- package/src/composables/useWellPainting.ts +5 -29
- package/src/composables/useWellPlateEditor.ts +1 -11
- package/src/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/well-plate.css +8 -0
- package/src/styles/index.css +8 -0
- package/src/styles/variables.css +29 -0
- package/src/templates/builderPresetControls.ts +1 -1
- package/src/templates/componentPlateHelpers.ts +3 -1
- package/src/templates/doseResponseAdapters.ts +2 -13
- package/src/templates/elisaAssayCollectionBuilder.ts +2 -2
- package/src/templates/plateMapBuilder.ts +2 -2
- package/src/templates/qpcrPlateBuilder.ts +1 -1
- package/src/templates/templateValidators.ts +1 -1
- package/src/templates/timeCourseBuilder.ts +2 -2
- package/src/templates/wellPlateScreenCollectionBuilder.ts +2 -2
- package/src/utils/color.ts +51 -0
- package/src/utils/errors.ts +29 -0
- package/dist/EmptyState-NBqf2_8z.js.map +0 -1
- package/dist/ExperimentPopover-CfOdh5Rm.js.map +0 -1
- package/dist/ExperimentSelectorModal-DvIagrxY.js.map +0 -1
- package/dist/SettingsModal-W3TYfnP9.js.map +0 -1
- package/dist/components-B80gV0P6.js.map +0 -1
- package/dist/composables/autoGroupConstants.d.ts +0 -1
- package/dist/composables/autoGroupGrouping.d.ts +0 -3
- package/dist/composables/useAutoGroupInputSources.d.ts +0 -32
- package/dist/composables-C8mmD5ta.js.map +0 -1
- package/dist/experiment-Y9Iz7KYi.js.map +0 -1
- package/dist/templates/builderDefaults.d.ts +0 -1
- package/dist/templates/plateGeometry.d.ts +0 -4
- package/dist/templates-CK72aKO1.js.map +0 -1
- package/dist/useApi-CDBq6z23.js.map +0 -1
- package/dist/useControlSchema-_OVhavKq.js.map +0 -1
- package/dist/useDropdownState-D5G4UvWd.js.map +0 -1
- package/dist/useProtocolTemplates-Dq5Pzwyd.js.map +0 -1
- package/src/__tests__/composables/useAutoGroupInputSources.test.ts +0 -127
- package/src/composables/autoGroupConstants.ts +0 -4
- package/src/composables/autoGroupGrouping.ts +0 -148
- package/src/composables/useAutoGroupInputSources.ts +0 -147
- package/src/templates/builderDefaults.ts +0 -11
- package/src/templates/plateGeometry.ts +0 -62
- /package/dist/__tests__/{composables/useAutoGroupInputSources.test.d.ts → components/DataStates.test.d.ts} +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Drag-and-drop two-zone assigner for splitting sample groups into control and treatment arms. */
|
|
3
|
-
import { ref } from 'vue'
|
|
3
|
+
import { nextTick, ref, type ComponentPublicInstance } from 'vue'
|
|
4
4
|
import type { GroupItem } from '../types'
|
|
5
5
|
import { useGroupAssignment, type GroupAssignmentState, type GroupAssignmentZone } from '../composables/useGroupAssignment'
|
|
6
|
+
import { useReorderAnnouncer } from '../composables/useListReorder'
|
|
6
7
|
|
|
7
8
|
interface Props {
|
|
8
9
|
groups: GroupItem[]
|
|
@@ -98,6 +99,56 @@ function emitAssignment(state: GroupAssignmentState) {
|
|
|
98
99
|
emit('update:group1', state.group1)
|
|
99
100
|
emit('update:group2', state.group2)
|
|
100
101
|
}
|
|
102
|
+
|
|
103
|
+
// Keyboard alternative to the pointer drag. GroupAssigner moves items between
|
|
104
|
+
// zones rather than reordering one list, so it shares the announcer (and the
|
|
105
|
+
// Alt+Arrow idiom) with useListReorder without its index maths.
|
|
106
|
+
const { announcement, announce, announcerStyle } = useReorderAnnouncer()
|
|
107
|
+
const pillRefs = new Map<string, HTMLElement>()
|
|
108
|
+
|
|
109
|
+
function setPillRef(name: string, el: Element | ComponentPublicInstance | null) {
|
|
110
|
+
if (el instanceof HTMLElement) {
|
|
111
|
+
pillRefs.set(name, el)
|
|
112
|
+
} else {
|
|
113
|
+
pillRefs.delete(name)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function zoneLabel(zone: GroupAssignmentZone | null): string {
|
|
118
|
+
if (zone === 'zone1') return props.label1
|
|
119
|
+
if (zone === 'zone2') return props.label2
|
|
120
|
+
return 'Available Groups'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Alt+Left / Alt+Right walk a group through Control → unassigned → Treatment. */
|
|
124
|
+
function handlePillKeydown(event: KeyboardEvent, groupName: string) {
|
|
125
|
+
if (!event.altKey) return
|
|
126
|
+
const direction =
|
|
127
|
+
event.key === 'ArrowLeft' ? -1 : event.key === 'ArrowRight' ? 1 : 0
|
|
128
|
+
if (direction === 0) return
|
|
129
|
+
|
|
130
|
+
event.preventDefault()
|
|
131
|
+
const order: Array<GroupAssignmentZone | null> = ['zone1', null, 'zone2']
|
|
132
|
+
const current = order.indexOf(assignment.getZoneForGroup(groupName))
|
|
133
|
+
const next = order[current + direction]
|
|
134
|
+
if (current === -1 || next === undefined) return
|
|
135
|
+
|
|
136
|
+
if (next === null) {
|
|
137
|
+
const from = assignment.getZoneForGroup(groupName)
|
|
138
|
+
if (from) removeFromZone(groupName, from)
|
|
139
|
+
} else {
|
|
140
|
+
assignToZone(groupName, next)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
announce(`${groupName} moved to ${zoneLabel(next)}.`)
|
|
144
|
+
void nextTick(() => pillRefs.get(groupName)?.focus())
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Instructions read out with each pill. */
|
|
148
|
+
function pillLabel(group: GroupItem): string {
|
|
149
|
+
const zone = assignment.getZoneForGroup(group.name)
|
|
150
|
+
return `${group.name}, ${zoneLabel(zone)}. Press Alt with the left or right arrow keys to move it.`
|
|
151
|
+
}
|
|
101
152
|
</script>
|
|
102
153
|
|
|
103
154
|
<template>
|
|
@@ -125,10 +176,14 @@ function emitAssignment(state: GroupAssignmentState) {
|
|
|
125
176
|
<div
|
|
126
177
|
v-for="group in zone1Groups"
|
|
127
178
|
:key="group.name"
|
|
179
|
+
:ref="(el) => setPillRef(group.name, el)"
|
|
128
180
|
class="mint-group-assigner__pill"
|
|
129
181
|
draggable="true"
|
|
182
|
+
tabindex="0"
|
|
183
|
+
:aria-label="pillLabel(group)"
|
|
130
184
|
@dragstart="handleDragStart($event, group.name)"
|
|
131
185
|
@dragend="handleDragEnd"
|
|
186
|
+
@keydown="handlePillKeydown($event, group.name)"
|
|
132
187
|
>
|
|
133
188
|
<span
|
|
134
189
|
class="mint-group-assigner__pill-color"
|
|
@@ -174,10 +229,14 @@ function emitAssignment(state: GroupAssignmentState) {
|
|
|
174
229
|
<div
|
|
175
230
|
v-for="group in zone2Groups"
|
|
176
231
|
:key="group.name"
|
|
232
|
+
:ref="(el) => setPillRef(group.name, el)"
|
|
177
233
|
class="mint-group-assigner__pill"
|
|
178
234
|
draggable="true"
|
|
235
|
+
tabindex="0"
|
|
236
|
+
:aria-label="pillLabel(group)"
|
|
179
237
|
@dragstart="handleDragStart($event, group.name)"
|
|
180
238
|
@dragend="handleDragEnd"
|
|
239
|
+
@keydown="handlePillKeydown($event, group.name)"
|
|
181
240
|
>
|
|
182
241
|
<span
|
|
183
242
|
class="mint-group-assigner__pill-color"
|
|
@@ -221,14 +280,18 @@ function emitAssignment(state: GroupAssignmentState) {
|
|
|
221
280
|
<div
|
|
222
281
|
v-for="group in unassignedGroups"
|
|
223
282
|
:key="group.name"
|
|
283
|
+
:ref="(el) => setPillRef(group.name, el)"
|
|
224
284
|
:class="[
|
|
225
285
|
'mint-group-assigner__pill',
|
|
226
286
|
'mint-group-assigner__pill--unassigned',
|
|
227
287
|
draggingGroup === group.name ? 'mint-group-assigner__pill--dragging' : '',
|
|
228
288
|
]"
|
|
229
289
|
draggable="true"
|
|
290
|
+
tabindex="0"
|
|
291
|
+
:aria-label="pillLabel(group)"
|
|
230
292
|
@dragstart="handleDragStart($event, group.name)"
|
|
231
293
|
@dragend="handleDragEnd"
|
|
294
|
+
@keydown="handlePillKeydown($event, group.name)"
|
|
232
295
|
>
|
|
233
296
|
<span
|
|
234
297
|
class="mint-group-assigner__pill-color"
|
|
@@ -240,6 +303,9 @@ function emitAssignment(state: GroupAssignmentState) {
|
|
|
240
303
|
</div>
|
|
241
304
|
</div>
|
|
242
305
|
|
|
306
|
+
<!-- Assignment announcements for screen readers -->
|
|
307
|
+
<div role="status" aria-live="polite" :style="announcerStyle">{{ announcement }}</div>
|
|
308
|
+
|
|
243
309
|
<!-- Validation message -->
|
|
244
310
|
<div v-if="validationMessage" class="mint-group-assigner__validation">
|
|
245
311
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
@@ -251,7 +317,3 @@ function emitAssignment(state: GroupAssignmentState) {
|
|
|
251
317
|
</div>
|
|
252
318
|
</div>
|
|
253
319
|
</template>
|
|
254
|
-
|
|
255
|
-
<style>
|
|
256
|
-
@import '../styles/components/group-assigner.css';
|
|
257
|
-
</style>
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Table for Xcalibur-compatible LCMS sequence rows with optional reorder/remove/duplicate controls. */
|
|
3
|
-
import { computed,
|
|
3
|
+
import { computed, type ComponentPublicInstance } from 'vue'
|
|
4
|
+
import { useListReorder } from '../composables/useListReorder'
|
|
4
5
|
import {
|
|
5
6
|
basenameFromWindowsPath,
|
|
6
7
|
DEFAULT_LCMS_SEQUENCE_COLUMNS,
|
|
7
8
|
type LcmsSequenceItem,
|
|
8
9
|
type LcmsSequenceTableColumn,
|
|
9
10
|
} from '../lcms'
|
|
11
|
+
import DataStateInternal from './internal/DataStateInternal.vue'
|
|
10
12
|
|
|
11
13
|
interface Props {
|
|
12
14
|
items?: LcmsSequenceItem[]
|
|
@@ -15,6 +17,13 @@ interface Props {
|
|
|
15
17
|
maxRows?: number
|
|
16
18
|
showMoreLabel?: boolean
|
|
17
19
|
emptyMessage?: string
|
|
20
|
+
/** Replace the table with a loading placeholder. */
|
|
21
|
+
loading?: boolean
|
|
22
|
+
/**
|
|
23
|
+
* Error message. When set, the table is replaced by an error state.
|
|
24
|
+
* `null`/`undefined` renders normally.
|
|
25
|
+
*/
|
|
26
|
+
error?: string | null
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -24,6 +33,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
24
33
|
maxRows: undefined,
|
|
25
34
|
showMoreLabel: true,
|
|
26
35
|
emptyMessage: 'No sequence items to display',
|
|
36
|
+
loading: false,
|
|
37
|
+
error: null,
|
|
27
38
|
})
|
|
28
39
|
|
|
29
40
|
const emit = defineEmits<{
|
|
@@ -32,8 +43,38 @@ const emit = defineEmits<{
|
|
|
32
43
|
duplicate: [index: number]
|
|
33
44
|
}>()
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
// Rows are a plain ordered list: pointer drag plus an Alt+Arrow keyboard
|
|
47
|
+
// alternative, both from the shared reorder composable.
|
|
48
|
+
const handleRefs = new Map<string, HTMLElement>()
|
|
49
|
+
|
|
50
|
+
const {
|
|
51
|
+
draggingId,
|
|
52
|
+
dragOverId,
|
|
53
|
+
announcement: reorderAnnouncement,
|
|
54
|
+
announcerStyle,
|
|
55
|
+
onDragStart,
|
|
56
|
+
onDragOver,
|
|
57
|
+
onDragLeave,
|
|
58
|
+
onDrop,
|
|
59
|
+
onDragEnd,
|
|
60
|
+
onKeydown: onReorderKeydown,
|
|
61
|
+
} = useListReorder<LcmsSequenceItem>({
|
|
62
|
+
items: () => props.items,
|
|
63
|
+
getId: (_item, index) => String(index),
|
|
64
|
+
getLabel: (item) => item.file_name,
|
|
65
|
+
disabled: () => !props.editable,
|
|
66
|
+
getElement: (id) => handleRefs.get(id),
|
|
67
|
+
itemNoun: 'Row',
|
|
68
|
+
onReorder: ({ fromIndex, toIndex }) => emit('reorder', fromIndex, toIndex),
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
function setHandleRef(index: number, el: Element | ComponentPublicInstance | null) {
|
|
72
|
+
if (el instanceof HTMLElement) {
|
|
73
|
+
handleRefs.set(String(index), el)
|
|
74
|
+
} else {
|
|
75
|
+
handleRefs.delete(String(index))
|
|
76
|
+
}
|
|
77
|
+
}
|
|
37
78
|
|
|
38
79
|
const displayItems = computed(() => {
|
|
39
80
|
if (!props.maxRows) return props.items
|
|
@@ -60,42 +101,11 @@ function cellValue(item: LcmsSequenceItem, column: LcmsSequenceTableColumn, inde
|
|
|
60
101
|
return item[column.key] ?? ''
|
|
61
102
|
}
|
|
62
103
|
|
|
63
|
-
function onDragStart(event: DragEvent, index: number) {
|
|
64
|
-
if (!props.editable) return
|
|
65
|
-
dragIndex.value = index
|
|
66
|
-
if (event.dataTransfer) {
|
|
67
|
-
event.dataTransfer.effectAllowed = 'move'
|
|
68
|
-
event.dataTransfer.setData('text/plain', String(index))
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function onDragOver(event: DragEvent, index: number) {
|
|
73
|
-
if (!props.editable) return
|
|
74
|
-
event.preventDefault()
|
|
75
|
-
if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'
|
|
76
|
-
if (dragIndex.value !== null && dragIndex.value !== index) {
|
|
77
|
-
dropTargetIndex.value = index
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function clearDragState() {
|
|
82
|
-
dragIndex.value = null
|
|
83
|
-
dropTargetIndex.value = null
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function onDrop(event: DragEvent, toIndex: number) {
|
|
87
|
-
if (!props.editable) return
|
|
88
|
-
event.preventDefault()
|
|
89
|
-
if (dragIndex.value !== null && dragIndex.value !== toIndex) {
|
|
90
|
-
emit('reorder', dragIndex.value, toIndex)
|
|
91
|
-
}
|
|
92
|
-
clearDragState()
|
|
93
|
-
}
|
|
94
104
|
</script>
|
|
95
105
|
|
|
96
106
|
<template>
|
|
97
107
|
<div class="mint-lcms-sequence-table">
|
|
98
|
-
<div class="mint-lcms-sequence-table__scroller">
|
|
108
|
+
<div v-if="!loading && !error && items.length > 0" class="mint-lcms-sequence-table__scroller">
|
|
99
109
|
<table class="mint-lcms-sequence-table__table">
|
|
100
110
|
<thead>
|
|
101
111
|
<tr>
|
|
@@ -120,19 +130,27 @@ function onDrop(event: DragEvent, toIndex: number) {
|
|
|
120
130
|
:draggable="editable"
|
|
121
131
|
class="mint-lcms-sequence-table__row"
|
|
122
132
|
:class="{
|
|
123
|
-
'mint-lcms-sequence-table__row--dragging':
|
|
124
|
-
'mint-lcms-sequence-table__row--drop-above':
|
|
125
|
-
'mint-lcms-sequence-table__row--drop-below':
|
|
133
|
+
'mint-lcms-sequence-table__row--dragging': draggingId === String(index),
|
|
134
|
+
'mint-lcms-sequence-table__row--drop-above': dragOverId === String(index) && draggingId !== null && Number(draggingId) > index,
|
|
135
|
+
'mint-lcms-sequence-table__row--drop-below': dragOverId === String(index) && draggingId !== null && Number(draggingId) < index,
|
|
126
136
|
}"
|
|
127
|
-
@dragstart="onDragStart($event, index)"
|
|
137
|
+
@dragstart="onDragStart($event, item, index)"
|
|
128
138
|
@dragenter.prevent
|
|
129
|
-
@dragover="onDragOver($event, index)"
|
|
130
|
-
@dragleave="
|
|
131
|
-
@drop="onDrop($event, index)"
|
|
132
|
-
@dragend="
|
|
139
|
+
@dragover="onDragOver($event, item, index)"
|
|
140
|
+
@dragleave="onDragLeave"
|
|
141
|
+
@drop="onDrop($event, item, index)"
|
|
142
|
+
@dragend="onDragEnd"
|
|
133
143
|
>
|
|
134
144
|
<td v-if="editable" class="mint-lcms-sequence-table__drag-cell">
|
|
135
|
-
<
|
|
145
|
+
<button
|
|
146
|
+
:ref="(el) => setHandleRef(index, el)"
|
|
147
|
+
type="button"
|
|
148
|
+
class="mint-lcms-sequence-table__drag-handle"
|
|
149
|
+
:aria-label="`Reorder ${item.file_name}. Press Alt with the arrow keys to move it.`"
|
|
150
|
+
@keydown="onReorderKeydown($event, item, index)"
|
|
151
|
+
>
|
|
152
|
+
::
|
|
153
|
+
</button>
|
|
136
154
|
</td>
|
|
137
155
|
<td
|
|
138
156
|
v-for="column in columns"
|
|
@@ -176,16 +194,29 @@ function onDrop(event: DragEvent, toIndex: number) {
|
|
|
176
194
|
</table>
|
|
177
195
|
</div>
|
|
178
196
|
|
|
179
|
-
<div v-if="
|
|
180
|
-
|
|
197
|
+
<div v-else-if="error" class="mint-lcms-sequence-table__state">
|
|
198
|
+
<slot name="error" :error="error">
|
|
199
|
+
<DataStateInternal state="error" size="sm" :title="error" />
|
|
200
|
+
</slot>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<div v-else-if="loading" class="mint-lcms-sequence-table__state">
|
|
204
|
+
<slot name="loading">
|
|
205
|
+
<DataStateInternal state="loading" size="sm" loading-label="Loading sequence" />
|
|
206
|
+
</slot>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<div v-else class="mint-lcms-sequence-table__state">
|
|
210
|
+
<slot name="empty">
|
|
211
|
+
<DataStateInternal state="empty" size="sm" :title="emptyMessage" />
|
|
212
|
+
</slot>
|
|
181
213
|
</div>
|
|
182
214
|
|
|
183
215
|
<div v-if="hasMore && showMoreLabel" class="mint-lcms-sequence-table__more">
|
|
184
216
|
Showing first {{ maxRows }} of {{ items.length }} items
|
|
185
217
|
</div>
|
|
218
|
+
|
|
219
|
+
<!-- Reorder announcements for screen readers -->
|
|
220
|
+
<div role="status" aria-live="polite" :style="announcerStyle">{{ reorderAnnouncement }}</div>
|
|
186
221
|
</div>
|
|
187
222
|
</template>
|
|
188
|
-
|
|
189
|
-
<style>
|
|
190
|
-
@import '../styles/components/lcms-sequence-table.css';
|
|
191
|
-
</style>
|
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Full plate-map design environment for 96/384-well plates with sample assignment, slot-color coding, undo/redo, and CSV/JSON import-export. */
|
|
3
3
|
import { ref, computed, watch } from 'vue'
|
|
4
|
-
import type { PlateMapEditorState, WellPlateFormat, SampleType, Well, PlateMap } from '../types'
|
|
4
|
+
import type { PlateMapEditorState, WellPlateFormat, SampleType, Well, PlateMap, SlotPosition } from '../types'
|
|
5
|
+
// Slot colors matching MSExpDesigner — shared with RackEditor.
|
|
6
|
+
import { SLOT_COLORS, SLOT_ORDER } from '../utils/color'
|
|
5
7
|
import { useWellPlateEditor } from '../composables/useWellPlateEditor'
|
|
6
8
|
import { useEventListener } from '../composables/useEventListener'
|
|
7
9
|
import WellPlate from './WellPlate.vue'
|
|
8
10
|
import SampleLegend from './SampleLegend.vue'
|
|
9
11
|
import PlateMapEditorToolbarInternal from './internal/PlateMapEditorToolbarInternal.vue'
|
|
10
12
|
|
|
11
|
-
// Slot colors matching MSExpDesigner
|
|
12
|
-
type SlotPosition = 'R' | 'G' | 'B' | 'Y'
|
|
13
|
-
const SLOT_COLORS: Record<SlotPosition, string> = {
|
|
14
|
-
R: '#ef4444', // red
|
|
15
|
-
G: '#22c55e', // green
|
|
16
|
-
B: '#3b82f6', // blue
|
|
17
|
-
Y: '#eab308', // yellow
|
|
18
|
-
}
|
|
19
|
-
const SLOT_ORDER: SlotPosition[] = ['R', 'G', 'B', 'Y']
|
|
20
13
|
|
|
21
14
|
interface Props {
|
|
22
15
|
modelValue?: PlateMapEditorState
|
|
@@ -428,7 +421,3 @@ useEventListener(() => document, 'keydown', handleKeyDown)
|
|
|
428
421
|
</Teleport>
|
|
429
422
|
</div>
|
|
430
423
|
</template>
|
|
431
|
-
|
|
432
|
-
<style>
|
|
433
|
-
@import '../styles/components/plate-map-editor.css';
|
|
434
|
-
</style>
|
|
@@ -53,6 +53,7 @@ const selectedTemplateId = ref<string | null>(null)
|
|
|
53
53
|
const {
|
|
54
54
|
isOpen: templateDropdownOpen,
|
|
55
55
|
rootRef: templateDropdownRef,
|
|
56
|
+
triggerRef: templateTriggerRef,
|
|
56
57
|
close: closeTemplateDropdown,
|
|
57
58
|
toggle: toggleTemplateDropdown,
|
|
58
59
|
} = useDropdownState()
|
|
@@ -204,6 +205,7 @@ watch(
|
|
|
204
205
|
<label class="mint-protocol-editor__template-label">Template</label>
|
|
205
206
|
<div ref="templateDropdownRef" class="mint-protocol-editor__template-dropdown">
|
|
206
207
|
<button
|
|
208
|
+
ref="templateTriggerRef"
|
|
207
209
|
type="button"
|
|
208
210
|
class="mint-protocol-editor__template-btn"
|
|
209
211
|
@click="toggleTemplateDropdown"
|
|
@@ -376,7 +378,3 @@ watch(
|
|
|
376
378
|
</div>
|
|
377
379
|
</div>
|
|
378
380
|
</template>
|
|
379
|
-
|
|
380
|
-
<style>
|
|
381
|
-
@import '../styles/components/protocol-step-editor.css';
|
|
382
|
-
</style>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Multi-rack editor for managing collections of well-plate racks with add/remove/reorder controls and per-well editing. */
|
|
3
|
-
import {
|
|
3
|
+
import { computed, watch, type ComponentPublicInstance } from 'vue'
|
|
4
|
+
import { SLOT_COLORS, SLOT_ORDER } from '../utils/color'
|
|
4
5
|
import type { Rack, SlotPosition, WellPlateFormat, WellPlateSize, WellEditData, Well, WellEditField, WellSampleDropData, RackSampleDropMapper } from '../types'
|
|
6
|
+
import { useListReorder } from '../composables/useListReorder'
|
|
5
7
|
import { useRackEditor } from '../composables/useRackEditor'
|
|
6
8
|
import WellPlate from './WellPlate.vue'
|
|
7
9
|
|
|
@@ -104,23 +106,48 @@ watch(
|
|
|
104
106
|
},
|
|
105
107
|
)
|
|
106
108
|
|
|
107
|
-
const
|
|
108
|
-
R: '#ef4444',
|
|
109
|
-
G: '#22c55e',
|
|
110
|
-
B: '#3b82f6',
|
|
111
|
-
Y: '#eab308',
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const SLOT_OPTIONS: SlotPosition[] = ['R', 'G', 'B', 'Y']
|
|
109
|
+
const SLOT_OPTIONS: readonly SlotPosition[] = SLOT_ORDER
|
|
115
110
|
|
|
116
111
|
const FORMAT_OPTIONS: { value: WellPlateFormat; label: string }[] = [
|
|
117
112
|
{ value: 54, label: '54' },
|
|
118
113
|
{ value: 96, label: '96' },
|
|
119
114
|
]
|
|
120
115
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
const
|
|
116
|
+
// Rack tab reordering: HTML5 drag for pointers, Alt+Arrow for the keyboard,
|
|
117
|
+
// both owned by the shared reorder composable.
|
|
118
|
+
const tabRefs = new Map<string, HTMLElement>()
|
|
119
|
+
|
|
120
|
+
const {
|
|
121
|
+
draggingId: draggingRackId,
|
|
122
|
+
dragOverId: dragOverRackId,
|
|
123
|
+
announcement: reorderAnnouncement,
|
|
124
|
+
announcerStyle,
|
|
125
|
+
onDragStart: onRackDragStart,
|
|
126
|
+
onDragOver: onRackDragOver,
|
|
127
|
+
onDragLeave: onRackDragLeave,
|
|
128
|
+
onDrop: onRackDrop,
|
|
129
|
+
onDragEnd: onRackDragEnd,
|
|
130
|
+
onKeydown: onRackReorderKeydown,
|
|
131
|
+
} = useListReorder<Rack>({
|
|
132
|
+
items: () => editor.racks.value,
|
|
133
|
+
getId: (rack) => rack.id,
|
|
134
|
+
getLabel: (rack) => rack.name,
|
|
135
|
+
disabled: () => !props.allowReorder || props.readonly,
|
|
136
|
+
getElement: (id) => tabRefs.get(id),
|
|
137
|
+
itemNoun: 'Rack',
|
|
138
|
+
onReorder: ({ fromIndex, toIndex }) => {
|
|
139
|
+
editor.reorderRacks(fromIndex, toIndex)
|
|
140
|
+
emit('rack-reorder', editor.racks.value.map(r => r.id))
|
|
141
|
+
},
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
function setTabRef(id: string, el: Element | ComponentPublicInstance | null) {
|
|
145
|
+
if (el instanceof HTMLElement) {
|
|
146
|
+
tabRefs.set(id, el)
|
|
147
|
+
} else {
|
|
148
|
+
tabRefs.delete(id)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
124
151
|
|
|
125
152
|
function handleAddRack() {
|
|
126
153
|
const rack = editor.addRack()
|
|
@@ -136,54 +163,6 @@ function handleTabClick(rackId: string) {
|
|
|
136
163
|
editor.setActiveRack(rackId)
|
|
137
164
|
}
|
|
138
165
|
|
|
139
|
-
// Rack drag handlers
|
|
140
|
-
function handleRackDragStart(event: DragEvent, rackId: string) {
|
|
141
|
-
if (!props.allowReorder) return
|
|
142
|
-
draggingRackId.value = rackId
|
|
143
|
-
if (event.dataTransfer) {
|
|
144
|
-
event.dataTransfer.effectAllowed = 'move'
|
|
145
|
-
event.dataTransfer.setData('text/plain', rackId)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function handleRackDragOver(event: DragEvent, rackId: string) {
|
|
150
|
-
event.preventDefault()
|
|
151
|
-
if (draggingRackId.value && draggingRackId.value !== rackId) {
|
|
152
|
-
dragOverRackId.value = rackId
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function handleRackDragLeave() {
|
|
157
|
-
dragOverRackId.value = null
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function handleRackDrop(event: DragEvent, targetRackId: string) {
|
|
161
|
-
event.preventDefault()
|
|
162
|
-
if (!draggingRackId.value || draggingRackId.value === targetRackId) {
|
|
163
|
-
clearDragState()
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const fromIndex = editor.racks.value.findIndex(r => r.id === draggingRackId.value)
|
|
168
|
-
const toIndex = editor.racks.value.findIndex(r => r.id === targetRackId)
|
|
169
|
-
|
|
170
|
-
if (fromIndex !== -1 && toIndex !== -1) {
|
|
171
|
-
editor.reorderRacks(fromIndex, toIndex)
|
|
172
|
-
emit('rack-reorder', editor.racks.value.map(r => r.id))
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
clearDragState()
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function handleRackDragEnd() {
|
|
179
|
-
clearDragState()
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function clearDragState() {
|
|
183
|
-
draggingRackId.value = null
|
|
184
|
-
dragOverRackId.value = null
|
|
185
|
-
}
|
|
186
|
-
|
|
187
166
|
// Toolbar actions
|
|
188
167
|
function handleFormatChange(format: WellPlateFormat) {
|
|
189
168
|
if (!editor.activeRack.value) return
|
|
@@ -281,9 +260,11 @@ const activeRackWells = computed(() => editor.activeRack.value?.wells ?? {})
|
|
|
281
260
|
<div class="mint-rack-editor__tabs">
|
|
282
261
|
<div class="mint-rack-editor__tabs-inner">
|
|
283
262
|
<button
|
|
284
|
-
v-for="rack in editor.racks.value"
|
|
263
|
+
v-for="(rack, rackIndex) in editor.racks.value"
|
|
285
264
|
:key="rack.id"
|
|
265
|
+
:ref="(el) => setTabRef(rack.id, el)"
|
|
286
266
|
:draggable="allowReorder && !readonly"
|
|
267
|
+
:title="allowReorder && !readonly ? `${rack.name} — press Alt with the arrow keys to reorder` : undefined"
|
|
287
268
|
:class="[
|
|
288
269
|
'mint-rack-editor__tab',
|
|
289
270
|
{
|
|
@@ -293,11 +274,12 @@ const activeRackWells = computed(() => editor.activeRack.value?.wells ?? {})
|
|
|
293
274
|
},
|
|
294
275
|
]"
|
|
295
276
|
@click="handleTabClick(rack.id)"
|
|
296
|
-
@
|
|
297
|
-
@
|
|
298
|
-
@
|
|
299
|
-
@
|
|
300
|
-
@
|
|
277
|
+
@keydown="onRackReorderKeydown($event, rack, rackIndex)"
|
|
278
|
+
@dragstart="onRackDragStart($event, rack, rackIndex)"
|
|
279
|
+
@dragover="onRackDragOver($event, rack, rackIndex)"
|
|
280
|
+
@dragleave="onRackDragLeave"
|
|
281
|
+
@drop="onRackDrop($event, rack, rackIndex)"
|
|
282
|
+
@dragend="onRackDragEnd"
|
|
301
283
|
>
|
|
302
284
|
<span
|
|
303
285
|
class="mint-rack-editor__slot-dot"
|
|
@@ -434,9 +416,8 @@ const activeRackWells = computed(() => editor.activeRack.value?.wells ?? {})
|
|
|
434
416
|
</template>
|
|
435
417
|
</WellPlate>
|
|
436
418
|
</div>
|
|
419
|
+
|
|
420
|
+
<!-- Reorder announcements for screen readers -->
|
|
421
|
+
<div role="status" aria-live="polite" :style="announcerStyle">{{ reorderAnnouncement }}</div>
|
|
437
422
|
</div>
|
|
438
423
|
</template>
|
|
439
|
-
|
|
440
|
-
<style>
|
|
441
|
-
@import '../styles/components/rack-editor.css';
|
|
442
|
-
</style>
|