@morscherlab/mint-sdk 1.0.0-rc.9 → 1.0.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/dist/BaseModal-B9UA8Y_I.js +165 -0
- package/dist/BaseModal-B9UA8Y_I.js.map +1 -0
- package/dist/BaseSelect-DksaKYq_.js +176 -0
- package/dist/BaseSelect-DksaKYq_.js.map +1 -0
- package/dist/ExperimentPopover-CCYB1oWp.js +361 -0
- package/dist/ExperimentPopover-CCYB1oWp.js.map +1 -0
- package/dist/ExperimentPopover-D0bg_fqM.js +3 -0
- package/dist/ExperimentSelectorModal-B_kPbXcg.js +4 -0
- package/dist/ExperimentSelectorModal-wm7yUdAr.js +720 -0
- package/dist/ExperimentSelectorModal-wm7yUdAr.js.map +1 -0
- package/dist/SettingsModal-L7Ejny45.js +5 -0
- package/dist/SettingsModal-LEKI6Ebl.js +521 -0
- package/dist/SettingsModal-LEKI6Ebl.js.map +1 -0
- package/dist/{auth-BulIv_km.js → auth-D9q2GIcv.js} +3 -80
- package/dist/auth-D9q2GIcv.js.map +1 -0
- package/dist/components/DataFrame.vue.d.ts +3 -0
- package/dist/components/ExperimentDataViewer.vue.d.ts +2 -0
- package/dist/components/PluginWorkspaceView.vue.d.ts +2 -2
- package/dist/components/index.js +7 -2
- package/dist/{components-DtX3LDLq.js → components-CdjRzHI2.js} +533 -2025
- package/dist/components-CdjRzHI2.js.map +1 -0
- package/dist/composables/index.js +9 -3
- package/dist/composables/usePluginClient.d.ts +2 -1
- package/dist/{composables-wNt7VtkF.js → composables-DJgqPrlR.js} +7 -12
- package/dist/{composables-wNt7VtkF.js.map → composables-DJgqPrlR.js.map} +1 -1
- package/dist/experiment-utils-hGXMHlAc.js +109 -0
- package/dist/experiment-utils-hGXMHlAc.js.map +1 -0
- package/dist/index.js +16 -5
- package/dist/index.js.map +1 -1
- package/dist/install.js +7 -2
- package/dist/install.js.map +1 -1
- package/dist/permissions.js +81 -0
- package/dist/permissions.js.map +1 -0
- package/dist/stores/index.js +1 -1
- package/dist/styles.css +3233 -3185
- package/dist/templates/index.js +3 -1
- package/dist/templates-Do43ZIMb.js +5065 -0
- package/dist/templates-Do43ZIMb.js.map +1 -0
- package/dist/{templates-DSbHJC4v.js → useControlSchema-0n8Bcftq.js} +10 -5335
- package/dist/useControlSchema-0n8Bcftq.js.map +1 -0
- package/dist/useDropdownState-Ben4DnjJ.js +47 -0
- package/dist/useDropdownState-Ben4DnjJ.js.map +1 -0
- package/dist/useEventListener-CfVkP9Xz.js +57 -0
- package/dist/useEventListener-CfVkP9Xz.js.map +1 -0
- package/dist/useExperimentSelector-BpZklTbV.js +469 -0
- package/dist/useExperimentSelector-BpZklTbV.js.map +1 -0
- package/dist/useFormBuilder-COfYWDuC.js +729 -0
- package/dist/useFormBuilder-COfYWDuC.js.map +1 -0
- package/dist/{useProtocolTemplates-DwBhEPPU.js → useProtocolTemplates-TUQO_F3n.js} +8 -1298
- package/dist/useProtocolTemplates-TUQO_F3n.js.map +1 -0
- package/dist/utils/pluginIcon.d.ts +29 -2
- package/package.json +5 -1
- package/src/__tests__/components/DataFrame.test.ts +37 -0
- package/src/__tests__/components/PluginIcon.test.ts +77 -0
- package/src/__tests__/composables/usePluginClient.test.ts +11 -10
- package/src/components/AppTopBar.vue +7 -6
- package/src/components/DataFrame.vue +27 -2
- package/src/components/ExperimentDataViewer.vue +5 -1
- package/src/components/PluginIcon.story.vue +31 -1
- package/src/components/PluginIcon.vue +94 -4
- package/src/composables/usePluginClient.ts +3 -12
- package/src/styles/components/dataframe.css +26 -0
- package/src/styles/components/plugin-icon.css +5 -0
- package/src/utils/pluginIcon.ts +159 -2
- package/dist/auth-BulIv_km.js.map +0 -1
- package/dist/components-DtX3LDLq.js.map +0 -1
- package/dist/templates-DSbHJC4v.js.map +0 -1
- package/dist/useProtocolTemplates-DwBhEPPU.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components-CdjRzHI2.js","names":["$attrs","$slots","$slots","$slots","$slots","$slots","$slots","$slots"],"sources":["../src/components/ColorSlider.vue","../src/components/ColorSlider.vue","../src/components/BaseTabs.vue","../src/components/BaseTabs.vue","../src/components/SegmentedControl.vue","../src/components/SegmentedControl.vue","../src/components/DropdownButton.vue","../src/components/DropdownButton.vue","../src/components/Calendar.vue","../src/components/Calendar.vue","../src/components/DataFrame.vue","../src/components/DataFrame.vue","../src/components/AlertBox.vue","../src/components/AlertBox.vue","../src/components/AppToastContainer.vue","../src/components/AppToastContainer.vue","../src/components/IconButton.vue","../src/components/IconButton.vue","../src/components/ThemeToggle.vue","../src/components/ThemeToggle.vue","../src/components/CollapsibleCard.vue","../src/components/CollapsibleCard.vue","../src/utils/pluginIcon.ts","../src/components/internal/ActionItemInternal.vue","../src/components/internal/ActionItemInternal.vue","../src/components/PluginIcon.vue","../src/components/PluginIcon.vue","../src/components/internal/AppTopBarPageSelectorInternal.vue","../src/components/internal/AppTopBarPageSelectorInternal.vue","../src/components/internal/AppTopBarPillNavInternal.vue","../src/components/internal/AppTopBarPillNavInternal.vue","../src/components/AppAvatarMenu.vue","../src/components/AppAvatarMenu.vue","../src/components/AppPluginSwitcher.vue","../src/components/AppPluginSwitcher.vue","../src/components/AppTopBar.navigation.ts","../src/components/AppTopBar.vue","../src/components/AppTopBar.vue","../src/components/StepWizard.vue","../src/components/StepWizard.vue","../src/components/internal/FormSectionRenderer.vue","../src/components/internal/FormSectionRenderer.vue","../src/components/FormActions.vue","../src/components/FormActions.vue","../src/components/FormBuilder.vue","../src/components/FormBuilder.vue","../src/components/AppSidebar.vue","../src/components/AppSidebar.vue","../src/components/AppLayout.vue","../src/components/AppLayout.vue","../src/components/PluginWorkspaceView.controls.ts","../src/components/PluginWorkspaceView.navigation.ts","../src/components/PluginWorkspaceView.shell.ts","../src/components/PluginWorkspaceView.vue","../src/components/PluginWorkspaceView.vue","../src/components/DoseCalculatorVolumeField.vue","../src/components/DoseCalculatorVolumeField.vue","../src/components/DoseCalculator.vue","../src/components/DoseCalculator.vue","../src/components/ProtocolStep.presentation.ts","../src/components/ExperimentTimeline.vue","../src/components/ExperimentTimeline.vue","../src/components/internal/WellEditPopupInternal.vue","../src/components/internal/WellEditPopupInternal.vue","../src/components/WellPlate.colors.ts","../src/components/WellPlate.conditions.ts","../src/components/WellPlate.geometry.ts","../src/components/WellPlate.sampleDrop.ts","../src/components/WellPlate.interaction.ts","../src/components/WellPlate.rendering.ts","../src/components/WellPlate.legend.ts","../src/components/WellPlate.vue","../src/components/WellPlate.vue","../src/components/SampleLegend.vue","../src/components/SampleLegend.vue","../src/components/internal/PlateMapEditorToolbarInternal.vue","../src/components/internal/PlateMapEditorToolbarInternal.vue","../src/components/PlateMapEditor.vue","../src/components/PlateMapEditor.vue","../src/components/ReagentList.presentation.ts","../src/components/ReagentList.vue","../src/components/ReagentList.vue","../src/components/LoadingSpinner.vue","../src/components/LoadingSpinner.vue","../src/components/AutoGroupModal.vue","../src/components/AutoGroupModal.vue","../src/components/SampleSelectorSampleRow.vue","../src/components/SampleSelectorSampleRow.vue","../src/components/SampleSelector.groups.ts","../src/components/SampleSelector.drag.ts","../src/components/SampleSelector.selection.ts","../src/components/SampleSelector.colors.ts","../src/components/SampleSelector.vue","../src/components/SampleSelector.vue","../src/composables/useScheduleCalendarLayout.ts","../src/components/ScheduleCalendar.vue","../src/components/ScheduleCalendar.vue","../src/components/ComponentBindingRenderer.vue","../src/components/ComponentBindingRenderer.vue","../src/components/ControlWorkspaceView.vue","../src/components/ControlWorkspaceView.vue","../src/components/DoseDesignWorkspaceView.vue","../src/components/DoseDesignWorkspaceView.vue","../src/components/AppContainer.vue","../src/components/AppContainer.vue","../src/components/Divider.vue","../src/components/Divider.vue","../src/components/StatusIndicator.vue","../src/components/StatusIndicator.vue","../src/components/ProgressBar.vue","../src/components/ProgressBar.vue","../src/components/Avatar.vue","../src/components/Avatar.vue","../src/components/Breadcrumb.vue","../src/components/Breadcrumb.vue","../src/components/ChartContainer.vue","../src/components/ChartContainer.vue","../src/components/BioTemplateRenderer.vue","../src/components/BioTemplateRenderer.vue","../src/components/BioTemplateExperimentWorkspaceView.vue","../src/components/BioTemplateExperimentWorkspaceView.vue","../src/components/BioTemplatePackWorkspaceView.vue","../src/components/BioTemplatePackWorkspaceView.vue","../src/components/BioTemplatePresetWorkspaceView.vue","../src/components/BioTemplatePresetWorkspaceView.vue","../src/components/RackEditor.vue","../src/components/RackEditor.vue","../src/components/GroupAssigner.vue","../src/components/GroupAssigner.vue","../src/components/ReagentEditor.vue","../src/components/ReagentEditor.vue","../src/components/SampleHierarchyTree.vue","../src/components/SampleHierarchyTree.vue","../src/components/ProtocolStepParameterField.vue","../src/components/ProtocolStepParameterField.vue","../src/components/ProtocolStepEditor.state.ts","../src/components/ProtocolStepEditor.vue","../src/components/ProtocolStepEditor.vue","../src/components/ScientificNumber.vue","../src/components/ScientificNumber.vue","../src/components/ChemicalFormula.vue","../src/components/ChemicalFormula.vue","../src/instrument.ts","../src/components/SequenceProgressBar.vue","../src/components/SequenceProgressBar.vue","../src/components/InstrumentAlertLog.vue","../src/components/InstrumentAlertLog.vue","../src/components/InstrumentStateBadge.vue","../src/components/InstrumentStateBadge.vue","../src/components/InstrumentStatusCard.vue","../src/components/InstrumentStatusCard.vue","../src/lcms.ts","../src/components/LcmsSequenceTable.vue","../src/components/LcmsSequenceTable.vue","../src/components/AuditTrail.vue","../src/components/AuditTrail.vue","../src/components/BatchProgressList.vue","../src/components/BatchProgressList.vue","../src/components/ExperimentDataViewer.vue","../src/components/ExperimentDataViewer.vue","../src/components/TimeRangeInput.vue","../src/components/TimeRangeInput.vue","../src/components/ResourceCard.vue","../src/components/ResourceCard.vue","../src/components/FitPanel.vue","../src/components/FitPanel.vue","../src/components/index.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/** Range slider with a gradient color track and color-coded value badge for threshold visualization. */\nimport { computed } from 'vue'\n\ninterface ColorStop {\n value: number\n color: string\n}\n\ninterface Props {\n modelValue?: number\n min?: number\n max?: number\n step?: number\n disabled?: boolean\n showValue?: boolean\n showLabels?: boolean\n minLabel?: string\n maxLabel?: string\n size?: 'sm' | 'md' | 'lg'\n /** Color stops for the gradient. Default: green (low) → yellow (mid) → red (high) */\n colorStops?: ColorStop[]\n /** Thresholds for value badge color. Default: green ≤ threshold1, yellow ≤ threshold2, red above */\n thresholds?: [number, number]\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n min: 0,\n max: 100,\n step: 1,\n disabled: false,\n showValue: true,\n showLabels: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: number]\n}>()\n\n// Default color stops: green at start, yellow at 30%, red at end\nconst defaultColorStops: ColorStop[] = [\n { value: 0, color: '#22c55e' },\n { value: 30, color: '#eab308' },\n { value: 100, color: '#ef4444' },\n]\n\n// Default thresholds: green ≤ 10%, yellow ≤ 30%, red above\nconst defaultThresholds: [number, number] = [10, 30]\n\nconst colorStops = computed(() => props.colorStops ?? defaultColorStops)\nconst thresholds = computed(() => props.thresholds ?? defaultThresholds)\n\nconst sizeConfig = computed(() => {\n switch (props.size) {\n case 'sm': return { thumb: 16, badgeSize: '24px', fontSize: '11px', labelFontSize: '0.625rem' }\n case 'lg': return { thumb: 22, badgeSize: '32px', fontSize: '14px', labelFontSize: '0.75rem' }\n default: return { thumb: 18, badgeSize: '28px', fontSize: '12px', labelFontSize: '0.625rem' }\n }\n})\n\nconst currentValue = computed(() => props.modelValue ?? props.min)\n\nconst percentage = computed(() => {\n const range = props.max - props.min\n if (range === 0) return 0\n return ((currentValue.value - props.min) / range) * 100\n})\n\n// Generate CSS gradient from color stops\nconst gradientStyle = computed(() => {\n const stops = colorStops.value\n .map(stop => `${stop.color} ${stop.value}%`)\n .join(', ')\n return `linear-gradient(to right, ${stops})`\n})\n\n// Value badge style based on thresholds\nconst valueBadgeStyle = computed(() => {\n const pct = percentage.value\n const [t1, t2] = thresholds.value\n\n if (pct <= t1) {\n return {\n backgroundColor: 'rgba(34, 197, 94, 0.15)',\n color: '#22c55e',\n border: '1px solid rgba(34, 197, 94, 0.3)'\n }\n }\n if (pct <= t2) {\n return {\n backgroundColor: 'rgba(234, 179, 8, 0.15)',\n color: '#eab308',\n border: '1px solid rgba(234, 179, 8, 0.3)'\n }\n }\n return {\n backgroundColor: 'rgba(239, 68, 68, 0.15)',\n color: '#ef4444',\n border: '1px solid rgba(239, 68, 68, 0.3)'\n }\n})\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n emit('update:modelValue', Number(target.value))\n}\n</script>\n\n<template>\n <div :class=\"['mint-color-slider', { 'mint-color-slider--disabled': disabled }]\">\n <div class=\"mint-color-slider__row\">\n <!-- Slider track with gradient -->\n <div class=\"mint-color-slider__track\">\n <input\n type=\"range\"\n :value=\"currentValue\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :disabled=\"disabled\"\n :aria-label=\"`Value: ${currentValue}`\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuenow=\"currentValue\"\n class=\"color-slider mint-color-slider__input\"\n :style=\"{\n '--slider-gradient': gradientStyle,\n '--thumb-size': `${sizeConfig.thumb}px`,\n }\"\n @input=\"handleInput\"\n />\n </div>\n\n <!-- Value badge -->\n <div\n v-if=\"showValue\"\n class=\"mint-color-slider__badge\"\n :style=\"{\n ...valueBadgeStyle,\n minWidth: sizeConfig.badgeSize,\n height: sizeConfig.badgeSize,\n fontSize: sizeConfig.fontSize,\n }\"\n >\n {{ currentValue }}\n </div>\n </div>\n\n <!-- Min/Max labels -->\n <div\n v-if=\"showLabels\"\n class=\"mint-color-slider__labels\"\n :style=\"{ fontSize: sizeConfig.labelFontSize }\"\n >\n <span>{{ minLabel ?? min }}</span>\n <span>{{ maxLabel ?? max }}</span>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/color-slider.css';\n</style>\n","<script setup lang=\"ts\">\n/** Range slider with a gradient color track and color-coded value badge for threshold visualization. */\nimport { computed } from 'vue'\n\ninterface ColorStop {\n value: number\n color: string\n}\n\ninterface Props {\n modelValue?: number\n min?: number\n max?: number\n step?: number\n disabled?: boolean\n showValue?: boolean\n showLabels?: boolean\n minLabel?: string\n maxLabel?: string\n size?: 'sm' | 'md' | 'lg'\n /** Color stops for the gradient. Default: green (low) → yellow (mid) → red (high) */\n colorStops?: ColorStop[]\n /** Thresholds for value badge color. Default: green ≤ threshold1, yellow ≤ threshold2, red above */\n thresholds?: [number, number]\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n min: 0,\n max: 100,\n step: 1,\n disabled: false,\n showValue: true,\n showLabels: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: number]\n}>()\n\n// Default color stops: green at start, yellow at 30%, red at end\nconst defaultColorStops: ColorStop[] = [\n { value: 0, color: '#22c55e' },\n { value: 30, color: '#eab308' },\n { value: 100, color: '#ef4444' },\n]\n\n// Default thresholds: green ≤ 10%, yellow ≤ 30%, red above\nconst defaultThresholds: [number, number] = [10, 30]\n\nconst colorStops = computed(() => props.colorStops ?? defaultColorStops)\nconst thresholds = computed(() => props.thresholds ?? defaultThresholds)\n\nconst sizeConfig = computed(() => {\n switch (props.size) {\n case 'sm': return { thumb: 16, badgeSize: '24px', fontSize: '11px', labelFontSize: '0.625rem' }\n case 'lg': return { thumb: 22, badgeSize: '32px', fontSize: '14px', labelFontSize: '0.75rem' }\n default: return { thumb: 18, badgeSize: '28px', fontSize: '12px', labelFontSize: '0.625rem' }\n }\n})\n\nconst currentValue = computed(() => props.modelValue ?? props.min)\n\nconst percentage = computed(() => {\n const range = props.max - props.min\n if (range === 0) return 0\n return ((currentValue.value - props.min) / range) * 100\n})\n\n// Generate CSS gradient from color stops\nconst gradientStyle = computed(() => {\n const stops = colorStops.value\n .map(stop => `${stop.color} ${stop.value}%`)\n .join(', ')\n return `linear-gradient(to right, ${stops})`\n})\n\n// Value badge style based on thresholds\nconst valueBadgeStyle = computed(() => {\n const pct = percentage.value\n const [t1, t2] = thresholds.value\n\n if (pct <= t1) {\n return {\n backgroundColor: 'rgba(34, 197, 94, 0.15)',\n color: '#22c55e',\n border: '1px solid rgba(34, 197, 94, 0.3)'\n }\n }\n if (pct <= t2) {\n return {\n backgroundColor: 'rgba(234, 179, 8, 0.15)',\n color: '#eab308',\n border: '1px solid rgba(234, 179, 8, 0.3)'\n }\n }\n return {\n backgroundColor: 'rgba(239, 68, 68, 0.15)',\n color: '#ef4444',\n border: '1px solid rgba(239, 68, 68, 0.3)'\n }\n})\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n emit('update:modelValue', Number(target.value))\n}\n</script>\n\n<template>\n <div :class=\"['mint-color-slider', { 'mint-color-slider--disabled': disabled }]\">\n <div class=\"mint-color-slider__row\">\n <!-- Slider track with gradient -->\n <div class=\"mint-color-slider__track\">\n <input\n type=\"range\"\n :value=\"currentValue\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :disabled=\"disabled\"\n :aria-label=\"`Value: ${currentValue}`\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuenow=\"currentValue\"\n class=\"color-slider mint-color-slider__input\"\n :style=\"{\n '--slider-gradient': gradientStyle,\n '--thumb-size': `${sizeConfig.thumb}px`,\n }\"\n @input=\"handleInput\"\n />\n </div>\n\n <!-- Value badge -->\n <div\n v-if=\"showValue\"\n class=\"mint-color-slider__badge\"\n :style=\"{\n ...valueBadgeStyle,\n minWidth: sizeConfig.badgeSize,\n height: sizeConfig.badgeSize,\n fontSize: sizeConfig.fontSize,\n }\"\n >\n {{ currentValue }}\n </div>\n </div>\n\n <!-- Min/Max labels -->\n <div\n v-if=\"showLabels\"\n class=\"mint-color-slider__labels\"\n :style=\"{ fontSize: sizeConfig.labelFontSize }\"\n >\n <span>{{ minLabel ?? min }}</span>\n <span>{{ maxLabel ?? max }}</span>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/color-slider.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a tab bar with underline or pills variant, badges, icons, and disabled tab support. */\nimport { computed } from 'vue'\nimport type { TabItem, TabItemInput } from '../types'\nimport { normalizeItemInput } from '../utils/items'\n\ninterface Props {\n modelValue: string\n tabs: TabItemInput[]\n variant?: 'underline' | 'pills'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'underline',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n}>()\n\nconst activeTab = computed(() => props.modelValue)\nconst normalizedTabs = computed<TabItem[]>(() => props.tabs.map(normalizeItemInput))\n\nfunction isSvgIcon(icon: TabItem['icon']): icon is string | string[] {\n if (!icon) return false\n return Array.isArray(icon) || icon.trim().startsWith('M') || icon.trim().startsWith('m')\n}\n\nfunction selectTab(tabId: string) {\n const tab = normalizedTabs.value.find(t => t.id === tabId)\n if (tab && !tab.disabled) {\n emit('update:modelValue', tabId)\n }\n}\n</script>\n\n<template>\n <div :class=\"['mint-tabs', `mint-tabs--${variant}`]\" role=\"tablist\">\n <button\n v-for=\"tab in normalizedTabs\"\n :key=\"tab.id\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"activeTab === tab.id\"\n :aria-disabled=\"tab.disabled\"\n :class=\"[\n 'mint-tab',\n activeTab === tab.id ? 'mint-tab--active' : '',\n tab.disabled ? 'mint-tab--disabled' : '',\n ]\"\n @click=\"selectTab(tab.id)\"\n >\n <span class=\"mint-tab__content\">\n <svg\n v-if=\"isSvgIcon(tab.icon)\"\n class=\"mint-tab__icon mint-tab__icon--svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <template v-if=\"Array.isArray(tab.icon)\">\n <path v-for=\"(d, index) in tab.icon\" :key=\"index\" :d=\"d\" />\n </template>\n <path v-else :d=\"tab.icon\" />\n </svg>\n <span v-else-if=\"tab.icon\" class=\"mint-tab__icon\">{{ tab.icon }}</span>\n {{ tab.label }}\n <span v-if=\"tab.badge !== undefined\" class=\"mint-tab__badge\">\n {{ tab.badge }}\n </span>\n </span>\n </button>\n </div>\n</template>\n\n<style>\n@import '../styles/components/tabs.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a tab bar with underline or pills variant, badges, icons, and disabled tab support. */\nimport { computed } from 'vue'\nimport type { TabItem, TabItemInput } from '../types'\nimport { normalizeItemInput } from '../utils/items'\n\ninterface Props {\n modelValue: string\n tabs: TabItemInput[]\n variant?: 'underline' | 'pills'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'underline',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n}>()\n\nconst activeTab = computed(() => props.modelValue)\nconst normalizedTabs = computed<TabItem[]>(() => props.tabs.map(normalizeItemInput))\n\nfunction isSvgIcon(icon: TabItem['icon']): icon is string | string[] {\n if (!icon) return false\n return Array.isArray(icon) || icon.trim().startsWith('M') || icon.trim().startsWith('m')\n}\n\nfunction selectTab(tabId: string) {\n const tab = normalizedTabs.value.find(t => t.id === tabId)\n if (tab && !tab.disabled) {\n emit('update:modelValue', tabId)\n }\n}\n</script>\n\n<template>\n <div :class=\"['mint-tabs', `mint-tabs--${variant}`]\" role=\"tablist\">\n <button\n v-for=\"tab in normalizedTabs\"\n :key=\"tab.id\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"activeTab === tab.id\"\n :aria-disabled=\"tab.disabled\"\n :class=\"[\n 'mint-tab',\n activeTab === tab.id ? 'mint-tab--active' : '',\n tab.disabled ? 'mint-tab--disabled' : '',\n ]\"\n @click=\"selectTab(tab.id)\"\n >\n <span class=\"mint-tab__content\">\n <svg\n v-if=\"isSvgIcon(tab.icon)\"\n class=\"mint-tab__icon mint-tab__icon--svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <template v-if=\"Array.isArray(tab.icon)\">\n <path v-for=\"(d, index) in tab.icon\" :key=\"index\" :d=\"d\" />\n </template>\n <path v-else :d=\"tab.icon\" />\n </svg>\n <span v-else-if=\"tab.icon\" class=\"mint-tab__icon\">{{ tab.icon }}</span>\n {{ tab.label }}\n <span v-if=\"tab.badge !== undefined\" class=\"mint-tab__badge\">\n {{ tab.badge }}\n </span>\n </span>\n </button>\n </div>\n</template>\n\n<style>\n@import '../styles/components/tabs.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a segmented button group for single-option selection, with simple or solid variants. */\nimport { computed } from 'vue'\nimport type { SegmentedOption, SegmentedOptionInput, SegmentedControlVariant, SegmentedControlSize } from '../types'\nimport { normalizeOptionInput } from '../utils/options'\n\ninterface Props {\n modelValue: string | number\n options: SegmentedOptionInput[]\n variant?: SegmentedControlVariant\n size?: SegmentedControlSize\n fullWidth?: boolean\n disabled?: boolean\n disabledValues?: Array<string | number>\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'simple',\n size: 'md',\n fullWidth: false,\n disabled: false,\n disabledValues: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nconst normalizedOptions = computed<SegmentedOption[]>(() => props.options.map(normalizeOptionInput))\nconst disabledValueSet = computed(() => new Set(props.disabledValues))\n\nfunction isOptionDisabled(option: SegmentedOption) {\n return option.disabled === true || disabledValueSet.value.has(option.value)\n}\n\nfunction isSelectionDisabled(option: SegmentedOption) {\n return props.disabled || isOptionDisabled(option)\n}\n\nfunction handleSelect(option: SegmentedOption) {\n if (isSelectionDisabled(option)) return\n emit('update:modelValue', option.value)\n}\n\nfunction handleKeydown(event: KeyboardEvent, option: SegmentedOption) {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleSelect(option)\n }\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-segmented-control',\n `mint-segmented-control--${variant}`,\n `mint-segmented-control--${size}`,\n fullWidth ? 'mint-segmented-control--full-width' : '',\n disabled ? 'mint-segmented-control--disabled' : '',\n ]\"\n role=\"radiogroup\"\n >\n <button\n v-for=\"option in normalizedOptions\"\n :key=\"String(option.value)\"\n type=\"button\"\n role=\"radio\"\n :aria-checked=\"modelValue === option.value\"\n :disabled=\"isSelectionDisabled(option)\"\n :class=\"[\n 'mint-segmented-control__option',\n `mint-segmented-control__option--${variant}`,\n `mint-segmented-control__option--${size}`,\n modelValue === option.value ? 'mint-segmented-control__option--active' : '',\n isOptionDisabled(option) ? 'mint-segmented-control__option--disabled' : '',\n ]\"\n @click=\"handleSelect(option)\"\n @keydown=\"handleKeydown($event, option)\"\n >\n <span class=\"mint-segmented-control__label\">{{ option.label }}</span>\n <span\n v-if=\"option.description && variant === 'card'\"\n class=\"mint-segmented-control__description\"\n >\n {{ option.description }}\n </span>\n </button>\n </div>\n</template>\n\n<style>\n@import '../styles/components/segmented-control.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a segmented button group for single-option selection, with simple or solid variants. */\nimport { computed } from 'vue'\nimport type { SegmentedOption, SegmentedOptionInput, SegmentedControlVariant, SegmentedControlSize } from '../types'\nimport { normalizeOptionInput } from '../utils/options'\n\ninterface Props {\n modelValue: string | number\n options: SegmentedOptionInput[]\n variant?: SegmentedControlVariant\n size?: SegmentedControlSize\n fullWidth?: boolean\n disabled?: boolean\n disabledValues?: Array<string | number>\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'simple',\n size: 'md',\n fullWidth: false,\n disabled: false,\n disabledValues: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nconst normalizedOptions = computed<SegmentedOption[]>(() => props.options.map(normalizeOptionInput))\nconst disabledValueSet = computed(() => new Set(props.disabledValues))\n\nfunction isOptionDisabled(option: SegmentedOption) {\n return option.disabled === true || disabledValueSet.value.has(option.value)\n}\n\nfunction isSelectionDisabled(option: SegmentedOption) {\n return props.disabled || isOptionDisabled(option)\n}\n\nfunction handleSelect(option: SegmentedOption) {\n if (isSelectionDisabled(option)) return\n emit('update:modelValue', option.value)\n}\n\nfunction handleKeydown(event: KeyboardEvent, option: SegmentedOption) {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleSelect(option)\n }\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-segmented-control',\n `mint-segmented-control--${variant}`,\n `mint-segmented-control--${size}`,\n fullWidth ? 'mint-segmented-control--full-width' : '',\n disabled ? 'mint-segmented-control--disabled' : '',\n ]\"\n role=\"radiogroup\"\n >\n <button\n v-for=\"option in normalizedOptions\"\n :key=\"String(option.value)\"\n type=\"button\"\n role=\"radio\"\n :aria-checked=\"modelValue === option.value\"\n :disabled=\"isSelectionDisabled(option)\"\n :class=\"[\n 'mint-segmented-control__option',\n `mint-segmented-control__option--${variant}`,\n `mint-segmented-control__option--${size}`,\n modelValue === option.value ? 'mint-segmented-control__option--active' : '',\n isOptionDisabled(option) ? 'mint-segmented-control__option--disabled' : '',\n ]\"\n @click=\"handleSelect(option)\"\n @keydown=\"handleKeydown($event, option)\"\n >\n <span class=\"mint-segmented-control__label\">{{ option.label }}</span>\n <span\n v-if=\"option.description && variant === 'card'\"\n class=\"mint-segmented-control__description\"\n >\n {{ option.description }}\n </span>\n </button>\n </div>\n</template>\n\n<style>\n@import '../styles/components/segmented-control.css';\n</style>\n","<script setup lang=\"ts\">\n/** Button that opens a dropdown menu for selecting one option from a list. */\nimport { computed } from 'vue'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport type { SelectOption, SelectOptionInput, ButtonVariant, ButtonSize } from '../types'\nimport { normalizeOptionInput } from '../utils/options'\n\n/**\n * DropdownButton - Button-style select with dropdown menu for option selection.\n *\n * @example\n * ```vue\n * <DropdownButton\n * v-model=\"selectedValue\"\n * :options=\"[\n * { value: 'a', label: 'Option A' },\n * { value: 'b', label: 'Option B', description: 'Additional info' }\n * ]\"\n * variant=\"secondary\"\n * />\n * ```\n */\ninterface Props {\n /** Selected option value */\n modelValue?: string | number\n /** Available options */\n options: SelectOptionInput<string | number>[]\n /** Placeholder text when no option is selected */\n placeholder?: string\n /** Button style variant */\n variant?: ButtonVariant\n /** Button size */\n size?: ButtonSize\n /** Disable interaction */\n disabled?: boolean\n /** Show loading spinner */\n loading?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'secondary',\n size: 'md',\n disabled: false,\n loading: false,\n})\n\n/**\n * @event update:modelValue - Emitted when an option is selected (v-model support)\n * @event select - Emitted when an option is selected, includes full option object\n */\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n select: [option: SelectOption<string | number>]\n}>()\n\nconst { isOpen, rootRef, close, toggle: toggleDropdown } = useDropdownState()\nconst normalizedOptions = computed<SelectOption<string | number>[]>(() =>\n props.options.map(normalizeOptionInput)\n)\n\nconst selectedLabel = computed(() => {\n if (props.modelValue === undefined) return props.placeholder ?? 'Select...'\n const found = normalizedOptions.value.find(o => o.value === props.modelValue)\n return found?.label ?? String(props.modelValue)\n})\n\nfunction toggle() {\n if (props.disabled || props.loading) return\n toggleDropdown()\n}\n\nfunction selectOption(option: SelectOption<string | number>) {\n if (option.disabled) return\n emit('update:modelValue', option.value)\n emit('select', option)\n close()\n}\n</script>\n\n<template>\n <div ref=\"rootRef\" class=\"mint-dropdown-button\">\n <button\n type=\"button\"\n :disabled=\"disabled || loading\"\n :class=\"[\n 'mint-dropdown-button__trigger',\n `mint-dropdown-button__trigger--${variant}`,\n `mint-dropdown-button__trigger--${size}`,\n {\n 'mint-dropdown-button__trigger--disabled': disabled || loading,\n 'mint-dropdown-button__trigger--open': isOpen,\n },\n ]\"\n :aria-expanded=\"isOpen\"\n aria-haspopup=\"listbox\"\n @click=\"toggle\"\n >\n <svg v-if=\"loading\" class=\"mint-dropdown-button__spinner\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"3\" opacity=\"0.25\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" />\n </svg>\n <span class=\"mint-dropdown-button__label\">{{ selectedLabel }}</span>\n <svg\n :class=\"['mint-dropdown-button__chevron', { 'mint-dropdown-button__chevron--open': isOpen }]\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <Transition name=\"mint-dropdown-button-menu\">\n <ul\n v-if=\"isOpen\"\n class=\"mint-dropdown-button__menu\"\n role=\"listbox\"\n :aria-activedescendant=\"modelValue !== undefined ? `mint-dropdown-option-${modelValue}` : undefined\"\n >\n <li\n v-for=\"option in normalizedOptions\"\n :id=\"`mint-dropdown-option-${option.value}`\"\n :key=\"String(option.value)\"\n role=\"option\"\n :aria-selected=\"option.value === modelValue\"\n :aria-disabled=\"option.disabled\"\n :class=\"[\n 'mint-dropdown-button__option',\n {\n 'mint-dropdown-button__option--selected': option.value === modelValue,\n 'mint-dropdown-button__option--disabled': option.disabled,\n },\n ]\"\n @click=\"selectOption(option)\"\n >\n <span class=\"mint-dropdown-button__option-label\">{{ option.label }}</span>\n <span v-if=\"option.description\" class=\"mint-dropdown-button__option-description\">\n {{ option.description }}\n </span>\n <svg\n v-if=\"option.value === modelValue\"\n class=\"mint-dropdown-button__option-check\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </li>\n </ul>\n </Transition>\n </div>\n</template>\n\n<style>\n@import '../styles/components/dropdown-button.css';\n</style>\n","<script setup lang=\"ts\">\n/** Button that opens a dropdown menu for selecting one option from a list. */\nimport { computed } from 'vue'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport type { SelectOption, SelectOptionInput, ButtonVariant, ButtonSize } from '../types'\nimport { normalizeOptionInput } from '../utils/options'\n\n/**\n * DropdownButton - Button-style select with dropdown menu for option selection.\n *\n * @example\n * ```vue\n * <DropdownButton\n * v-model=\"selectedValue\"\n * :options=\"[\n * { value: 'a', label: 'Option A' },\n * { value: 'b', label: 'Option B', description: 'Additional info' }\n * ]\"\n * variant=\"secondary\"\n * />\n * ```\n */\ninterface Props {\n /** Selected option value */\n modelValue?: string | number\n /** Available options */\n options: SelectOptionInput<string | number>[]\n /** Placeholder text when no option is selected */\n placeholder?: string\n /** Button style variant */\n variant?: ButtonVariant\n /** Button size */\n size?: ButtonSize\n /** Disable interaction */\n disabled?: boolean\n /** Show loading spinner */\n loading?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'secondary',\n size: 'md',\n disabled: false,\n loading: false,\n})\n\n/**\n * @event update:modelValue - Emitted when an option is selected (v-model support)\n * @event select - Emitted when an option is selected, includes full option object\n */\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n select: [option: SelectOption<string | number>]\n}>()\n\nconst { isOpen, rootRef, close, toggle: toggleDropdown } = useDropdownState()\nconst normalizedOptions = computed<SelectOption<string | number>[]>(() =>\n props.options.map(normalizeOptionInput)\n)\n\nconst selectedLabel = computed(() => {\n if (props.modelValue === undefined) return props.placeholder ?? 'Select...'\n const found = normalizedOptions.value.find(o => o.value === props.modelValue)\n return found?.label ?? String(props.modelValue)\n})\n\nfunction toggle() {\n if (props.disabled || props.loading) return\n toggleDropdown()\n}\n\nfunction selectOption(option: SelectOption<string | number>) {\n if (option.disabled) return\n emit('update:modelValue', option.value)\n emit('select', option)\n close()\n}\n</script>\n\n<template>\n <div ref=\"rootRef\" class=\"mint-dropdown-button\">\n <button\n type=\"button\"\n :disabled=\"disabled || loading\"\n :class=\"[\n 'mint-dropdown-button__trigger',\n `mint-dropdown-button__trigger--${variant}`,\n `mint-dropdown-button__trigger--${size}`,\n {\n 'mint-dropdown-button__trigger--disabled': disabled || loading,\n 'mint-dropdown-button__trigger--open': isOpen,\n },\n ]\"\n :aria-expanded=\"isOpen\"\n aria-haspopup=\"listbox\"\n @click=\"toggle\"\n >\n <svg v-if=\"loading\" class=\"mint-dropdown-button__spinner\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"3\" opacity=\"0.25\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" />\n </svg>\n <span class=\"mint-dropdown-button__label\">{{ selectedLabel }}</span>\n <svg\n :class=\"['mint-dropdown-button__chevron', { 'mint-dropdown-button__chevron--open': isOpen }]\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <Transition name=\"mint-dropdown-button-menu\">\n <ul\n v-if=\"isOpen\"\n class=\"mint-dropdown-button__menu\"\n role=\"listbox\"\n :aria-activedescendant=\"modelValue !== undefined ? `mint-dropdown-option-${modelValue}` : undefined\"\n >\n <li\n v-for=\"option in normalizedOptions\"\n :id=\"`mint-dropdown-option-${option.value}`\"\n :key=\"String(option.value)\"\n role=\"option\"\n :aria-selected=\"option.value === modelValue\"\n :aria-disabled=\"option.disabled\"\n :class=\"[\n 'mint-dropdown-button__option',\n {\n 'mint-dropdown-button__option--selected': option.value === modelValue,\n 'mint-dropdown-button__option--disabled': option.disabled,\n },\n ]\"\n @click=\"selectOption(option)\"\n >\n <span class=\"mint-dropdown-button__option-label\">{{ option.label }}</span>\n <span v-if=\"option.description\" class=\"mint-dropdown-button__option-description\">\n {{ option.description }}\n </span>\n <svg\n v-if=\"option.value === modelValue\"\n class=\"mint-dropdown-button__option-check\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </li>\n </ul>\n </Transition>\n </div>\n</template>\n\n<style>\n@import '../styles/components/dropdown-button.css';\n</style>\n","<script setup lang=\"ts\">\n/** Month calendar for single date, multi-date, or range selection with markers and navigation. */\nimport { ref, computed, watch } from 'vue'\nimport type { CalendarSelectionMode, CalendarMarker, CalendarDayContext } from '../types'\n\n/**\n * Calendar - Month calendar with date selection, range selection, and custom markers.\n *\n * @example\n * ```vue\n * <!-- Single date selection -->\n * <Calendar v-model=\"selectedDate\" />\n *\n * <!-- Range selection -->\n * <Calendar v-model=\"dateRange\" selection-mode=\"range\" />\n *\n * <!-- With markers -->\n * <Calendar :markers=\"[{ date: '2024-01-15', color: '#ff0000', label: 'Event' }]\" />\n * ```\n */\ninterface Props {\n /** Selected date(s) - type depends on selectionMode */\n modelValue?: Date | Date[] | { start: Date; end: Date } | null\n /** Selection behavior mode */\n selectionMode?: CalendarSelectionMode\n /** Display month (0-11) */\n month?: number\n /** Display year */\n year?: number\n /** Always show 6 weeks (42 days) */\n fixedWeeks?: boolean\n /** First day of week (0=Sunday, 1=Monday, etc.) */\n weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6\n /** Show days from adjacent months */\n showOutsideDays?: boolean\n /** Show month navigation buttons */\n showNavigation?: boolean\n /** Visual markers on specific dates */\n markers?: CalendarMarker[]\n /** Earliest selectable date */\n minDate?: Date | string\n /** Latest selectable date */\n maxDate?: Date | string\n /** Explicitly disabled dates */\n disabledDates?: Array<Date | string>\n /** Custom disabled date logic */\n isDateDisabled?: (date: Date) => boolean\n /** Locale for date formatting */\n locale?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n selectionMode: 'single',\n fixedWeeks: true,\n weekStartsOn: 1,\n showOutsideDays: true,\n showNavigation: true,\n markers: () => [],\n disabledDates: () => [],\n locale: 'en-US',\n})\n\n/**\n * @event update:modelValue - Emitted when date selection changes (v-model support)\n * @event update:month - Emitted when displayed month changes\n * @event update:year - Emitted when displayed year changes\n * @event day-click - Emitted when a day is clicked, includes day context\n * @event navigate - Emitted when month navigation occurs\n */\nconst emit = defineEmits<{\n 'update:modelValue': [value: Date | Date[] | { start: Date; end: Date } | null]\n 'update:month': [month: number]\n 'update:year': [year: number]\n 'day-click': [context: CalendarDayContext]\n navigate: [direction: 'prev' | 'next', month: number, year: number]\n}>()\n\nconst today = new Date()\ntoday.setHours(0, 0, 0, 0)\n\nconst currentMonth = ref(props.month ?? today.getMonth())\nconst currentYear = ref(props.year ?? today.getFullYear())\n\n// Range hover tracking\nconst hoveredDate = ref<Date | null>(null)\n\nwatch(() => props.month, (val) => { if (val !== undefined) currentMonth.value = val })\nwatch(() => props.year, (val) => { if (val !== undefined) currentYear.value = val })\n\nconst monthLabel = computed(() => {\n const date = new Date(currentYear.value, currentMonth.value, 1)\n return date.toLocaleDateString(props.locale, { month: 'long', year: 'numeric' })\n})\n\nconst weekDayLabels = computed(() => {\n const labels: string[] = []\n // Use Jan 7, 2024 (Sunday) as reference, then offset by weekStartsOn\n const base = new Date(2024, 0, 7 + props.weekStartsOn)\n for (let i = 0; i < 7; i++) {\n const d = new Date(base)\n d.setDate(d.getDate() + i)\n labels.push(d.toLocaleDateString(props.locale, { weekday: 'short' }))\n }\n return labels\n})\n\nfunction toDateKey(d: Date): string {\n return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`\n}\n\nfunction parseDate(val: Date | string): Date {\n if (val instanceof Date) return val\n const d = new Date(val + 'T00:00:00')\n return d\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n}\n\nconst disabledDateSet = computed(() => {\n const set = new Set<string>()\n for (const d of props.disabledDates) {\n const parsed = parseDate(d)\n set.add(toDateKey(parsed))\n }\n return set\n})\n\nconst markerMap = computed(() => {\n const map = new Map<string, CalendarMarker[]>()\n for (const marker of props.markers) {\n const date = parseDate(marker.date)\n const key = toDateKey(date)\n const markers = map.get(key)\n if (markers) {\n markers.push(marker)\n } else {\n map.set(key, [marker])\n }\n }\n return map\n})\n\nfunction isDateSelected(date: Date): boolean {\n const val = props.modelValue\n if (!val) return false\n if (val instanceof Date) return isSameDay(date, val)\n if (Array.isArray(val)) return val.some(d => isSameDay(date, d))\n if ('start' in val && 'end' in val) {\n return isSameDay(date, val.start) || isSameDay(date, val.end)\n }\n return false\n}\n\nfunction isInRange(date: Date): boolean {\n if (props.selectionMode !== 'range') return false\n const val = props.modelValue\n if (!val || !('start' in (val as object))) return false\n const range = val as { start: Date; end: Date }\n\n const t = date.getTime()\n const start = range.start?.getTime()\n if (!start) return false\n\n // If hovering during selection (before end is set)\n if (!range.end && hoveredDate.value) {\n const hov = hoveredDate.value.getTime()\n const [lo, hi] = start <= hov ? [start, hov] : [hov, start]\n return t > lo && t < hi\n }\n\n // Complete range\n if (range.end) {\n const end = range.end.getTime()\n return t > start && t < end\n }\n\n return false\n}\n\nfunction isDisabled(date: Date): boolean {\n if (disabledDateSet.value.has(toDateKey(date))) return true\n if (props.isDateDisabled?.(date)) return true\n if (props.minDate) {\n const min = parseDate(props.minDate)\n if (date < min) return true\n }\n if (props.maxDate) {\n const max = parseDate(props.maxDate)\n if (date > max) return true\n }\n return false\n}\n\nconst calendarDays = computed<CalendarDayContext[]>(() => {\n const first = new Date(currentYear.value, currentMonth.value, 1)\n const startDow = first.getDay()\n const offset = (startDow - props.weekStartsOn + 7) % 7\n\n const totalCells = props.fixedWeeks ? 42 : (() => {\n const daysInMonth = new Date(currentYear.value, currentMonth.value + 1, 0).getDate()\n const needed = offset + daysInMonth\n return Math.ceil(needed / 7) * 7\n })()\n\n const days: CalendarDayContext[] = []\n for (let i = 0; i < totalCells; i++) {\n const date = new Date(currentYear.value, currentMonth.value, 1 - offset + i)\n date.setHours(0, 0, 0, 0)\n const key = toDateKey(date)\n days.push({\n date,\n dayOfMonth: date.getDate(),\n isToday: isSameDay(date, today),\n isSelected: isDateSelected(date),\n isInRange: isInRange(date),\n isDisabled: isDisabled(date),\n isOutsideMonth: date.getMonth() !== currentMonth.value,\n markers: markerMap.value.get(key) ?? [],\n })\n }\n return days\n})\n\n// Range selection state\nconst rangeStart = ref<Date | null>(null)\n\nfunction handleDayClick(day: CalendarDayContext) {\n if (day.isDisabled) return\n if (!props.showOutsideDays && day.isOutsideMonth) return\n\n emit('day-click', day)\n\n if (props.selectionMode === 'none') return\n\n if (props.selectionMode === 'single') {\n emit('update:modelValue', day.date)\n } else if (props.selectionMode === 'multiple') {\n const current = (props.modelValue as Date[] | undefined) ?? []\n const idx = current.findIndex(d => isSameDay(d, day.date))\n if (idx >= 0) {\n const next = [...current]\n next.splice(idx, 1)\n emit('update:modelValue', next)\n } else {\n emit('update:modelValue', [...current, day.date])\n }\n } else if (props.selectionMode === 'range') {\n if (!rangeStart.value) {\n rangeStart.value = day.date\n } else {\n const start = rangeStart.value\n const end = day.date\n rangeStart.value = null\n const ordered = start.getTime() <= end.getTime()\n ? { start, end }\n : { start: end, end: start }\n emit('update:modelValue', ordered)\n }\n }\n}\n\nfunction handleDayHover(day: CalendarDayContext) {\n if (props.selectionMode === 'range' && rangeStart.value) {\n hoveredDate.value = day.date\n }\n}\n\nfunction prevMonth() {\n if (currentMonth.value === 0) {\n currentMonth.value = 11\n currentYear.value--\n emit('update:year', currentYear.value)\n } else {\n currentMonth.value--\n }\n emit('update:month', currentMonth.value)\n emit('navigate', 'prev', currentMonth.value, currentYear.value)\n}\n\nfunction nextMonth() {\n if (currentMonth.value === 11) {\n currentMonth.value = 0\n currentYear.value++\n emit('update:year', currentYear.value)\n } else {\n currentMonth.value++\n }\n emit('update:month', currentMonth.value)\n emit('navigate', 'next', currentMonth.value, currentYear.value)\n}\n</script>\n\n<template>\n <div class=\"mint-calendar\">\n <div v-if=\"showNavigation\" class=\"mint-calendar__header\">\n <button type=\"button\" class=\"mint-calendar__nav-btn\" aria-label=\"Previous month\" @click=\"prevMonth\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n <slot name=\"header\" :month=\"currentMonth\" :year=\"currentYear\" :prev-month=\"prevMonth\" :next-month=\"nextMonth\">\n <span class=\"mint-calendar__title\">{{ monthLabel }}</span>\n </slot>\n <button type=\"button\" class=\"mint-calendar__nav-btn\" aria-label=\"Next month\" @click=\"nextMonth\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n </div>\n\n <div class=\"mint-calendar__weekdays\">\n <span\n v-for=\"(label, i) in weekDayLabels\"\n :key=\"i\"\n class=\"mint-calendar__weekday\"\n >\n <slot name=\"week-day\" :day-name=\"label\" :index=\"i\">{{ label }}</slot>\n </span>\n </div>\n\n <div class=\"mint-calendar__grid\">\n <button\n v-for=\"(day, i) in calendarDays\"\n :key=\"i\"\n type=\"button\"\n :disabled=\"day.isDisabled\"\n :class=\"[\n 'mint-calendar__day',\n {\n 'mint-calendar__day--today': day.isToday,\n 'mint-calendar__day--selected': day.isSelected,\n 'mint-calendar__day--in-range': day.isInRange,\n 'mint-calendar__day--disabled': day.isDisabled,\n 'mint-calendar__day--outside': day.isOutsideMonth,\n 'mint-calendar__day--interactive': selectionMode !== 'none' && !day.isDisabled,\n },\n ]\"\n @click=\"handleDayClick(day)\"\n @mouseenter=\"handleDayHover(day)\"\n >\n <slot name=\"day-content\" :day=\"day\">\n <span class=\"mint-calendar__day-number\">{{ day.dayOfMonth }}</span>\n <div v-if=\"day.markers.length > 0\" class=\"mint-calendar__markers\">\n <span\n v-for=\"(marker, mi) in day.markers.slice(0, 3)\"\n :key=\"mi\"\n :class=\"[\n 'mint-calendar__marker',\n `mint-calendar__marker--${marker.type ?? 'dot'}`,\n ]\"\n :style=\"marker.color ? { '--marker-color': marker.color } : {}\"\n :title=\"marker.label\"\n />\n </div>\n </slot>\n </button>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/calendar.css';\n</style>\n","<script setup lang=\"ts\">\n/** Month calendar for single date, multi-date, or range selection with markers and navigation. */\nimport { ref, computed, watch } from 'vue'\nimport type { CalendarSelectionMode, CalendarMarker, CalendarDayContext } from '../types'\n\n/**\n * Calendar - Month calendar with date selection, range selection, and custom markers.\n *\n * @example\n * ```vue\n * <!-- Single date selection -->\n * <Calendar v-model=\"selectedDate\" />\n *\n * <!-- Range selection -->\n * <Calendar v-model=\"dateRange\" selection-mode=\"range\" />\n *\n * <!-- With markers -->\n * <Calendar :markers=\"[{ date: '2024-01-15', color: '#ff0000', label: 'Event' }]\" />\n * ```\n */\ninterface Props {\n /** Selected date(s) - type depends on selectionMode */\n modelValue?: Date | Date[] | { start: Date; end: Date } | null\n /** Selection behavior mode */\n selectionMode?: CalendarSelectionMode\n /** Display month (0-11) */\n month?: number\n /** Display year */\n year?: number\n /** Always show 6 weeks (42 days) */\n fixedWeeks?: boolean\n /** First day of week (0=Sunday, 1=Monday, etc.) */\n weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6\n /** Show days from adjacent months */\n showOutsideDays?: boolean\n /** Show month navigation buttons */\n showNavigation?: boolean\n /** Visual markers on specific dates */\n markers?: CalendarMarker[]\n /** Earliest selectable date */\n minDate?: Date | string\n /** Latest selectable date */\n maxDate?: Date | string\n /** Explicitly disabled dates */\n disabledDates?: Array<Date | string>\n /** Custom disabled date logic */\n isDateDisabled?: (date: Date) => boolean\n /** Locale for date formatting */\n locale?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n selectionMode: 'single',\n fixedWeeks: true,\n weekStartsOn: 1,\n showOutsideDays: true,\n showNavigation: true,\n markers: () => [],\n disabledDates: () => [],\n locale: 'en-US',\n})\n\n/**\n * @event update:modelValue - Emitted when date selection changes (v-model support)\n * @event update:month - Emitted when displayed month changes\n * @event update:year - Emitted when displayed year changes\n * @event day-click - Emitted when a day is clicked, includes day context\n * @event navigate - Emitted when month navigation occurs\n */\nconst emit = defineEmits<{\n 'update:modelValue': [value: Date | Date[] | { start: Date; end: Date } | null]\n 'update:month': [month: number]\n 'update:year': [year: number]\n 'day-click': [context: CalendarDayContext]\n navigate: [direction: 'prev' | 'next', month: number, year: number]\n}>()\n\nconst today = new Date()\ntoday.setHours(0, 0, 0, 0)\n\nconst currentMonth = ref(props.month ?? today.getMonth())\nconst currentYear = ref(props.year ?? today.getFullYear())\n\n// Range hover tracking\nconst hoveredDate = ref<Date | null>(null)\n\nwatch(() => props.month, (val) => { if (val !== undefined) currentMonth.value = val })\nwatch(() => props.year, (val) => { if (val !== undefined) currentYear.value = val })\n\nconst monthLabel = computed(() => {\n const date = new Date(currentYear.value, currentMonth.value, 1)\n return date.toLocaleDateString(props.locale, { month: 'long', year: 'numeric' })\n})\n\nconst weekDayLabels = computed(() => {\n const labels: string[] = []\n // Use Jan 7, 2024 (Sunday) as reference, then offset by weekStartsOn\n const base = new Date(2024, 0, 7 + props.weekStartsOn)\n for (let i = 0; i < 7; i++) {\n const d = new Date(base)\n d.setDate(d.getDate() + i)\n labels.push(d.toLocaleDateString(props.locale, { weekday: 'short' }))\n }\n return labels\n})\n\nfunction toDateKey(d: Date): string {\n return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`\n}\n\nfunction parseDate(val: Date | string): Date {\n if (val instanceof Date) return val\n const d = new Date(val + 'T00:00:00')\n return d\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n}\n\nconst disabledDateSet = computed(() => {\n const set = new Set<string>()\n for (const d of props.disabledDates) {\n const parsed = parseDate(d)\n set.add(toDateKey(parsed))\n }\n return set\n})\n\nconst markerMap = computed(() => {\n const map = new Map<string, CalendarMarker[]>()\n for (const marker of props.markers) {\n const date = parseDate(marker.date)\n const key = toDateKey(date)\n const markers = map.get(key)\n if (markers) {\n markers.push(marker)\n } else {\n map.set(key, [marker])\n }\n }\n return map\n})\n\nfunction isDateSelected(date: Date): boolean {\n const val = props.modelValue\n if (!val) return false\n if (val instanceof Date) return isSameDay(date, val)\n if (Array.isArray(val)) return val.some(d => isSameDay(date, d))\n if ('start' in val && 'end' in val) {\n return isSameDay(date, val.start) || isSameDay(date, val.end)\n }\n return false\n}\n\nfunction isInRange(date: Date): boolean {\n if (props.selectionMode !== 'range') return false\n const val = props.modelValue\n if (!val || !('start' in (val as object))) return false\n const range = val as { start: Date; end: Date }\n\n const t = date.getTime()\n const start = range.start?.getTime()\n if (!start) return false\n\n // If hovering during selection (before end is set)\n if (!range.end && hoveredDate.value) {\n const hov = hoveredDate.value.getTime()\n const [lo, hi] = start <= hov ? [start, hov] : [hov, start]\n return t > lo && t < hi\n }\n\n // Complete range\n if (range.end) {\n const end = range.end.getTime()\n return t > start && t < end\n }\n\n return false\n}\n\nfunction isDisabled(date: Date): boolean {\n if (disabledDateSet.value.has(toDateKey(date))) return true\n if (props.isDateDisabled?.(date)) return true\n if (props.minDate) {\n const min = parseDate(props.minDate)\n if (date < min) return true\n }\n if (props.maxDate) {\n const max = parseDate(props.maxDate)\n if (date > max) return true\n }\n return false\n}\n\nconst calendarDays = computed<CalendarDayContext[]>(() => {\n const first = new Date(currentYear.value, currentMonth.value, 1)\n const startDow = first.getDay()\n const offset = (startDow - props.weekStartsOn + 7) % 7\n\n const totalCells = props.fixedWeeks ? 42 : (() => {\n const daysInMonth = new Date(currentYear.value, currentMonth.value + 1, 0).getDate()\n const needed = offset + daysInMonth\n return Math.ceil(needed / 7) * 7\n })()\n\n const days: CalendarDayContext[] = []\n for (let i = 0; i < totalCells; i++) {\n const date = new Date(currentYear.value, currentMonth.value, 1 - offset + i)\n date.setHours(0, 0, 0, 0)\n const key = toDateKey(date)\n days.push({\n date,\n dayOfMonth: date.getDate(),\n isToday: isSameDay(date, today),\n isSelected: isDateSelected(date),\n isInRange: isInRange(date),\n isDisabled: isDisabled(date),\n isOutsideMonth: date.getMonth() !== currentMonth.value,\n markers: markerMap.value.get(key) ?? [],\n })\n }\n return days\n})\n\n// Range selection state\nconst rangeStart = ref<Date | null>(null)\n\nfunction handleDayClick(day: CalendarDayContext) {\n if (day.isDisabled) return\n if (!props.showOutsideDays && day.isOutsideMonth) return\n\n emit('day-click', day)\n\n if (props.selectionMode === 'none') return\n\n if (props.selectionMode === 'single') {\n emit('update:modelValue', day.date)\n } else if (props.selectionMode === 'multiple') {\n const current = (props.modelValue as Date[] | undefined) ?? []\n const idx = current.findIndex(d => isSameDay(d, day.date))\n if (idx >= 0) {\n const next = [...current]\n next.splice(idx, 1)\n emit('update:modelValue', next)\n } else {\n emit('update:modelValue', [...current, day.date])\n }\n } else if (props.selectionMode === 'range') {\n if (!rangeStart.value) {\n rangeStart.value = day.date\n } else {\n const start = rangeStart.value\n const end = day.date\n rangeStart.value = null\n const ordered = start.getTime() <= end.getTime()\n ? { start, end }\n : { start: end, end: start }\n emit('update:modelValue', ordered)\n }\n }\n}\n\nfunction handleDayHover(day: CalendarDayContext) {\n if (props.selectionMode === 'range' && rangeStart.value) {\n hoveredDate.value = day.date\n }\n}\n\nfunction prevMonth() {\n if (currentMonth.value === 0) {\n currentMonth.value = 11\n currentYear.value--\n emit('update:year', currentYear.value)\n } else {\n currentMonth.value--\n }\n emit('update:month', currentMonth.value)\n emit('navigate', 'prev', currentMonth.value, currentYear.value)\n}\n\nfunction nextMonth() {\n if (currentMonth.value === 11) {\n currentMonth.value = 0\n currentYear.value++\n emit('update:year', currentYear.value)\n } else {\n currentMonth.value++\n }\n emit('update:month', currentMonth.value)\n emit('navigate', 'next', currentMonth.value, currentYear.value)\n}\n</script>\n\n<template>\n <div class=\"mint-calendar\">\n <div v-if=\"showNavigation\" class=\"mint-calendar__header\">\n <button type=\"button\" class=\"mint-calendar__nav-btn\" aria-label=\"Previous month\" @click=\"prevMonth\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n <slot name=\"header\" :month=\"currentMonth\" :year=\"currentYear\" :prev-month=\"prevMonth\" :next-month=\"nextMonth\">\n <span class=\"mint-calendar__title\">{{ monthLabel }}</span>\n </slot>\n <button type=\"button\" class=\"mint-calendar__nav-btn\" aria-label=\"Next month\" @click=\"nextMonth\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n </div>\n\n <div class=\"mint-calendar__weekdays\">\n <span\n v-for=\"(label, i) in weekDayLabels\"\n :key=\"i\"\n class=\"mint-calendar__weekday\"\n >\n <slot name=\"week-day\" :day-name=\"label\" :index=\"i\">{{ label }}</slot>\n </span>\n </div>\n\n <div class=\"mint-calendar__grid\">\n <button\n v-for=\"(day, i) in calendarDays\"\n :key=\"i\"\n type=\"button\"\n :disabled=\"day.isDisabled\"\n :class=\"[\n 'mint-calendar__day',\n {\n 'mint-calendar__day--today': day.isToday,\n 'mint-calendar__day--selected': day.isSelected,\n 'mint-calendar__day--in-range': day.isInRange,\n 'mint-calendar__day--disabled': day.isDisabled,\n 'mint-calendar__day--outside': day.isOutsideMonth,\n 'mint-calendar__day--interactive': selectionMode !== 'none' && !day.isDisabled,\n },\n ]\"\n @click=\"handleDayClick(day)\"\n @mouseenter=\"handleDayHover(day)\"\n >\n <slot name=\"day-content\" :day=\"day\">\n <span class=\"mint-calendar__day-number\">{{ day.dayOfMonth }}</span>\n <div v-if=\"day.markers.length > 0\" class=\"mint-calendar__markers\">\n <span\n v-for=\"(marker, mi) in day.markers.slice(0, 3)\"\n :key=\"mi\"\n :class=\"[\n 'mint-calendar__marker',\n `mint-calendar__marker--${marker.type ?? 'dot'}`,\n ]\"\n :style=\"marker.color ? { '--marker-color': marker.color } : {}\"\n :title=\"marker.label\"\n />\n </div>\n </slot>\n </button>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/calendar.css';\n</style>\n","<script setup lang=\"ts\">\n/** Data table with sortable columns, search, pagination, and optional row selection. */\nimport { ref, computed } from 'vue'\nimport type { DataFrameColumn, SortState, PaginationState } from '../types'\nimport { useSortedItems } from '../composables/useSortedItems'\nimport { useTextSearch } from '../composables/useTextSearch'\nimport { useListSelection } from '../composables/useListSelection'\n\n/**\n * DataFrame - Data table with sorting, searching, pagination, and row selection.\n *\n * @example\n * ```vue\n * <DataFrame\n * :data=\"rows\"\n * :columns=\"[\n * { key: 'name', label: 'Name', sortable: true },\n * { key: 'age', label: 'Age', align: 'right' }\n * ]\"\n * sortable\n * searchable\n * :pagination=\"{ page: 1, pageSize: 10, total: 100 }\"\n * />\n * ```\n */\ninterface Props {\n /** Array of data objects */\n data: Record<string, unknown>[]\n /** Column definitions */\n columns: DataFrameColumn[]\n /** Unique key for each row (property name or function) */\n rowKey?: string | ((row: Record<string, unknown>) => string | number)\n /** Alternate row background colors */\n striped?: boolean\n /** Show table borders */\n bordered?: boolean\n /** Table size */\n size?: 'sm' | 'md' | 'lg'\n /** Make header sticky on scroll */\n stickyHeader?: boolean\n /** Maximum table height (enables vertical scroll) */\n maxHeight?: string | number\n /** Show loading overlay */\n loading?: boolean\n /** Empty state message */\n emptyText?: string\n /** Enable column sorting */\n sortable?: boolean\n /** Current sort state (controlled mode) */\n sort?: SortState | null\n /** Pagination state or false to disable */\n pagination?: PaginationState | false\n /** Enable search input */\n searchable?: boolean\n /** Search input placeholder */\n searchPlaceholder?: string\n /** Column keys to search (defaults to all) */\n searchKeys?: string[]\n /** Enable row selection with checkboxes */\n selectable?: boolean\n /** Currently selected row keys */\n selectedKeys?: (string | number)[]\n /** Make each body row act as a click/keyboard target. */\n clickableRows?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n striped: true,\n bordered: true,\n size: 'md',\n stickyHeader: false,\n loading: false,\n emptyText: 'No data',\n sortable: false,\n pagination: false,\n searchable: false,\n searchPlaceholder: 'Search...',\n selectable: false,\n selectedKeys: () => [],\n clickableRows: false,\n})\n\n/**\n * @event update:sort - Emitted when sort state changes\n * @event update:pagination - Emitted when page changes\n * @event update:selectedKeys - Emitted when row selection changes\n * @event row-click - Emitted when a row is clicked\n * @event cell-click - Emitted when a cell is clicked\n */\nconst emit = defineEmits<{\n 'update:sort': [sort: SortState | null]\n 'update:pagination': [pagination: PaginationState]\n 'update:selectedKeys': [keys: (string | number)[]]\n 'row-click': [row: Record<string, unknown>, index: number]\n 'cell-click': [value: unknown, column: DataFrameColumn, row: Record<string, unknown>]\n}>()\n\nconst searchQuery = ref('')\nconst internalSort = ref<SortState | null>(null)\n\nconst activeSort = computed(() => props.sort !== undefined ? props.sort : internalSort.value)\n\nfunction getRowKey(row: Record<string, unknown>, index: number): string | number {\n if (!props.rowKey) return index\n if (typeof props.rowKey === 'function') return props.rowKey(row)\n return row[props.rowKey] as string | number\n}\n\nfunction getCellValue(row: Record<string, unknown>, key: string): unknown {\n return key.split('.').reduce((obj: unknown, k) => {\n if (obj && typeof obj === 'object') return (obj as Record<string, unknown>)[k]\n return undefined\n }, row)\n}\n\nfunction formatCell(col: DataFrameColumn, row: Record<string, unknown>, index: number): string {\n const value = getCellValue(row, col.key)\n if (col.formatter) return col.formatter(value, row, index)\n if (value === null || value === undefined) return ''\n return String(value)\n}\n\nconst textSearch = useTextSearch({\n items: () => props.data,\n query: searchQuery,\n enabled: () => props.searchable,\n getText: (row) => {\n const keys = props.searchKeys ?? props.columns.map(c => c.key)\n return keys.map(key => getCellValue(row, key))\n },\n})\nconst filteredData = textSearch.filteredItems\n\nconst tableSort = useSortedItems({\n items: filteredData,\n sort: activeSort,\n enabled: () => {\n const sort = activeSort.value\n return Boolean(sort?.direction && props.columns.some(c => c.key === sort.key))\n },\n getValue: (row, key) => getCellValue(row, key),\n})\nconst sortedData = tableSort.sortedItems\n\n// Pagination\nconst paginatedData = computed(() => {\n if (!props.pagination) return sortedData.value\n const { page, pageSize } = props.pagination\n const start = (page - 1) * pageSize\n return sortedData.value.slice(start, start + pageSize)\n})\n\nconst totalPages = computed(() => {\n if (!props.pagination) return 1\n return Math.max(1, Math.ceil(sortedData.value.length / props.pagination.pageSize))\n})\n\nfunction handleSort(col: DataFrameColumn) {\n if (!col.sortable && !props.sortable) return\n\n const current = activeSort.value\n let next: SortState | null\n\n if (current?.key === col.key) {\n if (current.direction === 'asc') {\n next = { key: col.key, direction: 'desc' }\n } else if (current.direction === 'desc') {\n next = null\n } else {\n next = { key: col.key, direction: 'asc' }\n }\n } else {\n next = { key: col.key, direction: 'asc' }\n }\n\n if (props.sort === undefined) {\n internalSort.value = next\n }\n emit('update:sort', next)\n}\n\nfunction handlePageChange(page: number) {\n if (!props.pagination) return\n emit('update:pagination', { ...props.pagination, page })\n}\n\nfunction handleRowClick(row: Record<string, unknown>, index: number) {\n emit('row-click', row, index)\n}\n\nfunction handleRowKeydown(event: KeyboardEvent, row: Record<string, unknown>, index: number) {\n if (!props.clickableRows) return\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n handleRowClick(row, index)\n}\n\nfunction handleCellClick(row: Record<string, unknown>, col: DataFrameColumn, index: number) {\n emit('cell-click', getCellValue(row, col.key), col, row)\n if (props.clickableRows) {\n handleRowClick(row, index)\n }\n}\n\nfunction isColumnSortable(col: DataFrameColumn): boolean {\n return col.sortable === true || (props.sortable && col.sortable !== false)\n}\n\n// Selection\nconst pageRowKeys = computed(() =>\n paginatedData.value.map((row, i) => getRowKey(row, i))\n)\nconst rowSelection = useListSelection({\n selected: () => props.selectedKeys,\n items: pageRowKeys,\n})\nconst allSelected = computed(() => {\n return props.selectable && rowSelection.isAllSelected.value\n})\n\nconst selectIndeterminate = computed(() => {\n return props.selectedKeys.length > 0 && !allSelected.value\n})\n\nfunction toggleSelectAll() {\n if (allSelected.value) {\n emit('update:selectedKeys', [])\n } else {\n emit('update:selectedKeys', rowSelection.itemValues.value)\n }\n}\n\nfunction toggleRowSelect(key: string | number) {\n emit('update:selectedKeys', rowSelection.toggleValue(key))\n}\n\nconst wrapperStyle = computed(() => {\n if (!props.maxHeight) return undefined\n const maxHeight = typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight\n return { maxHeight, overflowY: 'auto' as const }\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-dataframe',\n { 'mint-dataframe--bordered': bordered },\n ]\"\n >\n <!-- Search header -->\n <div v-if=\"searchable\" class=\"mint-dataframe__header\">\n <div class=\"mint-dataframe__search-wrapper\">\n <svg class=\"mint-dataframe__search-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\" /><path d=\"m21 21-4.34-4.34\" />\n </svg>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n class=\"mint-dataframe__search\"\n :placeholder=\"searchPlaceholder\"\n />\n </div>\n </div>\n\n <!-- Table wrapper -->\n <div class=\"mint-dataframe__table-wrapper\" :style=\"wrapperStyle\">\n <table class=\"mint-dataframe__table\">\n <thead :class=\"['mint-dataframe__thead', { 'mint-dataframe__thead--sticky': stickyHeader }]\">\n <tr>\n <th v-if=\"selectable\" class=\"mint-dataframe__th mint-dataframe__th--checkbox\">\n <input\n type=\"checkbox\"\n :checked=\"allSelected\"\n :indeterminate=\"selectIndeterminate\"\n class=\"mint-dataframe__checkbox\"\n @change=\"toggleSelectAll\"\n />\n </th>\n <th\n v-for=\"col in columns\"\n :key=\"col.key\"\n :class=\"[\n 'mint-dataframe__th',\n `mint-dataframe__th--${size}`,\n `mint-dataframe__th--align-${col.align ?? 'left'}`,\n {\n 'mint-dataframe__th--sortable': isColumnSortable(col),\n 'mint-dataframe__th--sorted': activeSort?.key === col.key && activeSort?.direction,\n },\n ]\"\n :style=\"{\n width: col.width ? (typeof col.width === 'number' ? `${col.width}px` : col.width) : undefined,\n minWidth: col.minWidth ? (typeof col.minWidth === 'number' ? `${col.minWidth}px` : col.minWidth) : undefined,\n }\"\n @click=\"isColumnSortable(col) ? handleSort(col) : undefined\"\n >\n <slot :name=\"`header-${col.key}`\" :column=\"col\">\n <div class=\"mint-dataframe__th-content\">\n <span>{{ col.label }}</span>\n <svg\n v-if=\"isColumnSortable(col)\"\n :class=\"[\n 'mint-dataframe__sort-icon',\n {\n 'mint-dataframe__sort-icon--asc': activeSort?.key === col.key && activeSort?.direction === 'asc',\n 'mint-dataframe__sort-icon--desc': activeSort?.key === col.key && activeSort?.direction === 'desc',\n },\n ]\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"m21 16-4 4-4-4\" /><path d=\"M17 20V4\" /><path d=\"m3 8 4-4 4 4\" /><path d=\"M7 4v16\" />\n </svg>\n </div>\n </slot>\n </th>\n </tr>\n </thead>\n\n <tbody>\n <tr\n v-for=\"(row, rowIndex) in paginatedData\"\n :key=\"getRowKey(row, rowIndex)\"\n :class=\"[\n 'mint-dataframe__row',\n {\n 'mint-dataframe__row--striped': striped && rowIndex % 2 === 1,\n 'mint-dataframe__row--selected': rowSelection.isSelected(getRowKey(row, rowIndex)),\n 'mint-dataframe__row--clickable': clickableRows,\n },\n ]\"\n :tabindex=\"clickableRows ? 0 : undefined\"\n :role=\"clickableRows ? 'button' : undefined\"\n @click=\"handleRowClick(row, rowIndex)\"\n @keydown=\"handleRowKeydown($event, row, rowIndex)\"\n >\n <td v-if=\"selectable\" class=\"mint-dataframe__td mint-dataframe__td--checkbox\">\n <input\n type=\"checkbox\"\n :checked=\"rowSelection.isSelected(getRowKey(row, rowIndex))\"\n class=\"mint-dataframe__checkbox\"\n @click.stop\n @change=\"toggleRowSelect(getRowKey(row, rowIndex))\"\n />\n </td>\n <td\n v-for=\"col in columns\"\n :key=\"col.key\"\n :class=\"[\n 'mint-dataframe__td',\n `mint-dataframe__td--${size}`,\n `mint-dataframe__td--align-${col.align ?? 'left'}`,\n { 'mint-dataframe__td--ellipsis': col.ellipsis },\n ]\"\n @click.stop=\"handleCellClick(row, col, rowIndex)\"\n >\n <slot :name=\"`cell-${col.key}`\" :value=\"getCellValue(row, col.key)\" :row=\"row\" :column=\"col\" :index=\"rowIndex\">\n {{ formatCell(col, row, rowIndex) }}\n </slot>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!-- Empty state -->\n <div v-if=\"paginatedData.length === 0 && !loading\" class=\"mint-dataframe__empty\">\n <slot name=\"empty\">\n <svg class=\"mint-dataframe__empty-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"22 12 16 12 14 15 10 15 8 12 2 12\" /><path d=\"M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z\" />\n </svg>\n <p class=\"mint-dataframe__empty-text\">{{ emptyText }}</p>\n </slot>\n </div>\n\n <!-- Loading overlay -->\n <div v-if=\"loading\" class=\"mint-dataframe__loading\">\n <slot name=\"loading\">\n <svg class=\"mint-dataframe__loading-spinner\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"3\" opacity=\"0.25\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" />\n </svg>\n </slot>\n </div>\n </div>\n\n <!-- Footer / Pagination -->\n <div v-if=\"pagination\" class=\"mint-dataframe__footer\">\n <slot name=\"footer\">\n <span class=\"mint-dataframe__page-info\">\n {{ (pagination.page - 1) * pagination.pageSize + 1 }}–{{ Math.min(pagination.page * pagination.pageSize, sortedData.length) }}\n of {{ sortedData.length }}\n </span>\n <div class=\"mint-dataframe__page-controls\">\n <button\n type=\"button\"\n class=\"mint-dataframe__page-btn\"\n :disabled=\"pagination.page <= 1\"\n @click=\"handlePageChange(pagination.page - 1)\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n <span class=\"mint-dataframe__page-number\">{{ pagination.page }} / {{ totalPages }}</span>\n <button\n type=\"button\"\n class=\"mint-dataframe__page-btn\"\n :disabled=\"pagination.page >= totalPages\"\n @click=\"handlePageChange(pagination.page + 1)\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n </div>\n </slot>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/dataframe.css';\n</style>\n","<script setup lang=\"ts\">\n/** Data table with sortable columns, search, pagination, and optional row selection. */\nimport { ref, computed } from 'vue'\nimport type { DataFrameColumn, SortState, PaginationState } from '../types'\nimport { useSortedItems } from '../composables/useSortedItems'\nimport { useTextSearch } from '../composables/useTextSearch'\nimport { useListSelection } from '../composables/useListSelection'\n\n/**\n * DataFrame - Data table with sorting, searching, pagination, and row selection.\n *\n * @example\n * ```vue\n * <DataFrame\n * :data=\"rows\"\n * :columns=\"[\n * { key: 'name', label: 'Name', sortable: true },\n * { key: 'age', label: 'Age', align: 'right' }\n * ]\"\n * sortable\n * searchable\n * :pagination=\"{ page: 1, pageSize: 10, total: 100 }\"\n * />\n * ```\n */\ninterface Props {\n /** Array of data objects */\n data: Record<string, unknown>[]\n /** Column definitions */\n columns: DataFrameColumn[]\n /** Unique key for each row (property name or function) */\n rowKey?: string | ((row: Record<string, unknown>) => string | number)\n /** Alternate row background colors */\n striped?: boolean\n /** Show table borders */\n bordered?: boolean\n /** Table size */\n size?: 'sm' | 'md' | 'lg'\n /** Make header sticky on scroll */\n stickyHeader?: boolean\n /** Maximum table height (enables vertical scroll) */\n maxHeight?: string | number\n /** Show loading overlay */\n loading?: boolean\n /** Empty state message */\n emptyText?: string\n /** Enable column sorting */\n sortable?: boolean\n /** Current sort state (controlled mode) */\n sort?: SortState | null\n /** Pagination state or false to disable */\n pagination?: PaginationState | false\n /** Enable search input */\n searchable?: boolean\n /** Search input placeholder */\n searchPlaceholder?: string\n /** Column keys to search (defaults to all) */\n searchKeys?: string[]\n /** Enable row selection with checkboxes */\n selectable?: boolean\n /** Currently selected row keys */\n selectedKeys?: (string | number)[]\n /** Make each body row act as a click/keyboard target. */\n clickableRows?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n striped: true,\n bordered: true,\n size: 'md',\n stickyHeader: false,\n loading: false,\n emptyText: 'No data',\n sortable: false,\n pagination: false,\n searchable: false,\n searchPlaceholder: 'Search...',\n selectable: false,\n selectedKeys: () => [],\n clickableRows: false,\n})\n\n/**\n * @event update:sort - Emitted when sort state changes\n * @event update:pagination - Emitted when page changes\n * @event update:selectedKeys - Emitted when row selection changes\n * @event row-click - Emitted when a row is clicked\n * @event cell-click - Emitted when a cell is clicked\n */\nconst emit = defineEmits<{\n 'update:sort': [sort: SortState | null]\n 'update:pagination': [pagination: PaginationState]\n 'update:selectedKeys': [keys: (string | number)[]]\n 'row-click': [row: Record<string, unknown>, index: number]\n 'cell-click': [value: unknown, column: DataFrameColumn, row: Record<string, unknown>]\n}>()\n\nconst searchQuery = ref('')\nconst internalSort = ref<SortState | null>(null)\n\nconst activeSort = computed(() => props.sort !== undefined ? props.sort : internalSort.value)\n\nfunction getRowKey(row: Record<string, unknown>, index: number): string | number {\n if (!props.rowKey) return index\n if (typeof props.rowKey === 'function') return props.rowKey(row)\n return row[props.rowKey] as string | number\n}\n\nfunction getCellValue(row: Record<string, unknown>, key: string): unknown {\n return key.split('.').reduce((obj: unknown, k) => {\n if (obj && typeof obj === 'object') return (obj as Record<string, unknown>)[k]\n return undefined\n }, row)\n}\n\nfunction formatCell(col: DataFrameColumn, row: Record<string, unknown>, index: number): string {\n const value = getCellValue(row, col.key)\n if (col.formatter) return col.formatter(value, row, index)\n if (value === null || value === undefined) return ''\n return String(value)\n}\n\nconst textSearch = useTextSearch({\n items: () => props.data,\n query: searchQuery,\n enabled: () => props.searchable,\n getText: (row) => {\n const keys = props.searchKeys ?? props.columns.map(c => c.key)\n return keys.map(key => getCellValue(row, key))\n },\n})\nconst filteredData = textSearch.filteredItems\n\nconst tableSort = useSortedItems({\n items: filteredData,\n sort: activeSort,\n enabled: () => {\n const sort = activeSort.value\n return Boolean(sort?.direction && props.columns.some(c => c.key === sort.key))\n },\n getValue: (row, key) => getCellValue(row, key),\n})\nconst sortedData = tableSort.sortedItems\n\n// Pagination\nconst paginatedData = computed(() => {\n if (!props.pagination) return sortedData.value\n const { page, pageSize } = props.pagination\n const start = (page - 1) * pageSize\n return sortedData.value.slice(start, start + pageSize)\n})\n\nconst totalPages = computed(() => {\n if (!props.pagination) return 1\n return Math.max(1, Math.ceil(sortedData.value.length / props.pagination.pageSize))\n})\n\nfunction handleSort(col: DataFrameColumn) {\n if (!col.sortable && !props.sortable) return\n\n const current = activeSort.value\n let next: SortState | null\n\n if (current?.key === col.key) {\n if (current.direction === 'asc') {\n next = { key: col.key, direction: 'desc' }\n } else if (current.direction === 'desc') {\n next = null\n } else {\n next = { key: col.key, direction: 'asc' }\n }\n } else {\n next = { key: col.key, direction: 'asc' }\n }\n\n if (props.sort === undefined) {\n internalSort.value = next\n }\n emit('update:sort', next)\n}\n\nfunction handlePageChange(page: number) {\n if (!props.pagination) return\n emit('update:pagination', { ...props.pagination, page })\n}\n\nfunction handleRowClick(row: Record<string, unknown>, index: number) {\n emit('row-click', row, index)\n}\n\nfunction handleRowKeydown(event: KeyboardEvent, row: Record<string, unknown>, index: number) {\n if (!props.clickableRows) return\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n handleRowClick(row, index)\n}\n\nfunction handleCellClick(row: Record<string, unknown>, col: DataFrameColumn, index: number) {\n emit('cell-click', getCellValue(row, col.key), col, row)\n if (props.clickableRows) {\n handleRowClick(row, index)\n }\n}\n\nfunction isColumnSortable(col: DataFrameColumn): boolean {\n return col.sortable === true || (props.sortable && col.sortable !== false)\n}\n\n// Selection\nconst pageRowKeys = computed(() =>\n paginatedData.value.map((row, i) => getRowKey(row, i))\n)\nconst rowSelection = useListSelection({\n selected: () => props.selectedKeys,\n items: pageRowKeys,\n})\nconst allSelected = computed(() => {\n return props.selectable && rowSelection.isAllSelected.value\n})\n\nconst selectIndeterminate = computed(() => {\n return props.selectedKeys.length > 0 && !allSelected.value\n})\n\nfunction toggleSelectAll() {\n if (allSelected.value) {\n emit('update:selectedKeys', [])\n } else {\n emit('update:selectedKeys', rowSelection.itemValues.value)\n }\n}\n\nfunction toggleRowSelect(key: string | number) {\n emit('update:selectedKeys', rowSelection.toggleValue(key))\n}\n\nconst wrapperStyle = computed(() => {\n if (!props.maxHeight) return undefined\n const maxHeight = typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight\n return { maxHeight, overflowY: 'auto' as const }\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-dataframe',\n { 'mint-dataframe--bordered': bordered },\n ]\"\n >\n <!-- Search header -->\n <div v-if=\"searchable\" class=\"mint-dataframe__header\">\n <div class=\"mint-dataframe__search-wrapper\">\n <svg class=\"mint-dataframe__search-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\" /><path d=\"m21 21-4.34-4.34\" />\n </svg>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n class=\"mint-dataframe__search\"\n :placeholder=\"searchPlaceholder\"\n />\n </div>\n </div>\n\n <!-- Table wrapper -->\n <div class=\"mint-dataframe__table-wrapper\" :style=\"wrapperStyle\">\n <table class=\"mint-dataframe__table\">\n <thead :class=\"['mint-dataframe__thead', { 'mint-dataframe__thead--sticky': stickyHeader }]\">\n <tr>\n <th v-if=\"selectable\" class=\"mint-dataframe__th mint-dataframe__th--checkbox\">\n <input\n type=\"checkbox\"\n :checked=\"allSelected\"\n :indeterminate=\"selectIndeterminate\"\n class=\"mint-dataframe__checkbox\"\n @change=\"toggleSelectAll\"\n />\n </th>\n <th\n v-for=\"col in columns\"\n :key=\"col.key\"\n :class=\"[\n 'mint-dataframe__th',\n `mint-dataframe__th--${size}`,\n `mint-dataframe__th--align-${col.align ?? 'left'}`,\n {\n 'mint-dataframe__th--sortable': isColumnSortable(col),\n 'mint-dataframe__th--sorted': activeSort?.key === col.key && activeSort?.direction,\n },\n ]\"\n :style=\"{\n width: col.width ? (typeof col.width === 'number' ? `${col.width}px` : col.width) : undefined,\n minWidth: col.minWidth ? (typeof col.minWidth === 'number' ? `${col.minWidth}px` : col.minWidth) : undefined,\n }\"\n @click=\"isColumnSortable(col) ? handleSort(col) : undefined\"\n >\n <slot :name=\"`header-${col.key}`\" :column=\"col\">\n <div class=\"mint-dataframe__th-content\">\n <span>{{ col.label }}</span>\n <svg\n v-if=\"isColumnSortable(col)\"\n :class=\"[\n 'mint-dataframe__sort-icon',\n {\n 'mint-dataframe__sort-icon--asc': activeSort?.key === col.key && activeSort?.direction === 'asc',\n 'mint-dataframe__sort-icon--desc': activeSort?.key === col.key && activeSort?.direction === 'desc',\n },\n ]\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"m21 16-4 4-4-4\" /><path d=\"M17 20V4\" /><path d=\"m3 8 4-4 4 4\" /><path d=\"M7 4v16\" />\n </svg>\n </div>\n </slot>\n </th>\n </tr>\n </thead>\n\n <tbody>\n <tr\n v-for=\"(row, rowIndex) in paginatedData\"\n :key=\"getRowKey(row, rowIndex)\"\n :class=\"[\n 'mint-dataframe__row',\n {\n 'mint-dataframe__row--striped': striped && rowIndex % 2 === 1,\n 'mint-dataframe__row--selected': rowSelection.isSelected(getRowKey(row, rowIndex)),\n 'mint-dataframe__row--clickable': clickableRows,\n },\n ]\"\n :tabindex=\"clickableRows ? 0 : undefined\"\n :role=\"clickableRows ? 'button' : undefined\"\n @click=\"handleRowClick(row, rowIndex)\"\n @keydown=\"handleRowKeydown($event, row, rowIndex)\"\n >\n <td v-if=\"selectable\" class=\"mint-dataframe__td mint-dataframe__td--checkbox\">\n <input\n type=\"checkbox\"\n :checked=\"rowSelection.isSelected(getRowKey(row, rowIndex))\"\n class=\"mint-dataframe__checkbox\"\n @click.stop\n @change=\"toggleRowSelect(getRowKey(row, rowIndex))\"\n />\n </td>\n <td\n v-for=\"col in columns\"\n :key=\"col.key\"\n :class=\"[\n 'mint-dataframe__td',\n `mint-dataframe__td--${size}`,\n `mint-dataframe__td--align-${col.align ?? 'left'}`,\n { 'mint-dataframe__td--ellipsis': col.ellipsis },\n ]\"\n @click.stop=\"handleCellClick(row, col, rowIndex)\"\n >\n <slot :name=\"`cell-${col.key}`\" :value=\"getCellValue(row, col.key)\" :row=\"row\" :column=\"col\" :index=\"rowIndex\">\n {{ formatCell(col, row, rowIndex) }}\n </slot>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!-- Empty state -->\n <div v-if=\"paginatedData.length === 0 && !loading\" class=\"mint-dataframe__empty\">\n <slot name=\"empty\">\n <svg class=\"mint-dataframe__empty-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"22 12 16 12 14 15 10 15 8 12 2 12\" /><path d=\"M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z\" />\n </svg>\n <p class=\"mint-dataframe__empty-text\">{{ emptyText }}</p>\n </slot>\n </div>\n\n <!-- Loading overlay -->\n <div v-if=\"loading\" class=\"mint-dataframe__loading\">\n <slot name=\"loading\">\n <svg class=\"mint-dataframe__loading-spinner\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"3\" opacity=\"0.25\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" />\n </svg>\n </slot>\n </div>\n </div>\n\n <!-- Footer / Pagination -->\n <div v-if=\"pagination\" class=\"mint-dataframe__footer\">\n <slot name=\"footer\">\n <span class=\"mint-dataframe__page-info\">\n {{ (pagination.page - 1) * pagination.pageSize + 1 }}–{{ Math.min(pagination.page * pagination.pageSize, sortedData.length) }}\n of {{ sortedData.length }}\n </span>\n <div class=\"mint-dataframe__page-controls\">\n <button\n type=\"button\"\n class=\"mint-dataframe__page-btn\"\n :disabled=\"pagination.page <= 1\"\n @click=\"handlePageChange(pagination.page - 1)\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n <span class=\"mint-dataframe__page-number\">{{ pagination.page }} / {{ totalPages }}</span>\n <button\n type=\"button\"\n class=\"mint-dataframe__page-btn\"\n :disabled=\"pagination.page >= totalPages\"\n @click=\"handlePageChange(pagination.page + 1)\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n </div>\n </slot>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/dataframe.css';\n</style>\n","<script setup lang=\"ts\">\n/** Inline alert banner with semantic icon, title, message slot, optional action button, and optional dismiss button. */\nimport { useSlots } from 'vue'\nimport type { AlertType } from '../types'\n\ninterface Props {\n type?: AlertType\n title?: string\n dismissible?: boolean\n actionLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n type: 'info',\n dismissible: false,\n})\n\nconst emit = defineEmits<{\n dismiss: []\n action: []\n}>()\n\nconst slots = useSlots()\nconst hasActions = () => !!props.actionLabel || !!slots.actions\n</script>\n\n<template>\n <div\n class=\"mint-alert\"\n :class=\"`mint-alert--${props.type}`\"\n role=\"alert\"\n >\n <!-- Lucide circle-check -->\n <svg v-if=\"props.type === 'success'\" class=\"mint-alert__icon mint-alert__icon--success\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"m9 12 2 2 4-4\" />\n </svg>\n <!-- Lucide circle-x -->\n <svg v-else-if=\"props.type === 'error'\" class=\"mint-alert__icon mint-alert__icon--error\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"m15 9-6 6\" /><path d=\"m9 9 6 6\" />\n </svg>\n <!-- Lucide triangle-alert -->\n <svg v-else-if=\"props.type === 'warning'\" class=\"mint-alert__icon mint-alert__icon--warning\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" /><path d=\"M12 9v4\" /><path d=\"M12 17h.01\" />\n </svg>\n <!-- Lucide info -->\n <svg v-else class=\"mint-alert__icon mint-alert__icon--info\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"M12 16v-4\" /><path d=\"M12 8h.01\" />\n </svg>\n\n <div class=\"mint-alert__content\">\n <h4 v-if=\"props.title\" class=\"mint-alert__title\">\n {{ props.title }}\n </h4>\n <div class=\"mint-alert__message\">\n <slot />\n </div>\n </div>\n\n <div v-if=\"hasActions()\" class=\"mint-alert__actions\">\n <slot name=\"actions\">\n <button\n type=\"button\"\n class=\"mint-alert__action-btn\"\n :class=\"`mint-alert__action-btn--${props.type}`\"\n @click=\"emit('action')\"\n >\n {{ props.actionLabel }}\n </button>\n </slot>\n </div>\n\n <button\n v-if=\"props.dismissible\"\n type=\"button\"\n class=\"mint-alert__dismiss\"\n aria-label=\"Dismiss\"\n @click=\"emit('dismiss')\"\n >\n <svg class=\"mint-alert__dismiss-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </div>\n</template>\n\n<style>\n@import '../styles/components/alert-box.css';\n</style>\n","<script setup lang=\"ts\">\n/** Inline alert banner with semantic icon, title, message slot, optional action button, and optional dismiss button. */\nimport { useSlots } from 'vue'\nimport type { AlertType } from '../types'\n\ninterface Props {\n type?: AlertType\n title?: string\n dismissible?: boolean\n actionLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n type: 'info',\n dismissible: false,\n})\n\nconst emit = defineEmits<{\n dismiss: []\n action: []\n}>()\n\nconst slots = useSlots()\nconst hasActions = () => !!props.actionLabel || !!slots.actions\n</script>\n\n<template>\n <div\n class=\"mint-alert\"\n :class=\"`mint-alert--${props.type}`\"\n role=\"alert\"\n >\n <!-- Lucide circle-check -->\n <svg v-if=\"props.type === 'success'\" class=\"mint-alert__icon mint-alert__icon--success\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"m9 12 2 2 4-4\" />\n </svg>\n <!-- Lucide circle-x -->\n <svg v-else-if=\"props.type === 'error'\" class=\"mint-alert__icon mint-alert__icon--error\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"m15 9-6 6\" /><path d=\"m9 9 6 6\" />\n </svg>\n <!-- Lucide triangle-alert -->\n <svg v-else-if=\"props.type === 'warning'\" class=\"mint-alert__icon mint-alert__icon--warning\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" /><path d=\"M12 9v4\" /><path d=\"M12 17h.01\" />\n </svg>\n <!-- Lucide info -->\n <svg v-else class=\"mint-alert__icon mint-alert__icon--info\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"M12 16v-4\" /><path d=\"M12 8h.01\" />\n </svg>\n\n <div class=\"mint-alert__content\">\n <h4 v-if=\"props.title\" class=\"mint-alert__title\">\n {{ props.title }}\n </h4>\n <div class=\"mint-alert__message\">\n <slot />\n </div>\n </div>\n\n <div v-if=\"hasActions()\" class=\"mint-alert__actions\">\n <slot name=\"actions\">\n <button\n type=\"button\"\n class=\"mint-alert__action-btn\"\n :class=\"`mint-alert__action-btn--${props.type}`\"\n @click=\"emit('action')\"\n >\n {{ props.actionLabel }}\n </button>\n </slot>\n </div>\n\n <button\n v-if=\"props.dismissible\"\n type=\"button\"\n class=\"mint-alert__dismiss\"\n aria-label=\"Dismiss\"\n @click=\"emit('dismiss')\"\n >\n <svg class=\"mint-alert__dismiss-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </div>\n</template>\n\n<style>\n@import '../styles/components/alert-box.css';\n</style>\n","<script setup lang=\"ts\">\n/** Preferred toast container driven by useToast, with auto-dismiss progress bar and click-to-close on each notification. */\nimport { useToast } from '../composables/useToast'\nimport type { Toast } from '../types'\n\nconst { toasts, dismiss } = useToast()\n\nconst accentColors: Record<Toast['type'], string> = {\n success: '#10B981',\n error: '#EF4444',\n warning: '#F59E0B',\n info: '#3B82F6',\n}\n</script>\n\n<template>\n <Teleport to=\"body\">\n <div class=\"mint-toast__container\">\n <TransitionGroup name=\"toast\">\n <div\n v-for=\"toast in toasts\"\n :key=\"toast.id\"\n :class=\"['mint-toast__item', `mint-toast__item--${toast.type}`]\"\n :style=\"{\n borderLeftColor: accentColors[toast.type],\n }\"\n role=\"alert\"\n @click=\"dismiss(toast.id)\"\n >\n <!-- Lucide circle-check -->\n <svg v-if=\"toast.type === 'success'\" class=\"mint-toast__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" :style=\"{ color: accentColors[toast.type] }\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"m9 12 2 2 4-4\" />\n </svg>\n <!-- Lucide circle-x -->\n <svg v-else-if=\"toast.type === 'error'\" class=\"mint-toast__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" :style=\"{ color: accentColors[toast.type] }\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"m15 9-6 6\" /><path d=\"m9 9 6 6\" />\n </svg>\n <!-- Lucide triangle-alert -->\n <svg v-else-if=\"toast.type === 'warning'\" class=\"mint-toast__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" :style=\"{ color: accentColors[toast.type] }\">\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" /><path d=\"M12 9v4\" /><path d=\"M12 17h.01\" />\n </svg>\n <!-- Lucide info -->\n <svg v-else class=\"mint-toast__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" :style=\"{ color: accentColors[toast.type] }\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"M12 16v-4\" /><path d=\"M12 8h.01\" />\n </svg>\n <span class=\"mint-toast__message\">{{ toast.message }}</span>\n <span\n class=\"mint-toast__progress\"\n :style=\"{\n backgroundColor: accentColors[toast.type],\n animationDuration: `${toast.duration ?? 3500}ms`,\n }\"\n />\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n</template>\n\n<style>\n@import '../styles/components/toast.css';\n</style>\n","<script setup lang=\"ts\">\n/** Preferred toast container driven by useToast, with auto-dismiss progress bar and click-to-close on each notification. */\nimport { useToast } from '../composables/useToast'\nimport type { Toast } from '../types'\n\nconst { toasts, dismiss } = useToast()\n\nconst accentColors: Record<Toast['type'], string> = {\n success: '#10B981',\n error: '#EF4444',\n warning: '#F59E0B',\n info: '#3B82F6',\n}\n</script>\n\n<template>\n <Teleport to=\"body\">\n <div class=\"mint-toast__container\">\n <TransitionGroup name=\"toast\">\n <div\n v-for=\"toast in toasts\"\n :key=\"toast.id\"\n :class=\"['mint-toast__item', `mint-toast__item--${toast.type}`]\"\n :style=\"{\n borderLeftColor: accentColors[toast.type],\n }\"\n role=\"alert\"\n @click=\"dismiss(toast.id)\"\n >\n <!-- Lucide circle-check -->\n <svg v-if=\"toast.type === 'success'\" class=\"mint-toast__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" :style=\"{ color: accentColors[toast.type] }\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"m9 12 2 2 4-4\" />\n </svg>\n <!-- Lucide circle-x -->\n <svg v-else-if=\"toast.type === 'error'\" class=\"mint-toast__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" :style=\"{ color: accentColors[toast.type] }\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"m15 9-6 6\" /><path d=\"m9 9 6 6\" />\n </svg>\n <!-- Lucide triangle-alert -->\n <svg v-else-if=\"toast.type === 'warning'\" class=\"mint-toast__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" :style=\"{ color: accentColors[toast.type] }\">\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" /><path d=\"M12 9v4\" /><path d=\"M12 17h.01\" />\n </svg>\n <!-- Lucide info -->\n <svg v-else class=\"mint-toast__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" :style=\"{ color: accentColors[toast.type] }\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"M12 16v-4\" /><path d=\"M12 8h.01\" />\n </svg>\n <span class=\"mint-toast__message\">{{ toast.message }}</span>\n <span\n class=\"mint-toast__progress\"\n :style=\"{\n backgroundColor: accentColors[toast.type],\n animationDuration: `${toast.duration ?? 3500}ms`,\n }\"\n />\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n</template>\n\n<style>\n@import '../styles/components/toast.css';\n</style>\n","<script setup lang=\"ts\">\n/** Square icon-only button with required aria-label, loading spinner state, and ghost/primary/danger variants. */\nimport type { ButtonVariant, ButtonSize } from '../types'\n\ninterface Props {\n variant?: ButtonVariant\n size?: ButtonSize\n disabled?: boolean\n loading?: boolean\n label: string // Required for accessibility\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'ghost',\n size: 'md',\n disabled: false,\n loading: false,\n})\n\nconst emit = defineEmits<{\n click: [event: MouseEvent]\n}>()\n\nfunction handleClick(event: MouseEvent) {\n if (!props.disabled && !props.loading) {\n emit('click', event)\n }\n}\n</script>\n\n<template>\n <button\n type=\"button\"\n :disabled=\"disabled || loading\"\n :aria-label=\"label\"\n :title=\"label\"\n :class=\"[\n 'mint-icon-button',\n `mint-icon-button--${variant}`,\n `mint-icon-button--${size}`,\n (disabled || loading) ? 'mint-icon-button--disabled' : '',\n ]\"\n @click=\"handleClick\"\n >\n <svg\n v-if=\"loading\"\n :class=\"['mint-icon-button__spinner', `mint-icon-button__icon--${size}`]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n style=\"opacity: 0.25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"4\"\n />\n <path\n style=\"opacity: 0.75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n <span v-else :class=\"`mint-icon-button__icon--${size}`\">\n <slot />\n </span>\n </button>\n</template>\n\n<style>\n@import '../styles/components/icon-button.css';\n</style>\n","<script setup lang=\"ts\">\n/** Square icon-only button with required aria-label, loading spinner state, and ghost/primary/danger variants. */\nimport type { ButtonVariant, ButtonSize } from '../types'\n\ninterface Props {\n variant?: ButtonVariant\n size?: ButtonSize\n disabled?: boolean\n loading?: boolean\n label: string // Required for accessibility\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'ghost',\n size: 'md',\n disabled: false,\n loading: false,\n})\n\nconst emit = defineEmits<{\n click: [event: MouseEvent]\n}>()\n\nfunction handleClick(event: MouseEvent) {\n if (!props.disabled && !props.loading) {\n emit('click', event)\n }\n}\n</script>\n\n<template>\n <button\n type=\"button\"\n :disabled=\"disabled || loading\"\n :aria-label=\"label\"\n :title=\"label\"\n :class=\"[\n 'mint-icon-button',\n `mint-icon-button--${variant}`,\n `mint-icon-button--${size}`,\n (disabled || loading) ? 'mint-icon-button--disabled' : '',\n ]\"\n @click=\"handleClick\"\n >\n <svg\n v-if=\"loading\"\n :class=\"['mint-icon-button__spinner', `mint-icon-button__icon--${size}`]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n style=\"opacity: 0.25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"4\"\n />\n <path\n style=\"opacity: 0.75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n <span v-else :class=\"`mint-icon-button__icon--${size}`\">\n <slot />\n </span>\n </button>\n</template>\n\n<style>\n@import '../styles/components/icon-button.css';\n</style>\n","<script setup lang=\"ts\">\n/** Icon button that toggles between light and dark mode, swapping sun/moon icons to reflect the active theme. */\nimport { useTheme } from '../composables/useTheme'\nimport IconButton from './IconButton.vue'\n\ninterface Props {\n size?: 'sm' | 'md' | 'lg'\n}\n\nwithDefaults(defineProps<Props>(), {\n size: 'md',\n})\n\nconst { isDark, toggleTheme } = useTheme()\n</script>\n\n<template>\n <IconButton\n class=\"mint-theme-toggle\"\n variant=\"ghost\"\n :size=\"size\"\n :label=\"isDark ? 'Switch to light mode' : 'Switch to dark mode'\"\n @click=\"toggleTheme\"\n >\n <!-- Lucide sun -->\n <svg\n v-if=\"isDark\"\n class=\"mint-theme-toggle__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"4\" /><path d=\"M12 2v2\" /><path d=\"M12 20v2\" /><path d=\"m4.93 4.93 1.41 1.41\" /><path d=\"m17.66 17.66 1.41 1.41\" /><path d=\"M2 12h2\" /><path d=\"M20 12h2\" /><path d=\"m6.34 17.66-1.41 1.41\" /><path d=\"m19.07 4.93-1.41 1.41\" />\n </svg>\n <!-- Lucide moon -->\n <svg\n v-else\n class=\"mint-theme-toggle__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\" />\n </svg>\n </IconButton>\n</template>\n\n<style>\n@import '../styles/components/theme-toggle.css';\n</style>\n","<script setup lang=\"ts\">\n/** Icon button that toggles between light and dark mode, swapping sun/moon icons to reflect the active theme. */\nimport { useTheme } from '../composables/useTheme'\nimport IconButton from './IconButton.vue'\n\ninterface Props {\n size?: 'sm' | 'md' | 'lg'\n}\n\nwithDefaults(defineProps<Props>(), {\n size: 'md',\n})\n\nconst { isDark, toggleTheme } = useTheme()\n</script>\n\n<template>\n <IconButton\n class=\"mint-theme-toggle\"\n variant=\"ghost\"\n :size=\"size\"\n :label=\"isDark ? 'Switch to light mode' : 'Switch to dark mode'\"\n @click=\"toggleTheme\"\n >\n <!-- Lucide sun -->\n <svg\n v-if=\"isDark\"\n class=\"mint-theme-toggle__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"4\" /><path d=\"M12 2v2\" /><path d=\"M12 20v2\" /><path d=\"m4.93 4.93 1.41 1.41\" /><path d=\"m17.66 17.66 1.41 1.41\" /><path d=\"M2 12h2\" /><path d=\"M20 12h2\" /><path d=\"m6.34 17.66-1.41 1.41\" /><path d=\"m19.07 4.93-1.41 1.41\" />\n </svg>\n <!-- Lucide moon -->\n <svg\n v-else\n class=\"mint-theme-toggle__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\" />\n </svg>\n </IconButton>\n</template>\n\n<style>\n@import '../styles/components/theme-toggle.css';\n</style>\n","<script setup lang=\"ts\">\n/** Card whose body collapses with a chevron-rotate animation, with an optional inline toggle switch independent of expand state. */\nimport { ref, computed } from 'vue'\nimport type { SidebarBadgeTone, SidebarToolSectionAction } from '../types'\n\ninterface Props {\n title: string\n subtitle?: string\n badge?: string | number\n badgeTone?: SidebarBadgeTone\n defaultOpen?: boolean\n disabled?: boolean\n dense?: boolean\n icon?: string | string[]\n iconColor?: string\n iconBg?: string\n actions?: SidebarToolSectionAction[]\n showToggle?: boolean\n toggleValue?: boolean\n toggleColor?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n badge: undefined,\n badgeTone: 'cta',\n defaultOpen: false,\n disabled: false,\n dense: false,\n actions: () => [],\n showToggle: false,\n toggleValue: false,\n toggleColor: '',\n})\n\nconst emit = defineEmits<{\n 'update:toggleValue': [value: boolean]\n action: [actionId: string]\n}>()\n\nconst isOpen = ref(props.defaultOpen)\n\nconst toggle = () => {\n if (!props.disabled) {\n isOpen.value = !isOpen.value\n }\n}\n\nconst handleToggleClick = (event: Event) => {\n event.stopPropagation()\n emit('update:toggleValue', !props.toggleValue)\n}\n\nconst headerClasses = computed(() => [\n 'mint-collapsible-card__header',\n props.disabled ? 'mint-collapsible-card__header--disabled' : '',\n])\n\nconst iconBgStyle = computed(() => ({\n backgroundColor: props.iconBg || 'var(--color-primary-soft)',\n}))\n\nconst iconColorStyle = computed(() => ({\n color: props.iconColor || 'var(--color-primary)',\n}))\n\nconst isSvgIcon = computed(() => isSvgIconValue(props.icon))\n\nconst toggleTrackStyle = computed(() => {\n if (!props.toggleValue || !props.toggleColor) return {}\n return {\n backgroundColor: props.toggleColor,\n borderColor: props.toggleColor,\n }\n})\n\nconst badgeClasses = computed(() => [\n 'mint-collapsible-card__badge',\n `mint-collapsible-card__badge--${props.badgeTone}`,\n])\n\nfunction isSvgIconValue(icon?: string | string[]): boolean {\n if (!icon) return false\n return Array.isArray(icon) || icon.startsWith('M') || icon.startsWith('m')\n}\n\nfunction actionClasses(action: SidebarToolSectionAction): string[] {\n return [\n 'mint-collapsible-card__action',\n action.tone ? `mint-collapsible-card__action--${action.tone}` : '',\n action.disabled ? 'mint-collapsible-card__action--disabled' : '',\n ]\n}\n\nfunction actionStyle(action: SidebarToolSectionAction): Record<string, string> {\n return action.iconColor ? { color: action.iconColor } : {}\n}\n\nfunction handleActionClick(action: SidebarToolSectionAction) {\n if (props.disabled || action.disabled) return\n emit('action', action.id)\n}\n</script>\n\n<template>\n <div :class=\"['mint-collapsible-card', dense ? 'mint-collapsible-card--dense' : '']\">\n <div class=\"mint-collapsible-card__header-shell\">\n <button\n type=\"button\"\n :class=\"headerClasses\"\n :disabled=\"disabled\"\n :aria-expanded=\"isOpen\"\n @click=\"toggle\"\n >\n <div class=\"mint-collapsible-card__title-section\">\n <!-- Icon badge -->\n <div v-if=\"icon\" class=\"mint-collapsible-card__icon-badge\" :style=\"iconBgStyle\">\n <svg\n v-if=\"isSvgIcon\"\n class=\"mint-collapsible-card__icon\"\n :style=\"iconColorStyle\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <template v-if=\"Array.isArray(icon)\">\n <path v-for=\"(d, i) in icon\" :key=\"i\" :d=\"d\" />\n </template>\n <path v-else :d=\"icon\" />\n </svg>\n <span v-else class=\"mint-collapsible-card__icon-text\" :style=\"iconColorStyle\">{{ icon }}</span>\n </div>\n\n <div class=\"mint-collapsible-card__titles\">\n <h3 class=\"mint-collapsible-card__title\">{{ title }}</h3>\n <p v-if=\"subtitle\" class=\"mint-collapsible-card__subtitle\">{{ subtitle }}</p>\n </div>\n </div>\n </button>\n\n <div class=\"mint-collapsible-card__actions\">\n <span v-if=\"badge !== undefined\" :class=\"badgeClasses\">\n {{ badge }}\n </span>\n\n <button\n v-for=\"action in actions\"\n :key=\"action.id\"\n type=\"button\"\n :class=\"actionClasses(action)\"\n :style=\"actionStyle(action)\"\n :disabled=\"disabled || action.disabled\"\n :aria-label=\"action.label\"\n :title=\"action.label\"\n @click=\"handleActionClick(action)\"\n >\n <svg\n v-if=\"action.icon && isSvgIconValue(action.icon)\"\n class=\"mint-collapsible-card__action-icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <template v-if=\"Array.isArray(action.icon)\">\n <path v-for=\"(d, i) in action.icon\" :key=\"i\" :d=\"d\" />\n </template>\n <path v-else :d=\"action.icon\" />\n </svg>\n <span v-else-if=\"action.icon\" class=\"mint-collapsible-card__action-text-icon\">\n {{ action.icon }}\n </span>\n </button>\n\n <div\n v-if=\"showToggle\"\n class=\"mint-collapsible-card__toggle\"\n @click=\"handleToggleClick\"\n >\n <div\n role=\"switch\"\n tabindex=\"0\"\n :aria-checked=\"toggleValue\"\n :class=\"[\n 'mint-collapsible-card__toggle-track',\n toggleValue ? 'mint-collapsible-card__toggle-track--on' : '',\n ]\"\n :style=\"toggleTrackStyle\"\n @keydown.enter.prevent=\"handleToggleClick\"\n @keydown.space.prevent=\"handleToggleClick\"\n >\n <span\n :class=\"[\n 'mint-collapsible-card__toggle-knob',\n toggleValue ? 'mint-collapsible-card__toggle-knob--on' : '',\n ]\"\n />\n </div>\n </div>\n\n <button\n type=\"button\"\n class=\"mint-collapsible-card__chevron-button\"\n :disabled=\"disabled\"\n :aria-label=\"isOpen ? 'Collapse section' : 'Expand section'\"\n :aria-expanded=\"isOpen\"\n @click=\"toggle\"\n >\n <svg\n :class=\"[\n 'mint-collapsible-card__chevron',\n isOpen ? 'mint-collapsible-card__chevron--open' : '',\n ]\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n </div>\n </div>\n\n <Transition name=\"collapse\">\n <div v-show=\"isOpen\" class=\"mint-collapsible-card__body\">\n <div class=\"mint-collapsible-card__content\">\n <slot />\n </div>\n </div>\n </Transition>\n </div>\n</template>\n\n<style>\n@import '../styles/components/collapsible-card.css';\n</style>\n","<script setup lang=\"ts\">\n/** Card whose body collapses with a chevron-rotate animation, with an optional inline toggle switch independent of expand state. */\nimport { ref, computed } from 'vue'\nimport type { SidebarBadgeTone, SidebarToolSectionAction } from '../types'\n\ninterface Props {\n title: string\n subtitle?: string\n badge?: string | number\n badgeTone?: SidebarBadgeTone\n defaultOpen?: boolean\n disabled?: boolean\n dense?: boolean\n icon?: string | string[]\n iconColor?: string\n iconBg?: string\n actions?: SidebarToolSectionAction[]\n showToggle?: boolean\n toggleValue?: boolean\n toggleColor?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n badge: undefined,\n badgeTone: 'cta',\n defaultOpen: false,\n disabled: false,\n dense: false,\n actions: () => [],\n showToggle: false,\n toggleValue: false,\n toggleColor: '',\n})\n\nconst emit = defineEmits<{\n 'update:toggleValue': [value: boolean]\n action: [actionId: string]\n}>()\n\nconst isOpen = ref(props.defaultOpen)\n\nconst toggle = () => {\n if (!props.disabled) {\n isOpen.value = !isOpen.value\n }\n}\n\nconst handleToggleClick = (event: Event) => {\n event.stopPropagation()\n emit('update:toggleValue', !props.toggleValue)\n}\n\nconst headerClasses = computed(() => [\n 'mint-collapsible-card__header',\n props.disabled ? 'mint-collapsible-card__header--disabled' : '',\n])\n\nconst iconBgStyle = computed(() => ({\n backgroundColor: props.iconBg || 'var(--color-primary-soft)',\n}))\n\nconst iconColorStyle = computed(() => ({\n color: props.iconColor || 'var(--color-primary)',\n}))\n\nconst isSvgIcon = computed(() => isSvgIconValue(props.icon))\n\nconst toggleTrackStyle = computed(() => {\n if (!props.toggleValue || !props.toggleColor) return {}\n return {\n backgroundColor: props.toggleColor,\n borderColor: props.toggleColor,\n }\n})\n\nconst badgeClasses = computed(() => [\n 'mint-collapsible-card__badge',\n `mint-collapsible-card__badge--${props.badgeTone}`,\n])\n\nfunction isSvgIconValue(icon?: string | string[]): boolean {\n if (!icon) return false\n return Array.isArray(icon) || icon.startsWith('M') || icon.startsWith('m')\n}\n\nfunction actionClasses(action: SidebarToolSectionAction): string[] {\n return [\n 'mint-collapsible-card__action',\n action.tone ? `mint-collapsible-card__action--${action.tone}` : '',\n action.disabled ? 'mint-collapsible-card__action--disabled' : '',\n ]\n}\n\nfunction actionStyle(action: SidebarToolSectionAction): Record<string, string> {\n return action.iconColor ? { color: action.iconColor } : {}\n}\n\nfunction handleActionClick(action: SidebarToolSectionAction) {\n if (props.disabled || action.disabled) return\n emit('action', action.id)\n}\n</script>\n\n<template>\n <div :class=\"['mint-collapsible-card', dense ? 'mint-collapsible-card--dense' : '']\">\n <div class=\"mint-collapsible-card__header-shell\">\n <button\n type=\"button\"\n :class=\"headerClasses\"\n :disabled=\"disabled\"\n :aria-expanded=\"isOpen\"\n @click=\"toggle\"\n >\n <div class=\"mint-collapsible-card__title-section\">\n <!-- Icon badge -->\n <div v-if=\"icon\" class=\"mint-collapsible-card__icon-badge\" :style=\"iconBgStyle\">\n <svg\n v-if=\"isSvgIcon\"\n class=\"mint-collapsible-card__icon\"\n :style=\"iconColorStyle\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <template v-if=\"Array.isArray(icon)\">\n <path v-for=\"(d, i) in icon\" :key=\"i\" :d=\"d\" />\n </template>\n <path v-else :d=\"icon\" />\n </svg>\n <span v-else class=\"mint-collapsible-card__icon-text\" :style=\"iconColorStyle\">{{ icon }}</span>\n </div>\n\n <div class=\"mint-collapsible-card__titles\">\n <h3 class=\"mint-collapsible-card__title\">{{ title }}</h3>\n <p v-if=\"subtitle\" class=\"mint-collapsible-card__subtitle\">{{ subtitle }}</p>\n </div>\n </div>\n </button>\n\n <div class=\"mint-collapsible-card__actions\">\n <span v-if=\"badge !== undefined\" :class=\"badgeClasses\">\n {{ badge }}\n </span>\n\n <button\n v-for=\"action in actions\"\n :key=\"action.id\"\n type=\"button\"\n :class=\"actionClasses(action)\"\n :style=\"actionStyle(action)\"\n :disabled=\"disabled || action.disabled\"\n :aria-label=\"action.label\"\n :title=\"action.label\"\n @click=\"handleActionClick(action)\"\n >\n <svg\n v-if=\"action.icon && isSvgIconValue(action.icon)\"\n class=\"mint-collapsible-card__action-icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <template v-if=\"Array.isArray(action.icon)\">\n <path v-for=\"(d, i) in action.icon\" :key=\"i\" :d=\"d\" />\n </template>\n <path v-else :d=\"action.icon\" />\n </svg>\n <span v-else-if=\"action.icon\" class=\"mint-collapsible-card__action-text-icon\">\n {{ action.icon }}\n </span>\n </button>\n\n <div\n v-if=\"showToggle\"\n class=\"mint-collapsible-card__toggle\"\n @click=\"handleToggleClick\"\n >\n <div\n role=\"switch\"\n tabindex=\"0\"\n :aria-checked=\"toggleValue\"\n :class=\"[\n 'mint-collapsible-card__toggle-track',\n toggleValue ? 'mint-collapsible-card__toggle-track--on' : '',\n ]\"\n :style=\"toggleTrackStyle\"\n @keydown.enter.prevent=\"handleToggleClick\"\n @keydown.space.prevent=\"handleToggleClick\"\n >\n <span\n :class=\"[\n 'mint-collapsible-card__toggle-knob',\n toggleValue ? 'mint-collapsible-card__toggle-knob--on' : '',\n ]\"\n />\n </div>\n </div>\n\n <button\n type=\"button\"\n class=\"mint-collapsible-card__chevron-button\"\n :disabled=\"disabled\"\n :aria-label=\"isOpen ? 'Collapse section' : 'Expand section'\"\n :aria-expanded=\"isOpen\"\n @click=\"toggle\"\n >\n <svg\n :class=\"[\n 'mint-collapsible-card__chevron',\n isOpen ? 'mint-collapsible-card__chevron--open' : '',\n ]\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n </div>\n </div>\n\n <Transition name=\"collapse\">\n <div v-show=\"isOpen\" class=\"mint-collapsible-card__body\">\n <div class=\"mint-collapsible-card__content\">\n <slot />\n </div>\n </div>\n </Transition>\n </div>\n</template>\n\n<style>\n@import '../styles/components/collapsible-card.css';\n</style>\n","export type PluginIconFormat = 'path' | 'data-url' | 'https-url' | 'structured' | 'fallback'\n\nexport interface StructuredPluginIconStop {\n offset: string\n color: string\n opacity?: number\n}\n\nexport interface StructuredPluginIconBackground {\n fill: string\n radius: number\n}\n\nexport interface StructuredPluginIconPath {\n d: string\n fill?: string\n stroke?: string\n strokeWidth?: number\n opacity?: number\n}\n\nexport interface StructuredPluginIcon {\n type: 'mint-icon'\n viewBox: string\n background?: StructuredPluginIconBackground\n gradient?: {\n type: 'linear'\n angle: number\n stops: StructuredPluginIconStop[]\n }\n paths: StructuredPluginIconPath[]\n}\n\nexport interface DetectedPluginIcon {\n format: PluginIconFormat\n value: string | StructuredPluginIcon\n}\n\n// MINT canonical plugin placeholder — page-with-corner-fold glyph.\nexport const PLUGIN_ICON_FALLBACK_PATH = 'M14 7v4a1 1 0 0 0 1 1h4M5 3h9l5 5v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z'\n\nconst PATH_REGEX = /^[Mm][\\s,\\d\\-.]/\nconst RASTER_DATA_URL_REGEX = /^data:image\\/(png|jpeg|jpg|gif|webp);/\nconst VIEW_BOX_REGEX = /^-?\\d+(?:\\.\\d+)?\\s+-?\\d+(?:\\.\\d+)?\\s+\\d+(?:\\.\\d+)?\\s+\\d+(?:\\.\\d+)?$/\nconst COLOR_REGEX = /^(#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})|currentColor|white|black|none)$/i\nconst MAX_STRUCTURED_ICON_LENGTH = 8000\nconst MAX_PATHS = 8\nconst MAX_STOPS = 8\n\nexport function normalizePluginIconSource(icon?: string): string {\n return (icon ?? '').replace(/^[\\s]+/, '')\n}\n\nexport function detectPluginIcon(icon?: string): DetectedPluginIcon {\n const raw = normalizePluginIconSource(icon)\n if (!raw) return { format: 'fallback', value: PLUGIN_ICON_FALLBACK_PATH }\n const structured = parseStructuredPluginIcon(raw)\n if (structured) return { format: 'structured', value: structured }\n if (PATH_REGEX.test(raw)) return { format: 'path', value: raw }\n if (RASTER_DATA_URL_REGEX.test(raw)) return { format: 'data-url', value: raw }\n if (raw.startsWith('https://')) return { format: 'https-url', value: raw }\n return { format: 'fallback', value: PLUGIN_ICON_FALLBACK_PATH }\n}\n\nexport function isPluginIconFormat(icon?: string): boolean {\n const raw = normalizePluginIconSource(icon)\n return !!raw && detectPluginIcon(raw).format !== 'fallback'\n}\n\nfunction parseStructuredPluginIcon(raw: string): StructuredPluginIcon | undefined {\n if (!raw.startsWith('{') || raw.length > MAX_STRUCTURED_ICON_LENGTH) return undefined\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return undefined\n }\n\n if (!isRecord(parsed) || parsed.type !== 'mint-icon') return undefined\n\n const viewBox = typeof parsed.viewBox === 'string' && VIEW_BOX_REGEX.test(parsed.viewBox)\n ? parsed.viewBox\n : '0 0 24 24'\n\n const paths = parseStructuredPaths(parsed.paths)\n if (!paths.length) return undefined\n\n const gradient = parseStructuredGradient(parsed.gradient)\n const background = parseStructuredBackground(parsed.background, gradient)\n\n return {\n type: 'mint-icon',\n viewBox,\n ...(background ? { background } : {}),\n ...(gradient ? { gradient } : {}),\n paths,\n }\n}\n\nfunction parseStructuredPaths(value: unknown): StructuredPluginIconPath[] {\n if (!Array.isArray(value)) return []\n\n return value.slice(0, MAX_PATHS).flatMap((item) => {\n if (!isRecord(item) || typeof item.d !== 'string' || !item.d.trim()) return []\n const path: StructuredPluginIconPath = { d: item.d.slice(0, 2000) }\n if (isSafeColor(item.fill)) path.fill = item.fill\n if (isSafeColor(item.stroke)) path.stroke = item.stroke\n if (isFiniteNumber(item.strokeWidth, 0, 24)) path.strokeWidth = item.strokeWidth\n if (isFiniteNumber(item.opacity, 0, 1)) path.opacity = item.opacity\n return [path]\n })\n}\n\nfunction parseStructuredGradient(value: unknown): StructuredPluginIcon['gradient'] | undefined {\n if (!isRecord(value) || value.type !== 'linear') return undefined\n\n const stops = parseStructuredStops(value.stops)\n if (stops.length < 2 && isSafeColor(value.from) && isSafeColor(value.to)) {\n stops.push(\n { offset: '0%', color: value.from },\n { offset: '100%', color: value.to },\n )\n }\n if (stops.length < 2) return undefined\n\n return {\n type: 'linear',\n angle: isFiniteNumber(value.angle, 0, 360) ? value.angle : 135,\n stops,\n }\n}\n\nfunction parseStructuredStops(value: unknown): StructuredPluginIconStop[] {\n if (!Array.isArray(value)) return []\n\n return value.slice(0, MAX_STOPS).flatMap((item) => {\n if (!isRecord(item) || !isSafeColor(item.color)) return []\n\n const offset = parseOffset(item.offset)\n if (!offset) return []\n\n return [{\n offset,\n color: item.color,\n ...(isFiniteNumber(item.opacity, 0, 1) ? { opacity: item.opacity } : {}),\n }]\n })\n}\n\nfunction parseStructuredBackground(\n value: unknown,\n gradient: StructuredPluginIcon['gradient'] | undefined,\n): StructuredPluginIconBackground | undefined {\n if (!isRecord(value)) return undefined\n\n const fill = value.fill === 'gradient' && gradient\n ? 'gradient'\n : isSafeColor(value.fill)\n ? value.fill\n : undefined\n if (!fill) return undefined\n\n return {\n fill,\n radius: isFiniteNumber(value.radius, 0, 12) ? value.radius : 5,\n }\n}\n\nfunction parseOffset(value: unknown): string | undefined {\n if (isFiniteNumber(value, 0, 1)) return String(value)\n if (typeof value !== 'string') return undefined\n if (/^(?:100|(?:[1-9]?\\d)(?:\\.\\d+)?)%$/.test(value)) return value\n return undefined\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction isSafeColor(value: unknown): value is string {\n return typeof value === 'string' && COLOR_REGEX.test(value)\n}\n\nfunction isFiniteNumber(value: unknown, min: number, max: number): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value >= min && value <= max\n}\n","<script setup lang=\"ts\">\n/** Private routable action primitive for SDK nav and menu components. */\ndefineOptions({ inheritAttrs: false })\n\ninterface Props {\n href?: string\n to?: string | Record<string, unknown>\n disabled?: boolean\n type?: 'button' | 'submit' | 'reset'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n type: 'button',\n})\n\nconst emit = defineEmits<{\n click: [event: MouseEvent]\n}>()\n\ntype RouterNavigate = (event?: MouseEvent) => unknown\n\nfunction blockDisabledEvent(event: MouseEvent) {\n event.preventDefault()\n event.stopImmediatePropagation()\n}\n\nfunction handleClick(event: MouseEvent) {\n if (props.disabled) {\n blockDisabledEvent(event)\n return\n }\n emit('click', event)\n}\n\nfunction handleRouterClick(event: MouseEvent, navigate: RouterNavigate) {\n if (props.disabled) {\n blockDisabledEvent(event)\n return\n }\n emit('click', event)\n void navigate(event)\n}\n</script>\n\n<template>\n <a\n v-if=\"href\"\n v-bind=\"$attrs\"\n :href=\"disabled ? undefined : href\"\n :aria-disabled=\"disabled || undefined\"\n :tabindex=\"disabled ? -1 : undefined\"\n @click=\"handleClick\"\n >\n <slot />\n </a>\n <router-link\n v-else-if=\"to\"\n :to=\"to\"\n custom\n v-slot=\"{ href: routerHref, navigate }\"\n >\n <a\n v-bind=\"$attrs\"\n :href=\"disabled ? undefined : routerHref\"\n :aria-disabled=\"disabled || undefined\"\n :tabindex=\"disabled ? -1 : undefined\"\n @click=\"handleRouterClick($event, navigate)\"\n >\n <slot />\n </a>\n </router-link>\n <button\n v-else\n v-bind=\"$attrs\"\n :type=\"type\"\n :disabled=\"disabled\"\n @click=\"handleClick\"\n >\n <slot />\n </button>\n</template>\n","<script setup lang=\"ts\">\n/** Private routable action primitive for SDK nav and menu components. */\ndefineOptions({ inheritAttrs: false })\n\ninterface Props {\n href?: string\n to?: string | Record<string, unknown>\n disabled?: boolean\n type?: 'button' | 'submit' | 'reset'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n type: 'button',\n})\n\nconst emit = defineEmits<{\n click: [event: MouseEvent]\n}>()\n\ntype RouterNavigate = (event?: MouseEvent) => unknown\n\nfunction blockDisabledEvent(event: MouseEvent) {\n event.preventDefault()\n event.stopImmediatePropagation()\n}\n\nfunction handleClick(event: MouseEvent) {\n if (props.disabled) {\n blockDisabledEvent(event)\n return\n }\n emit('click', event)\n}\n\nfunction handleRouterClick(event: MouseEvent, navigate: RouterNavigate) {\n if (props.disabled) {\n blockDisabledEvent(event)\n return\n }\n emit('click', event)\n void navigate(event)\n}\n</script>\n\n<template>\n <a\n v-if=\"href\"\n v-bind=\"$attrs\"\n :href=\"disabled ? undefined : href\"\n :aria-disabled=\"disabled || undefined\"\n :tabindex=\"disabled ? -1 : undefined\"\n @click=\"handleClick\"\n >\n <slot />\n </a>\n <router-link\n v-else-if=\"to\"\n :to=\"to\"\n custom\n v-slot=\"{ href: routerHref, navigate }\"\n >\n <a\n v-bind=\"$attrs\"\n :href=\"disabled ? undefined : routerHref\"\n :aria-disabled=\"disabled || undefined\"\n :tabindex=\"disabled ? -1 : undefined\"\n @click=\"handleRouterClick($event, navigate)\"\n >\n <slot />\n </a>\n </router-link>\n <button\n v-else\n v-bind=\"$attrs\"\n :type=\"type\"\n :disabled=\"disabled\"\n @click=\"handleClick\"\n >\n <slot />\n </button>\n</template>\n","<script setup lang=\"ts\">\n/** Renders a plugin's icon as a sized chip. Auto-detects format:\n * - Structured MINT icon JSON ({\"type\":\"mint-icon\",...}) → controlled multi-color SVG\n * - SVG path data (e.g. \"M13 10V3...\") → inline <svg><path>\n * - Raster data URL (data:image/png|jpeg|jpg|gif|webp;...) → <img>\n * - https:// URL → <img> with no-referrer + lazy loading\n * - Anything else → MINT canonical plugin placeholder path\n *\n * http:// URLs and data:image/svg+xml URLs are deliberately rejected\n * (mixed-content + XSS, see spec 2026-05-04-plugin-icon-component-design.md). */\nimport { computed } from 'vue'\nimport { detectPluginIcon, type DetectedPluginIcon, type StructuredPluginIcon } from '../utils/pluginIcon'\n\ndefineOptions({ name: 'PluginIcon' })\n\ninterface Props {\n icon?: string\n size?: 'sm' | 'md' | 'lg'\n variant?: 'solid' | 'tinted'\n tone?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 'md',\n variant: 'solid',\n})\n\nconst detected = computed<DetectedPluginIcon>(() => detectPluginIcon(props.icon))\nconst structuredIcon = computed<StructuredPluginIcon | undefined>(() =>\n detected.value.format === 'structured' ? detected.value.value as StructuredPluginIcon : undefined,\n)\nconst iconValue = computed<string>(() =>\n typeof detected.value.value === 'string' ? detected.value.value : '',\n)\n\nconst gradientId = computed(() => {\n const source = props.icon ?? ''\n let hash = 0\n for (let index = 0; index < source.length; index += 1) {\n hash = ((hash << 5) - hash + source.charCodeAt(index)) | 0\n }\n return `mint-plugin-icon-gradient-${Math.abs(hash)}`\n})\n\nconst gradientCoords = computed(() => {\n const angle = structuredIcon.value?.gradient?.angle ?? 135\n const radians = (angle - 90) * Math.PI / 180\n const x = Math.cos(radians) * 50\n const y = Math.sin(radians) * 50\n\n return {\n x1: `${50 - x}%`,\n y1: `${50 - y}%`,\n x2: `${50 + x}%`,\n y2: `${50 + y}%`,\n }\n})\n\nconst structuredViewBox = computed(() => {\n const parts = structuredIcon.value?.viewBox.split(/\\s+/).map(Number) ?? [0, 0, 24, 24]\n return {\n x: parts[0],\n y: parts[1],\n width: parts[2],\n height: parts[3],\n }\n})\n\nconst backgroundFill = computed(() =>\n structuredIcon.value?.background?.fill === 'gradient'\n ? `url(#${gradientId.value})`\n : structuredIcon.value?.background?.fill,\n)\n\nconst rootClasses = computed(() => [\n 'mint-plugin-icon',\n `mint-plugin-icon--${props.size}`,\n `mint-plugin-icon--${props.variant}`,\n])\n\nconst rootStyle = computed(() =>\n props.tone ? { '--mint-plugin-icon-tone': props.tone } : undefined,\n)\n</script>\n\n<template>\n <span :class=\"rootClasses\" :style=\"rootStyle\">\n <svg\n v-if=\"structuredIcon\"\n class=\"mint-plugin-icon__svg mint-plugin-icon__svg--structured\"\n :viewBox=\"structuredIcon.viewBox\"\n aria-hidden=\"true\"\n >\n <defs v-if=\"structuredIcon.gradient\">\n <linearGradient\n :id=\"gradientId\"\n :x1=\"gradientCoords.x1\"\n :y1=\"gradientCoords.y1\"\n :x2=\"gradientCoords.x2\"\n :y2=\"gradientCoords.y2\"\n >\n <stop\n v-for=\"stop in structuredIcon.gradient.stops\"\n :key=\"`${stop.offset}-${stop.color}`\"\n :offset=\"stop.offset\"\n :stop-color=\"stop.color\"\n :stop-opacity=\"stop.opacity\"\n />\n </linearGradient>\n </defs>\n <rect\n v-if=\"structuredIcon.background\"\n :x=\"structuredViewBox.x\"\n :y=\"structuredViewBox.y\"\n :width=\"structuredViewBox.width\"\n :height=\"structuredViewBox.height\"\n :rx=\"structuredIcon.background.radius\"\n :fill=\"backgroundFill\"\n />\n <path\n v-for=\"(path, index) in structuredIcon.paths\"\n :key=\"index\"\n :d=\"path.d\"\n :fill=\"path.fill ?? 'none'\"\n :stroke=\"path.stroke\"\n :stroke-width=\"path.strokeWidth\"\n :opacity=\"path.opacity\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n <svg\n v-else-if=\"detected.format === 'path' || detected.format === 'fallback'\"\n class=\"mint-plugin-icon__svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path :d=\"iconValue\" />\n </svg>\n <img\n v-else\n class=\"mint-plugin-icon__img\"\n :src=\"iconValue\"\n alt=\"\"\n referrerpolicy=\"no-referrer\"\n loading=\"lazy\"\n />\n </span>\n</template>\n\n<style>\n@import '../styles/components/plugin-icon.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a plugin's icon as a sized chip. Auto-detects format:\n * - Structured MINT icon JSON ({\"type\":\"mint-icon\",...}) → controlled multi-color SVG\n * - SVG path data (e.g. \"M13 10V3...\") → inline <svg><path>\n * - Raster data URL (data:image/png|jpeg|jpg|gif|webp;...) → <img>\n * - https:// URL → <img> with no-referrer + lazy loading\n * - Anything else → MINT canonical plugin placeholder path\n *\n * http:// URLs and data:image/svg+xml URLs are deliberately rejected\n * (mixed-content + XSS, see spec 2026-05-04-plugin-icon-component-design.md). */\nimport { computed } from 'vue'\nimport { detectPluginIcon, type DetectedPluginIcon, type StructuredPluginIcon } from '../utils/pluginIcon'\n\ndefineOptions({ name: 'PluginIcon' })\n\ninterface Props {\n icon?: string\n size?: 'sm' | 'md' | 'lg'\n variant?: 'solid' | 'tinted'\n tone?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 'md',\n variant: 'solid',\n})\n\nconst detected = computed<DetectedPluginIcon>(() => detectPluginIcon(props.icon))\nconst structuredIcon = computed<StructuredPluginIcon | undefined>(() =>\n detected.value.format === 'structured' ? detected.value.value as StructuredPluginIcon : undefined,\n)\nconst iconValue = computed<string>(() =>\n typeof detected.value.value === 'string' ? detected.value.value : '',\n)\n\nconst gradientId = computed(() => {\n const source = props.icon ?? ''\n let hash = 0\n for (let index = 0; index < source.length; index += 1) {\n hash = ((hash << 5) - hash + source.charCodeAt(index)) | 0\n }\n return `mint-plugin-icon-gradient-${Math.abs(hash)}`\n})\n\nconst gradientCoords = computed(() => {\n const angle = structuredIcon.value?.gradient?.angle ?? 135\n const radians = (angle - 90) * Math.PI / 180\n const x = Math.cos(radians) * 50\n const y = Math.sin(radians) * 50\n\n return {\n x1: `${50 - x}%`,\n y1: `${50 - y}%`,\n x2: `${50 + x}%`,\n y2: `${50 + y}%`,\n }\n})\n\nconst structuredViewBox = computed(() => {\n const parts = structuredIcon.value?.viewBox.split(/\\s+/).map(Number) ?? [0, 0, 24, 24]\n return {\n x: parts[0],\n y: parts[1],\n width: parts[2],\n height: parts[3],\n }\n})\n\nconst backgroundFill = computed(() =>\n structuredIcon.value?.background?.fill === 'gradient'\n ? `url(#${gradientId.value})`\n : structuredIcon.value?.background?.fill,\n)\n\nconst rootClasses = computed(() => [\n 'mint-plugin-icon',\n `mint-plugin-icon--${props.size}`,\n `mint-plugin-icon--${props.variant}`,\n])\n\nconst rootStyle = computed(() =>\n props.tone ? { '--mint-plugin-icon-tone': props.tone } : undefined,\n)\n</script>\n\n<template>\n <span :class=\"rootClasses\" :style=\"rootStyle\">\n <svg\n v-if=\"structuredIcon\"\n class=\"mint-plugin-icon__svg mint-plugin-icon__svg--structured\"\n :viewBox=\"structuredIcon.viewBox\"\n aria-hidden=\"true\"\n >\n <defs v-if=\"structuredIcon.gradient\">\n <linearGradient\n :id=\"gradientId\"\n :x1=\"gradientCoords.x1\"\n :y1=\"gradientCoords.y1\"\n :x2=\"gradientCoords.x2\"\n :y2=\"gradientCoords.y2\"\n >\n <stop\n v-for=\"stop in structuredIcon.gradient.stops\"\n :key=\"`${stop.offset}-${stop.color}`\"\n :offset=\"stop.offset\"\n :stop-color=\"stop.color\"\n :stop-opacity=\"stop.opacity\"\n />\n </linearGradient>\n </defs>\n <rect\n v-if=\"structuredIcon.background\"\n :x=\"structuredViewBox.x\"\n :y=\"structuredViewBox.y\"\n :width=\"structuredViewBox.width\"\n :height=\"structuredViewBox.height\"\n :rx=\"structuredIcon.background.radius\"\n :fill=\"backgroundFill\"\n />\n <path\n v-for=\"(path, index) in structuredIcon.paths\"\n :key=\"index\"\n :d=\"path.d\"\n :fill=\"path.fill ?? 'none'\"\n :stroke=\"path.stroke\"\n :stroke-width=\"path.strokeWidth\"\n :opacity=\"path.opacity\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n <svg\n v-else-if=\"detected.format === 'path' || detected.format === 'fallback'\"\n class=\"mint-plugin-icon__svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path :d=\"iconValue\" />\n </svg>\n <img\n v-else\n class=\"mint-plugin-icon__img\"\n :src=\"iconValue\"\n alt=\"\"\n referrerpolicy=\"no-referrer\"\n loading=\"lazy\"\n />\n </span>\n</template>\n\n<style>\n@import '../styles/components/plugin-icon.css';\n</style>\n","<script setup lang=\"ts\">\n/** Dropdown trigger that switches between named pages, closing on outside click or Escape. */\nimport { computed } from 'vue'\nimport { useDropdownState } from '../../composables/useDropdownState'\nimport type { PageSelectorItem, PageSelectorItemInput } from '../../types/components'\nimport { normalizeItemInput } from '../../utils/items'\nimport { isPluginIconFormat } from '../../utils/pluginIcon'\nimport ActionItemInternal from './ActionItemInternal.vue'\nimport PluginIcon from '../PluginIcon.vue'\n\ninterface Props {\n pages: PageSelectorItemInput[]\n currentPageId?: string\n placeholder?: string\n minWidth?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: 'Go to…',\n minWidth: '11.25rem',\n})\n\nconst emit = defineEmits<{\n select: [page: PageSelectorItem]\n}>()\n\nconst { isOpen, rootRef, close, toggle } = useDropdownState()\nconst normalizedPages = computed<PageSelectorItem[]>(() => props.pages.map(normalizeItemInput))\n\nconst current = computed<PageSelectorItem | undefined>(() =>\n normalizedPages.value.find((p) => p.id === props.currentPageId) ?? normalizedPages.value[0],\n)\n\nfunction handleSelect(page: PageSelectorItem) {\n if (page.disabled) return\n if (page.href || page.to) {\n close()\n return\n }\n emit('select', page)\n close()\n}\n</script>\n\n<template>\n <div ref=\"rootRef\" class=\"mint-page-selector\" :style=\"{ minWidth }\">\n <button\n type=\"button\"\n class=\"mint-page-selector__trigger\"\n :class=\"{ 'mint-page-selector__trigger--open': isOpen }\"\n :aria-expanded=\"isOpen\"\n aria-haspopup=\"menu\"\n @click.stop=\"toggle\"\n >\n <span\n v-if=\"current\"\n class=\"mint-page-selector__icon\"\n :class=\"{ 'mint-page-selector__icon--metadata': isPluginIconFormat(current.icon) }\"\n >\n <slot name=\"icon\" :page=\"current\">\n <PluginIcon\n v-if=\"isPluginIconFormat(current.icon)\"\n class=\"mint-page-selector__trigger-metadata-icon\"\n :icon=\"current.icon\"\n size=\"sm\"\n variant=\"tinted\"\n />\n <span v-else class=\"mint-page-selector__icon-fallback\">{{ current.label.charAt(0) }}</span>\n </slot>\n </span>\n <span class=\"mint-page-selector__label\">{{ current?.label ?? placeholder }}</span>\n <span class=\"mint-page-selector__chevron-wrap\" aria-hidden=\"true\">\n <svg\n class=\"mint-page-selector__chevron\"\n :class=\"{ 'mint-page-selector__chevron--open': isOpen }\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </span>\n </button>\n\n <div v-if=\"isOpen\" class=\"mint-page-selector__menu\" role=\"menu\">\n <div class=\"mint-page-selector__menu-title\">Go to</div>\n <ActionItemInternal\n v-for=\"page in normalizedPages\"\n :key=\"page.id\"\n :href=\"page.href\"\n :to=\"page.to\"\n :disabled=\"page.disabled\"\n :class=\"[\n 'mint-page-selector__item',\n { 'mint-page-selector__item--active': page.id === current?.id },\n { 'mint-page-selector__item--disabled': page.disabled },\n ]\"\n role=\"menuitem\"\n @click=\"handleSelect(page)\"\n >\n <span\n class=\"mint-page-selector__item-icon\"\n :class=\"{ 'mint-page-selector__item-icon--metadata': isPluginIconFormat(page.icon) }\"\n >\n <slot name=\"item-icon\" :page=\"page\">\n <PluginIcon\n v-if=\"isPluginIconFormat(page.icon)\"\n class=\"mint-page-selector__metadata-icon\"\n :icon=\"page.icon\"\n size=\"sm\"\n variant=\"tinted\"\n />\n <span v-else class=\"mint-page-selector__icon-fallback\">{{ page.label.charAt(0) }}</span>\n </slot>\n </span>\n <span class=\"mint-page-selector__item-label\">{{ page.label }}</span>\n <span v-if=\"page.hint\" class=\"mint-page-selector__item-hint\">{{ page.hint }}</span>\n </ActionItemInternal>\n </div>\n </div>\n</template>\n\n<style>\n@import '../../styles/components/app-page-selector.css';\n</style>\n","<script setup lang=\"ts\">\n/** Dropdown trigger that switches between named pages, closing on outside click or Escape. */\nimport { computed } from 'vue'\nimport { useDropdownState } from '../../composables/useDropdownState'\nimport type { PageSelectorItem, PageSelectorItemInput } from '../../types/components'\nimport { normalizeItemInput } from '../../utils/items'\nimport { isPluginIconFormat } from '../../utils/pluginIcon'\nimport ActionItemInternal from './ActionItemInternal.vue'\nimport PluginIcon from '../PluginIcon.vue'\n\ninterface Props {\n pages: PageSelectorItemInput[]\n currentPageId?: string\n placeholder?: string\n minWidth?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: 'Go to…',\n minWidth: '11.25rem',\n})\n\nconst emit = defineEmits<{\n select: [page: PageSelectorItem]\n}>()\n\nconst { isOpen, rootRef, close, toggle } = useDropdownState()\nconst normalizedPages = computed<PageSelectorItem[]>(() => props.pages.map(normalizeItemInput))\n\nconst current = computed<PageSelectorItem | undefined>(() =>\n normalizedPages.value.find((p) => p.id === props.currentPageId) ?? normalizedPages.value[0],\n)\n\nfunction handleSelect(page: PageSelectorItem) {\n if (page.disabled) return\n if (page.href || page.to) {\n close()\n return\n }\n emit('select', page)\n close()\n}\n</script>\n\n<template>\n <div ref=\"rootRef\" class=\"mint-page-selector\" :style=\"{ minWidth }\">\n <button\n type=\"button\"\n class=\"mint-page-selector__trigger\"\n :class=\"{ 'mint-page-selector__trigger--open': isOpen }\"\n :aria-expanded=\"isOpen\"\n aria-haspopup=\"menu\"\n @click.stop=\"toggle\"\n >\n <span\n v-if=\"current\"\n class=\"mint-page-selector__icon\"\n :class=\"{ 'mint-page-selector__icon--metadata': isPluginIconFormat(current.icon) }\"\n >\n <slot name=\"icon\" :page=\"current\">\n <PluginIcon\n v-if=\"isPluginIconFormat(current.icon)\"\n class=\"mint-page-selector__trigger-metadata-icon\"\n :icon=\"current.icon\"\n size=\"sm\"\n variant=\"tinted\"\n />\n <span v-else class=\"mint-page-selector__icon-fallback\">{{ current.label.charAt(0) }}</span>\n </slot>\n </span>\n <span class=\"mint-page-selector__label\">{{ current?.label ?? placeholder }}</span>\n <span class=\"mint-page-selector__chevron-wrap\" aria-hidden=\"true\">\n <svg\n class=\"mint-page-selector__chevron\"\n :class=\"{ 'mint-page-selector__chevron--open': isOpen }\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </span>\n </button>\n\n <div v-if=\"isOpen\" class=\"mint-page-selector__menu\" role=\"menu\">\n <div class=\"mint-page-selector__menu-title\">Go to</div>\n <ActionItemInternal\n v-for=\"page in normalizedPages\"\n :key=\"page.id\"\n :href=\"page.href\"\n :to=\"page.to\"\n :disabled=\"page.disabled\"\n :class=\"[\n 'mint-page-selector__item',\n { 'mint-page-selector__item--active': page.id === current?.id },\n { 'mint-page-selector__item--disabled': page.disabled },\n ]\"\n role=\"menuitem\"\n @click=\"handleSelect(page)\"\n >\n <span\n class=\"mint-page-selector__item-icon\"\n :class=\"{ 'mint-page-selector__item-icon--metadata': isPluginIconFormat(page.icon) }\"\n >\n <slot name=\"item-icon\" :page=\"page\">\n <PluginIcon\n v-if=\"isPluginIconFormat(page.icon)\"\n class=\"mint-page-selector__metadata-icon\"\n :icon=\"page.icon\"\n size=\"sm\"\n variant=\"tinted\"\n />\n <span v-else class=\"mint-page-selector__icon-fallback\">{{ page.label.charAt(0) }}</span>\n </slot>\n </span>\n <span class=\"mint-page-selector__item-label\">{{ page.label }}</span>\n <span v-if=\"page.hint\" class=\"mint-page-selector__item-hint\">{{ page.hint }}</span>\n </ActionItemInternal>\n </div>\n </div>\n</template>\n\n<style>\n@import '../../styles/components/app-page-selector.css';\n</style>\n","<script setup lang=\"ts\">\n/** Horizontal pill-style navigation bar that emits select events and supports href, router-link, button, or dropdown items. */\nimport { computed, ref } from 'vue'\nimport type { PillNavItem, PillNavItemInput, PillNavOption, PillNavOptionInput } from '../../types/components'\nimport { normalizeItemInput } from '../../utils/items'\nimport { useEventListener } from '../../composables/useEventListener'\nimport ActionItemInternal from './ActionItemInternal.vue'\n\ninterface Props {\n items: PillNavItemInput[]\n currentItemId?: string\n}\n\nconst props = defineProps<Props>()\n\nconst emit = defineEmits<{\n select: [item: PillNavItem]\n 'option-select': [option: PillNavOption, item: PillNavItem]\n}>()\n\nconst openItemId = ref<string | null>(null)\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map())\n\nconst normalizedItems = computed<PillNavItem[]>(() => props.items.map(normalizePillNavItem))\n\nfunction handleClick(item: PillNavItem) {\n if (item.disabled) return\n if (item.children?.length) {\n toggleDropdown(item.id)\n return\n }\n if (item.href || item.to) return\n emit('select', item)\n}\n\nfunction handleOptionClick(option: PillNavOption, item: PillNavItem) {\n if (option.disabled) return\n if (!option.href && !option.to) emit('option-select', option, item)\n openItemId.value = null\n}\n\nfunction normalizePillNavItem(item: PillNavItemInput): PillNavItem {\n const normalized = normalizeItemInput<Omit<PillNavItem, 'children'> & { children?: PillNavOptionInput[] }>(item)\n return {\n ...normalized,\n children: normalized.children?.map(normalizeItemInput),\n }\n}\n\nfunction toggleDropdown(itemId: string) {\n openItemId.value = openItemId.value === itemId ? null : itemId\n}\n\nfunction setItemRef(el: HTMLElement | null, itemId: string) {\n if (el) {\n itemRefs.value.set(itemId, el)\n } else {\n itemRefs.value.delete(itemId)\n }\n}\n\nfunction handleClickOutside(event: MouseEvent) {\n const target = event.target as Node\n const clickedInside = Array.from(itemRefs.value.values()).some(el => el.contains(target))\n if (!clickedInside) openItemId.value = null\n}\n\nconst hasActiveChild = (item: PillNavItem) =>\n item.children?.some(child => child.id === props.currentItemId) ?? false\n\nfunction isSvgIcon(icon: PillNavItem['icon']): icon is string | string[] {\n if (!icon) return false\n return Array.isArray(icon) || icon.startsWith('M') || icon.startsWith('m')\n}\n\nuseEventListener(() => document, 'click', handleClickOutside)\n</script>\n\n<template>\n <nav class=\"mint-pill-nav\" aria-label=\"Primary\">\n <div\n v-for=\"item in normalizedItems\"\n :key=\"item.id\"\n :ref=\"(el) => setItemRef(el as HTMLElement | null, item.id)\"\n class=\"mint-pill-nav__item-wrap\"\n >\n <button\n v-if=\"item.children?.length\"\n type=\"button\"\n :class=\"[\n 'mint-pill-nav__item',\n { 'mint-pill-nav__item--active': item.id === currentItemId || hasActiveChild(item) },\n { 'mint-pill-nav__item--disabled': item.disabled },\n ]\"\n :disabled=\"item.disabled\"\n :aria-expanded=\"openItemId === item.id\"\n aria-haspopup=\"menu\"\n @click.stop=\"handleClick(item)\"\n >\n <svg\n v-if=\"isSvgIcon(item.icon)\"\n class=\"mint-pill-nav__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <template v-if=\"Array.isArray(item.icon)\">\n <path v-for=\"(d, index) in item.icon\" :key=\"index\" :d=\"d\" />\n </template>\n <path v-else :d=\"item.icon\" />\n </svg>\n {{ item.label }}\n <svg\n class=\"mint-pill-nav__chevron\"\n :class=\"{ 'mint-pill-nav__chevron--open': openItemId === item.id }\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <ActionItemInternal\n v-else\n :href=\"item.href\"\n :to=\"item.to\"\n :disabled=\"item.disabled\"\n :class=\"[\n 'mint-pill-nav__item',\n { 'mint-pill-nav__item--active': item.id === currentItemId },\n { 'mint-pill-nav__item--disabled': item.disabled },\n ]\"\n @click=\"handleClick(item)\"\n >\n <svg\n v-if=\"isSvgIcon(item.icon)\"\n class=\"mint-pill-nav__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <template v-if=\"Array.isArray(item.icon)\">\n <path v-for=\"(d, index) in item.icon\" :key=\"index\" :d=\"d\" />\n </template>\n <path v-else :d=\"item.icon\" />\n </svg>\n {{ item.label }}\n </ActionItemInternal>\n\n <div\n v-if=\"item.children?.length && openItemId === item.id\"\n class=\"mint-pill-nav__dropdown\"\n role=\"menu\"\n >\n <ActionItemInternal\n v-for=\"option in item.children\"\n :key=\"option.id\"\n :href=\"option.href\"\n :to=\"option.to\"\n :disabled=\"option.disabled\"\n :class=\"[\n 'mint-pill-nav__dropdown-item',\n { 'mint-pill-nav__dropdown-item--active': option.id === currentItemId },\n { 'mint-pill-nav__dropdown-item--disabled': option.disabled },\n ]\"\n role=\"menuitem\"\n @click=\"handleOptionClick(option, item)\"\n >\n <span class=\"mint-pill-nav__dropdown-label\">{{ option.label }}</span>\n <span v-if=\"option.description\" class=\"mint-pill-nav__dropdown-description\">\n {{ option.description }}\n </span>\n </ActionItemInternal>\n </div>\n </div>\n </nav>\n</template>\n\n<style>\n@import '../../styles/components/app-pill-nav.css';\n</style>\n","<script setup lang=\"ts\">\n/** Horizontal pill-style navigation bar that emits select events and supports href, router-link, button, or dropdown items. */\nimport { computed, ref } from 'vue'\nimport type { PillNavItem, PillNavItemInput, PillNavOption, PillNavOptionInput } from '../../types/components'\nimport { normalizeItemInput } from '../../utils/items'\nimport { useEventListener } from '../../composables/useEventListener'\nimport ActionItemInternal from './ActionItemInternal.vue'\n\ninterface Props {\n items: PillNavItemInput[]\n currentItemId?: string\n}\n\nconst props = defineProps<Props>()\n\nconst emit = defineEmits<{\n select: [item: PillNavItem]\n 'option-select': [option: PillNavOption, item: PillNavItem]\n}>()\n\nconst openItemId = ref<string | null>(null)\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map())\n\nconst normalizedItems = computed<PillNavItem[]>(() => props.items.map(normalizePillNavItem))\n\nfunction handleClick(item: PillNavItem) {\n if (item.disabled) return\n if (item.children?.length) {\n toggleDropdown(item.id)\n return\n }\n if (item.href || item.to) return\n emit('select', item)\n}\n\nfunction handleOptionClick(option: PillNavOption, item: PillNavItem) {\n if (option.disabled) return\n if (!option.href && !option.to) emit('option-select', option, item)\n openItemId.value = null\n}\n\nfunction normalizePillNavItem(item: PillNavItemInput): PillNavItem {\n const normalized = normalizeItemInput<Omit<PillNavItem, 'children'> & { children?: PillNavOptionInput[] }>(item)\n return {\n ...normalized,\n children: normalized.children?.map(normalizeItemInput),\n }\n}\n\nfunction toggleDropdown(itemId: string) {\n openItemId.value = openItemId.value === itemId ? null : itemId\n}\n\nfunction setItemRef(el: HTMLElement | null, itemId: string) {\n if (el) {\n itemRefs.value.set(itemId, el)\n } else {\n itemRefs.value.delete(itemId)\n }\n}\n\nfunction handleClickOutside(event: MouseEvent) {\n const target = event.target as Node\n const clickedInside = Array.from(itemRefs.value.values()).some(el => el.contains(target))\n if (!clickedInside) openItemId.value = null\n}\n\nconst hasActiveChild = (item: PillNavItem) =>\n item.children?.some(child => child.id === props.currentItemId) ?? false\n\nfunction isSvgIcon(icon: PillNavItem['icon']): icon is string | string[] {\n if (!icon) return false\n return Array.isArray(icon) || icon.startsWith('M') || icon.startsWith('m')\n}\n\nuseEventListener(() => document, 'click', handleClickOutside)\n</script>\n\n<template>\n <nav class=\"mint-pill-nav\" aria-label=\"Primary\">\n <div\n v-for=\"item in normalizedItems\"\n :key=\"item.id\"\n :ref=\"(el) => setItemRef(el as HTMLElement | null, item.id)\"\n class=\"mint-pill-nav__item-wrap\"\n >\n <button\n v-if=\"item.children?.length\"\n type=\"button\"\n :class=\"[\n 'mint-pill-nav__item',\n { 'mint-pill-nav__item--active': item.id === currentItemId || hasActiveChild(item) },\n { 'mint-pill-nav__item--disabled': item.disabled },\n ]\"\n :disabled=\"item.disabled\"\n :aria-expanded=\"openItemId === item.id\"\n aria-haspopup=\"menu\"\n @click.stop=\"handleClick(item)\"\n >\n <svg\n v-if=\"isSvgIcon(item.icon)\"\n class=\"mint-pill-nav__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <template v-if=\"Array.isArray(item.icon)\">\n <path v-for=\"(d, index) in item.icon\" :key=\"index\" :d=\"d\" />\n </template>\n <path v-else :d=\"item.icon\" />\n </svg>\n {{ item.label }}\n <svg\n class=\"mint-pill-nav__chevron\"\n :class=\"{ 'mint-pill-nav__chevron--open': openItemId === item.id }\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <ActionItemInternal\n v-else\n :href=\"item.href\"\n :to=\"item.to\"\n :disabled=\"item.disabled\"\n :class=\"[\n 'mint-pill-nav__item',\n { 'mint-pill-nav__item--active': item.id === currentItemId },\n { 'mint-pill-nav__item--disabled': item.disabled },\n ]\"\n @click=\"handleClick(item)\"\n >\n <svg\n v-if=\"isSvgIcon(item.icon)\"\n class=\"mint-pill-nav__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <template v-if=\"Array.isArray(item.icon)\">\n <path v-for=\"(d, index) in item.icon\" :key=\"index\" :d=\"d\" />\n </template>\n <path v-else :d=\"item.icon\" />\n </svg>\n {{ item.label }}\n </ActionItemInternal>\n\n <div\n v-if=\"item.children?.length && openItemId === item.id\"\n class=\"mint-pill-nav__dropdown\"\n role=\"menu\"\n >\n <ActionItemInternal\n v-for=\"option in item.children\"\n :key=\"option.id\"\n :href=\"option.href\"\n :to=\"option.to\"\n :disabled=\"option.disabled\"\n :class=\"[\n 'mint-pill-nav__dropdown-item',\n { 'mint-pill-nav__dropdown-item--active': option.id === currentItemId },\n { 'mint-pill-nav__dropdown-item--disabled': option.disabled },\n ]\"\n role=\"menuitem\"\n @click=\"handleOptionClick(option, item)\"\n >\n <span class=\"mint-pill-nav__dropdown-label\">{{ option.label }}</span>\n <span v-if=\"option.description\" class=\"mint-pill-nav__dropdown-description\">\n {{ option.description }}\n </span>\n </ActionItemInternal>\n </div>\n </div>\n </nav>\n</template>\n\n<style>\n@import '../../styles/components/app-pill-nav.css';\n</style>\n","<script setup lang=\"ts\">\n/** Avatar-triggered dropdown with user name/email header, configurable menu items, dividers, and a sign-out action. */\nimport { computed } from 'vue'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport type { AccountMenuItem } from '../types/components'\nimport ActionItemInternal from './internal/ActionItemInternal.vue'\n\ninterface Props {\n userName?: string\n userInitial?: string\n userEmail?: string\n items?: AccountMenuItem[]\n showSignOut?: boolean\n signOutLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n items: () => [] as AccountMenuItem[],\n showSignOut: true,\n signOutLabel: 'Sign out',\n})\n\nconst emit = defineEmits<{\n select: [item: AccountMenuItem]\n 'sign-out': []\n 'menu-open': []\n 'menu-close': []\n}>()\n\nconst { isOpen, rootRef, close, toggle } = useDropdownState({\n onOpen: () => emit('menu-open'),\n onClose: () => emit('menu-close'),\n})\n\nconst initials = computed(() => {\n if (props.userInitial) return props.userInitial.slice(0, 2).toUpperCase()\n if (props.userName) {\n const parts = props.userName.trim().split(/\\s+/).filter(Boolean)\n const first = parts[0]\n if (!first) return 'U'\n const second = parts[1]\n if (second) return (first.charAt(0) + second.charAt(0)).toUpperCase()\n return first.slice(0, 2).toUpperCase()\n }\n return 'U'\n})\n\nfunction closeSilently() {\n isOpen.value = false\n}\n\nfunction handleSelect(item: AccountMenuItem) {\n if (item.divider) return\n emit('select', item)\n close()\n}\n\nfunction handleSignOut() {\n emit('sign-out')\n close()\n}\n</script>\n\n<template>\n <div ref=\"rootRef\" class=\"mint-avatar-menu\">\n <button\n type=\"button\"\n class=\"mint-avatar-menu__trigger\"\n :class=\"{ 'mint-avatar-menu__trigger--open': isOpen }\"\n :aria-expanded=\"isOpen\"\n aria-haspopup=\"menu\"\n :title=\"userName ? `${userName} · Account menu` : 'Account menu'\"\n @click.stop=\"toggle\"\n >\n <span class=\"mint-avatar-menu__avatar\">\n <slot name=\"avatar\" :initials=\"initials\">\n {{ initials }}\n </slot>\n </span>\n <span v-if=\"userName\" class=\"mint-avatar-menu__name\">{{ userName }}</span>\n </button>\n\n <div v-if=\"isOpen\" class=\"mint-avatar-menu__panel\" role=\"menu\">\n <div v-if=\"userName || userEmail\" class=\"mint-avatar-menu__header\">\n <div v-if=\"userName\" class=\"mint-avatar-menu__user-name\">{{ userName }}</div>\n <div v-if=\"userEmail\" class=\"mint-avatar-menu__user-email\">{{ userEmail }}</div>\n </div>\n\n <slot name=\"items\" :close=\"closeSilently\">\n <template v-for=\"item in items\" :key=\"item.id\">\n <div v-if=\"item.divider\" class=\"mint-avatar-menu__divider\" role=\"separator\" />\n <ActionItemInternal\n v-else\n :href=\"item.href\"\n :to=\"item.to\"\n :class=\"['mint-avatar-menu__item', { 'mint-avatar-menu__item--danger': item.danger }]\"\n role=\"menuitem\"\n @click=\"handleSelect(item)\"\n >\n <span v-if=\"item.icon\" class=\"mint-avatar-menu__item-icon\">\n <slot name=\"item-icon\" :item=\"item\" />\n </span>\n <span class=\"mint-avatar-menu__item-label\">{{ item.label }}</span>\n <span v-if=\"item.rightLabel\" class=\"mint-avatar-menu__item-right\">{{ item.rightLabel }}</span>\n </ActionItemInternal>\n </template>\n </slot>\n\n <template v-if=\"showSignOut\">\n <div class=\"mint-avatar-menu__divider\" role=\"separator\" />\n <button\n type=\"button\"\n class=\"mint-avatar-menu__item mint-avatar-menu__item--danger\"\n role=\"menuitem\"\n @click=\"handleSignOut\"\n >\n <span class=\"mint-avatar-menu__item-icon\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\" />\n <polyline points=\"16 17 21 12 16 7\" />\n <line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\" />\n </svg>\n </span>\n <span class=\"mint-avatar-menu__item-label\">{{ signOutLabel }}</span>\n </button>\n </template>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/app-avatar-menu.css';\n</style>\n","<script setup lang=\"ts\">\n/** Avatar-triggered dropdown with user name/email header, configurable menu items, dividers, and a sign-out action. */\nimport { computed } from 'vue'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport type { AccountMenuItem } from '../types/components'\nimport ActionItemInternal from './internal/ActionItemInternal.vue'\n\ninterface Props {\n userName?: string\n userInitial?: string\n userEmail?: string\n items?: AccountMenuItem[]\n showSignOut?: boolean\n signOutLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n items: () => [] as AccountMenuItem[],\n showSignOut: true,\n signOutLabel: 'Sign out',\n})\n\nconst emit = defineEmits<{\n select: [item: AccountMenuItem]\n 'sign-out': []\n 'menu-open': []\n 'menu-close': []\n}>()\n\nconst { isOpen, rootRef, close, toggle } = useDropdownState({\n onOpen: () => emit('menu-open'),\n onClose: () => emit('menu-close'),\n})\n\nconst initials = computed(() => {\n if (props.userInitial) return props.userInitial.slice(0, 2).toUpperCase()\n if (props.userName) {\n const parts = props.userName.trim().split(/\\s+/).filter(Boolean)\n const first = parts[0]\n if (!first) return 'U'\n const second = parts[1]\n if (second) return (first.charAt(0) + second.charAt(0)).toUpperCase()\n return first.slice(0, 2).toUpperCase()\n }\n return 'U'\n})\n\nfunction closeSilently() {\n isOpen.value = false\n}\n\nfunction handleSelect(item: AccountMenuItem) {\n if (item.divider) return\n emit('select', item)\n close()\n}\n\nfunction handleSignOut() {\n emit('sign-out')\n close()\n}\n</script>\n\n<template>\n <div ref=\"rootRef\" class=\"mint-avatar-menu\">\n <button\n type=\"button\"\n class=\"mint-avatar-menu__trigger\"\n :class=\"{ 'mint-avatar-menu__trigger--open': isOpen }\"\n :aria-expanded=\"isOpen\"\n aria-haspopup=\"menu\"\n :title=\"userName ? `${userName} · Account menu` : 'Account menu'\"\n @click.stop=\"toggle\"\n >\n <span class=\"mint-avatar-menu__avatar\">\n <slot name=\"avatar\" :initials=\"initials\">\n {{ initials }}\n </slot>\n </span>\n <span v-if=\"userName\" class=\"mint-avatar-menu__name\">{{ userName }}</span>\n </button>\n\n <div v-if=\"isOpen\" class=\"mint-avatar-menu__panel\" role=\"menu\">\n <div v-if=\"userName || userEmail\" class=\"mint-avatar-menu__header\">\n <div v-if=\"userName\" class=\"mint-avatar-menu__user-name\">{{ userName }}</div>\n <div v-if=\"userEmail\" class=\"mint-avatar-menu__user-email\">{{ userEmail }}</div>\n </div>\n\n <slot name=\"items\" :close=\"closeSilently\">\n <template v-for=\"item in items\" :key=\"item.id\">\n <div v-if=\"item.divider\" class=\"mint-avatar-menu__divider\" role=\"separator\" />\n <ActionItemInternal\n v-else\n :href=\"item.href\"\n :to=\"item.to\"\n :class=\"['mint-avatar-menu__item', { 'mint-avatar-menu__item--danger': item.danger }]\"\n role=\"menuitem\"\n @click=\"handleSelect(item)\"\n >\n <span v-if=\"item.icon\" class=\"mint-avatar-menu__item-icon\">\n <slot name=\"item-icon\" :item=\"item\" />\n </span>\n <span class=\"mint-avatar-menu__item-label\">{{ item.label }}</span>\n <span v-if=\"item.rightLabel\" class=\"mint-avatar-menu__item-right\">{{ item.rightLabel }}</span>\n </ActionItemInternal>\n </template>\n </slot>\n\n <template v-if=\"showSignOut\">\n <div class=\"mint-avatar-menu__divider\" role=\"separator\" />\n <button\n type=\"button\"\n class=\"mint-avatar-menu__item mint-avatar-menu__item--danger\"\n role=\"menuitem\"\n @click=\"handleSignOut\"\n >\n <span class=\"mint-avatar-menu__item-icon\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\" />\n <polyline points=\"16 17 21 12 16 7\" />\n <line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\" />\n </svg>\n </span>\n <span class=\"mint-avatar-menu__item-label\">{{ signOutLabel }}</span>\n </button>\n </template>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/app-avatar-menu.css';\n</style>\n","<script setup lang=\"ts\">\n/** Dropdown menu for switching between installed plugins with color swatches, version badges, and an install link. */\nimport { useDropdownState } from '../composables/useDropdownState'\nimport type { PluginSwitcherPlugin } from '../types/components'\nimport ActionItemInternal from './internal/ActionItemInternal.vue'\n\ninterface Props {\n current: PluginSwitcherPlugin\n plugins?: PluginSwitcherPlugin[]\n installLabel?: string\n installTo?: string\n installHref?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n plugins: () => [] as PluginSwitcherPlugin[],\n installLabel: 'Install plugin…',\n})\n\nconst emit = defineEmits<{\n select: [plugin: PluginSwitcherPlugin]\n 'install-click': []\n}>()\n\nconst { isOpen, rootRef, close, toggle } = useDropdownState()\n\nfunction initialsOf(plugin: PluginSwitcherPlugin) {\n const label = plugin.label || plugin.id\n return label.slice(0, 2).toUpperCase()\n}\n\nfunction handleSelect(plugin: PluginSwitcherPlugin) {\n if (plugin.id === props.current.id) {\n close()\n return\n }\n emit('select', plugin)\n close()\n}\n\nfunction handleInstall() {\n emit('install-click')\n close()\n}\n</script>\n\n<template>\n <div ref=\"rootRef\" class=\"mint-plugin-switcher\">\n <button\n type=\"button\"\n class=\"mint-plugin-switcher__trigger\"\n :class=\"{ 'mint-plugin-switcher__trigger--open': isOpen }\"\n :aria-expanded=\"isOpen\"\n aria-haspopup=\"menu\"\n @click.stop=\"toggle\"\n >\n <span class=\"mint-plugin-switcher__label\">{{ current.label }}</span>\n <span v-if=\"current.version\" class=\"mint-plugin-switcher__version\">v{{ current.version }}</span>\n <span class=\"mint-plugin-switcher__chevron-wrap\" aria-hidden=\"true\">\n <svg\n class=\"mint-plugin-switcher__chevron\"\n :class=\"{ 'mint-plugin-switcher__chevron--open': isOpen }\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </span>\n </button>\n\n <div v-if=\"isOpen\" class=\"mint-plugin-switcher__menu\" role=\"menu\">\n <div class=\"mint-plugin-switcher__menu-title\">Switch plugin</div>\n <ActionItemInternal\n v-for=\"plugin in plugins\"\n :key=\"plugin.id\"\n :href=\"plugin.href\"\n :to=\"plugin.to\"\n :class=\"[\n 'mint-plugin-switcher__item',\n { 'mint-plugin-switcher__item--active': plugin.id === current.id },\n ]\"\n role=\"menuitem\"\n @click=\"handleSelect(plugin)\"\n >\n <span\n class=\"mint-plugin-switcher__item-swatch\"\n :style=\"{ background: plugin.color ?? 'var(--color-primary)' }\"\n >\n {{ initialsOf(plugin) }}\n </span>\n <span class=\"mint-plugin-switcher__item-label\">{{ plugin.label }}</span>\n <span v-if=\"plugin.version\" class=\"mint-plugin-switcher__item-version\">v{{ plugin.version }}</span>\n <svg\n v-if=\"plugin.id === current.id\"\n class=\"mint-plugin-switcher__item-check\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M5 13l4 4L19 7\" />\n </svg>\n </ActionItemInternal>\n\n <template v-if=\"plugins.length && (installHref || installTo || $slots.install)\">\n <div class=\"mint-plugin-switcher__divider\" role=\"separator\" />\n </template>\n <slot name=\"install\">\n <ActionItemInternal\n v-if=\"installHref || installTo\"\n :href=\"installHref\"\n :to=\"installTo\"\n class=\"mint-plugin-switcher__install\"\n role=\"menuitem\"\n @click=\"handleInstall\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\" />\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\n </svg>\n <span>{{ installLabel }}</span>\n </ActionItemInternal>\n </slot>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/app-plugin-switcher.css';\n</style>\n","<script setup lang=\"ts\">\n/** Dropdown menu for switching between installed plugins with color swatches, version badges, and an install link. */\nimport { useDropdownState } from '../composables/useDropdownState'\nimport type { PluginSwitcherPlugin } from '../types/components'\nimport ActionItemInternal from './internal/ActionItemInternal.vue'\n\ninterface Props {\n current: PluginSwitcherPlugin\n plugins?: PluginSwitcherPlugin[]\n installLabel?: string\n installTo?: string\n installHref?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n plugins: () => [] as PluginSwitcherPlugin[],\n installLabel: 'Install plugin…',\n})\n\nconst emit = defineEmits<{\n select: [plugin: PluginSwitcherPlugin]\n 'install-click': []\n}>()\n\nconst { isOpen, rootRef, close, toggle } = useDropdownState()\n\nfunction initialsOf(plugin: PluginSwitcherPlugin) {\n const label = plugin.label || plugin.id\n return label.slice(0, 2).toUpperCase()\n}\n\nfunction handleSelect(plugin: PluginSwitcherPlugin) {\n if (plugin.id === props.current.id) {\n close()\n return\n }\n emit('select', plugin)\n close()\n}\n\nfunction handleInstall() {\n emit('install-click')\n close()\n}\n</script>\n\n<template>\n <div ref=\"rootRef\" class=\"mint-plugin-switcher\">\n <button\n type=\"button\"\n class=\"mint-plugin-switcher__trigger\"\n :class=\"{ 'mint-plugin-switcher__trigger--open': isOpen }\"\n :aria-expanded=\"isOpen\"\n aria-haspopup=\"menu\"\n @click.stop=\"toggle\"\n >\n <span class=\"mint-plugin-switcher__label\">{{ current.label }}</span>\n <span v-if=\"current.version\" class=\"mint-plugin-switcher__version\">v{{ current.version }}</span>\n <span class=\"mint-plugin-switcher__chevron-wrap\" aria-hidden=\"true\">\n <svg\n class=\"mint-plugin-switcher__chevron\"\n :class=\"{ 'mint-plugin-switcher__chevron--open': isOpen }\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </span>\n </button>\n\n <div v-if=\"isOpen\" class=\"mint-plugin-switcher__menu\" role=\"menu\">\n <div class=\"mint-plugin-switcher__menu-title\">Switch plugin</div>\n <ActionItemInternal\n v-for=\"plugin in plugins\"\n :key=\"plugin.id\"\n :href=\"plugin.href\"\n :to=\"plugin.to\"\n :class=\"[\n 'mint-plugin-switcher__item',\n { 'mint-plugin-switcher__item--active': plugin.id === current.id },\n ]\"\n role=\"menuitem\"\n @click=\"handleSelect(plugin)\"\n >\n <span\n class=\"mint-plugin-switcher__item-swatch\"\n :style=\"{ background: plugin.color ?? 'var(--color-primary)' }\"\n >\n {{ initialsOf(plugin) }}\n </span>\n <span class=\"mint-plugin-switcher__item-label\">{{ plugin.label }}</span>\n <span v-if=\"plugin.version\" class=\"mint-plugin-switcher__item-version\">v{{ plugin.version }}</span>\n <svg\n v-if=\"plugin.id === current.id\"\n class=\"mint-plugin-switcher__item-check\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M5 13l4 4L19 7\" />\n </svg>\n </ActionItemInternal>\n\n <template v-if=\"plugins.length && (installHref || installTo || $slots.install)\">\n <div class=\"mint-plugin-switcher__divider\" role=\"separator\" />\n </template>\n <slot name=\"install\">\n <ActionItemInternal\n v-if=\"installHref || installTo\"\n :href=\"installHref\"\n :to=\"installTo\"\n class=\"mint-plugin-switcher__install\"\n role=\"menuitem\"\n @click=\"handleInstall\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\" />\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\n </svg>\n <span>{{ installLabel }}</span>\n </ActionItemInternal>\n </slot>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/app-plugin-switcher.css';\n</style>\n","import type { PageSelectorItem } from '../types/components'\nimport type { PluginNavItem } from '../types/platform'\n\nexport interface PluginNavPageSelectorOptions {\n pluginIcon?: string\n pluginName?: string\n}\n\nexport function normalizeNavPath(path?: string): string {\n const raw = path?.trim() || '/'\n const prefixed = raw.startsWith('/') ? raw : `/${raw}`\n return prefixed.replace(/\\/+$/, '') || '/'\n}\n\nexport function pageIdFromPath(path: string, fallback: string): string {\n const normalized = normalizeNavPath(path)\n if (normalized === '/') return 'dashboard'\n return normalized.replace(/^\\/+/, '').replace(/\\/+/g, '-') || fallback\n}\n\nexport function pluginNavItemToPageSelectorItem(\n item: PluginNavItem,\n index: number,\n options: PluginNavPageSelectorOptions = {},\n): PageSelectorItem {\n const path = normalizeNavPath(item.path)\n return {\n id: item.id || pageIdFromPath(path, `page-${index + 1}`),\n label: item.label,\n to: path,\n icon: item.icon || options.pluginIcon,\n hint: item.description || options.pluginName,\n }\n}\n\nexport function currentPagePath(\n pathname: string | undefined,\n routePrefix?: string,\n): string | undefined {\n if (!pathname) return undefined\n const normalizedPathname = normalizeNavPath(pathname)\n const normalizedRoutePrefix = normalizeNavPath(routePrefix)\n\n if (normalizedPathname === normalizedRoutePrefix) return '/'\n if (\n normalizedRoutePrefix !== '/' &&\n normalizedPathname.startsWith(`${normalizedRoutePrefix}/`)\n ) {\n return normalizeNavPath(normalizedPathname.slice(normalizedRoutePrefix.length))\n }\n return normalizedPathname\n}\n\nexport function currentItemIdFromLocation(\n pages: Array<Pick<PageSelectorItem, 'id' | 'to' | 'href'>>,\n pathname: string | undefined,\n routePrefix?: string,\n): string | undefined {\n const path = currentPagePath(pathname, routePrefix)\n if (!path) return undefined\n return pages.find((page) => normalizeNavPath(page.to || page.href) === path)?.id\n}\n","<script setup lang=\"ts\">\n/** Full application top bar with brand logo, page selector or plugin switcher, centered pill nav, experiment popover, and avatar menu. */\nimport { ref, computed, inject, defineAsyncComponent } from 'vue'\nimport type {\n PillNavOption,\n TopBarSettingsConfig,\n TopBarVariant,\n PillNavItem,\n PillNavItemInput,\n PageSelectorItem,\n PageSelectorItemInput,\n PluginSwitcherInfo,\n PluginSwitcherPlugin,\n AccountMenuItem,\n SettingsTab,\n} from '../types/components'\nimport { normalizeItemInput } from '../utils/items'\nimport ThemeToggle from './ThemeToggle.vue'\nimport AppTopBarPageSelectorInternal from './internal/AppTopBarPageSelectorInternal.vue'\nimport AppTopBarPillNavInternal from './internal/AppTopBarPillNavInternal.vue'\nimport AppAvatarMenu from './AppAvatarMenu.vue'\nimport AppPluginSwitcher from './AppPluginSwitcher.vue'\nimport PluginIcon from './PluginIcon.vue'\nimport { usePlatformContext } from '../composables/usePlatformContext'\nimport { APP_EXPERIMENT_KEY } from '../composables/useAppExperiment'\nimport {\n currentItemIdFromLocation,\n pluginNavItemToPageSelectorItem,\n} from './AppTopBar.navigation'\n\ninterface Props {\n /** App or plugin title shown in the left title group when no page selector is present. */\n title?: string\n /** Secondary title copy shown under the title in title-group layouts. */\n subtitle?: string\n /** Show the default MINT logo when the icon/logo slot is not provided. */\n showLogo?: boolean\n /** Top bar visual treatment. */\n variant?: TopBarVariant\n /** Home link used by the brand icon. */\n homePath?: string\n\n /** Preferred route-level page switch entries for plugin and platform pages. Integrated plugins read platform plugin.nav_items metadata automatically when pageSelector is omitted. */\n pageSelector?: PageSelectorItemInput[]\n /** Active id for the preferred page selector. */\n currentPageSelectorId?: string\n /** Optional plugin switcher shown in the left navigation position instead of pageSelector. */\n pluginSwitcher?: PluginSwitcherInfo\n\n /** Preferred centered navigation for local modes inside the current route. */\n pillNav?: PillNavItemInput[]\n /** Active id for the preferred centered pill navigation. */\n currentPillId?: string\n\n /** Account dropdown entries. Takes precedence over the classic profile button. */\n accountMenu?: AccountMenuItem[]\n /** Show the notifications icon button. */\n showNotifications?: boolean\n /** Draw a notification dot on the notifications icon. */\n hasNotificationDot?: boolean\n\n /** Show the theme toggle button. */\n showThemeToggle?: boolean\n /** Show the settings button and modal. */\n showSettings?: boolean\n /** Built-in SettingsModal configuration. */\n settingsConfig?: TopBarSettingsConfig\n /** Show the standalone badge when the plugin is not integrated into the platform. */\n showStandaloneLabel?: boolean\n /** Custom standalone badge label. */\n standaloneLabel?: string\n /** Show the classic admin shortcut. */\n showAdmin?: boolean\n /** Route used by the classic admin shortcut. */\n adminPath?: string\n /** Show the classic profile button when accountMenu is not provided. */\n showProfile?: boolean\n /** Classic profile display name. */\n userName?: string\n /** Explicit classic profile initial. */\n userInitial?: string\n /** Classic profile email, used by avatar/account layouts. */\n userEmail?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showLogo: true,\n variant: 'card',\n homePath: '/',\n showThemeToggle: false,\n showSettings: false,\n showStandaloneLabel: true,\n standaloneLabel: 'Standalone',\n showAdmin: false,\n adminPath: '/admin',\n showProfile: false,\n showNotifications: false,\n hasNotificationDot: false,\n})\n\nconst SettingsModal = defineAsyncComponent(() => import('./SettingsModal.vue'))\nconst ExperimentPopover = defineAsyncComponent(() => import('./ExperimentPopover.vue'))\nconst ExperimentSelectorModal = defineAsyncComponent(() => import('./ExperimentSelectorModal.vue'))\n\nconst emit = defineEmits<{\n 'profile-click': []\n 'admin-click': []\n 'page-selector-select': [page: PageSelectorItem]\n 'pill-select': [item: PillNavItem]\n 'pill-option-select': [option: PillNavOption, item: PillNavItem]\n 'plugin-switcher-select': [plugin: PluginSwitcherPlugin]\n 'plugin-switcher-install': []\n 'account-menu-select': [item: AccountMenuItem]\n 'sign-out': []\n 'notifications-click': []\n 'settings-values-change': [data: Record<string, unknown>]\n}>()\n\nconst settingsOpen = ref(false)\nconst { isIntegrated, plugin } = usePlatformContext()\nconst isStandalone = computed(() => !isIntegrated.value)\nconst appExperiment = inject(APP_EXPERIMENT_KEY, null)\n\nconst pluginIcon = computed(() => {\n const currentPlugin = plugin.value\n return currentPlugin?.icon\n ? { icon: currentPlugin.icon, color: currentPlugin.color }\n : null\n})\n\nconst profileInitial = computed(() => {\n if (props.userInitial) return props.userInitial\n if (props.userName) return props.userName.charAt(0).toUpperCase()\n return 'U'\n})\n\nconst hasPlatformPageSelector = computed(() =>\n props.pageSelector === undefined && (plugin.value?.nav_items?.length ?? 0) > 1,\n)\nconst hasPageSelector = computed(() => (props.pageSelector?.length ?? 0) > 1 || hasPlatformPageSelector.value)\nconst hasPluginSwitcher = computed(() => !!props.pluginSwitcher)\nconst hasPillNav = computed(() => !!props.pillNav?.length)\nconst hasAccountMenu = computed(() => !!props.accountMenu?.length)\nconst hasTitleGroup = computed(\n () =>\n !hasPageSelector.value &&\n !hasPluginSwitcher.value &&\n !!props.title &&\n !!props.subtitle\n)\nconst hasTitleOnly = computed(\n () =>\n !hasPageSelector.value &&\n !hasPluginSwitcher.value &&\n !hasTitleGroup.value &&\n !!props.title,\n)\nconst platformPageSelector = computed<PageSelectorItem[]>(() =>\n hasPlatformPageSelector.value\n ? plugin.value?.nav_items?.map((item, index) =>\n pluginNavItemToPageSelectorItem(item, index, {\n pluginIcon: plugin.value?.icon,\n pluginName: plugin.value?.name,\n })\n ) ?? []\n : [],\n)\nconst normalizedPageSelector = computed<PageSelectorItem[]>(() =>\n props.pageSelector !== undefined\n ? props.pageSelector.map(normalizeItemInput)\n : platformPageSelector.value,\n)\nconst normalizedPillNav = computed<PillNavItemInput[]>(() => props.pillNav ?? [])\nconst normalizedSettingsTabs = computed<SettingsTab[]>(() =>\n props.settingsConfig?.tabs?.map(normalizeItemInput) ?? [],\n)\nconst effectiveCurrentPageSelectorId = computed(() =>\n props.currentPageSelectorId\n ?? (hasPlatformPageSelector.value\n ? currentItemIdFromLocation(\n normalizedPageSelector.value,\n typeof window === 'undefined' ? undefined : window.location.pathname,\n plugin.value?.route_prefix,\n )\n : undefined)\n ?? (hasPlatformPageSelector.value ? normalizedPageSelector.value[0]?.id : undefined),\n)\n</script>\n\n<template>\n <header\n :class=\"[\n 'mint-topbar',\n `mint-topbar--${props.variant}`,\n ]\"\n >\n <div class=\"mint-topbar__container\">\n <!-- Left: brand -->\n <div class=\"mint-topbar__brand\">\n <a\n v-if=\"homePath && (homePath.startsWith('http') || homePath.startsWith('/'))\"\n :href=\"homePath\"\n class=\"mint-topbar-home-link\"\n >\n <slot name=\"icon\">\n <PluginIcon\n v-if=\"pluginIcon\"\n class=\"mint-topbar__plugin-icon\"\n :icon=\"pluginIcon.icon\"\n :tone=\"pluginIcon.color\"\n size=\"md\"\n variant=\"solid\"\n />\n <slot v-else name=\"logo\">\n <div v-if=\"showLogo\" class=\"mint-topbar__logo\">\n <div class=\"mint-topbar__logo-icon\">\n <span class=\"mint-topbar__logo-text\">M</span>\n </div>\n </div>\n </slot>\n </slot>\n </a>\n <router-link\n v-else-if=\"homePath\"\n :to=\"homePath\"\n class=\"mint-topbar-home-link\"\n >\n <slot name=\"icon\">\n <PluginIcon\n v-if=\"pluginIcon\"\n class=\"mint-topbar__plugin-icon\"\n :icon=\"pluginIcon.icon\"\n :tone=\"pluginIcon.color\"\n size=\"md\"\n variant=\"solid\"\n />\n <slot v-else name=\"logo\">\n <div v-if=\"showLogo\" class=\"mint-topbar__logo\">\n <div class=\"mint-topbar__logo-icon\">\n <span class=\"mint-topbar__logo-text\">M</span>\n </div>\n </div>\n </slot>\n </slot>\n </router-link>\n <template v-else>\n <slot name=\"icon\">\n <PluginIcon\n v-if=\"pluginIcon\"\n class=\"mint-topbar__plugin-icon\"\n :icon=\"pluginIcon.icon\"\n :tone=\"pluginIcon.color\"\n size=\"md\"\n variant=\"solid\"\n />\n <slot v-else name=\"logo\">\n <div v-if=\"showLogo\" class=\"mint-topbar__logo\">\n <div class=\"mint-topbar__logo-icon\">\n <span class=\"mint-topbar__logo-text\">M</span>\n </div>\n </div>\n </slot>\n </slot>\n </template>\n </div>\n\n <!-- Left: page selector or plugin switcher (new) -->\n <AppPluginSwitcher\n v-if=\"hasPluginSwitcher && pluginSwitcher\"\n :current=\"pluginSwitcher.current\"\n :plugins=\"pluginSwitcher.plugins\"\n :install-to=\"pluginSwitcher.installTo\"\n :install-href=\"pluginSwitcher.installHref\"\n @select=\"emit('plugin-switcher-select', $event)\"\n @install-click=\"emit('plugin-switcher-install')\"\n />\n <AppTopBarPageSelectorInternal\n v-else-if=\"hasPageSelector\"\n :pages=\"normalizedPageSelector\"\n :current-page-id=\"effectiveCurrentPageSelectorId\"\n @select=\"emit('page-selector-select', $event)\"\n >\n <template v-if=\"$slots['page-selector-icon']\" #icon=\"slotProps\">\n <slot name=\"page-selector-icon\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"$slots['page-selector-item-icon']\" #item-icon=\"slotProps\">\n <slot name=\"page-selector-item-icon\" v-bind=\"slotProps\" />\n </template>\n </AppTopBarPageSelectorInternal>\n\n <!-- Left: title -->\n <div v-if=\"hasTitleGroup\" class=\"mint-topbar-title-group\">\n <span class=\"mint-topbar-title\">{{ title }}</span>\n <span class=\"mint-topbar-subtitle\">{{ subtitle }}</span>\n </div>\n\n <span v-else-if=\"hasTitleOnly\" class=\"mint-topbar__title-only\">{{ title }}</span>\n\n <!-- Nav slot (inline, after brand/selector) -->\n <slot name=\"nav\" />\n\n <!-- Center: pill nav (new) -->\n <div v-if=\"hasPillNav || $slots.center\" class=\"mint-topbar__center\">\n <slot name=\"center\">\n <AppTopBarPillNavInternal\n v-if=\"hasPillNav && pillNav\"\n :items=\"normalizedPillNav\"\n :current-item-id=\"currentPillId\"\n @select=\"emit('pill-select', $event)\"\n @option-select=\"(option, item) => emit('pill-option-select', option, item)\"\n />\n </slot>\n </div>\n\n <!-- Right section -->\n <div class=\"mint-topbar__right\">\n <span v-if=\"showStandaloneLabel && isStandalone && !appExperiment\" class=\"mint-topbar__standalone-badge\">\n {{ standaloneLabel }}\n </span>\n\n <ExperimentPopover\n v-if=\"appExperiment && !isStandalone\"\n v-bind=\"appExperiment.popover.value\"\n @select=\"appExperiment.openModal()\"\n @save=\"appExperiment.handleSave()\"\n @detach=\"appExperiment.handleDetach()\"\n />\n\n <span v-if=\"appExperiment && isStandalone\" class=\"mint-topbar__standalone-badge\">\n {{ standaloneLabel }}\n </span>\n\n <slot name=\"actions\" />\n\n <!-- Notifications (new) -->\n <button\n v-if=\"showNotifications\"\n type=\"button\"\n class=\"mint-topbar__icon-btn\"\n aria-label=\"Notifications\"\n @click=\"emit('notifications-click')\"\n >\n <svg class=\"mint-topbar__icon-btn-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9\" />\n <path d=\"M10.3 21a1.94 1.94 0 0 0 3.4 0\" />\n </svg>\n <span v-if=\"hasNotificationDot\" class=\"mint-topbar__icon-btn-dot\" aria-hidden=\"true\" />\n </button>\n\n <!-- Classic: theme toggle -->\n <ThemeToggle v-if=\"showThemeToggle\" size=\"sm\" />\n\n <!-- Classic: settings gear -->\n <button\n v-if=\"showSettings\"\n type=\"button\"\n class=\"mint-topbar__settings-btn\"\n aria-label=\"Open settings\"\n @click=\"settingsOpen = true\"\n >\n <svg class=\"mint-topbar__settings-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915\" /><circle cx=\"12\" cy=\"12\" r=\"3\" />\n </svg>\n </button>\n\n <!-- Classic: admin link -->\n <router-link\n v-if=\"showAdmin\"\n :to=\"adminPath\"\n class=\"mint-topbar__admin-btn\"\n aria-label=\"Admin Dashboard\"\n @click=\"emit('admin-click')\"\n >\n <svg class=\"mint-topbar__admin-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z\" /><path d=\"m9 12 2 2 4-4\" />\n </svg>\n </router-link>\n\n <!-- New: avatar menu (takes precedence over classic profile button) -->\n <AppAvatarMenu\n v-if=\"hasAccountMenu || $slots['account-menu-items']\"\n :user-name=\"userName\"\n :user-initial=\"userInitial\"\n :user-email=\"userEmail\"\n :items=\"accountMenu\"\n @select=\"emit('account-menu-select', $event)\"\n @sign-out=\"emit('sign-out')\"\n >\n <template v-if=\"$slots['account-menu-items']\" #items=\"slotProps\">\n <slot name=\"account-menu-items\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"$slots['account-menu-item-icon']\" #item-icon=\"slotProps\">\n <slot name=\"account-menu-item-icon\" v-bind=\"slotProps\" />\n </template>\n </AppAvatarMenu>\n\n <!-- Classic: profile button (only when avatar menu not provided) -->\n <button\n v-else-if=\"showProfile\"\n type=\"button\"\n class=\"mint-topbar__profile-btn\"\n aria-label=\"Edit profile\"\n @click=\"emit('profile-click')\"\n >\n <div class=\"mint-topbar__profile-avatar\">\n {{ profileInitial }}\n </div>\n <span v-if=\"userName\" class=\"mint-topbar__profile-name\">{{ userName }}</span>\n </button>\n </div>\n </div>\n </header>\n\n <SettingsModal\n v-if=\"showSettings && settingsOpen\"\n v-model=\"settingsOpen\"\n :title=\"settingsConfig?.title\"\n :tabs=\"normalizedSettingsTabs\"\n :show-appearance=\"settingsConfig?.showAppearance ?? true\"\n :size=\"settingsConfig?.size\"\n :layout=\"settingsConfig?.layout\"\n :schema=\"settingsConfig?.schema\"\n :model=\"settingsConfig?.model\"\n :controls=\"settingsConfig?.controls\"\n :control-options=\"settingsConfig?.controlOptions\"\n :values=\"settingsConfig?.values\"\n :enhancements=\"settingsConfig?.enhancements\"\n :user-type=\"settingsConfig?.userType\"\n @update:values=\"emit('settings-values-change', $event)\"\n >\n <template v-for=\"tab in normalizedSettingsTabs\" :key=\"tab.id\" #[`tab-${tab.id}`]>\n <slot :name=\"`settings-tab-${tab.id}`\" />\n </template>\n <template #appearance>\n <slot name=\"settings-appearance\" />\n </template>\n </SettingsModal>\n\n <ExperimentSelectorModal\n v-if=\"appExperiment && !isStandalone && appExperiment.selectorModal.value.modelValue\"\n v-bind=\"appExperiment.selectorModal.value\"\n @update:model-value=\"$event ? appExperiment.openModal() : appExperiment.closeModal()\"\n @select=\"appExperiment.handleSelect($event)\"\n @deselect=\"appExperiment.handleDetach()\"\n />\n</template>\n\n<style>\n@import '../styles/components/app-top-bar.css';\n</style>\n","<script setup lang=\"ts\">\n/** Full application top bar with brand logo, page selector or plugin switcher, centered pill nav, experiment popover, and avatar menu. */\nimport { ref, computed, inject, defineAsyncComponent } from 'vue'\nimport type {\n PillNavOption,\n TopBarSettingsConfig,\n TopBarVariant,\n PillNavItem,\n PillNavItemInput,\n PageSelectorItem,\n PageSelectorItemInput,\n PluginSwitcherInfo,\n PluginSwitcherPlugin,\n AccountMenuItem,\n SettingsTab,\n} from '../types/components'\nimport { normalizeItemInput } from '../utils/items'\nimport ThemeToggle from './ThemeToggle.vue'\nimport AppTopBarPageSelectorInternal from './internal/AppTopBarPageSelectorInternal.vue'\nimport AppTopBarPillNavInternal from './internal/AppTopBarPillNavInternal.vue'\nimport AppAvatarMenu from './AppAvatarMenu.vue'\nimport AppPluginSwitcher from './AppPluginSwitcher.vue'\nimport PluginIcon from './PluginIcon.vue'\nimport { usePlatformContext } from '../composables/usePlatformContext'\nimport { APP_EXPERIMENT_KEY } from '../composables/useAppExperiment'\nimport {\n currentItemIdFromLocation,\n pluginNavItemToPageSelectorItem,\n} from './AppTopBar.navigation'\n\ninterface Props {\n /** App or plugin title shown in the left title group when no page selector is present. */\n title?: string\n /** Secondary title copy shown under the title in title-group layouts. */\n subtitle?: string\n /** Show the default MINT logo when the icon/logo slot is not provided. */\n showLogo?: boolean\n /** Top bar visual treatment. */\n variant?: TopBarVariant\n /** Home link used by the brand icon. */\n homePath?: string\n\n /** Preferred route-level page switch entries for plugin and platform pages. Integrated plugins read platform plugin.nav_items metadata automatically when pageSelector is omitted. */\n pageSelector?: PageSelectorItemInput[]\n /** Active id for the preferred page selector. */\n currentPageSelectorId?: string\n /** Optional plugin switcher shown in the left navigation position instead of pageSelector. */\n pluginSwitcher?: PluginSwitcherInfo\n\n /** Preferred centered navigation for local modes inside the current route. */\n pillNav?: PillNavItemInput[]\n /** Active id for the preferred centered pill navigation. */\n currentPillId?: string\n\n /** Account dropdown entries. Takes precedence over the classic profile button. */\n accountMenu?: AccountMenuItem[]\n /** Show the notifications icon button. */\n showNotifications?: boolean\n /** Draw a notification dot on the notifications icon. */\n hasNotificationDot?: boolean\n\n /** Show the theme toggle button. */\n showThemeToggle?: boolean\n /** Show the settings button and modal. */\n showSettings?: boolean\n /** Built-in SettingsModal configuration. */\n settingsConfig?: TopBarSettingsConfig\n /** Show the standalone badge when the plugin is not integrated into the platform. */\n showStandaloneLabel?: boolean\n /** Custom standalone badge label. */\n standaloneLabel?: string\n /** Show the classic admin shortcut. */\n showAdmin?: boolean\n /** Route used by the classic admin shortcut. */\n adminPath?: string\n /** Show the classic profile button when accountMenu is not provided. */\n showProfile?: boolean\n /** Classic profile display name. */\n userName?: string\n /** Explicit classic profile initial. */\n userInitial?: string\n /** Classic profile email, used by avatar/account layouts. */\n userEmail?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showLogo: true,\n variant: 'card',\n homePath: '/',\n showThemeToggle: false,\n showSettings: false,\n showStandaloneLabel: true,\n standaloneLabel: 'Standalone',\n showAdmin: false,\n adminPath: '/admin',\n showProfile: false,\n showNotifications: false,\n hasNotificationDot: false,\n})\n\nconst SettingsModal = defineAsyncComponent(() => import('./SettingsModal.vue'))\nconst ExperimentPopover = defineAsyncComponent(() => import('./ExperimentPopover.vue'))\nconst ExperimentSelectorModal = defineAsyncComponent(() => import('./ExperimentSelectorModal.vue'))\n\nconst emit = defineEmits<{\n 'profile-click': []\n 'admin-click': []\n 'page-selector-select': [page: PageSelectorItem]\n 'pill-select': [item: PillNavItem]\n 'pill-option-select': [option: PillNavOption, item: PillNavItem]\n 'plugin-switcher-select': [plugin: PluginSwitcherPlugin]\n 'plugin-switcher-install': []\n 'account-menu-select': [item: AccountMenuItem]\n 'sign-out': []\n 'notifications-click': []\n 'settings-values-change': [data: Record<string, unknown>]\n}>()\n\nconst settingsOpen = ref(false)\nconst { isIntegrated, plugin } = usePlatformContext()\nconst isStandalone = computed(() => !isIntegrated.value)\nconst appExperiment = inject(APP_EXPERIMENT_KEY, null)\n\nconst pluginIcon = computed(() => {\n const currentPlugin = plugin.value\n return currentPlugin?.icon\n ? { icon: currentPlugin.icon, color: currentPlugin.color }\n : null\n})\n\nconst profileInitial = computed(() => {\n if (props.userInitial) return props.userInitial\n if (props.userName) return props.userName.charAt(0).toUpperCase()\n return 'U'\n})\n\nconst hasPlatformPageSelector = computed(() =>\n props.pageSelector === undefined && (plugin.value?.nav_items?.length ?? 0) > 1,\n)\nconst hasPageSelector = computed(() => (props.pageSelector?.length ?? 0) > 1 || hasPlatformPageSelector.value)\nconst hasPluginSwitcher = computed(() => !!props.pluginSwitcher)\nconst hasPillNav = computed(() => !!props.pillNav?.length)\nconst hasAccountMenu = computed(() => !!props.accountMenu?.length)\nconst hasTitleGroup = computed(\n () =>\n !hasPageSelector.value &&\n !hasPluginSwitcher.value &&\n !!props.title &&\n !!props.subtitle\n)\nconst hasTitleOnly = computed(\n () =>\n !hasPageSelector.value &&\n !hasPluginSwitcher.value &&\n !hasTitleGroup.value &&\n !!props.title,\n)\nconst platformPageSelector = computed<PageSelectorItem[]>(() =>\n hasPlatformPageSelector.value\n ? plugin.value?.nav_items?.map((item, index) =>\n pluginNavItemToPageSelectorItem(item, index, {\n pluginIcon: plugin.value?.icon,\n pluginName: plugin.value?.name,\n })\n ) ?? []\n : [],\n)\nconst normalizedPageSelector = computed<PageSelectorItem[]>(() =>\n props.pageSelector !== undefined\n ? props.pageSelector.map(normalizeItemInput)\n : platformPageSelector.value,\n)\nconst normalizedPillNav = computed<PillNavItemInput[]>(() => props.pillNav ?? [])\nconst normalizedSettingsTabs = computed<SettingsTab[]>(() =>\n props.settingsConfig?.tabs?.map(normalizeItemInput) ?? [],\n)\nconst effectiveCurrentPageSelectorId = computed(() =>\n props.currentPageSelectorId\n ?? (hasPlatformPageSelector.value\n ? currentItemIdFromLocation(\n normalizedPageSelector.value,\n typeof window === 'undefined' ? undefined : window.location.pathname,\n plugin.value?.route_prefix,\n )\n : undefined)\n ?? (hasPlatformPageSelector.value ? normalizedPageSelector.value[0]?.id : undefined),\n)\n</script>\n\n<template>\n <header\n :class=\"[\n 'mint-topbar',\n `mint-topbar--${props.variant}`,\n ]\"\n >\n <div class=\"mint-topbar__container\">\n <!-- Left: brand -->\n <div class=\"mint-topbar__brand\">\n <a\n v-if=\"homePath && (homePath.startsWith('http') || homePath.startsWith('/'))\"\n :href=\"homePath\"\n class=\"mint-topbar-home-link\"\n >\n <slot name=\"icon\">\n <PluginIcon\n v-if=\"pluginIcon\"\n class=\"mint-topbar__plugin-icon\"\n :icon=\"pluginIcon.icon\"\n :tone=\"pluginIcon.color\"\n size=\"md\"\n variant=\"solid\"\n />\n <slot v-else name=\"logo\">\n <div v-if=\"showLogo\" class=\"mint-topbar__logo\">\n <div class=\"mint-topbar__logo-icon\">\n <span class=\"mint-topbar__logo-text\">M</span>\n </div>\n </div>\n </slot>\n </slot>\n </a>\n <router-link\n v-else-if=\"homePath\"\n :to=\"homePath\"\n class=\"mint-topbar-home-link\"\n >\n <slot name=\"icon\">\n <PluginIcon\n v-if=\"pluginIcon\"\n class=\"mint-topbar__plugin-icon\"\n :icon=\"pluginIcon.icon\"\n :tone=\"pluginIcon.color\"\n size=\"md\"\n variant=\"solid\"\n />\n <slot v-else name=\"logo\">\n <div v-if=\"showLogo\" class=\"mint-topbar__logo\">\n <div class=\"mint-topbar__logo-icon\">\n <span class=\"mint-topbar__logo-text\">M</span>\n </div>\n </div>\n </slot>\n </slot>\n </router-link>\n <template v-else>\n <slot name=\"icon\">\n <PluginIcon\n v-if=\"pluginIcon\"\n class=\"mint-topbar__plugin-icon\"\n :icon=\"pluginIcon.icon\"\n :tone=\"pluginIcon.color\"\n size=\"md\"\n variant=\"solid\"\n />\n <slot v-else name=\"logo\">\n <div v-if=\"showLogo\" class=\"mint-topbar__logo\">\n <div class=\"mint-topbar__logo-icon\">\n <span class=\"mint-topbar__logo-text\">M</span>\n </div>\n </div>\n </slot>\n </slot>\n </template>\n </div>\n\n <!-- Left: page selector or plugin switcher (new) -->\n <AppPluginSwitcher\n v-if=\"hasPluginSwitcher && pluginSwitcher\"\n :current=\"pluginSwitcher.current\"\n :plugins=\"pluginSwitcher.plugins\"\n :install-to=\"pluginSwitcher.installTo\"\n :install-href=\"pluginSwitcher.installHref\"\n @select=\"emit('plugin-switcher-select', $event)\"\n @install-click=\"emit('plugin-switcher-install')\"\n />\n <AppTopBarPageSelectorInternal\n v-else-if=\"hasPageSelector\"\n :pages=\"normalizedPageSelector\"\n :current-page-id=\"effectiveCurrentPageSelectorId\"\n @select=\"emit('page-selector-select', $event)\"\n >\n <template v-if=\"$slots['page-selector-icon']\" #icon=\"slotProps\">\n <slot name=\"page-selector-icon\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"$slots['page-selector-item-icon']\" #item-icon=\"slotProps\">\n <slot name=\"page-selector-item-icon\" v-bind=\"slotProps\" />\n </template>\n </AppTopBarPageSelectorInternal>\n\n <!-- Left: title -->\n <div v-if=\"hasTitleGroup\" class=\"mint-topbar-title-group\">\n <span class=\"mint-topbar-title\">{{ title }}</span>\n <span class=\"mint-topbar-subtitle\">{{ subtitle }}</span>\n </div>\n\n <span v-else-if=\"hasTitleOnly\" class=\"mint-topbar__title-only\">{{ title }}</span>\n\n <!-- Nav slot (inline, after brand/selector) -->\n <slot name=\"nav\" />\n\n <!-- Center: pill nav (new) -->\n <div v-if=\"hasPillNav || $slots.center\" class=\"mint-topbar__center\">\n <slot name=\"center\">\n <AppTopBarPillNavInternal\n v-if=\"hasPillNav && pillNav\"\n :items=\"normalizedPillNav\"\n :current-item-id=\"currentPillId\"\n @select=\"emit('pill-select', $event)\"\n @option-select=\"(option, item) => emit('pill-option-select', option, item)\"\n />\n </slot>\n </div>\n\n <!-- Right section -->\n <div class=\"mint-topbar__right\">\n <span v-if=\"showStandaloneLabel && isStandalone && !appExperiment\" class=\"mint-topbar__standalone-badge\">\n {{ standaloneLabel }}\n </span>\n\n <ExperimentPopover\n v-if=\"appExperiment && !isStandalone\"\n v-bind=\"appExperiment.popover.value\"\n @select=\"appExperiment.openModal()\"\n @save=\"appExperiment.handleSave()\"\n @detach=\"appExperiment.handleDetach()\"\n />\n\n <span v-if=\"appExperiment && isStandalone\" class=\"mint-topbar__standalone-badge\">\n {{ standaloneLabel }}\n </span>\n\n <slot name=\"actions\" />\n\n <!-- Notifications (new) -->\n <button\n v-if=\"showNotifications\"\n type=\"button\"\n class=\"mint-topbar__icon-btn\"\n aria-label=\"Notifications\"\n @click=\"emit('notifications-click')\"\n >\n <svg class=\"mint-topbar__icon-btn-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9\" />\n <path d=\"M10.3 21a1.94 1.94 0 0 0 3.4 0\" />\n </svg>\n <span v-if=\"hasNotificationDot\" class=\"mint-topbar__icon-btn-dot\" aria-hidden=\"true\" />\n </button>\n\n <!-- Classic: theme toggle -->\n <ThemeToggle v-if=\"showThemeToggle\" size=\"sm\" />\n\n <!-- Classic: settings gear -->\n <button\n v-if=\"showSettings\"\n type=\"button\"\n class=\"mint-topbar__settings-btn\"\n aria-label=\"Open settings\"\n @click=\"settingsOpen = true\"\n >\n <svg class=\"mint-topbar__settings-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915\" /><circle cx=\"12\" cy=\"12\" r=\"3\" />\n </svg>\n </button>\n\n <!-- Classic: admin link -->\n <router-link\n v-if=\"showAdmin\"\n :to=\"adminPath\"\n class=\"mint-topbar__admin-btn\"\n aria-label=\"Admin Dashboard\"\n @click=\"emit('admin-click')\"\n >\n <svg class=\"mint-topbar__admin-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z\" /><path d=\"m9 12 2 2 4-4\" />\n </svg>\n </router-link>\n\n <!-- New: avatar menu (takes precedence over classic profile button) -->\n <AppAvatarMenu\n v-if=\"hasAccountMenu || $slots['account-menu-items']\"\n :user-name=\"userName\"\n :user-initial=\"userInitial\"\n :user-email=\"userEmail\"\n :items=\"accountMenu\"\n @select=\"emit('account-menu-select', $event)\"\n @sign-out=\"emit('sign-out')\"\n >\n <template v-if=\"$slots['account-menu-items']\" #items=\"slotProps\">\n <slot name=\"account-menu-items\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"$slots['account-menu-item-icon']\" #item-icon=\"slotProps\">\n <slot name=\"account-menu-item-icon\" v-bind=\"slotProps\" />\n </template>\n </AppAvatarMenu>\n\n <!-- Classic: profile button (only when avatar menu not provided) -->\n <button\n v-else-if=\"showProfile\"\n type=\"button\"\n class=\"mint-topbar__profile-btn\"\n aria-label=\"Edit profile\"\n @click=\"emit('profile-click')\"\n >\n <div class=\"mint-topbar__profile-avatar\">\n {{ profileInitial }}\n </div>\n <span v-if=\"userName\" class=\"mint-topbar__profile-name\">{{ userName }}</span>\n </button>\n </div>\n </div>\n </header>\n\n <SettingsModal\n v-if=\"showSettings && settingsOpen\"\n v-model=\"settingsOpen\"\n :title=\"settingsConfig?.title\"\n :tabs=\"normalizedSettingsTabs\"\n :show-appearance=\"settingsConfig?.showAppearance ?? true\"\n :size=\"settingsConfig?.size\"\n :layout=\"settingsConfig?.layout\"\n :schema=\"settingsConfig?.schema\"\n :model=\"settingsConfig?.model\"\n :controls=\"settingsConfig?.controls\"\n :control-options=\"settingsConfig?.controlOptions\"\n :values=\"settingsConfig?.values\"\n :enhancements=\"settingsConfig?.enhancements\"\n :user-type=\"settingsConfig?.userType\"\n @update:values=\"emit('settings-values-change', $event)\"\n >\n <template v-for=\"tab in normalizedSettingsTabs\" :key=\"tab.id\" #[`tab-${tab.id}`]>\n <slot :name=\"`settings-tab-${tab.id}`\" />\n </template>\n <template #appearance>\n <slot name=\"settings-appearance\" />\n </template>\n </SettingsModal>\n\n <ExperimentSelectorModal\n v-if=\"appExperiment && !isStandalone && appExperiment.selectorModal.value.modelValue\"\n v-bind=\"appExperiment.selectorModal.value\"\n @update:model-value=\"$event ? appExperiment.openModal() : appExperiment.closeModal()\"\n @select=\"appExperiment.handleSelect($event)\"\n @deselect=\"appExperiment.handleDetach()\"\n />\n</template>\n\n<style>\n@import '../styles/components/app-top-bar.css';\n</style>\n","<script setup lang=\"ts\">\n/** Multi-step wizard with a progress stepper, per-step validity tracking, and Back/Next navigation. */\nimport { ref, computed } from 'vue'\nimport type { WizardStep, WizardStepState } from '../types'\n\nexport interface Props {\n steps: WizardStep[]\n modelValue?: number\n linear?: boolean\n showProgress?: boolean\n showStepNumbers?: boolean\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: 0,\n linear: true,\n showProgress: true,\n showStepNumbers: true,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [index: number]\n 'complete': []\n 'cancel': []\n 'step-change': [change: [from: number, to: number]]\n}>()\n\nconst stepValidity = ref<boolean[]>([])\n\n// Initialize validity for all steps to true\nif (stepValidity.value.length === 0) {\n stepValidity.value = props.steps.map(() => true)\n}\n\nconst stepStates = computed<WizardStepState[]>(() => {\n return props.steps.map((_, index) => {\n if (index === props.modelValue) return 'active'\n if (index < props.modelValue && stepValidity.value[index] !== false) return 'completed'\n if (index > props.modelValue && props.linear) {\n for (let i = props.modelValue; i < index; i++) {\n if (stepValidity.value[i] === false) return 'disabled'\n }\n }\n return 'pending'\n })\n})\n\nfunction setStepValid(index: number, valid: boolean) {\n if (index >= 0 && index < props.steps.length) {\n stepValidity.value[index] = valid\n }\n}\n\nfunction goToStep(index: number) {\n if (index < 0 || index >= props.steps.length) return\n if (stepStates.value[index] === 'disabled') return\n\n if (props.linear && index > props.modelValue) {\n for (let i = props.modelValue; i < index; i++) {\n if (stepValidity.value[i] === false) return\n }\n }\n\n const from = props.modelValue\n emit('step-change', [from, index])\n emit('update:modelValue', index)\n}\n\nfunction goNext() {\n if (stepValidity.value[props.modelValue] === false) return\n if (props.modelValue < props.steps.length - 1) {\n goToStep(props.modelValue + 1)\n }\n}\n\nfunction goBack() {\n if (props.modelValue > 0) {\n goToStep(props.modelValue - 1)\n }\n}\n\nfunction finish() {\n if (stepValidity.value[props.modelValue] === false) return\n if (props.modelValue === props.steps.length - 1) {\n emit('complete')\n }\n}\n\nfunction cancel() {\n emit('cancel')\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n goBack()\n break\n case 'ArrowRight':\n event.preventDefault()\n goNext()\n break\n case 'Escape':\n cancel()\n break\n }\n}\n\ndefineExpose({ setStepValid })\n</script>\n\n<template>\n <div\n :class=\"['mint-wizard', `mint-wizard--${props.size}`]\"\n tabindex=\"0\"\n @keydown=\"handleKeydown\"\n >\n <!-- Progress indicator -->\n <div v-if=\"props.showProgress\" class=\"mint-wizard__progress\">\n <slot name=\"progress\" :steps=\"props.steps\" :current=\"props.modelValue\" :states=\"stepStates\">\n <div class=\"mint-wizard__steps-indicator\">\n <template v-for=\"(step, index) in props.steps\" :key=\"step.id\">\n <!-- Connector line (before each step except first) -->\n <div\n v-if=\"index > 0\"\n :class=\"[\n 'mint-wizard__step-connector',\n stepStates[index - 1] === 'completed' ? 'mint-wizard__step-connector--completed' : '',\n ]\"\n />\n <!-- Step indicator -->\n <div\n :class=\"[\n 'mint-wizard__step-indicator',\n `mint-wizard__step-indicator--${stepStates[index]}`,\n ]\"\n @click=\"goToStep(index)\"\n >\n <div class=\"mint-wizard__step-dot\">\n <template v-if=\"stepStates[index] === 'completed'\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </template>\n <template v-else-if=\"props.showStepNumbers\">\n {{ index + 1 }}\n </template>\n </div>\n <div class=\"mint-wizard__step-label\">{{ step.label }}</div>\n </div>\n </template>\n </div>\n </slot>\n </div>\n\n <!-- Step content -->\n <div class=\"mint-wizard__body\">\n <div\n v-for=\"(step, index) in props.steps\"\n :key=\"step.id\"\n v-show=\"index === props.modelValue\"\n class=\"mint-wizard__step-content\"\n >\n <slot :name=\"`step-${step.id}`\" :step=\"step\" :index=\"index\" />\n </div>\n </div>\n\n <!-- Navigation -->\n <div class=\"mint-wizard__navigation\">\n <slot\n name=\"navigation\"\n :go-back=\"goBack\"\n :go-next=\"goNext\"\n :finish=\"finish\"\n :cancel=\"cancel\"\n :is-first=\"props.modelValue === 0\"\n :is-last=\"props.modelValue === props.steps.length - 1\"\n :can-proceed=\"stepValidity[props.modelValue] !== false\"\n >\n <button\n type=\"button\"\n class=\"mint-wizard__nav-btn mint-wizard__nav-btn--cancel\"\n @click=\"cancel\"\n >\n Cancel\n </button>\n <div style=\"flex: 1\" />\n <button\n type=\"button\"\n class=\"mint-wizard__nav-btn mint-wizard__nav-btn--back\"\n :disabled=\"props.modelValue === 0\"\n @click=\"goBack\"\n >\n Back\n </button>\n <button\n v-if=\"props.modelValue < props.steps.length - 1\"\n type=\"button\"\n class=\"mint-wizard__nav-btn mint-wizard__nav-btn--next\"\n :disabled=\"stepValidity[props.modelValue] === false\"\n @click=\"goNext\"\n >\n Next\n </button>\n <button\n v-else\n type=\"button\"\n class=\"mint-wizard__nav-btn mint-wizard__nav-btn--finish\"\n :disabled=\"stepValidity[props.modelValue] === false\"\n @click=\"finish\"\n >\n Finish\n </button>\n </slot>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/step-wizard.css';\n</style>\n","<script setup lang=\"ts\">\n/** Multi-step wizard with a progress stepper, per-step validity tracking, and Back/Next navigation. */\nimport { ref, computed } from 'vue'\nimport type { WizardStep, WizardStepState } from '../types'\n\nexport interface Props {\n steps: WizardStep[]\n modelValue?: number\n linear?: boolean\n showProgress?: boolean\n showStepNumbers?: boolean\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: 0,\n linear: true,\n showProgress: true,\n showStepNumbers: true,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [index: number]\n 'complete': []\n 'cancel': []\n 'step-change': [change: [from: number, to: number]]\n}>()\n\nconst stepValidity = ref<boolean[]>([])\n\n// Initialize validity for all steps to true\nif (stepValidity.value.length === 0) {\n stepValidity.value = props.steps.map(() => true)\n}\n\nconst stepStates = computed<WizardStepState[]>(() => {\n return props.steps.map((_, index) => {\n if (index === props.modelValue) return 'active'\n if (index < props.modelValue && stepValidity.value[index] !== false) return 'completed'\n if (index > props.modelValue && props.linear) {\n for (let i = props.modelValue; i < index; i++) {\n if (stepValidity.value[i] === false) return 'disabled'\n }\n }\n return 'pending'\n })\n})\n\nfunction setStepValid(index: number, valid: boolean) {\n if (index >= 0 && index < props.steps.length) {\n stepValidity.value[index] = valid\n }\n}\n\nfunction goToStep(index: number) {\n if (index < 0 || index >= props.steps.length) return\n if (stepStates.value[index] === 'disabled') return\n\n if (props.linear && index > props.modelValue) {\n for (let i = props.modelValue; i < index; i++) {\n if (stepValidity.value[i] === false) return\n }\n }\n\n const from = props.modelValue\n emit('step-change', [from, index])\n emit('update:modelValue', index)\n}\n\nfunction goNext() {\n if (stepValidity.value[props.modelValue] === false) return\n if (props.modelValue < props.steps.length - 1) {\n goToStep(props.modelValue + 1)\n }\n}\n\nfunction goBack() {\n if (props.modelValue > 0) {\n goToStep(props.modelValue - 1)\n }\n}\n\nfunction finish() {\n if (stepValidity.value[props.modelValue] === false) return\n if (props.modelValue === props.steps.length - 1) {\n emit('complete')\n }\n}\n\nfunction cancel() {\n emit('cancel')\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n goBack()\n break\n case 'ArrowRight':\n event.preventDefault()\n goNext()\n break\n case 'Escape':\n cancel()\n break\n }\n}\n\ndefineExpose({ setStepValid })\n</script>\n\n<template>\n <div\n :class=\"['mint-wizard', `mint-wizard--${props.size}`]\"\n tabindex=\"0\"\n @keydown=\"handleKeydown\"\n >\n <!-- Progress indicator -->\n <div v-if=\"props.showProgress\" class=\"mint-wizard__progress\">\n <slot name=\"progress\" :steps=\"props.steps\" :current=\"props.modelValue\" :states=\"stepStates\">\n <div class=\"mint-wizard__steps-indicator\">\n <template v-for=\"(step, index) in props.steps\" :key=\"step.id\">\n <!-- Connector line (before each step except first) -->\n <div\n v-if=\"index > 0\"\n :class=\"[\n 'mint-wizard__step-connector',\n stepStates[index - 1] === 'completed' ? 'mint-wizard__step-connector--completed' : '',\n ]\"\n />\n <!-- Step indicator -->\n <div\n :class=\"[\n 'mint-wizard__step-indicator',\n `mint-wizard__step-indicator--${stepStates[index]}`,\n ]\"\n @click=\"goToStep(index)\"\n >\n <div class=\"mint-wizard__step-dot\">\n <template v-if=\"stepStates[index] === 'completed'\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </template>\n <template v-else-if=\"props.showStepNumbers\">\n {{ index + 1 }}\n </template>\n </div>\n <div class=\"mint-wizard__step-label\">{{ step.label }}</div>\n </div>\n </template>\n </div>\n </slot>\n </div>\n\n <!-- Step content -->\n <div class=\"mint-wizard__body\">\n <div\n v-for=\"(step, index) in props.steps\"\n :key=\"step.id\"\n v-show=\"index === props.modelValue\"\n class=\"mint-wizard__step-content\"\n >\n <slot :name=\"`step-${step.id}`\" :step=\"step\" :index=\"index\" />\n </div>\n </div>\n\n <!-- Navigation -->\n <div class=\"mint-wizard__navigation\">\n <slot\n name=\"navigation\"\n :go-back=\"goBack\"\n :go-next=\"goNext\"\n :finish=\"finish\"\n :cancel=\"cancel\"\n :is-first=\"props.modelValue === 0\"\n :is-last=\"props.modelValue === props.steps.length - 1\"\n :can-proceed=\"stepValidity[props.modelValue] !== false\"\n >\n <button\n type=\"button\"\n class=\"mint-wizard__nav-btn mint-wizard__nav-btn--cancel\"\n @click=\"cancel\"\n >\n Cancel\n </button>\n <div style=\"flex: 1\" />\n <button\n type=\"button\"\n class=\"mint-wizard__nav-btn mint-wizard__nav-btn--back\"\n :disabled=\"props.modelValue === 0\"\n @click=\"goBack\"\n >\n Back\n </button>\n <button\n v-if=\"props.modelValue < props.steps.length - 1\"\n type=\"button\"\n class=\"mint-wizard__nav-btn mint-wizard__nav-btn--next\"\n :disabled=\"stepValidity[props.modelValue] === false\"\n @click=\"goNext\"\n >\n Next\n </button>\n <button\n v-else\n type=\"button\"\n class=\"mint-wizard__nav-btn mint-wizard__nav-btn--finish\"\n :disabled=\"stepValidity[props.modelValue] === false\"\n @click=\"finish\"\n >\n Finish\n </button>\n </slot>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/step-wizard.css';\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { FormSectionSchema, UseFormBuilderReturn } from '../../types/form-builder'\nimport CollapsibleCard from '../CollapsibleCard.vue'\nimport FormFieldRendererInternal from './FormFieldRendererInternal.vue'\n\ninterface Props {\n section: FormSectionSchema\n builder: UseFormBuilderReturn<Record<string, unknown>>\n}\n\nconst props = defineProps<Props>()\n\nconst visibleFields = computed(() =>\n props.section.fields.filter((f) => props.builder.isFieldVisible(f.name)),\n)\n\nconst gridStyle = computed(() => ({\n gridTemplateColumns: `repeat(${props.section.columns ?? 1}, 1fr)`,\n}))\n</script>\n\n<template>\n <div v-if=\"visibleFields.length > 0\" class=\"mint-form-section\">\n <slot :name=\"`section:${section.id}`\" :section=\"section\" :form=\"builder.form\">\n <CollapsibleCard\n v-if=\"section.collapsible\"\n :title=\"section.title\"\n :subtitle=\"section.description\"\n :default-open=\"section.defaultOpen ?? true\"\n >\n <div class=\"mint-form-section__grid\" :style=\"gridStyle\">\n <div\n v-for=\"field in visibleFields\"\n :key=\"field.name\"\n :style=\"field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined\"\n >\n <slot :name=\"`field:${field.name}`\" :field=\"field\" :form=\"builder.form\" :field-props=\"builder.form.getFieldProps(field.name)\">\n <FormFieldRendererInternal\n :field=\"field\"\n :resolved-props=\"builder.getResolvedFieldProps(field)\"\n :form=\"builder.form\"\n />\n </slot>\n </div>\n </div>\n </CollapsibleCard>\n\n <div v-else class=\"mint-form-section--static\">\n <div v-if=\"section.title || section.description\" class=\"mint-form-section__header\">\n <h3 v-if=\"section.title\" class=\"mint-form-section__title\">{{ section.title }}</h3>\n <p v-if=\"section.description\" class=\"mint-form-section__description\">{{ section.description }}</p>\n </div>\n <div class=\"mint-form-section__grid\" :style=\"gridStyle\">\n <div\n v-for=\"field in visibleFields\"\n :key=\"field.name\"\n :style=\"field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined\"\n >\n <slot :name=\"`field:${field.name}`\" :field=\"field\" :form=\"builder.form\" :field-props=\"builder.form.getFieldProps(field.name)\">\n <FormFieldRendererInternal\n :field=\"field\"\n :resolved-props=\"builder.getResolvedFieldProps(field)\"\n :form=\"builder.form\"\n />\n </slot>\n </div>\n </div>\n </div>\n </slot>\n\n <slot :name=\"`section:${section.id}:after`\" :form=\"builder.form\" />\n </div>\n</template>\n\n<style>\n@import '../../styles/components/form-builder.css';\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { FormSectionSchema, UseFormBuilderReturn } from '../../types/form-builder'\nimport CollapsibleCard from '../CollapsibleCard.vue'\nimport FormFieldRendererInternal from './FormFieldRendererInternal.vue'\n\ninterface Props {\n section: FormSectionSchema\n builder: UseFormBuilderReturn<Record<string, unknown>>\n}\n\nconst props = defineProps<Props>()\n\nconst visibleFields = computed(() =>\n props.section.fields.filter((f) => props.builder.isFieldVisible(f.name)),\n)\n\nconst gridStyle = computed(() => ({\n gridTemplateColumns: `repeat(${props.section.columns ?? 1}, 1fr)`,\n}))\n</script>\n\n<template>\n <div v-if=\"visibleFields.length > 0\" class=\"mint-form-section\">\n <slot :name=\"`section:${section.id}`\" :section=\"section\" :form=\"builder.form\">\n <CollapsibleCard\n v-if=\"section.collapsible\"\n :title=\"section.title\"\n :subtitle=\"section.description\"\n :default-open=\"section.defaultOpen ?? true\"\n >\n <div class=\"mint-form-section__grid\" :style=\"gridStyle\">\n <div\n v-for=\"field in visibleFields\"\n :key=\"field.name\"\n :style=\"field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined\"\n >\n <slot :name=\"`field:${field.name}`\" :field=\"field\" :form=\"builder.form\" :field-props=\"builder.form.getFieldProps(field.name)\">\n <FormFieldRendererInternal\n :field=\"field\"\n :resolved-props=\"builder.getResolvedFieldProps(field)\"\n :form=\"builder.form\"\n />\n </slot>\n </div>\n </div>\n </CollapsibleCard>\n\n <div v-else class=\"mint-form-section--static\">\n <div v-if=\"section.title || section.description\" class=\"mint-form-section__header\">\n <h3 v-if=\"section.title\" class=\"mint-form-section__title\">{{ section.title }}</h3>\n <p v-if=\"section.description\" class=\"mint-form-section__description\">{{ section.description }}</p>\n </div>\n <div class=\"mint-form-section__grid\" :style=\"gridStyle\">\n <div\n v-for=\"field in visibleFields\"\n :key=\"field.name\"\n :style=\"field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined\"\n >\n <slot :name=\"`field:${field.name}`\" :field=\"field\" :form=\"builder.form\" :field-props=\"builder.form.getFieldProps(field.name)\">\n <FormFieldRendererInternal\n :field=\"field\"\n :resolved-props=\"builder.getResolvedFieldProps(field)\"\n :form=\"builder.form\"\n />\n </slot>\n </div>\n </div>\n </div>\n </slot>\n\n <slot :name=\"`section:${section.id}:after`\" :form=\"builder.form\" />\n </div>\n</template>\n\n<style>\n@import '../../styles/components/form-builder.css';\n</style>\n","<script setup lang=\"ts\">\n/**\n * Action bar rendered at the bottom of a FormBuilder.\n *\n * Supports two modes:\n * - Flat form: shows an optional Cancel button and a Submit button.\n * - Wizard (`isWizard=true`): shows Back / Next on intermediate steps and\n * Submit only on the last step. Back is hidden on the first step.\n *\n * The `canProceed` prop gates the primary action (Next or Submit) to prevent\n * advancing when the current step has validation errors.\n */\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n isWizard?: boolean\n isFirst?: boolean\n isLast?: boolean\n canProceed?: boolean\n loading?: boolean\n disabled?: boolean\n submitLabel?: string\n cancelLabel?: string\n showCancel?: boolean\n}\n\nwithDefaults(defineProps<Props>(), {\n isWizard: false,\n isFirst: true,\n isLast: true,\n canProceed: true,\n loading: false,\n disabled: false,\n submitLabel: 'Submit',\n cancelLabel: 'Cancel',\n showCancel: false,\n})\n\nconst emit = defineEmits<{\n submit: []\n cancel: []\n back: []\n next: []\n}>()\n</script>\n\n<template>\n <div class=\"mint-form-actions\">\n <BaseButton\n v-if=\"showCancel\"\n variant=\"ghost\"\n :disabled=\"loading || disabled\"\n @click=\"emit('cancel')\"\n >\n {{ cancelLabel }}\n </BaseButton>\n\n <div style=\"flex: 1\" />\n\n <BaseButton\n v-if=\"isWizard && !isFirst\"\n variant=\"secondary\"\n :disabled=\"loading || disabled\"\n @click=\"emit('back')\"\n >\n Back\n </BaseButton>\n\n <BaseButton\n v-if=\"isWizard && !isLast\"\n variant=\"primary\"\n :disabled=\"!canProceed || loading || disabled\"\n @click=\"emit('next')\"\n >\n Next\n </BaseButton>\n\n <BaseButton\n v-if=\"!isWizard || isLast\"\n variant=\"primary\"\n :loading=\"loading\"\n :disabled=\"!canProceed || loading || disabled\"\n @click=\"emit('submit')\"\n >\n {{ submitLabel }}\n </BaseButton>\n </div>\n</template>\n\n<style>\n@import '../styles/components/form-builder.css';\n</style>\n","<script setup lang=\"ts\">\n/**\n * Action bar rendered at the bottom of a FormBuilder.\n *\n * Supports two modes:\n * - Flat form: shows an optional Cancel button and a Submit button.\n * - Wizard (`isWizard=true`): shows Back / Next on intermediate steps and\n * Submit only on the last step. Back is hidden on the first step.\n *\n * The `canProceed` prop gates the primary action (Next or Submit) to prevent\n * advancing when the current step has validation errors.\n */\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n isWizard?: boolean\n isFirst?: boolean\n isLast?: boolean\n canProceed?: boolean\n loading?: boolean\n disabled?: boolean\n submitLabel?: string\n cancelLabel?: string\n showCancel?: boolean\n}\n\nwithDefaults(defineProps<Props>(), {\n isWizard: false,\n isFirst: true,\n isLast: true,\n canProceed: true,\n loading: false,\n disabled: false,\n submitLabel: 'Submit',\n cancelLabel: 'Cancel',\n showCancel: false,\n})\n\nconst emit = defineEmits<{\n submit: []\n cancel: []\n back: []\n next: []\n}>()\n</script>\n\n<template>\n <div class=\"mint-form-actions\">\n <BaseButton\n v-if=\"showCancel\"\n variant=\"ghost\"\n :disabled=\"loading || disabled\"\n @click=\"emit('cancel')\"\n >\n {{ cancelLabel }}\n </BaseButton>\n\n <div style=\"flex: 1\" />\n\n <BaseButton\n v-if=\"isWizard && !isFirst\"\n variant=\"secondary\"\n :disabled=\"loading || disabled\"\n @click=\"emit('back')\"\n >\n Back\n </BaseButton>\n\n <BaseButton\n v-if=\"isWizard && !isLast\"\n variant=\"primary\"\n :disabled=\"!canProceed || loading || disabled\"\n @click=\"emit('next')\"\n >\n Next\n </BaseButton>\n\n <BaseButton\n v-if=\"!isWizard || isLast\"\n variant=\"primary\"\n :loading=\"loading\"\n :disabled=\"!canProceed || loading || disabled\"\n @click=\"emit('submit')\"\n >\n {{ submitLabel }}\n </BaseButton>\n </div>\n</template>\n\n<style>\n@import '../styles/components/form-builder.css';\n</style>\n","<script setup lang=\"ts\">\n/**\n * Schema-driven form component that renders flat or multi-step wizard forms.\n *\n * Pass a `FormSchema` with either `sections` (flat) or `steps` (wizard), or\n * pass a compact `controls` schema for standard generated forms.\n * Use `v-model` for two-way binding of the form data; the `submit` event\n * carries only the data for currently-visible fields. Supply `enhancements`\n * for dynamic options, custom validators, a submit handler, and field-change\n * callbacks that cannot be expressed in JSON.\n * Compact control schemas can be bound directly:\n * `<FormBuilder :controls=\"controls\" v-model=\"values\" />`.\n * Complete control models can also be bound directly:\n * `<FormBuilder :model=\"workspaceModel\" v-model=\"values\" />`.\n *\n * Exposes `form`, `validate`, `reset`, `updateSchema`, `goNext`, `goBack`,\n * `goToStep`, and `builder` for imperative control via template refs.\n *\n * Slots:\n * - `field:<name>` — override a single field's input component\n * - `section:<id>` — replace an entire section body\n * - `section:<id>:after` — inject content after a section\n * - `actions` — replace the default FormActions bar when `showActions` is true\n */\nimport { ref, computed, watch } from 'vue'\nimport type { FormSchema, FormEnhancements, UseFormBuilderReturn } from '../types/form-builder'\nimport type { WizardStep } from '../types'\nimport {\n controlsToFormSchema,\n getControlDefaults,\n mergeControlWorkspaceOptions,\n resolveControlModel,\n type ControlModel,\n type ControlModelBinding,\n type ControlSchema,\n type ControlWorkspaceOptions,\n} from '../composables/useControlSchema'\nimport { useFormBuilder } from '../composables/useFormBuilder'\nimport {\n formSchemaFieldNames,\n pickExistingRecordKeys,\n recordValuesEqualForKeys,\n} from '../utils/formModelSync'\nimport StepWizard from './StepWizard.vue'\nimport FormSectionRenderer from './internal/FormSectionRenderer.vue'\nimport FormActions from './FormActions.vue'\n\ninterface Props {\n /** Full form or wizard schema. Takes precedence when `controls` is also provided. */\n schema?: FormSchema\n /** Model returned by defineControlModel(), or a raw nested ControlModel for one-step form generation. */\n model?: ControlModel | ControlModelBinding\n /** Compact control schema used to generate a flat FormSchema. */\n controls?: ControlSchema\n /** Options passed to compact control schema generation, including shared initialValues. */\n controlOptions?: ControlWorkspaceOptions\n modelValue?: Record<string, unknown>\n enhancements?: FormEnhancements<Record<string, unknown>>\n loading?: boolean\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n readonly?: boolean\n /** Show the default or slotted form actions. */\n showActions?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n model: undefined,\n controlOptions: () => ({}),\n loading: false,\n disabled: false,\n readonly: false,\n showActions: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [data: Record<string, unknown>]\n submit: [data: Record<string, unknown>]\n cancel: []\n}>()\n\nconst resolvedModel = computed<ControlModelBinding | undefined>(() => {\n return resolveControlModel(props.model)\n})\n\nconst resolvedControls = computed<ControlSchema | undefined>(() =>\n props.controls ?? resolvedModel.value?.controls,\n)\n\nconst resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>\n mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)\n)\n\nconst resolvedSchema = computed<FormSchema>(() => {\n if (props.schema) return props.schema\n if (resolvedControls.value) return controlsToFormSchema(resolvedControls.value, resolvedControlOptions.value)\n return { sections: [] }\n})\n\nconst resolvedModelValue = computed<Record<string, unknown>>(() => ({\n ...(resolvedControls.value ? getControlDefaults(resolvedControls.value) : {}),\n ...(resolvedControlOptions.value.initialValues ?? {}),\n ...(props.modelValue ?? {}),\n}))\n\nconst builder = useFormBuilder(\n resolvedSchema.value,\n resolvedModelValue.value,\n props.enhancements,\n)\n\n// Sync schema/control changes into the internal builder so generated forms stay coherent.\nwatch(\n resolvedSchema,\n (schema) => {\n const fieldNames = formSchemaFieldNames(schema)\n const schemaValues = pickExistingRecordKeys(resolvedModelValue.value, fieldNames)\n const existingValues = pickExistingRecordKeys(\n builder.form.data as Record<string, unknown>,\n fieldNames,\n )\n const nextValues = props.modelValue === undefined\n ? { ...schemaValues, ...existingValues }\n : schemaValues\n\n builder.updateSchema(schema, nextValues)\n },\n { deep: true },\n)\n\n// Sync external modelValue changes into the internal form without resetting on no-op echo updates.\nwatch(\n () => props.modelValue,\n () => {\n const data = resolvedModelValue.value\n const fieldNames = builderFieldNames()\n if (recordValuesEqualForKeys(data, builder.form.data as Record<string, unknown>, fieldNames)) return\n builder.reset(pickExistingRecordKeys(data, fieldNames))\n },\n { deep: true },\n)\n\n// Sync modelValue changes back to parent\nwatch(\n () => ({ ...builder.form.data }),\n (data) => emit('update:modelValue', data as Record<string, unknown>),\n { deep: true },\n)\n\n// Wizard support\nconst isWizard = computed(() => !!resolvedSchema.value.steps)\nconst wizardRef = ref<InstanceType<typeof StepWizard> | null>(null)\n\nconst wizardSteps = computed<WizardStep[]>(() => {\n if (!resolvedSchema.value.steps) return []\n return resolvedSchema.value.steps.map((step) => ({\n id: step.id,\n label: step.label,\n description: step.description,\n icon: step.icon,\n optional: step.optional,\n }))\n})\n\n// Sync step validation state with StepWizard\nwatch(\n () => builder.isCurrentStepValid.value,\n (valid) => {\n wizardRef.value?.setStepValid(builder.currentStep.value, valid)\n },\n)\n\nfunction handleNext() {\n const success = builder.goNext()\n if (success) {\n wizardRef.value?.setStepValid(builder.currentStep.value - 1, true)\n }\n}\n\nasync function handleSubmit() {\n await builder.submit()\n const formData = builder.form.data as Record<string, unknown>\n const visibleData = Object.fromEntries(\n builder.fields\n .filter((f) => builder.isFieldVisible(f.name))\n .map((f) => [f.name, formData[f.name]]),\n )\n emit('submit', visibleData)\n}\n\nfunction handleCancel() {\n emit('cancel')\n}\n\nfunction builderFieldNames(): string[] {\n return builder.fields.map(field => field.name)\n}\n\ndefineExpose({\n form: builder.form,\n validate: builder.validate,\n reset: builder.reset,\n updateSchema: builder.updateSchema,\n goNext: builder.goNext,\n goBack: builder.goBack,\n goToStep: builder.goToStep,\n builder,\n})\n</script>\n\n<template>\n <div :class=\"['mint-form-builder', size ? `mint-form-builder--${size}` : '']\">\n <!-- Wizard mode -->\n <template v-if=\"isWizard && resolvedSchema.steps\">\n <StepWizard\n ref=\"wizardRef\"\n :steps=\"wizardSteps\"\n :model-value=\"builder.currentStep.value\"\n @update:model-value=\"builder.goToStep($event)\"\n >\n <template v-for=\"step in resolvedSchema.steps\" :key=\"step.id\" #[`step-${step.id}`]>\n <div class=\"mint-form-builder__step\">\n <template v-for=\"section in step.sections\" :key=\"section.id\">\n <FormSectionRenderer\n v-if=\"builder.isSectionVisible(section.id)\"\n :section=\"section\"\n :builder=\"(builder as UseFormBuilderReturn<Record<string, unknown>>)\"\n >\n <!-- Forward field slots -->\n <template v-for=\"field in section.fields\" :key=\"field.name\" #[`field:${field.name}`]=\"slotProps\">\n <slot :name=\"`field:${field.name}`\" v-bind=\"slotProps\" />\n </template>\n <!-- Forward section slots -->\n <template #[`section:${section.id}`]=\"slotProps\">\n <slot :name=\"`section:${section.id}`\" v-bind=\"slotProps\" />\n </template>\n <template #[`section:${section.id}:after`]=\"slotProps\">\n <slot :name=\"`section:${section.id}:after`\" v-bind=\"slotProps\" />\n </template>\n </FormSectionRenderer>\n </template>\n </div>\n </template>\n\n <template v-if=\"showActions\" #navigation=\"{ isFirst, isLast }\">\n <slot name=\"actions\" :form=\"builder.form\" :builder=\"builder\">\n <FormActions\n is-wizard\n :is-first=\"isFirst\"\n :is-last=\"isLast\"\n :can-proceed=\"builder.isCurrentStepValid.value\"\n :loading=\"loading || builder.form.isSubmitting.value\"\n :disabled=\"disabled\"\n :submit-label=\"resolvedSchema.submitLabel ?? 'Submit'\"\n :cancel-label=\"resolvedSchema.cancelLabel ?? 'Cancel'\"\n :show-cancel=\"resolvedSchema.showCancel ?? false\"\n @next=\"handleNext\"\n @back=\"builder.goBack\"\n @submit=\"handleSubmit\"\n @cancel=\"handleCancel\"\n />\n </slot>\n </template>\n </StepWizard>\n </template>\n\n <!-- Flat mode -->\n <template v-else-if=\"resolvedSchema.sections\">\n <template v-for=\"section in resolvedSchema.sections\" :key=\"section.id\">\n <FormSectionRenderer\n v-if=\"builder.isSectionVisible(section.id)\"\n :section=\"section\"\n :builder=\"(builder as UseFormBuilderReturn<Record<string, unknown>>)\"\n >\n <!-- Forward field slots -->\n <template v-for=\"field in section.fields\" :key=\"field.name\" #[`field:${field.name}`]=\"slotProps\">\n <slot :name=\"`field:${field.name}`\" v-bind=\"slotProps\" />\n </template>\n <!-- Forward section slots -->\n <template #[`section:${section.id}`]=\"slotProps\">\n <slot :name=\"`section:${section.id}`\" v-bind=\"slotProps\" />\n </template>\n <template #[`section:${section.id}:after`]=\"slotProps\">\n <slot :name=\"`section:${section.id}:after`\" v-bind=\"slotProps\" />\n </template>\n </FormSectionRenderer>\n </template>\n\n <slot v-if=\"showActions\" name=\"actions\" :form=\"builder.form\" :builder=\"builder\">\n <FormActions\n :loading=\"loading || builder.form.isSubmitting.value\"\n :disabled=\"disabled\"\n :submit-label=\"resolvedSchema.submitLabel ?? 'Submit'\"\n :cancel-label=\"resolvedSchema.cancelLabel ?? 'Cancel'\"\n :show-cancel=\"resolvedSchema.showCancel ?? false\"\n @submit=\"handleSubmit\"\n @cancel=\"handleCancel\"\n />\n </slot>\n </template>\n </div>\n</template>\n\n<style>\n@import '../styles/components/form-builder.css';\n</style>\n","<script setup lang=\"ts\">\n/**\n * Schema-driven form component that renders flat or multi-step wizard forms.\n *\n * Pass a `FormSchema` with either `sections` (flat) or `steps` (wizard), or\n * pass a compact `controls` schema for standard generated forms.\n * Use `v-model` for two-way binding of the form data; the `submit` event\n * carries only the data for currently-visible fields. Supply `enhancements`\n * for dynamic options, custom validators, a submit handler, and field-change\n * callbacks that cannot be expressed in JSON.\n * Compact control schemas can be bound directly:\n * `<FormBuilder :controls=\"controls\" v-model=\"values\" />`.\n * Complete control models can also be bound directly:\n * `<FormBuilder :model=\"workspaceModel\" v-model=\"values\" />`.\n *\n * Exposes `form`, `validate`, `reset`, `updateSchema`, `goNext`, `goBack`,\n * `goToStep`, and `builder` for imperative control via template refs.\n *\n * Slots:\n * - `field:<name>` — override a single field's input component\n * - `section:<id>` — replace an entire section body\n * - `section:<id>:after` — inject content after a section\n * - `actions` — replace the default FormActions bar when `showActions` is true\n */\nimport { ref, computed, watch } from 'vue'\nimport type { FormSchema, FormEnhancements, UseFormBuilderReturn } from '../types/form-builder'\nimport type { WizardStep } from '../types'\nimport {\n controlsToFormSchema,\n getControlDefaults,\n mergeControlWorkspaceOptions,\n resolveControlModel,\n type ControlModel,\n type ControlModelBinding,\n type ControlSchema,\n type ControlWorkspaceOptions,\n} from '../composables/useControlSchema'\nimport { useFormBuilder } from '../composables/useFormBuilder'\nimport {\n formSchemaFieldNames,\n pickExistingRecordKeys,\n recordValuesEqualForKeys,\n} from '../utils/formModelSync'\nimport StepWizard from './StepWizard.vue'\nimport FormSectionRenderer from './internal/FormSectionRenderer.vue'\nimport FormActions from './FormActions.vue'\n\ninterface Props {\n /** Full form or wizard schema. Takes precedence when `controls` is also provided. */\n schema?: FormSchema\n /** Model returned by defineControlModel(), or a raw nested ControlModel for one-step form generation. */\n model?: ControlModel | ControlModelBinding\n /** Compact control schema used to generate a flat FormSchema. */\n controls?: ControlSchema\n /** Options passed to compact control schema generation, including shared initialValues. */\n controlOptions?: ControlWorkspaceOptions\n modelValue?: Record<string, unknown>\n enhancements?: FormEnhancements<Record<string, unknown>>\n loading?: boolean\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n readonly?: boolean\n /** Show the default or slotted form actions. */\n showActions?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n model: undefined,\n controlOptions: () => ({}),\n loading: false,\n disabled: false,\n readonly: false,\n showActions: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [data: Record<string, unknown>]\n submit: [data: Record<string, unknown>]\n cancel: []\n}>()\n\nconst resolvedModel = computed<ControlModelBinding | undefined>(() => {\n return resolveControlModel(props.model)\n})\n\nconst resolvedControls = computed<ControlSchema | undefined>(() =>\n props.controls ?? resolvedModel.value?.controls,\n)\n\nconst resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>\n mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)\n)\n\nconst resolvedSchema = computed<FormSchema>(() => {\n if (props.schema) return props.schema\n if (resolvedControls.value) return controlsToFormSchema(resolvedControls.value, resolvedControlOptions.value)\n return { sections: [] }\n})\n\nconst resolvedModelValue = computed<Record<string, unknown>>(() => ({\n ...(resolvedControls.value ? getControlDefaults(resolvedControls.value) : {}),\n ...(resolvedControlOptions.value.initialValues ?? {}),\n ...(props.modelValue ?? {}),\n}))\n\nconst builder = useFormBuilder(\n resolvedSchema.value,\n resolvedModelValue.value,\n props.enhancements,\n)\n\n// Sync schema/control changes into the internal builder so generated forms stay coherent.\nwatch(\n resolvedSchema,\n (schema) => {\n const fieldNames = formSchemaFieldNames(schema)\n const schemaValues = pickExistingRecordKeys(resolvedModelValue.value, fieldNames)\n const existingValues = pickExistingRecordKeys(\n builder.form.data as Record<string, unknown>,\n fieldNames,\n )\n const nextValues = props.modelValue === undefined\n ? { ...schemaValues, ...existingValues }\n : schemaValues\n\n builder.updateSchema(schema, nextValues)\n },\n { deep: true },\n)\n\n// Sync external modelValue changes into the internal form without resetting on no-op echo updates.\nwatch(\n () => props.modelValue,\n () => {\n const data = resolvedModelValue.value\n const fieldNames = builderFieldNames()\n if (recordValuesEqualForKeys(data, builder.form.data as Record<string, unknown>, fieldNames)) return\n builder.reset(pickExistingRecordKeys(data, fieldNames))\n },\n { deep: true },\n)\n\n// Sync modelValue changes back to parent\nwatch(\n () => ({ ...builder.form.data }),\n (data) => emit('update:modelValue', data as Record<string, unknown>),\n { deep: true },\n)\n\n// Wizard support\nconst isWizard = computed(() => !!resolvedSchema.value.steps)\nconst wizardRef = ref<InstanceType<typeof StepWizard> | null>(null)\n\nconst wizardSteps = computed<WizardStep[]>(() => {\n if (!resolvedSchema.value.steps) return []\n return resolvedSchema.value.steps.map((step) => ({\n id: step.id,\n label: step.label,\n description: step.description,\n icon: step.icon,\n optional: step.optional,\n }))\n})\n\n// Sync step validation state with StepWizard\nwatch(\n () => builder.isCurrentStepValid.value,\n (valid) => {\n wizardRef.value?.setStepValid(builder.currentStep.value, valid)\n },\n)\n\nfunction handleNext() {\n const success = builder.goNext()\n if (success) {\n wizardRef.value?.setStepValid(builder.currentStep.value - 1, true)\n }\n}\n\nasync function handleSubmit() {\n await builder.submit()\n const formData = builder.form.data as Record<string, unknown>\n const visibleData = Object.fromEntries(\n builder.fields\n .filter((f) => builder.isFieldVisible(f.name))\n .map((f) => [f.name, formData[f.name]]),\n )\n emit('submit', visibleData)\n}\n\nfunction handleCancel() {\n emit('cancel')\n}\n\nfunction builderFieldNames(): string[] {\n return builder.fields.map(field => field.name)\n}\n\ndefineExpose({\n form: builder.form,\n validate: builder.validate,\n reset: builder.reset,\n updateSchema: builder.updateSchema,\n goNext: builder.goNext,\n goBack: builder.goBack,\n goToStep: builder.goToStep,\n builder,\n})\n</script>\n\n<template>\n <div :class=\"['mint-form-builder', size ? `mint-form-builder--${size}` : '']\">\n <!-- Wizard mode -->\n <template v-if=\"isWizard && resolvedSchema.steps\">\n <StepWizard\n ref=\"wizardRef\"\n :steps=\"wizardSteps\"\n :model-value=\"builder.currentStep.value\"\n @update:model-value=\"builder.goToStep($event)\"\n >\n <template v-for=\"step in resolvedSchema.steps\" :key=\"step.id\" #[`step-${step.id}`]>\n <div class=\"mint-form-builder__step\">\n <template v-for=\"section in step.sections\" :key=\"section.id\">\n <FormSectionRenderer\n v-if=\"builder.isSectionVisible(section.id)\"\n :section=\"section\"\n :builder=\"(builder as UseFormBuilderReturn<Record<string, unknown>>)\"\n >\n <!-- Forward field slots -->\n <template v-for=\"field in section.fields\" :key=\"field.name\" #[`field:${field.name}`]=\"slotProps\">\n <slot :name=\"`field:${field.name}`\" v-bind=\"slotProps\" />\n </template>\n <!-- Forward section slots -->\n <template #[`section:${section.id}`]=\"slotProps\">\n <slot :name=\"`section:${section.id}`\" v-bind=\"slotProps\" />\n </template>\n <template #[`section:${section.id}:after`]=\"slotProps\">\n <slot :name=\"`section:${section.id}:after`\" v-bind=\"slotProps\" />\n </template>\n </FormSectionRenderer>\n </template>\n </div>\n </template>\n\n <template v-if=\"showActions\" #navigation=\"{ isFirst, isLast }\">\n <slot name=\"actions\" :form=\"builder.form\" :builder=\"builder\">\n <FormActions\n is-wizard\n :is-first=\"isFirst\"\n :is-last=\"isLast\"\n :can-proceed=\"builder.isCurrentStepValid.value\"\n :loading=\"loading || builder.form.isSubmitting.value\"\n :disabled=\"disabled\"\n :submit-label=\"resolvedSchema.submitLabel ?? 'Submit'\"\n :cancel-label=\"resolvedSchema.cancelLabel ?? 'Cancel'\"\n :show-cancel=\"resolvedSchema.showCancel ?? false\"\n @next=\"handleNext\"\n @back=\"builder.goBack\"\n @submit=\"handleSubmit\"\n @cancel=\"handleCancel\"\n />\n </slot>\n </template>\n </StepWizard>\n </template>\n\n <!-- Flat mode -->\n <template v-else-if=\"resolvedSchema.sections\">\n <template v-for=\"section in resolvedSchema.sections\" :key=\"section.id\">\n <FormSectionRenderer\n v-if=\"builder.isSectionVisible(section.id)\"\n :section=\"section\"\n :builder=\"(builder as UseFormBuilderReturn<Record<string, unknown>>)\"\n >\n <!-- Forward field slots -->\n <template v-for=\"field in section.fields\" :key=\"field.name\" #[`field:${field.name}`]=\"slotProps\">\n <slot :name=\"`field:${field.name}`\" v-bind=\"slotProps\" />\n </template>\n <!-- Forward section slots -->\n <template #[`section:${section.id}`]=\"slotProps\">\n <slot :name=\"`section:${section.id}`\" v-bind=\"slotProps\" />\n </template>\n <template #[`section:${section.id}:after`]=\"slotProps\">\n <slot :name=\"`section:${section.id}:after`\" v-bind=\"slotProps\" />\n </template>\n </FormSectionRenderer>\n </template>\n\n <slot v-if=\"showActions\" name=\"actions\" :form=\"builder.form\" :builder=\"builder\">\n <FormActions\n :loading=\"loading || builder.form.isSubmitting.value\"\n :disabled=\"disabled\"\n :submit-label=\"resolvedSchema.submitLabel ?? 'Submit'\"\n :cancel-label=\"resolvedSchema.cancelLabel ?? 'Cancel'\"\n :show-cancel=\"resolvedSchema.showCancel ?? false\"\n @submit=\"handleSubmit\"\n @cancel=\"handleCancel\"\n />\n </slot>\n </template>\n </div>\n</template>\n\n<style>\n@import '../styles/components/form-builder.css';\n</style>\n","<script setup lang=\"ts\">\n/**\n * AppSidebar - Context-sensitive toolkit panel\n *\n * Shows tool sections relevant to the active view. Sections are defined via\n * the `panels` config and rendered as CollapsibleCards. Controls can be\n * provided through named slots or auto-rendered from FormBuilder schemas.\n *\n * When activeView is omitted, the first non-empty panel view is selected.\n * When no view has matching panels, the sidebar hides entirely.\n *\n * @example\n * ```vue\n * <AppSidebar :panels=\"toolPanels\" :active-view=\"activeTab\">\n * <template #section-parameters>\n * <BaseSlider v-model=\"threshold\" label=\"Threshold\" />\n * </template>\n * <template #section-display>\n * <BaseToggle v-model=\"showOutliers\" label=\"Show outliers\" />\n * </template>\n * <template #header>Plugin Tools</template>\n * </AppSidebar>\n *\n * <!-- Direct schema-driven controls -->\n * <AppSidebar :controls=\"controls\" :active-view=\"activeTab\" v-model=\"values\" />\n * ```\n */\nimport { computed, ref, useSlots, type Slots } from 'vue'\nimport type { PillNavItem, SidebarToolSection } from '../types'\nimport type { FormEnhancements, FormSchema } from '../types/form-builder'\nimport {\n controlsToSectionFormSchemas,\n controlsToSidebarPanels,\n getControlDefaults,\n mergeControlWorkspaceOptions,\n resolveControlModel,\n type ControlModel,\n type ControlModelBinding,\n type ControlSchema,\n type ControlWorkspaceOptions,\n} from '../composables/useControlSchema'\nimport CollapsibleCard from './CollapsibleCard.vue'\nimport FormBuilder from './FormBuilder.vue'\n\ninterface Props {\n /** Optional chrome title rendered above generated sections. */\n title?: string\n /** Optional secondary chrome copy rendered below title. */\n subtitle?: string\n /** Optional compact badge/count rendered in the chrome header. */\n badge?: string | number\n /** Visual preset for common plugin sidebars. `analysis` preserves the LEAF-style MINT analysis sidebar design language. */\n variant?: 'default' | 'analysis'\n /** Map of view IDs to their tool sections */\n panels?: Record<string, SidebarToolSection[]>\n /** Which view's panels to display. Defaults to the first non-empty panel view. */\n activeView?: string\n /** Floating variant with absolute positioning. Defaults to false for analysis variant. */\n floating?: boolean\n /** Compact layout: smaller headers, tighter spacing, no icon backgrounds */\n dense?: boolean\n /** Width when visible. Defaults to 20rem for analysis variant, otherwise 280px. */\n width?: string\n /** Position sidebar on left or right side */\n side?: 'left' | 'right'\n /** Toggle state map: sectionId → boolean */\n toggleState?: Record<string, boolean>\n /** Optional FormBuilder schemas keyed by section ID. Used when no section slot is provided. */\n forms?: Record<string, FormSchema>\n /** Generated view IDs from useControlSchema(). Consumed for clean v-bind ergonomics. */\n viewIds?: string[]\n /** Generated AppTopBar pillNav-compatible view items from useControlSchema(). Consumed for clean v-bind ergonomics. */\n viewItems?: PillNavItem[]\n /** Default view ID used when activeView is omitted. */\n defaultView?: string\n /** Model returned by defineControlModel(), or a raw nested ControlModel for one-step sidebar/form generation. */\n model?: ControlModel | ControlModelBinding\n /** Compact control schema. When provided, AppSidebar generates panels, section forms, and default values. */\n controls?: ControlSchema\n /** Options passed to compact control schema generation, including shared initialValues. */\n controlOptions?: ControlWorkspaceOptions\n /** DOM id for the scrollable content area. Use with Teleport when route/tab children own sidebar controls. */\n contentId?: string\n /** Render the sidebar shell even when no panel matches the active view. Useful for default-slot or Teleport-driven sidebars. */\n showWhenEmpty?: boolean\n /** Shared values for auto-rendered section forms. Supports default v-model. */\n modelValue?: Record<string, unknown>\n /** Shared values for auto-rendered section forms */\n values?: Record<string, unknown>\n /** Runtime FormBuilder enhancements for auto-rendered section forms */\n formEnhancements?: FormEnhancements<Record<string, unknown>>\n /** Show submit/cancel actions inside auto-rendered section forms */\n showFormActions?: boolean\n /** Loading/saving state for auto-rendered section forms */\n formLoading?: boolean\n /** Disabled state for auto-rendered section forms */\n formDisabled?: boolean\n /** Readonly state for auto-rendered section forms */\n formReadonly?: boolean\n /** Size passed to auto-rendered section forms */\n formSize?: 'sm' | 'md' | 'lg'\n /** Show a built-in collapse/expand button in the sidebar chrome. Defaults to true for analysis variant. */\n collapsible?: boolean\n /** Controlled collapsed state. */\n collapsed?: boolean\n /** Initial collapsed state when collapsed is uncontrolled. */\n defaultCollapsed?: boolean\n /** Width when collapsed. */\n collapsedWidth?: string\n /** Accessible label for the collapse action. */\n collapseButtonLabel?: string\n /** Accessible label for the expand action. */\n expandButtonLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: undefined,\n subtitle: undefined,\n badge: undefined,\n variant: 'default',\n panels: () => ({}),\n activeView: '',\n floating: undefined,\n dense: false,\n width: undefined,\n side: 'left',\n toggleState: () => ({}),\n forms: () => ({}),\n viewIds: () => [],\n viewItems: () => [],\n defaultView: '',\n model: undefined,\n controlOptions: () => ({}),\n contentId: undefined,\n showWhenEmpty: false,\n modelValue: undefined,\n values: () => ({}),\n showFormActions: false,\n formLoading: false,\n formDisabled: false,\n formReadonly: false,\n formSize: 'sm',\n collapsible: undefined,\n collapsed: undefined,\n defaultCollapsed: false,\n collapsedWidth: '3rem',\n collapseButtonLabel: 'Collapse sidebar',\n expandButtonLabel: 'Expand sidebar',\n})\n\nconst emit = defineEmits<{\n 'update:toggle': [sectionId: string, value: boolean]\n 'update:modelValue': [values: Record<string, unknown>]\n 'update:values': [values: Record<string, unknown>]\n 'update:collapsed': [value: boolean]\n 'section-action': [sectionId: string, actionId: string]\n 'form-submit': [sectionId: string, values: Record<string, unknown>]\n 'form-cancel': [sectionId: string]\n}>()\n\nconst slots: Slots = useSlots()\nconst internalCollapsed = ref(props.defaultCollapsed)\nconst collapsedModel = computed({\n get: () => props.collapsed ?? internalCollapsed.value,\n set: (value: boolean) => {\n internalCollapsed.value = value\n emit('update:collapsed', value)\n },\n})\n\nconst isAnalysisVariant = computed(() => props.variant === 'analysis')\nconst resolvedFloating = computed(() => props.floating ?? !isAnalysisVariant.value)\nconst resolvedWidth = computed(() => props.width ?? (isAnalysisVariant.value ? '20rem' : '280px'))\nconst resolvedCollapsible = computed(() => props.collapsible ?? isAnalysisVariant.value)\n\nconst resolvedModel = computed<ControlModelBinding | undefined>(() => {\n return resolveControlModel(props.model)\n})\n\nconst resolvedControls = computed<ControlSchema | undefined>(() =>\n props.controls ?? resolvedModel.value?.controls,\n)\n\nconst resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>\n mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)\n)\n\nconst generatedPanels = computed<Record<string, SidebarToolSection[]>>(() => {\n if (!resolvedControls.value) return {}\n return controlsToSidebarPanels(resolvedControls.value, resolvedControlOptions.value)\n})\n\nconst generatedForms = computed<Record<string, FormSchema>>(() => {\n if (!resolvedControls.value) return {}\n return controlsToSectionFormSchemas(resolvedControls.value, resolvedControlOptions.value)\n})\n\nconst generatedDefaults = computed<Record<string, unknown>>(() => {\n if (!resolvedControls.value) return {}\n return {\n ...getControlDefaults(resolvedControls.value),\n ...(resolvedControlOptions.value.initialValues ?? {}),\n }\n})\n\nconst resolvedPanels = computed<Record<string, SidebarToolSection[]>>(() =>\n mergeSidebarPanels(generatedPanels.value, props.panels),\n)\n\nconst resolvedForms = computed<Record<string, FormSchema>>(() => ({\n ...generatedForms.value,\n ...props.forms,\n}))\n\nconst resolvedValues = computed<Record<string, unknown>>(() => ({\n ...generatedDefaults.value,\n ...(props.modelValue ?? props.values),\n}))\n\nconst resolvedActiveView = computed(() => {\n if (props.activeView) return props.activeView\n if (props.defaultView) return props.defaultView\n return firstVisibleViewId(resolvedPanels.value)\n})\n\nconst activeSections = computed<SidebarToolSection[]>(() => {\n if (!resolvedActiveView.value || !resolvedPanels.value[resolvedActiveView.value]) return []\n return resolvedPanels.value[resolvedActiveView.value]\n})\n\nconst isVisible = computed<boolean>(() =>\n activeSections.value.length > 0 || props.showWhenEmpty || Boolean(slots.default),\n)\n\nconst sidebarClasses = computed<string[]>(() => [\n 'mint-sidebar',\n `mint-sidebar--${props.side}`,\n `mint-sidebar--${props.variant}`,\n resolvedFloating.value ? 'mint-sidebar--floating' : 'mint-sidebar--static',\n props.dense ? 'mint-sidebar--dense' : '',\n resolvedCollapsible.value ? 'mint-sidebar--collapsible' : '',\n collapsedModel.value ? 'mint-sidebar--collapsed' : '',\n !isVisible.value ? 'mint-sidebar--hidden' : '',\n])\n\nconst sidebarStyle = computed<Record<string, string>>(() => ({\n width: collapsedModel.value ? props.collapsedWidth : resolvedWidth.value,\n}))\n\nfunction handleFormUpdate(values: Record<string, unknown>) {\n const nextValues = { ...resolvedValues.value, ...values }\n emit('update:modelValue', nextValues)\n emit('update:values', nextValues)\n}\n\nfunction handleFormSubmit(sectionId: string, values: Record<string, unknown>) {\n emit('form-submit', sectionId, values)\n}\n\nfunction handleFormCancel(sectionId: string) {\n emit('form-cancel', sectionId)\n}\n\nfunction toggleCollapsed() {\n collapsedModel.value = !collapsedModel.value\n}\n\nfunction expandCollapsed() {\n collapsedModel.value = false\n}\n\nfunction handleSectionAction(sectionId: string, actionId: string) {\n emit('section-action', sectionId, actionId)\n}\n\nfunction isSvgIcon(icon?: string | string[]): boolean {\n if (!icon) return false\n return Array.isArray(icon) || icon.startsWith('M') || icon.startsWith('m')\n}\n\nfunction sectionInitial(section: SidebarToolSection): string {\n return section.label.trim().charAt(0).toUpperCase() || '?'\n}\n\nfunction railIconStyle(section: SidebarToolSection): Record<string, string> {\n return {\n backgroundColor: section.iconBg ?? 'var(--color-primary-soft)',\n color: section.iconColor ?? 'var(--color-primary)',\n }\n}\n\nfunction badgeToneClass(section: SidebarToolSection): string {\n return `mint-sidebar__rail-badge--${section.badgeTone ?? 'cta'}`\n}\n\nfunction mergeSidebarPanels(\n generated: Record<string, SidebarToolSection[]>,\n explicit: Record<string, SidebarToolSection[]>,\n): Record<string, SidebarToolSection[]> {\n const merged: Record<string, SidebarToolSection[]> = {}\n const viewIds = new Set([...Object.keys(generated), ...Object.keys(explicit)])\n\n for (const viewId of viewIds) {\n const sectionsById = new Map<string, SidebarToolSection>()\n for (const section of generated[viewId] ?? []) {\n sectionsById.set(section.id, section)\n }\n for (const section of explicit[viewId] ?? []) {\n sectionsById.set(section.id, section)\n }\n merged[viewId] = [...sectionsById.values()]\n }\n\n return merged\n}\n\nfunction firstVisibleViewId(panels: Record<string, SidebarToolSection[]>): string {\n for (const [viewId, sections] of Object.entries(panels)) {\n if (sections.length > 0) return viewId\n }\n return ''\n}\n\n</script>\n\n<template>\n <aside\n :class=\"sidebarClasses\"\n :style=\"sidebarStyle\"\n >\n <!-- Header slot / built-in chrome -->\n <div\n v-if=\"$slots.header || title || subtitle || badge !== undefined || resolvedCollapsible\"\n class=\"mint-sidebar__header\"\n >\n <slot\n name=\"header\"\n :collapsed=\"collapsedModel\"\n :toggle-collapsed=\"toggleCollapsed\"\n >\n <div v-if=\"!collapsedModel\" class=\"mint-sidebar__heading\">\n <div class=\"mint-sidebar__heading-copy\">\n <h2 v-if=\"title\" class=\"mint-sidebar__title\">{{ title }}</h2>\n <p v-if=\"subtitle\" class=\"mint-sidebar__subtitle\">{{ subtitle }}</p>\n </div>\n <span v-if=\"badge !== undefined\" class=\"mint-sidebar__badge\">{{ badge }}</span>\n </div>\n <button\n v-if=\"resolvedCollapsible\"\n type=\"button\"\n class=\"mint-sidebar__collapse-button\"\n :aria-label=\"collapsedModel ? expandButtonLabel : collapseButtonLabel\"\n :aria-expanded=\"!collapsedModel\"\n @click=\"toggleCollapsed\"\n >\n <svg\n class=\"mint-sidebar__collapse-icon\"\n :class=\"{ 'mint-sidebar__collapse-icon--collapsed': collapsedModel }\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M11 19l-7-7 7-7m8 14l-7-7 7-7\" />\n </svg>\n </button>\n </slot>\n </div>\n\n <!-- Tool sections -->\n <div\n v-if=\"!collapsedModel\"\n :id=\"contentId\"\n class=\"mint-sidebar__sections\"\n >\n <CollapsibleCard\n v-for=\"section in activeSections\"\n :key=\"section.id\"\n :title=\"section.label\"\n :subtitle=\"section.subtitle\"\n :icon=\"section.icon\"\n :icon-color=\"section.iconColor\"\n :icon-bg=\"section.iconBg\"\n :badge=\"section.badge\"\n :badge-tone=\"section.badgeTone\"\n :actions=\"section.actions\"\n :dense=\"dense\"\n :default-open=\"section.defaultOpen !== false\"\n :show-toggle=\"section.showToggle\"\n :toggle-value=\"toggleState[section.id] ?? false\"\n @action=\"handleSectionAction(section.id, $event)\"\n @update:toggle-value=\"emit('update:toggle', section.id, $event)\"\n >\n <slot :name=\"`section-${section.id}`\">\n <FormBuilder\n v-if=\"resolvedForms[section.id]\"\n :model-value=\"resolvedValues\"\n :schema=\"resolvedForms[section.id]\"\n :enhancements=\"formEnhancements\"\n :loading=\"formLoading\"\n :disabled=\"formDisabled\"\n :readonly=\"formReadonly\"\n :size=\"formSize\"\n :show-actions=\"showFormActions\"\n @update:model-value=\"handleFormUpdate\"\n @submit=\"handleFormSubmit(section.id, $event)\"\n @cancel=\"handleFormCancel(section.id)\"\n />\n </slot>\n </CollapsibleCard>\n\n <slot\n :sections=\"activeSections\"\n :active-view=\"resolvedActiveView\"\n :values=\"resolvedValues\"\n />\n </div>\n\n <div v-else-if=\"$slots.collapsed\" class=\"mint-sidebar__collapsed\">\n <slot\n name=\"collapsed\"\n :sections=\"activeSections\"\n :expand=\"expandCollapsed\"\n />\n </div>\n\n <div v-else class=\"mint-sidebar__collapsed mint-sidebar__collapsed--default\">\n <button\n v-for=\"section in activeSections\"\n :key=\"section.id\"\n type=\"button\"\n class=\"mint-sidebar__rail-item\"\n :aria-label=\"`Expand ${section.label}`\"\n :title=\"section.label\"\n @click=\"expandCollapsed\"\n >\n <span class=\"mint-sidebar__rail-icon\" :style=\"railIconStyle(section)\">\n <svg\n v-if=\"section.icon && isSvgIcon(section.icon)\"\n class=\"mint-sidebar__rail-svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <template v-if=\"Array.isArray(section.icon)\">\n <path v-for=\"(d, i) in section.icon\" :key=\"i\" :d=\"d\" />\n </template>\n <path v-else :d=\"section.icon\" />\n </svg>\n <span v-else class=\"mint-sidebar__rail-text-icon\">\n {{ section.icon || sectionInitial(section) }}\n </span>\n </span>\n <span\n v-if=\"section.badge !== undefined\"\n :class=\"['mint-sidebar__rail-badge', badgeToneClass(section)]\"\n >\n {{ section.badge }}\n </span>\n </button>\n </div>\n\n <!-- Footer slot -->\n <div v-if=\"!collapsedModel && $slots.footer\" class=\"mint-sidebar__footer\">\n <slot name=\"footer\" />\n </div>\n <div v-else-if=\"collapsedModel && $slots['collapsed-footer']\" class=\"mint-sidebar__footer mint-sidebar__footer--collapsed\">\n <slot name=\"collapsed-footer\" />\n </div>\n </aside>\n</template>\n\n<style>\n@import '../styles/components/app-sidebar.css';\n</style>\n","<script setup lang=\"ts\">\n/**\n * AppSidebar - Context-sensitive toolkit panel\n *\n * Shows tool sections relevant to the active view. Sections are defined via\n * the `panels` config and rendered as CollapsibleCards. Controls can be\n * provided through named slots or auto-rendered from FormBuilder schemas.\n *\n * When activeView is omitted, the first non-empty panel view is selected.\n * When no view has matching panels, the sidebar hides entirely.\n *\n * @example\n * ```vue\n * <AppSidebar :panels=\"toolPanels\" :active-view=\"activeTab\">\n * <template #section-parameters>\n * <BaseSlider v-model=\"threshold\" label=\"Threshold\" />\n * </template>\n * <template #section-display>\n * <BaseToggle v-model=\"showOutliers\" label=\"Show outliers\" />\n * </template>\n * <template #header>Plugin Tools</template>\n * </AppSidebar>\n *\n * <!-- Direct schema-driven controls -->\n * <AppSidebar :controls=\"controls\" :active-view=\"activeTab\" v-model=\"values\" />\n * ```\n */\nimport { computed, ref, useSlots, type Slots } from 'vue'\nimport type { PillNavItem, SidebarToolSection } from '../types'\nimport type { FormEnhancements, FormSchema } from '../types/form-builder'\nimport {\n controlsToSectionFormSchemas,\n controlsToSidebarPanels,\n getControlDefaults,\n mergeControlWorkspaceOptions,\n resolveControlModel,\n type ControlModel,\n type ControlModelBinding,\n type ControlSchema,\n type ControlWorkspaceOptions,\n} from '../composables/useControlSchema'\nimport CollapsibleCard from './CollapsibleCard.vue'\nimport FormBuilder from './FormBuilder.vue'\n\ninterface Props {\n /** Optional chrome title rendered above generated sections. */\n title?: string\n /** Optional secondary chrome copy rendered below title. */\n subtitle?: string\n /** Optional compact badge/count rendered in the chrome header. */\n badge?: string | number\n /** Visual preset for common plugin sidebars. `analysis` preserves the LEAF-style MINT analysis sidebar design language. */\n variant?: 'default' | 'analysis'\n /** Map of view IDs to their tool sections */\n panels?: Record<string, SidebarToolSection[]>\n /** Which view's panels to display. Defaults to the first non-empty panel view. */\n activeView?: string\n /** Floating variant with absolute positioning. Defaults to false for analysis variant. */\n floating?: boolean\n /** Compact layout: smaller headers, tighter spacing, no icon backgrounds */\n dense?: boolean\n /** Width when visible. Defaults to 20rem for analysis variant, otherwise 280px. */\n width?: string\n /** Position sidebar on left or right side */\n side?: 'left' | 'right'\n /** Toggle state map: sectionId → boolean */\n toggleState?: Record<string, boolean>\n /** Optional FormBuilder schemas keyed by section ID. Used when no section slot is provided. */\n forms?: Record<string, FormSchema>\n /** Generated view IDs from useControlSchema(). Consumed for clean v-bind ergonomics. */\n viewIds?: string[]\n /** Generated AppTopBar pillNav-compatible view items from useControlSchema(). Consumed for clean v-bind ergonomics. */\n viewItems?: PillNavItem[]\n /** Default view ID used when activeView is omitted. */\n defaultView?: string\n /** Model returned by defineControlModel(), or a raw nested ControlModel for one-step sidebar/form generation. */\n model?: ControlModel | ControlModelBinding\n /** Compact control schema. When provided, AppSidebar generates panels, section forms, and default values. */\n controls?: ControlSchema\n /** Options passed to compact control schema generation, including shared initialValues. */\n controlOptions?: ControlWorkspaceOptions\n /** DOM id for the scrollable content area. Use with Teleport when route/tab children own sidebar controls. */\n contentId?: string\n /** Render the sidebar shell even when no panel matches the active view. Useful for default-slot or Teleport-driven sidebars. */\n showWhenEmpty?: boolean\n /** Shared values for auto-rendered section forms. Supports default v-model. */\n modelValue?: Record<string, unknown>\n /** Shared values for auto-rendered section forms */\n values?: Record<string, unknown>\n /** Runtime FormBuilder enhancements for auto-rendered section forms */\n formEnhancements?: FormEnhancements<Record<string, unknown>>\n /** Show submit/cancel actions inside auto-rendered section forms */\n showFormActions?: boolean\n /** Loading/saving state for auto-rendered section forms */\n formLoading?: boolean\n /** Disabled state for auto-rendered section forms */\n formDisabled?: boolean\n /** Readonly state for auto-rendered section forms */\n formReadonly?: boolean\n /** Size passed to auto-rendered section forms */\n formSize?: 'sm' | 'md' | 'lg'\n /** Show a built-in collapse/expand button in the sidebar chrome. Defaults to true for analysis variant. */\n collapsible?: boolean\n /** Controlled collapsed state. */\n collapsed?: boolean\n /** Initial collapsed state when collapsed is uncontrolled. */\n defaultCollapsed?: boolean\n /** Width when collapsed. */\n collapsedWidth?: string\n /** Accessible label for the collapse action. */\n collapseButtonLabel?: string\n /** Accessible label for the expand action. */\n expandButtonLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: undefined,\n subtitle: undefined,\n badge: undefined,\n variant: 'default',\n panels: () => ({}),\n activeView: '',\n floating: undefined,\n dense: false,\n width: undefined,\n side: 'left',\n toggleState: () => ({}),\n forms: () => ({}),\n viewIds: () => [],\n viewItems: () => [],\n defaultView: '',\n model: undefined,\n controlOptions: () => ({}),\n contentId: undefined,\n showWhenEmpty: false,\n modelValue: undefined,\n values: () => ({}),\n showFormActions: false,\n formLoading: false,\n formDisabled: false,\n formReadonly: false,\n formSize: 'sm',\n collapsible: undefined,\n collapsed: undefined,\n defaultCollapsed: false,\n collapsedWidth: '3rem',\n collapseButtonLabel: 'Collapse sidebar',\n expandButtonLabel: 'Expand sidebar',\n})\n\nconst emit = defineEmits<{\n 'update:toggle': [sectionId: string, value: boolean]\n 'update:modelValue': [values: Record<string, unknown>]\n 'update:values': [values: Record<string, unknown>]\n 'update:collapsed': [value: boolean]\n 'section-action': [sectionId: string, actionId: string]\n 'form-submit': [sectionId: string, values: Record<string, unknown>]\n 'form-cancel': [sectionId: string]\n}>()\n\nconst slots: Slots = useSlots()\nconst internalCollapsed = ref(props.defaultCollapsed)\nconst collapsedModel = computed({\n get: () => props.collapsed ?? internalCollapsed.value,\n set: (value: boolean) => {\n internalCollapsed.value = value\n emit('update:collapsed', value)\n },\n})\n\nconst isAnalysisVariant = computed(() => props.variant === 'analysis')\nconst resolvedFloating = computed(() => props.floating ?? !isAnalysisVariant.value)\nconst resolvedWidth = computed(() => props.width ?? (isAnalysisVariant.value ? '20rem' : '280px'))\nconst resolvedCollapsible = computed(() => props.collapsible ?? isAnalysisVariant.value)\n\nconst resolvedModel = computed<ControlModelBinding | undefined>(() => {\n return resolveControlModel(props.model)\n})\n\nconst resolvedControls = computed<ControlSchema | undefined>(() =>\n props.controls ?? resolvedModel.value?.controls,\n)\n\nconst resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>\n mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)\n)\n\nconst generatedPanels = computed<Record<string, SidebarToolSection[]>>(() => {\n if (!resolvedControls.value) return {}\n return controlsToSidebarPanels(resolvedControls.value, resolvedControlOptions.value)\n})\n\nconst generatedForms = computed<Record<string, FormSchema>>(() => {\n if (!resolvedControls.value) return {}\n return controlsToSectionFormSchemas(resolvedControls.value, resolvedControlOptions.value)\n})\n\nconst generatedDefaults = computed<Record<string, unknown>>(() => {\n if (!resolvedControls.value) return {}\n return {\n ...getControlDefaults(resolvedControls.value),\n ...(resolvedControlOptions.value.initialValues ?? {}),\n }\n})\n\nconst resolvedPanels = computed<Record<string, SidebarToolSection[]>>(() =>\n mergeSidebarPanels(generatedPanels.value, props.panels),\n)\n\nconst resolvedForms = computed<Record<string, FormSchema>>(() => ({\n ...generatedForms.value,\n ...props.forms,\n}))\n\nconst resolvedValues = computed<Record<string, unknown>>(() => ({\n ...generatedDefaults.value,\n ...(props.modelValue ?? props.values),\n}))\n\nconst resolvedActiveView = computed(() => {\n if (props.activeView) return props.activeView\n if (props.defaultView) return props.defaultView\n return firstVisibleViewId(resolvedPanels.value)\n})\n\nconst activeSections = computed<SidebarToolSection[]>(() => {\n if (!resolvedActiveView.value || !resolvedPanels.value[resolvedActiveView.value]) return []\n return resolvedPanels.value[resolvedActiveView.value]\n})\n\nconst isVisible = computed<boolean>(() =>\n activeSections.value.length > 0 || props.showWhenEmpty || Boolean(slots.default),\n)\n\nconst sidebarClasses = computed<string[]>(() => [\n 'mint-sidebar',\n `mint-sidebar--${props.side}`,\n `mint-sidebar--${props.variant}`,\n resolvedFloating.value ? 'mint-sidebar--floating' : 'mint-sidebar--static',\n props.dense ? 'mint-sidebar--dense' : '',\n resolvedCollapsible.value ? 'mint-sidebar--collapsible' : '',\n collapsedModel.value ? 'mint-sidebar--collapsed' : '',\n !isVisible.value ? 'mint-sidebar--hidden' : '',\n])\n\nconst sidebarStyle = computed<Record<string, string>>(() => ({\n width: collapsedModel.value ? props.collapsedWidth : resolvedWidth.value,\n}))\n\nfunction handleFormUpdate(values: Record<string, unknown>) {\n const nextValues = { ...resolvedValues.value, ...values }\n emit('update:modelValue', nextValues)\n emit('update:values', nextValues)\n}\n\nfunction handleFormSubmit(sectionId: string, values: Record<string, unknown>) {\n emit('form-submit', sectionId, values)\n}\n\nfunction handleFormCancel(sectionId: string) {\n emit('form-cancel', sectionId)\n}\n\nfunction toggleCollapsed() {\n collapsedModel.value = !collapsedModel.value\n}\n\nfunction expandCollapsed() {\n collapsedModel.value = false\n}\n\nfunction handleSectionAction(sectionId: string, actionId: string) {\n emit('section-action', sectionId, actionId)\n}\n\nfunction isSvgIcon(icon?: string | string[]): boolean {\n if (!icon) return false\n return Array.isArray(icon) || icon.startsWith('M') || icon.startsWith('m')\n}\n\nfunction sectionInitial(section: SidebarToolSection): string {\n return section.label.trim().charAt(0).toUpperCase() || '?'\n}\n\nfunction railIconStyle(section: SidebarToolSection): Record<string, string> {\n return {\n backgroundColor: section.iconBg ?? 'var(--color-primary-soft)',\n color: section.iconColor ?? 'var(--color-primary)',\n }\n}\n\nfunction badgeToneClass(section: SidebarToolSection): string {\n return `mint-sidebar__rail-badge--${section.badgeTone ?? 'cta'}`\n}\n\nfunction mergeSidebarPanels(\n generated: Record<string, SidebarToolSection[]>,\n explicit: Record<string, SidebarToolSection[]>,\n): Record<string, SidebarToolSection[]> {\n const merged: Record<string, SidebarToolSection[]> = {}\n const viewIds = new Set([...Object.keys(generated), ...Object.keys(explicit)])\n\n for (const viewId of viewIds) {\n const sectionsById = new Map<string, SidebarToolSection>()\n for (const section of generated[viewId] ?? []) {\n sectionsById.set(section.id, section)\n }\n for (const section of explicit[viewId] ?? []) {\n sectionsById.set(section.id, section)\n }\n merged[viewId] = [...sectionsById.values()]\n }\n\n return merged\n}\n\nfunction firstVisibleViewId(panels: Record<string, SidebarToolSection[]>): string {\n for (const [viewId, sections] of Object.entries(panels)) {\n if (sections.length > 0) return viewId\n }\n return ''\n}\n\n</script>\n\n<template>\n <aside\n :class=\"sidebarClasses\"\n :style=\"sidebarStyle\"\n >\n <!-- Header slot / built-in chrome -->\n <div\n v-if=\"$slots.header || title || subtitle || badge !== undefined || resolvedCollapsible\"\n class=\"mint-sidebar__header\"\n >\n <slot\n name=\"header\"\n :collapsed=\"collapsedModel\"\n :toggle-collapsed=\"toggleCollapsed\"\n >\n <div v-if=\"!collapsedModel\" class=\"mint-sidebar__heading\">\n <div class=\"mint-sidebar__heading-copy\">\n <h2 v-if=\"title\" class=\"mint-sidebar__title\">{{ title }}</h2>\n <p v-if=\"subtitle\" class=\"mint-sidebar__subtitle\">{{ subtitle }}</p>\n </div>\n <span v-if=\"badge !== undefined\" class=\"mint-sidebar__badge\">{{ badge }}</span>\n </div>\n <button\n v-if=\"resolvedCollapsible\"\n type=\"button\"\n class=\"mint-sidebar__collapse-button\"\n :aria-label=\"collapsedModel ? expandButtonLabel : collapseButtonLabel\"\n :aria-expanded=\"!collapsedModel\"\n @click=\"toggleCollapsed\"\n >\n <svg\n class=\"mint-sidebar__collapse-icon\"\n :class=\"{ 'mint-sidebar__collapse-icon--collapsed': collapsedModel }\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M11 19l-7-7 7-7m8 14l-7-7 7-7\" />\n </svg>\n </button>\n </slot>\n </div>\n\n <!-- Tool sections -->\n <div\n v-if=\"!collapsedModel\"\n :id=\"contentId\"\n class=\"mint-sidebar__sections\"\n >\n <CollapsibleCard\n v-for=\"section in activeSections\"\n :key=\"section.id\"\n :title=\"section.label\"\n :subtitle=\"section.subtitle\"\n :icon=\"section.icon\"\n :icon-color=\"section.iconColor\"\n :icon-bg=\"section.iconBg\"\n :badge=\"section.badge\"\n :badge-tone=\"section.badgeTone\"\n :actions=\"section.actions\"\n :dense=\"dense\"\n :default-open=\"section.defaultOpen !== false\"\n :show-toggle=\"section.showToggle\"\n :toggle-value=\"toggleState[section.id] ?? false\"\n @action=\"handleSectionAction(section.id, $event)\"\n @update:toggle-value=\"emit('update:toggle', section.id, $event)\"\n >\n <slot :name=\"`section-${section.id}`\">\n <FormBuilder\n v-if=\"resolvedForms[section.id]\"\n :model-value=\"resolvedValues\"\n :schema=\"resolvedForms[section.id]\"\n :enhancements=\"formEnhancements\"\n :loading=\"formLoading\"\n :disabled=\"formDisabled\"\n :readonly=\"formReadonly\"\n :size=\"formSize\"\n :show-actions=\"showFormActions\"\n @update:model-value=\"handleFormUpdate\"\n @submit=\"handleFormSubmit(section.id, $event)\"\n @cancel=\"handleFormCancel(section.id)\"\n />\n </slot>\n </CollapsibleCard>\n\n <slot\n :sections=\"activeSections\"\n :active-view=\"resolvedActiveView\"\n :values=\"resolvedValues\"\n />\n </div>\n\n <div v-else-if=\"$slots.collapsed\" class=\"mint-sidebar__collapsed\">\n <slot\n name=\"collapsed\"\n :sections=\"activeSections\"\n :expand=\"expandCollapsed\"\n />\n </div>\n\n <div v-else class=\"mint-sidebar__collapsed mint-sidebar__collapsed--default\">\n <button\n v-for=\"section in activeSections\"\n :key=\"section.id\"\n type=\"button\"\n class=\"mint-sidebar__rail-item\"\n :aria-label=\"`Expand ${section.label}`\"\n :title=\"section.label\"\n @click=\"expandCollapsed\"\n >\n <span class=\"mint-sidebar__rail-icon\" :style=\"railIconStyle(section)\">\n <svg\n v-if=\"section.icon && isSvgIcon(section.icon)\"\n class=\"mint-sidebar__rail-svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <template v-if=\"Array.isArray(section.icon)\">\n <path v-for=\"(d, i) in section.icon\" :key=\"i\" :d=\"d\" />\n </template>\n <path v-else :d=\"section.icon\" />\n </svg>\n <span v-else class=\"mint-sidebar__rail-text-icon\">\n {{ section.icon || sectionInitial(section) }}\n </span>\n </span>\n <span\n v-if=\"section.badge !== undefined\"\n :class=\"['mint-sidebar__rail-badge', badgeToneClass(section)]\"\n >\n {{ section.badge }}\n </span>\n </button>\n </div>\n\n <!-- Footer slot -->\n <div v-if=\"!collapsedModel && $slots.footer\" class=\"mint-sidebar__footer\">\n <slot name=\"footer\" />\n </div>\n <div v-else-if=\"collapsedModel && $slots['collapsed-footer']\" class=\"mint-sidebar__footer mint-sidebar__footer--collapsed\">\n <slot name=\"collapsed-footer\" />\n </div>\n </aside>\n</template>\n\n<style>\n@import '../styles/components/app-sidebar.css';\n</style>\n","<script setup lang=\"ts\">\n/**\n * AppLayout - Page layout shell with topbar, sidebar, and main content slots\n *\n * Provides a responsive application layout structure with optional topbar and sidebar.\n * The sidebar slot is a simple pass-through on desktop. When responsiveSidebar is\n * enabled, AppLayout owns the mobile toggle/backdrop shell around the sidebar.\n *\n * @example\n * ```vue\n * <AppLayout sidebar-position=\"right\" floating>\n * <template #topbar>\n * <AppTopBar title=\"My App\" />\n * </template>\n * <template #sidebar>\n * <AppSidebar :panels=\"toolPanels\" :active-view=\"activeTab\">\n * <template #section-params>...</template>\n * </AppSidebar>\n * </template>\n * <main-content />\n * </AppLayout>\n * ```\n */\nimport { computed, ref } from 'vue'\n\ninterface Props {\n /** Position of sidebar (left or right side of screen) */\n sidebarPosition?: 'left' | 'right'\n /** Width of sidebar (use 'auto' to fit content) */\n sidebarWidth?: string\n /** When true, topbar/sidebar/main render as floating cards with gaps */\n floating?: boolean\n /** Convert the sidebar into a mobile overlay with built-in toggle and backdrop below 1024px. */\n responsiveSidebar?: boolean\n /** Controlled mobile sidebar open state. Desktop sidebar remains visible. */\n sidebarOpen?: boolean\n /** Initial mobile sidebar open state when sidebarOpen is uncontrolled. */\n defaultSidebarOpen?: boolean\n /** Accessible label for the mobile sidebar toggle. */\n sidebarToggleLabel?: string\n /** Accessible label used when the mobile sidebar is open. */\n sidebarCloseLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n sidebarPosition: 'left',\n sidebarWidth: 'auto',\n floating: false,\n responsiveSidebar: false,\n sidebarOpen: undefined,\n defaultSidebarOpen: false,\n sidebarToggleLabel: 'Open sidebar',\n sidebarCloseLabel: 'Close sidebar',\n})\n\nconst emit = defineEmits<{\n 'update:sidebarOpen': [value: boolean]\n}>()\n\nconst internalSidebarOpen = ref(props.defaultSidebarOpen)\nconst sidebarOpenModel = computed({\n get: () => props.sidebarOpen ?? internalSidebarOpen.value,\n set: (value: boolean) => {\n internalSidebarOpen.value = value\n emit('update:sidebarOpen', value)\n },\n})\n\nconst layoutClasses = computed(() => [\n 'mint-layout',\n props.sidebarPosition === 'right' ? 'mint-layout--sidebar-right' : '',\n props.floating ? 'mint-layout--floating' : '',\n props.responsiveSidebar ? 'mint-layout--responsive-sidebar' : '',\n sidebarOpenModel.value ? 'mint-layout--sidebar-open' : '',\n])\n\nconst sidebarStyle = computed(() => {\n return props.sidebarWidth !== 'auto' ? { width: props.sidebarWidth } : undefined\n})\n\nfunction toggleSidebar() {\n sidebarOpenModel.value = !sidebarOpenModel.value\n}\n\nfunction closeSidebar() {\n sidebarOpenModel.value = false\n}\n</script>\n\n<template>\n <div :class=\"layoutClasses\">\n <div v-if=\"$slots.topbar\" class=\"mint-layout__topbar\">\n <slot name=\"topbar\" />\n </div>\n\n <div class=\"mint-layout__body\">\n <button\n v-if=\"responsiveSidebar && $slots.sidebar\"\n type=\"button\"\n class=\"mint-layout__sidebar-toggle\"\n :aria-label=\"sidebarOpenModel ? sidebarCloseLabel : sidebarToggleLabel\"\n :aria-expanded=\"sidebarOpenModel\"\n @click=\"toggleSidebar\"\n >\n <svg\n v-if=\"!sidebarOpenModel\"\n class=\"mint-layout__sidebar-toggle-icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M4 6h16M4 12h16M4 18h16\" />\n </svg>\n <svg\n v-else\n class=\"mint-layout__sidebar-toggle-icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n\n <div\n v-if=\"responsiveSidebar && $slots.sidebar && sidebarOpenModel\"\n class=\"mint-layout__sidebar-backdrop\"\n @click=\"closeSidebar\"\n />\n\n <div\n v-if=\"$slots.sidebar\"\n class=\"mint-layout__sidebar\"\n :style=\"sidebarStyle\"\n >\n <slot name=\"sidebar\" />\n </div>\n\n <main class=\"mint-layout__main\">\n <slot />\n </main>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/app-layout.css';\n</style>\n","<script setup lang=\"ts\">\n/**\n * AppLayout - Page layout shell with topbar, sidebar, and main content slots\n *\n * Provides a responsive application layout structure with optional topbar and sidebar.\n * The sidebar slot is a simple pass-through on desktop. When responsiveSidebar is\n * enabled, AppLayout owns the mobile toggle/backdrop shell around the sidebar.\n *\n * @example\n * ```vue\n * <AppLayout sidebar-position=\"right\" floating>\n * <template #topbar>\n * <AppTopBar title=\"My App\" />\n * </template>\n * <template #sidebar>\n * <AppSidebar :panels=\"toolPanels\" :active-view=\"activeTab\">\n * <template #section-params>...</template>\n * </AppSidebar>\n * </template>\n * <main-content />\n * </AppLayout>\n * ```\n */\nimport { computed, ref } from 'vue'\n\ninterface Props {\n /** Position of sidebar (left or right side of screen) */\n sidebarPosition?: 'left' | 'right'\n /** Width of sidebar (use 'auto' to fit content) */\n sidebarWidth?: string\n /** When true, topbar/sidebar/main render as floating cards with gaps */\n floating?: boolean\n /** Convert the sidebar into a mobile overlay with built-in toggle and backdrop below 1024px. */\n responsiveSidebar?: boolean\n /** Controlled mobile sidebar open state. Desktop sidebar remains visible. */\n sidebarOpen?: boolean\n /** Initial mobile sidebar open state when sidebarOpen is uncontrolled. */\n defaultSidebarOpen?: boolean\n /** Accessible label for the mobile sidebar toggle. */\n sidebarToggleLabel?: string\n /** Accessible label used when the mobile sidebar is open. */\n sidebarCloseLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n sidebarPosition: 'left',\n sidebarWidth: 'auto',\n floating: false,\n responsiveSidebar: false,\n sidebarOpen: undefined,\n defaultSidebarOpen: false,\n sidebarToggleLabel: 'Open sidebar',\n sidebarCloseLabel: 'Close sidebar',\n})\n\nconst emit = defineEmits<{\n 'update:sidebarOpen': [value: boolean]\n}>()\n\nconst internalSidebarOpen = ref(props.defaultSidebarOpen)\nconst sidebarOpenModel = computed({\n get: () => props.sidebarOpen ?? internalSidebarOpen.value,\n set: (value: boolean) => {\n internalSidebarOpen.value = value\n emit('update:sidebarOpen', value)\n },\n})\n\nconst layoutClasses = computed(() => [\n 'mint-layout',\n props.sidebarPosition === 'right' ? 'mint-layout--sidebar-right' : '',\n props.floating ? 'mint-layout--floating' : '',\n props.responsiveSidebar ? 'mint-layout--responsive-sidebar' : '',\n sidebarOpenModel.value ? 'mint-layout--sidebar-open' : '',\n])\n\nconst sidebarStyle = computed(() => {\n return props.sidebarWidth !== 'auto' ? { width: props.sidebarWidth } : undefined\n})\n\nfunction toggleSidebar() {\n sidebarOpenModel.value = !sidebarOpenModel.value\n}\n\nfunction closeSidebar() {\n sidebarOpenModel.value = false\n}\n</script>\n\n<template>\n <div :class=\"layoutClasses\">\n <div v-if=\"$slots.topbar\" class=\"mint-layout__topbar\">\n <slot name=\"topbar\" />\n </div>\n\n <div class=\"mint-layout__body\">\n <button\n v-if=\"responsiveSidebar && $slots.sidebar\"\n type=\"button\"\n class=\"mint-layout__sidebar-toggle\"\n :aria-label=\"sidebarOpenModel ? sidebarCloseLabel : sidebarToggleLabel\"\n :aria-expanded=\"sidebarOpenModel\"\n @click=\"toggleSidebar\"\n >\n <svg\n v-if=\"!sidebarOpenModel\"\n class=\"mint-layout__sidebar-toggle-icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M4 6h16M4 12h16M4 18h16\" />\n </svg>\n <svg\n v-else\n class=\"mint-layout__sidebar-toggle-icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n\n <div\n v-if=\"responsiveSidebar && $slots.sidebar && sidebarOpenModel\"\n class=\"mint-layout__sidebar-backdrop\"\n @click=\"closeSidebar\"\n />\n\n <div\n v-if=\"$slots.sidebar\"\n class=\"mint-layout__sidebar\"\n :style=\"sidebarStyle\"\n >\n <slot name=\"sidebar\" />\n </div>\n\n <main class=\"mint-layout__main\">\n <slot />\n </main>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/app-layout.css';\n</style>\n","import { computed, reactive, watch } from 'vue'\nimport type { ComputedRef } from 'vue'\nimport type { PillNavItem } from '../types'\nimport type {\n ControlComponentBinding,\n ControlComponentBindingsById,\n ControlComponentBindingsConfig,\n ControlComponentPropsByIdMap,\n ControlComponentPropsMap,\n ControlModel,\n ControlModelBinding,\n ControlSchema,\n ControlWorkspaceOptions,\n} from '../composables/useControlSchema'\nimport {\n controlValuesToComponentBindings,\n controlValuesToComponentBindingsById,\n controlValuesToComponentProps,\n controlsToViewItems,\n getControlDefaults,\n getDefaultControlView,\n mergeControlWorkspaceOptions,\n resolveControlModel,\n} from '../composables/useControlSchema'\n\nexport interface PluginWorkspaceControlsOptions {\n model: () => ControlModel | ControlModelBinding | undefined\n controls: () => ControlSchema | undefined\n controlOptions: () => ControlWorkspaceOptions\n modelValue: () => Record<string, unknown> | undefined\n values: () => Record<string, unknown> | undefined\n componentBindings: () => ControlComponentBindingsConfig | undefined\n componentProps: () => ControlComponentPropsMap | undefined\n componentPropsById: () => ControlComponentPropsByIdMap | undefined\n emitValues: (values: Record<string, unknown>) => void\n}\n\nexport interface PluginWorkspaceControls {\n resolvedModel: ComputedRef<ControlModelBinding | undefined>\n resolvedControls: ComputedRef<ControlSchema | undefined>\n resolvedControlOptions: ComputedRef<ControlWorkspaceOptions>\n generatedPillNav: ComputedRef<PillNavItem[]>\n generatedDefaultView: ComputedRef<string>\n internalControlValues: Record<string, unknown>\n resolvedComponentBindings: ComputedRef<ControlComponentBinding[]>\n resolvedComponentBindingsById: ComputedRef<ControlComponentBindingsById>\n resolvedComponentProps: ComputedRef<Record<string, unknown>>\n resolvedComponentPropsById: ComputedRef<Record<string, Record<string, unknown>>>\n handleControlValuesUpdate: (values: Record<string, unknown>) => void\n}\n\nexport function usePluginWorkspaceControls(\n options: PluginWorkspaceControlsOptions,\n): PluginWorkspaceControls {\n const resolvedModel = computed<ControlModelBinding | undefined>(() =>\n resolveControlModel(options.model()),\n )\n\n const resolvedControls = computed<ControlSchema | undefined>(() =>\n options.controls() ?? resolvedModel.value?.controls,\n )\n\n const resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>\n mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, options.controlOptions()),\n )\n\n const externalControlValues = computed(() => options.modelValue() ?? options.values())\n const internalControlValues = reactive<Record<string, unknown>>({})\n let syncingControlValues = false\n\n const generatedControlDefaults = computed<Record<string, unknown>>(() => {\n if (!resolvedControls.value) return {}\n return {\n ...getControlDefaults(resolvedControls.value),\n ...(resolvedControlOptions.value.initialValues ?? {}),\n }\n })\n\n function replaceControlValues(values: Record<string, unknown>) {\n for (const key of Object.keys(internalControlValues)) {\n if (!(key in values)) {\n delete internalControlValues[key]\n }\n }\n Object.assign(internalControlValues, values)\n }\n\n function emitControlValues() {\n options.emitValues({ ...internalControlValues })\n }\n\n function syncResolvedControlValues() {\n syncingControlValues = true\n replaceControlValues({\n ...generatedControlDefaults.value,\n ...(externalControlValues.value === undefined ? internalControlValues : externalControlValues.value),\n })\n syncingControlValues = false\n }\n\n function handleControlValuesUpdate(values: Record<string, unknown>) {\n syncingControlValues = true\n replaceControlValues({\n ...generatedControlDefaults.value,\n ...values,\n })\n syncingControlValues = false\n emitControlValues()\n }\n\n const resolvedComponentBindings = computed(() =>\n controlValuesToComponentBindings(\n internalControlValues,\n options.componentBindings() ?? resolvedModel.value?.componentBindings,\n ),\n )\n\n const resolvedComponentBindingsById = computed(() =>\n controlValuesToComponentBindingsById(\n internalControlValues,\n options.componentBindings() ?? resolvedModel.value?.componentBindings,\n ),\n )\n\n const resolvedComponentProps = computed(() => {\n const mapping = options.componentProps() ?? resolvedModel.value?.componentProps\n return mapping === undefined ? {} : controlValuesToComponentProps(internalControlValues, mapping)\n })\n\n const resolvedComponentPropsById = computed(() => {\n const mappings = options.componentPropsById() ?? resolvedModel.value?.componentPropsById\n if (mappings === undefined) return {}\n\n return Object.fromEntries(\n Object.entries(mappings).map(([id, mapping]) => [\n id,\n controlValuesToComponentProps(internalControlValues, mapping),\n ]),\n )\n })\n\n const generatedPillNav = computed<PillNavItem[]>(() => {\n if (!resolvedControls.value) return []\n return controlsToViewItems(resolvedControls.value, resolvedControlOptions.value)\n })\n\n const generatedDefaultView = computed(() => {\n if (!resolvedControls.value) return ''\n return getDefaultControlView(resolvedControls.value, resolvedControlOptions.value)\n })\n\n syncResolvedControlValues()\n\n watch(\n [generatedControlDefaults, externalControlValues],\n syncResolvedControlValues,\n { deep: true },\n )\n\n watch(\n internalControlValues,\n () => {\n if (syncingControlValues) return\n emitControlValues()\n },\n { deep: true, flush: 'sync' },\n )\n\n return {\n resolvedModel,\n resolvedControls,\n resolvedControlOptions,\n generatedPillNav,\n generatedDefaultView,\n internalControlValues,\n resolvedComponentBindings,\n resolvedComponentBindingsById,\n resolvedComponentProps,\n resolvedComponentPropsById,\n handleControlValuesUpdate,\n }\n}\n","import { computed, ref, type ComputedRef, type Ref, type WritableComputedRef } from 'vue'\nimport type {\n PageSelectorItem,\n PageSelectorItemInput,\n PillNavItem,\n PillNavItemInput,\n} from '../types'\n\ntype ReadableRef<T> = Readonly<Ref<T>>\n\nexport interface PluginWorkspaceNavigationOptions {\n activeView: () => string | undefined\n defaultActiveView: () => string | undefined\n currentPillId: () => string | undefined\n currentPageSelectorId: () => string | undefined\n pillNav: () => PillNavItemInput[] | undefined\n pageSelector: () => PageSelectorItemInput[] | undefined\n panelIds: () => string[]\n generatedPillNav: ReadableRef<readonly PillNavItem[]>\n generatedDefaultView: ReadableRef<string>\n emitActiveView: (viewId: string) => void\n emitPageSelectorSelect: (page: PageSelectorItem) => void\n emitPillSelect: (item: PillNavItem) => void\n}\n\nexport interface PluginWorkspaceNavigation {\n resolvedPillNav: ComputedRef<PillNavItemInput[] | undefined>\n firstConfiguredViewId: ComputedRef<string>\n resolvedActiveView: WritableComputedRef<string>\n resolvedCurrentPillId: ComputedRef<string>\n resolvedCurrentPageSelectorId: ComputedRef<string>\n setActiveView: (viewId: string) => void\n handlePageSelectorSelect: (page: PageSelectorItem) => void\n handlePillSelect: (item: PillNavItem) => void\n}\n\nexport function usePluginWorkspaceNavigation(\n options: PluginWorkspaceNavigationOptions,\n): PluginWorkspaceNavigation {\n const internalActiveView = ref(options.defaultActiveView())\n\n const resolvedPillNav = computed<PillNavItemInput[] | undefined>(() => {\n if (options.pillNav() !== undefined) return options.pillNav()\n return options.generatedPillNav.value.length > 0\n ? [...options.generatedPillNav.value]\n : undefined\n })\n\n const firstConfiguredViewId = computed(() =>\n options.defaultActiveView()\n ?? firstItemId(options.pillNav())\n ?? options.generatedDefaultView.value\n ?? firstItemId(options.pageSelector())\n ?? options.panelIds()[0]\n ?? '',\n )\n\n const resolvedActiveView = computed({\n get: () =>\n options.activeView()\n ?? options.currentPillId()\n ?? options.currentPageSelectorId()\n ?? internalActiveView.value\n ?? firstConfiguredViewId.value,\n set: (value: string) => {\n internalActiveView.value = value\n options.emitActiveView(value)\n },\n })\n\n const resolvedCurrentPillId = computed(() => options.currentPillId() ?? resolvedActiveView.value)\n const resolvedCurrentPageSelectorId = computed(() =>\n options.currentPageSelectorId() ?? resolvedActiveView.value,\n )\n\n function setActiveView(viewId: string) {\n resolvedActiveView.value = viewId\n }\n\n function handlePageSelectorSelect(page: PageSelectorItem) {\n setActiveView(page.id)\n options.emitPageSelectorSelect(page)\n }\n\n function handlePillSelect(item: PillNavItem) {\n setActiveView(item.id)\n options.emitPillSelect(item)\n }\n\n return {\n resolvedPillNav,\n firstConfiguredViewId,\n resolvedActiveView,\n resolvedCurrentPillId,\n resolvedCurrentPageSelectorId,\n setActiveView,\n handlePageSelectorSelect,\n handlePillSelect,\n }\n}\n\nfunction firstItemId(items?: readonly (string | { id: string })[]): string | undefined {\n const item = items?.[0]\n if (item === undefined) return undefined\n return typeof item === 'string' ? item : item.id\n}\n","const topBarForwardedSlots = new Set([\n 'icon',\n 'logo',\n 'page-selector-icon',\n 'page-selector-item-icon',\n 'nav',\n 'center',\n 'actions',\n 'account-menu-items',\n 'account-menu-item-icon',\n 'settings-appearance',\n])\n\nconst sidebarForwardedSlots = new Set([\n 'header',\n 'collapsed',\n 'collapsed-footer',\n 'footer',\n])\n\ntype SlotKeyMap = Readonly<Record<string, unknown>>\n\nexport interface PluginWorkspaceSidebarSurfaceOptions {\n showSidebar: boolean\n panels: Record<string, unknown>\n forms: Record<string, unknown>\n model: unknown\n controls: unknown\n showSidebarWhenEmpty: boolean\n sidebarContentId?: string\n sidebarTitle?: string\n sidebarSubtitle?: string\n sidebarBadge?: string | number\n hasSidebarSlot: boolean\n sidebarSlotCount: number\n}\n\nexport function getPluginWorkspaceTopBarSlotNames(slots: SlotKeyMap): string[] {\n return Object.keys(slots).filter(name =>\n topBarForwardedSlots.has(name) || name.startsWith('settings-tab-'),\n )\n}\n\nexport function getPluginWorkspaceSidebarSlotNames(slots: SlotKeyMap): string[] {\n return Object.keys(slots).filter(name =>\n sidebarForwardedSlots.has(name) || name.startsWith('section-'),\n )\n}\n\nexport function hasPluginWorkspaceSidebarSurface(\n options: PluginWorkspaceSidebarSurfaceOptions,\n): boolean {\n if (!options.showSidebar) return false\n return (\n Object.keys(options.panels).length > 0\n || Object.keys(options.forms).length > 0\n || options.model !== undefined\n || options.controls !== undefined\n || options.showSidebarWhenEmpty\n || options.sidebarContentId !== undefined\n || options.sidebarTitle !== undefined\n || options.sidebarSubtitle !== undefined\n || options.sidebarBadge !== undefined\n || options.hasSidebarSlot\n || options.sidebarSlotCount > 0\n )\n}\n","<script setup lang=\"ts\">\n/** Standard plugin workspace shell that combines AppLayout, AppTopBar, and AppSidebar with LEAF-style defaults. */\nimport { computed, useSlots } from 'vue'\nimport type {\n AccountMenuItem,\n ExperimentSummary,\n PageSelectorItem,\n PillNavItem,\n PluginSwitcherPlugin,\n PillNavOption,\n} from '../types'\nimport { useAppExperiment } from '../composables/useAppExperiment'\nimport AppLayout from './AppLayout.vue'\nimport AppSidebar from './AppSidebar.vue'\nimport AppTopBar from './AppTopBar.vue'\nimport { usePluginWorkspaceControls } from './PluginWorkspaceView.controls'\nimport { usePluginWorkspaceNavigation } from './PluginWorkspaceView.navigation'\nimport {\n getPluginWorkspaceSidebarSlotNames,\n getPluginWorkspaceTopBarSlotNames,\n hasPluginWorkspaceSidebarSurface,\n} from './PluginWorkspaceView.shell'\nimport type {\n PluginWorkspaceForwardedSlotProps,\n PluginWorkspaceSlotProps,\n PluginWorkspaceViewProps,\n} from './PluginWorkspaceView.props'\n\nconst props = withDefaults(defineProps<PluginWorkspaceViewProps>(), {\n title: 'Workspace',\n subtitle: undefined,\n topBarVariant: 'card',\n homePath: '/',\n showLogo: true,\n pageSelector: undefined,\n currentPageSelectorId: undefined,\n pluginSwitcher: undefined,\n pillNav: undefined,\n currentPillId: undefined,\n showThemeToggle: false,\n showSettings: false,\n settingsConfig: undefined,\n showStandaloneLabel: true,\n standaloneLabel: 'Standalone',\n accountMenu: undefined,\n showNotifications: false,\n hasNotificationDot: false,\n showAdmin: false,\n adminPath: '/admin',\n showProfile: false,\n userName: undefined,\n userInitial: undefined,\n userEmail: undefined,\n experimentShell: false,\n experiment: undefined,\n experimentSave: undefined,\n experimentSaveDisabled: false,\n experimentSaveDisabledMessage: undefined,\n activeView: undefined,\n defaultActiveView: undefined,\n panels: () => ({}),\n forms: () => ({}),\n sidebarTitle: undefined,\n sidebarSubtitle: undefined,\n sidebarBadge: undefined,\n sidebarVariant: 'analysis',\n sidebarWidth: undefined,\n sidebarPosition: 'left',\n responsiveSidebar: true,\n showSidebarWhenEmpty: false,\n showSidebar: true,\n sidebarContentId: undefined,\n dense: true,\n sidebarCollapsible: undefined,\n sidebarCollapsedWidth: '3rem',\n sidebarCollapseButtonLabel: 'Collapse sidebar',\n sidebarExpandButtonLabel: 'Expand sidebar',\n floating: true,\n model: undefined,\n controls: undefined,\n controlOptions: () => ({}),\n componentBindings: undefined,\n componentProps: undefined,\n componentPropsById: undefined,\n modelValue: undefined,\n values: undefined,\n formEnhancements: undefined,\n showFormActions: false,\n formLoading: false,\n formDisabled: false,\n formReadonly: false,\n formSize: 'sm',\n sidebarCollapsed: undefined,\n defaultSidebarCollapsed: false,\n sidebarToggleLabel: 'Open workspace sidebar',\n sidebarCloseLabel: 'Close workspace sidebar',\n})\n\nconst emit = defineEmits<{\n 'update:activeView': [value: string]\n 'page-selector-select': [page: PageSelectorItem]\n 'pill-select': [item: PillNavItem]\n 'pill-option-select': [option: PillNavOption, item: PillNavItem]\n 'plugin-switcher-select': [plugin: PluginSwitcherPlugin]\n 'plugin-switcher-install': []\n 'account-menu-select': [item: AccountMenuItem]\n 'sign-out': []\n 'notifications-click': []\n 'settings-values-change': [data: Record<string, unknown>]\n 'profile-click': []\n 'admin-click': []\n 'experiment-select': [experiment: ExperimentSummary]\n 'experiment-detach': []\n 'update:modelValue': [values: Record<string, unknown>]\n 'update:values': [values: Record<string, unknown>]\n 'update:sidebarCollapsed': [value: boolean]\n 'update:toggle': [sectionId: string, value: boolean]\n 'section-action': [sectionId: string, actionId: string]\n 'form-submit': [sectionId: string, values: Record<string, unknown>]\n 'form-cancel': [sectionId: string]\n}>()\n\ndefineSlots<{\n default?: (props: PluginWorkspaceSlotProps) => unknown\n topbar?: (props: PluginWorkspaceSlotProps) => unknown\n sidebar?: (props: PluginWorkspaceSlotProps) => unknown\n icon?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n logo?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n 'page-selector-icon'?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n 'page-selector-item-icon'?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n nav?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n center?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n actions?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n 'account-menu-items'?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n 'account-menu-item-icon'?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n 'settings-appearance'?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n header?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n collapsed?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n 'collapsed-footer'?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n footer?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n}>()\n\nconst slots = useSlots()\n\nif (props.experimentShell) {\n useAppExperiment({\n experiment: computed(() => props.experiment),\n onSelect: experiment => emit('experiment-select', experiment),\n onSave: props.experimentSave,\n onDetach: () => emit('experiment-detach'),\n saveDisabled: computed(() => props.experimentSaveDisabled),\n saveDisabledMessage: computed(() => props.experimentSaveDisabledMessage),\n })\n}\n\nconst topBarSlotNames = computed<string[]>(() =>\n getPluginWorkspaceTopBarSlotNames(slots),\n)\n\nconst sidebarSlotNames = computed<string[]>(() =>\n getPluginWorkspaceSidebarSlotNames(slots),\n)\n\nconst {\n generatedPillNav,\n generatedDefaultView,\n internalControlValues,\n resolvedComponentBindings,\n resolvedComponentBindingsById,\n resolvedComponentProps,\n resolvedComponentPropsById,\n handleControlValuesUpdate,\n} = usePluginWorkspaceControls({\n model: () => props.model,\n controls: () => props.controls,\n controlOptions: () => props.controlOptions,\n modelValue: () => props.modelValue,\n values: () => props.values,\n componentBindings: () => props.componentBindings,\n componentProps: () => props.componentProps,\n componentPropsById: () => props.componentPropsById,\n emitValues: (values) => {\n emit('update:modelValue', values)\n emit('update:values', values)\n },\n})\n\nconst {\n resolvedPillNav,\n resolvedActiveView,\n resolvedCurrentPillId,\n resolvedCurrentPageSelectorId,\n setActiveView,\n handlePageSelectorSelect,\n handlePillSelect,\n} = usePluginWorkspaceNavigation({\n activeView: () => props.activeView,\n defaultActiveView: () => props.defaultActiveView,\n currentPillId: () => props.currentPillId,\n currentPageSelectorId: () => props.currentPageSelectorId,\n pillNav: () => props.pillNav,\n pageSelector: () => props.pageSelector,\n panelIds: () => Object.keys(props.panels),\n generatedPillNav,\n generatedDefaultView,\n emitActiveView: (viewId) => emit('update:activeView', viewId),\n emitPageSelectorSelect: (page) => emit('page-selector-select', page),\n emitPillSelect: (item) => emit('pill-select', item),\n})\n\nconst hasSidebarSurface = computed(() => {\n return hasPluginWorkspaceSidebarSurface({\n showSidebar: props.showSidebar,\n panels: props.panels,\n forms: props.forms,\n model: props.model,\n controls: props.controls,\n showSidebarWhenEmpty: props.showSidebarWhenEmpty,\n sidebarContentId: props.sidebarContentId,\n sidebarTitle: props.sidebarTitle,\n sidebarSubtitle: props.sidebarSubtitle,\n sidebarBadge: props.sidebarBadge,\n hasSidebarSlot: Boolean(slots.sidebar),\n sidebarSlotCount: sidebarSlotNames.value.length,\n })\n})\n\nfunction handlePillOptionSelect(option: PillNavOption, item: PillNavItem) {\n emit('pill-option-select', option, item)\n}\n\nfunction handleToggle(sectionId: string, value: boolean) {\n emit('update:toggle', sectionId, value)\n}\n\nfunction handleFormSubmit(sectionId: string, values: Record<string, unknown>) {\n emit('form-submit', sectionId, values)\n}\n\nfunction handleFormCancel(sectionId: string) {\n emit('form-cancel', sectionId)\n}\n\n</script>\n\n<template>\n <AppLayout\n class=\"mint-plugin-workspace-view\"\n :sidebar-position=\"sidebarPosition\"\n :sidebar-width=\"sidebarWidth\"\n :floating=\"floating\"\n :responsive-sidebar=\"responsiveSidebar\"\n :sidebar-toggle-label=\"sidebarToggleLabel\"\n :sidebar-close-label=\"sidebarCloseLabel\"\n >\n <template #topbar>\n <slot\n name=\"topbar\"\n :active-view=\"resolvedActiveView\"\n :set-active-view=\"setActiveView\"\n :values=\"internalControlValues\"\n :component-bindings=\"resolvedComponentBindings\"\n :component-bindings-by-id=\"resolvedComponentBindingsById\"\n :component-props=\"resolvedComponentProps\"\n :component-props-by-id=\"resolvedComponentPropsById\"\n >\n <AppTopBar\n :title=\"title\"\n :subtitle=\"subtitle\"\n :variant=\"topBarVariant\"\n :home-path=\"homePath\"\n :show-logo=\"showLogo\"\n :page-selector=\"pageSelector\"\n :current-page-selector-id=\"resolvedCurrentPageSelectorId\"\n :plugin-switcher=\"pluginSwitcher\"\n :pill-nav=\"resolvedPillNav\"\n :current-pill-id=\"resolvedCurrentPillId\"\n :show-theme-toggle=\"showThemeToggle\"\n :show-settings=\"showSettings\"\n :settings-config=\"settingsConfig\"\n :show-standalone-label=\"showStandaloneLabel\"\n :standalone-label=\"standaloneLabel\"\n :account-menu=\"accountMenu\"\n :show-notifications=\"showNotifications\"\n :has-notification-dot=\"hasNotificationDot\"\n :show-admin=\"showAdmin\"\n :admin-path=\"adminPath\"\n :show-profile=\"showProfile\"\n :user-name=\"userName\"\n :user-initial=\"userInitial\"\n :user-email=\"userEmail\"\n @page-selector-select=\"handlePageSelectorSelect\"\n @pill-select=\"handlePillSelect\"\n @pill-option-select=\"handlePillOptionSelect\"\n @plugin-switcher-select=\"emit('plugin-switcher-select', $event)\"\n @plugin-switcher-install=\"emit('plugin-switcher-install')\"\n @account-menu-select=\"emit('account-menu-select', $event)\"\n @sign-out=\"emit('sign-out')\"\n @notifications-click=\"emit('notifications-click')\"\n @settings-values-change=\"emit('settings-values-change', $event)\"\n @profile-click=\"emit('profile-click')\"\n @admin-click=\"emit('admin-click')\"\n >\n <template\n v-for=\"slotName in topBarSlotNames\"\n :key=\"slotName\"\n #[slotName]=\"slotProps\"\n >\n <!-- @vue-ignore dynamic SDK slot forwarding for AppTopBar passthrough slots -->\n <slot\n :name=\"slotName\"\n v-bind=\"slotProps ?? {}\"\n />\n </template>\n </AppTopBar>\n </slot>\n </template>\n\n <template v-if=\"hasSidebarSurface\" #sidebar>\n <slot\n name=\"sidebar\"\n :active-view=\"resolvedActiveView\"\n :set-active-view=\"setActiveView\"\n :values=\"internalControlValues\"\n :component-bindings=\"resolvedComponentBindings\"\n :component-bindings-by-id=\"resolvedComponentBindingsById\"\n :component-props=\"resolvedComponentProps\"\n :component-props-by-id=\"resolvedComponentPropsById\"\n >\n <AppSidebar\n :title=\"sidebarTitle\"\n :subtitle=\"sidebarSubtitle\"\n :badge=\"sidebarBadge\"\n :variant=\"sidebarVariant\"\n :panels=\"panels\"\n :active-view=\"resolvedActiveView\"\n :floating=\"false\"\n :dense=\"dense\"\n :width=\"sidebarWidth\"\n :side=\"sidebarPosition\"\n :forms=\"forms\"\n :content-id=\"sidebarContentId\"\n :show-when-empty=\"showSidebarWhenEmpty || sidebarContentId !== undefined\"\n :model=\"model\"\n :controls=\"controls\"\n :control-options=\"controlOptions\"\n :values=\"internalControlValues\"\n :form-enhancements=\"formEnhancements\"\n :show-form-actions=\"showFormActions\"\n :form-loading=\"formLoading\"\n :form-disabled=\"formDisabled\"\n :form-readonly=\"formReadonly\"\n :form-size=\"formSize\"\n :collapsible=\"sidebarCollapsible\"\n :collapsed=\"sidebarCollapsed\"\n :default-collapsed=\"defaultSidebarCollapsed\"\n :collapsed-width=\"sidebarCollapsedWidth\"\n :collapse-button-label=\"sidebarCollapseButtonLabel\"\n :expand-button-label=\"sidebarExpandButtonLabel\"\n @update:model-value=\"handleControlValuesUpdate\"\n @update:collapsed=\"emit('update:sidebarCollapsed', $event)\"\n @update:toggle=\"handleToggle\"\n @section-action=\"(sectionId, actionId) => emit('section-action', sectionId, actionId)\"\n @form-submit=\"handleFormSubmit\"\n @form-cancel=\"handleFormCancel\"\n >\n <template\n v-for=\"slotName in sidebarSlotNames\"\n :key=\"slotName\"\n #[slotName]=\"slotProps\"\n >\n <!-- @vue-ignore dynamic SDK slot forwarding for AppSidebar section-* passthrough slots -->\n <slot\n :name=\"slotName\"\n v-bind=\"slotProps ?? {}\"\n />\n </template>\n </AppSidebar>\n </slot>\n </template>\n\n <slot\n :active-view=\"resolvedActiveView\"\n :set-active-view=\"setActiveView\"\n :values=\"internalControlValues\"\n :component-bindings=\"resolvedComponentBindings\"\n :component-bindings-by-id=\"resolvedComponentBindingsById\"\n :component-props=\"resolvedComponentProps\"\n :component-props-by-id=\"resolvedComponentPropsById\"\n />\n </AppLayout>\n</template>\n","<script setup lang=\"ts\">\n/** Standard plugin workspace shell that combines AppLayout, AppTopBar, and AppSidebar with LEAF-style defaults. */\nimport { computed, useSlots } from 'vue'\nimport type {\n AccountMenuItem,\n ExperimentSummary,\n PageSelectorItem,\n PillNavItem,\n PluginSwitcherPlugin,\n PillNavOption,\n} from '../types'\nimport { useAppExperiment } from '../composables/useAppExperiment'\nimport AppLayout from './AppLayout.vue'\nimport AppSidebar from './AppSidebar.vue'\nimport AppTopBar from './AppTopBar.vue'\nimport { usePluginWorkspaceControls } from './PluginWorkspaceView.controls'\nimport { usePluginWorkspaceNavigation } from './PluginWorkspaceView.navigation'\nimport {\n getPluginWorkspaceSidebarSlotNames,\n getPluginWorkspaceTopBarSlotNames,\n hasPluginWorkspaceSidebarSurface,\n} from './PluginWorkspaceView.shell'\nimport type {\n PluginWorkspaceForwardedSlotProps,\n PluginWorkspaceSlotProps,\n PluginWorkspaceViewProps,\n} from './PluginWorkspaceView.props'\n\nconst props = withDefaults(defineProps<PluginWorkspaceViewProps>(), {\n title: 'Workspace',\n subtitle: undefined,\n topBarVariant: 'card',\n homePath: '/',\n showLogo: true,\n pageSelector: undefined,\n currentPageSelectorId: undefined,\n pluginSwitcher: undefined,\n pillNav: undefined,\n currentPillId: undefined,\n showThemeToggle: false,\n showSettings: false,\n settingsConfig: undefined,\n showStandaloneLabel: true,\n standaloneLabel: 'Standalone',\n accountMenu: undefined,\n showNotifications: false,\n hasNotificationDot: false,\n showAdmin: false,\n adminPath: '/admin',\n showProfile: false,\n userName: undefined,\n userInitial: undefined,\n userEmail: undefined,\n experimentShell: false,\n experiment: undefined,\n experimentSave: undefined,\n experimentSaveDisabled: false,\n experimentSaveDisabledMessage: undefined,\n activeView: undefined,\n defaultActiveView: undefined,\n panels: () => ({}),\n forms: () => ({}),\n sidebarTitle: undefined,\n sidebarSubtitle: undefined,\n sidebarBadge: undefined,\n sidebarVariant: 'analysis',\n sidebarWidth: undefined,\n sidebarPosition: 'left',\n responsiveSidebar: true,\n showSidebarWhenEmpty: false,\n showSidebar: true,\n sidebarContentId: undefined,\n dense: true,\n sidebarCollapsible: undefined,\n sidebarCollapsedWidth: '3rem',\n sidebarCollapseButtonLabel: 'Collapse sidebar',\n sidebarExpandButtonLabel: 'Expand sidebar',\n floating: true,\n model: undefined,\n controls: undefined,\n controlOptions: () => ({}),\n componentBindings: undefined,\n componentProps: undefined,\n componentPropsById: undefined,\n modelValue: undefined,\n values: undefined,\n formEnhancements: undefined,\n showFormActions: false,\n formLoading: false,\n formDisabled: false,\n formReadonly: false,\n formSize: 'sm',\n sidebarCollapsed: undefined,\n defaultSidebarCollapsed: false,\n sidebarToggleLabel: 'Open workspace sidebar',\n sidebarCloseLabel: 'Close workspace sidebar',\n})\n\nconst emit = defineEmits<{\n 'update:activeView': [value: string]\n 'page-selector-select': [page: PageSelectorItem]\n 'pill-select': [item: PillNavItem]\n 'pill-option-select': [option: PillNavOption, item: PillNavItem]\n 'plugin-switcher-select': [plugin: PluginSwitcherPlugin]\n 'plugin-switcher-install': []\n 'account-menu-select': [item: AccountMenuItem]\n 'sign-out': []\n 'notifications-click': []\n 'settings-values-change': [data: Record<string, unknown>]\n 'profile-click': []\n 'admin-click': []\n 'experiment-select': [experiment: ExperimentSummary]\n 'experiment-detach': []\n 'update:modelValue': [values: Record<string, unknown>]\n 'update:values': [values: Record<string, unknown>]\n 'update:sidebarCollapsed': [value: boolean]\n 'update:toggle': [sectionId: string, value: boolean]\n 'section-action': [sectionId: string, actionId: string]\n 'form-submit': [sectionId: string, values: Record<string, unknown>]\n 'form-cancel': [sectionId: string]\n}>()\n\ndefineSlots<{\n default?: (props: PluginWorkspaceSlotProps) => unknown\n topbar?: (props: PluginWorkspaceSlotProps) => unknown\n sidebar?: (props: PluginWorkspaceSlotProps) => unknown\n icon?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n logo?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n 'page-selector-icon'?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n 'page-selector-item-icon'?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n nav?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n center?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n actions?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n 'account-menu-items'?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n 'account-menu-item-icon'?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n 'settings-appearance'?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n header?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n collapsed?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n 'collapsed-footer'?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n footer?: (props: PluginWorkspaceForwardedSlotProps) => unknown\n}>()\n\nconst slots = useSlots()\n\nif (props.experimentShell) {\n useAppExperiment({\n experiment: computed(() => props.experiment),\n onSelect: experiment => emit('experiment-select', experiment),\n onSave: props.experimentSave,\n onDetach: () => emit('experiment-detach'),\n saveDisabled: computed(() => props.experimentSaveDisabled),\n saveDisabledMessage: computed(() => props.experimentSaveDisabledMessage),\n })\n}\n\nconst topBarSlotNames = computed<string[]>(() =>\n getPluginWorkspaceTopBarSlotNames(slots),\n)\n\nconst sidebarSlotNames = computed<string[]>(() =>\n getPluginWorkspaceSidebarSlotNames(slots),\n)\n\nconst {\n generatedPillNav,\n generatedDefaultView,\n internalControlValues,\n resolvedComponentBindings,\n resolvedComponentBindingsById,\n resolvedComponentProps,\n resolvedComponentPropsById,\n handleControlValuesUpdate,\n} = usePluginWorkspaceControls({\n model: () => props.model,\n controls: () => props.controls,\n controlOptions: () => props.controlOptions,\n modelValue: () => props.modelValue,\n values: () => props.values,\n componentBindings: () => props.componentBindings,\n componentProps: () => props.componentProps,\n componentPropsById: () => props.componentPropsById,\n emitValues: (values) => {\n emit('update:modelValue', values)\n emit('update:values', values)\n },\n})\n\nconst {\n resolvedPillNav,\n resolvedActiveView,\n resolvedCurrentPillId,\n resolvedCurrentPageSelectorId,\n setActiveView,\n handlePageSelectorSelect,\n handlePillSelect,\n} = usePluginWorkspaceNavigation({\n activeView: () => props.activeView,\n defaultActiveView: () => props.defaultActiveView,\n currentPillId: () => props.currentPillId,\n currentPageSelectorId: () => props.currentPageSelectorId,\n pillNav: () => props.pillNav,\n pageSelector: () => props.pageSelector,\n panelIds: () => Object.keys(props.panels),\n generatedPillNav,\n generatedDefaultView,\n emitActiveView: (viewId) => emit('update:activeView', viewId),\n emitPageSelectorSelect: (page) => emit('page-selector-select', page),\n emitPillSelect: (item) => emit('pill-select', item),\n})\n\nconst hasSidebarSurface = computed(() => {\n return hasPluginWorkspaceSidebarSurface({\n showSidebar: props.showSidebar,\n panels: props.panels,\n forms: props.forms,\n model: props.model,\n controls: props.controls,\n showSidebarWhenEmpty: props.showSidebarWhenEmpty,\n sidebarContentId: props.sidebarContentId,\n sidebarTitle: props.sidebarTitle,\n sidebarSubtitle: props.sidebarSubtitle,\n sidebarBadge: props.sidebarBadge,\n hasSidebarSlot: Boolean(slots.sidebar),\n sidebarSlotCount: sidebarSlotNames.value.length,\n })\n})\n\nfunction handlePillOptionSelect(option: PillNavOption, item: PillNavItem) {\n emit('pill-option-select', option, item)\n}\n\nfunction handleToggle(sectionId: string, value: boolean) {\n emit('update:toggle', sectionId, value)\n}\n\nfunction handleFormSubmit(sectionId: string, values: Record<string, unknown>) {\n emit('form-submit', sectionId, values)\n}\n\nfunction handleFormCancel(sectionId: string) {\n emit('form-cancel', sectionId)\n}\n\n</script>\n\n<template>\n <AppLayout\n class=\"mint-plugin-workspace-view\"\n :sidebar-position=\"sidebarPosition\"\n :sidebar-width=\"sidebarWidth\"\n :floating=\"floating\"\n :responsive-sidebar=\"responsiveSidebar\"\n :sidebar-toggle-label=\"sidebarToggleLabel\"\n :sidebar-close-label=\"sidebarCloseLabel\"\n >\n <template #topbar>\n <slot\n name=\"topbar\"\n :active-view=\"resolvedActiveView\"\n :set-active-view=\"setActiveView\"\n :values=\"internalControlValues\"\n :component-bindings=\"resolvedComponentBindings\"\n :component-bindings-by-id=\"resolvedComponentBindingsById\"\n :component-props=\"resolvedComponentProps\"\n :component-props-by-id=\"resolvedComponentPropsById\"\n >\n <AppTopBar\n :title=\"title\"\n :subtitle=\"subtitle\"\n :variant=\"topBarVariant\"\n :home-path=\"homePath\"\n :show-logo=\"showLogo\"\n :page-selector=\"pageSelector\"\n :current-page-selector-id=\"resolvedCurrentPageSelectorId\"\n :plugin-switcher=\"pluginSwitcher\"\n :pill-nav=\"resolvedPillNav\"\n :current-pill-id=\"resolvedCurrentPillId\"\n :show-theme-toggle=\"showThemeToggle\"\n :show-settings=\"showSettings\"\n :settings-config=\"settingsConfig\"\n :show-standalone-label=\"showStandaloneLabel\"\n :standalone-label=\"standaloneLabel\"\n :account-menu=\"accountMenu\"\n :show-notifications=\"showNotifications\"\n :has-notification-dot=\"hasNotificationDot\"\n :show-admin=\"showAdmin\"\n :admin-path=\"adminPath\"\n :show-profile=\"showProfile\"\n :user-name=\"userName\"\n :user-initial=\"userInitial\"\n :user-email=\"userEmail\"\n @page-selector-select=\"handlePageSelectorSelect\"\n @pill-select=\"handlePillSelect\"\n @pill-option-select=\"handlePillOptionSelect\"\n @plugin-switcher-select=\"emit('plugin-switcher-select', $event)\"\n @plugin-switcher-install=\"emit('plugin-switcher-install')\"\n @account-menu-select=\"emit('account-menu-select', $event)\"\n @sign-out=\"emit('sign-out')\"\n @notifications-click=\"emit('notifications-click')\"\n @settings-values-change=\"emit('settings-values-change', $event)\"\n @profile-click=\"emit('profile-click')\"\n @admin-click=\"emit('admin-click')\"\n >\n <template\n v-for=\"slotName in topBarSlotNames\"\n :key=\"slotName\"\n #[slotName]=\"slotProps\"\n >\n <!-- @vue-ignore dynamic SDK slot forwarding for AppTopBar passthrough slots -->\n <slot\n :name=\"slotName\"\n v-bind=\"slotProps ?? {}\"\n />\n </template>\n </AppTopBar>\n </slot>\n </template>\n\n <template v-if=\"hasSidebarSurface\" #sidebar>\n <slot\n name=\"sidebar\"\n :active-view=\"resolvedActiveView\"\n :set-active-view=\"setActiveView\"\n :values=\"internalControlValues\"\n :component-bindings=\"resolvedComponentBindings\"\n :component-bindings-by-id=\"resolvedComponentBindingsById\"\n :component-props=\"resolvedComponentProps\"\n :component-props-by-id=\"resolvedComponentPropsById\"\n >\n <AppSidebar\n :title=\"sidebarTitle\"\n :subtitle=\"sidebarSubtitle\"\n :badge=\"sidebarBadge\"\n :variant=\"sidebarVariant\"\n :panels=\"panels\"\n :active-view=\"resolvedActiveView\"\n :floating=\"false\"\n :dense=\"dense\"\n :width=\"sidebarWidth\"\n :side=\"sidebarPosition\"\n :forms=\"forms\"\n :content-id=\"sidebarContentId\"\n :show-when-empty=\"showSidebarWhenEmpty || sidebarContentId !== undefined\"\n :model=\"model\"\n :controls=\"controls\"\n :control-options=\"controlOptions\"\n :values=\"internalControlValues\"\n :form-enhancements=\"formEnhancements\"\n :show-form-actions=\"showFormActions\"\n :form-loading=\"formLoading\"\n :form-disabled=\"formDisabled\"\n :form-readonly=\"formReadonly\"\n :form-size=\"formSize\"\n :collapsible=\"sidebarCollapsible\"\n :collapsed=\"sidebarCollapsed\"\n :default-collapsed=\"defaultSidebarCollapsed\"\n :collapsed-width=\"sidebarCollapsedWidth\"\n :collapse-button-label=\"sidebarCollapseButtonLabel\"\n :expand-button-label=\"sidebarExpandButtonLabel\"\n @update:model-value=\"handleControlValuesUpdate\"\n @update:collapsed=\"emit('update:sidebarCollapsed', $event)\"\n @update:toggle=\"handleToggle\"\n @section-action=\"(sectionId, actionId) => emit('section-action', sectionId, actionId)\"\n @form-submit=\"handleFormSubmit\"\n @form-cancel=\"handleFormCancel\"\n >\n <template\n v-for=\"slotName in sidebarSlotNames\"\n :key=\"slotName\"\n #[slotName]=\"slotProps\"\n >\n <!-- @vue-ignore dynamic SDK slot forwarding for AppSidebar section-* passthrough slots -->\n <slot\n :name=\"slotName\"\n v-bind=\"slotProps ?? {}\"\n />\n </template>\n </AppSidebar>\n </slot>\n </template>\n\n <slot\n :active-view=\"resolvedActiveView\"\n :set-active-view=\"setActiveView\"\n :values=\"internalControlValues\"\n :component-bindings=\"resolvedComponentBindings\"\n :component-bindings-by-id=\"resolvedComponentBindingsById\"\n :component-props=\"resolvedComponentProps\"\n :component-props-by-id=\"resolvedComponentPropsById\"\n />\n </AppLayout>\n</template>\n","<script setup lang=\"ts\">\n/** Number + unit control used by DoseCalculator volume fields. */\nimport type { VolumeUnit, VolumeValue } from '../composables/useDoseCalculator'\n\ninterface Props {\n label: string\n modelValue: VolumeValue\n units: VolumeUnit[]\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: VolumeValue]\n}>()\n\nfunction handleValueInput(event: Event) {\n const input = event.target as HTMLInputElement\n const value = parseFloat(input.value)\n if (Number.isNaN(value)) return\n emit('update:modelValue', { ...props.modelValue, value })\n}\n\nfunction handleUnitChange(event: Event) {\n const select = event.target as HTMLSelectElement\n emit('update:modelValue', {\n ...props.modelValue,\n unit: select.value as VolumeUnit,\n })\n}\n</script>\n\n<template>\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">{{ label }}</label>\n <div class=\"mint-dose-calculator__field-row\">\n <input\n type=\"number\"\n :value=\"modelValue.value\"\n :disabled=\"disabled\"\n class=\"mint-dose-calculator__input\"\n min=\"0\"\n step=\"any\"\n @input=\"handleValueInput\"\n />\n <select\n :value=\"modelValue.unit\"\n :disabled=\"disabled\"\n class=\"mint-dose-calculator__select\"\n @change=\"handleUnitChange\"\n >\n <option v-for=\"unit in units\" :key=\"unit\" :value=\"unit\">\n {{ unit }}\n </option>\n </select>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Number + unit control used by DoseCalculator volume fields. */\nimport type { VolumeUnit, VolumeValue } from '../composables/useDoseCalculator'\n\ninterface Props {\n label: string\n modelValue: VolumeValue\n units: VolumeUnit[]\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: VolumeValue]\n}>()\n\nfunction handleValueInput(event: Event) {\n const input = event.target as HTMLInputElement\n const value = parseFloat(input.value)\n if (Number.isNaN(value)) return\n emit('update:modelValue', { ...props.modelValue, value })\n}\n\nfunction handleUnitChange(event: Event) {\n const select = event.target as HTMLSelectElement\n emit('update:modelValue', {\n ...props.modelValue,\n unit: select.value as VolumeUnit,\n })\n}\n</script>\n\n<template>\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">{{ label }}</label>\n <div class=\"mint-dose-calculator__field-row\">\n <input\n type=\"number\"\n :value=\"modelValue.value\"\n :disabled=\"disabled\"\n class=\"mint-dose-calculator__input\"\n min=\"0\"\n step=\"any\"\n @input=\"handleValueInput\"\n />\n <select\n :value=\"modelValue.unit\"\n :disabled=\"disabled\"\n class=\"mint-dose-calculator__select\"\n @change=\"handleUnitChange\"\n >\n <option v-for=\"unit in units\" :key=\"unit\" :value=\"unit\">\n {{ unit }}\n </option>\n </select>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Tabbed dosing calculator supporting single dilution, serial dilution, and unit conversion, with optional direct assignment to selected plate wells. */\nimport { ref, computed, watch } from 'vue'\nimport ConcentrationInput from './ConcentrationInput.vue'\nimport {\n useConcentrationUnits,\n type ConcentrationValue,\n} from '../composables/useConcentrationUnits'\nimport {\n useDoseCalculator,\n type VolumeValue,\n type DilutionResult,\n type SerialDilutionResult,\n type WellConcentration,\n} from '../composables/useDoseCalculator'\nimport DoseCalculatorVolumeField from './DoseCalculatorVolumeField.vue'\n\ntype CalculatorMode = 'dilution' | 'serial' | 'conversion'\n\ninterface Props {\n mode?: CalculatorMode | 'auto'\n molecularWeight?: number\n targetWells?: string[]\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n mode: 'auto',\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'apply-to-wells': [results: WellConcentration[]]\n 'calculate': [result: DilutionResult | SerialDilutionResult]\n}>()\n\nconst { formatWithUnit } = useConcentrationUnits()\nconst {\n volumeUnits,\n calculateDilution,\n calculateSerialDilution,\n formatVolume,\n generateWellConcentrations,\n} = useDoseCalculator()\n\n// Active mode (for tabbed interface)\nconst activeMode = ref<CalculatorMode>(props.mode === 'auto' ? 'dilution' : props.mode)\n\n// Dilution form state\nconst stockConcentration = ref<ConcentrationValue>({ value: 10, unit: 'mM' })\nconst finalConcentration = ref<ConcentrationValue>({ value: 100, unit: 'µM' })\nconst finalVolume = ref<VolumeValue>({ value: 1, unit: 'mL' })\n\n// Serial dilution form state\nconst serialStartConcentration = ref<ConcentrationValue>({ value: 100, unit: 'µM' })\nconst serialDilutionFactor = ref(3)\nconst serialNumberOfDilutions = ref(8)\nconst serialVolumePerWell = ref<VolumeValue>({ value: 100, unit: 'µL' })\n\n// Results\nconst dilutionResult = ref<DilutionResult | null>(null)\nconst serialResult = ref<SerialDilutionResult | null>(null)\n\n// Computed\nconst showTabs = computed(() => props.mode === 'auto')\n\nconst hasTargetWells = computed(() => {\n return props.targetWells && props.targetWells.length > 0\n})\n\nconst canApplyToWells = computed(() => {\n if (!hasTargetWells.value) return false\n if (activeMode.value === 'serial' && serialResult.value?.valid) return true\n if (activeMode.value === 'dilution' && dilutionResult.value?.valid) return true\n return false\n})\n\n// Calculate dilution result\nfunction recalculateDilution() {\n if (!stockConcentration.value || !finalConcentration.value || !finalVolume.value) {\n dilutionResult.value = null\n return\n }\n\n dilutionResult.value = calculateDilution({\n stockConcentration: stockConcentration.value,\n finalConcentration: finalConcentration.value,\n finalVolume: finalVolume.value,\n })\n\n if (dilutionResult.value.valid) {\n emit('calculate', dilutionResult.value)\n }\n}\n\n// Calculate serial dilution result\nfunction recalculateSerial() {\n if (\n !serialStartConcentration.value ||\n serialDilutionFactor.value <= 1 ||\n serialNumberOfDilutions.value < 1 ||\n !serialVolumePerWell.value\n ) {\n serialResult.value = null\n return\n }\n\n serialResult.value = calculateSerialDilution({\n startingConcentration: serialStartConcentration.value,\n dilutionFactor: serialDilutionFactor.value,\n numberOfDilutions: serialNumberOfDilutions.value,\n volumePerWell: serialVolumePerWell.value,\n })\n\n if (serialResult.value.valid) {\n emit('calculate', serialResult.value)\n }\n}\n\n// Apply to wells\nfunction handleApplyToWells() {\n if (!props.targetWells || props.targetWells.length === 0) return\n\n if (activeMode.value === 'serial' && serialResult.value?.valid) {\n const wellConcentrations = generateWellConcentrations(\n serialResult.value,\n props.targetWells\n )\n emit('apply-to-wells', wellConcentrations)\n }\n\n if (activeMode.value === 'dilution' && dilutionResult.value?.valid) {\n // For simple dilution, apply same concentration to all wells\n const wellConcentrations: WellConcentration[] = props.targetWells.map(wellId => ({\n wellId,\n concentration: finalConcentration.value,\n volume: finalVolume.value,\n }))\n emit('apply-to-wells', wellConcentrations)\n }\n}\n\n// Watch for changes and recalculate\nwatch(\n [stockConcentration, finalConcentration, finalVolume],\n () => {\n if (activeMode.value === 'dilution') {\n recalculateDilution()\n }\n },\n { deep: true, immediate: true }\n)\n\nwatch(\n [serialStartConcentration, serialDilutionFactor, serialNumberOfDilutions, serialVolumePerWell],\n () => {\n if (activeMode.value === 'serial') {\n recalculateSerial()\n }\n },\n { deep: true, immediate: true }\n)\n\nwatch(activeMode, (mode) => {\n if (mode === 'dilution') {\n recalculateDilution()\n } else if (mode === 'serial') {\n recalculateSerial()\n }\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-dose-calculator',\n disabled ? 'mint-dose-calculator--disabled' : '',\n ]\"\n >\n <!-- Tabs -->\n <div v-if=\"showTabs\" class=\"mint-dose-calculator__tabs\" role=\"tablist\">\n <button\n type=\"button\"\n :class=\"[\n 'mint-dose-calculator__tab',\n activeMode === 'dilution' ? 'mint-dose-calculator__tab--active' : '',\n ]\"\n role=\"tab\"\n :aria-selected=\"activeMode === 'dilution'\"\n @click=\"activeMode = 'dilution'\"\n >\n Dilution\n </button>\n <button\n type=\"button\"\n :class=\"[\n 'mint-dose-calculator__tab',\n activeMode === 'serial' ? 'mint-dose-calculator__tab--active' : '',\n ]\"\n role=\"tab\"\n :aria-selected=\"activeMode === 'serial'\"\n @click=\"activeMode = 'serial'\"\n >\n Serial\n </button>\n <button\n type=\"button\"\n :class=\"[\n 'mint-dose-calculator__tab',\n activeMode === 'conversion' ? 'mint-dose-calculator__tab--active' : '',\n ]\"\n role=\"tab\"\n :aria-selected=\"activeMode === 'conversion'\"\n @click=\"activeMode = 'conversion'\"\n >\n Conversion\n </button>\n </div>\n\n <!-- Dilution form -->\n <div\n v-if=\"activeMode === 'dilution'\"\n class=\"mint-dose-calculator__form\"\n role=\"tabpanel\"\n >\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">Stock Concentration</label>\n <ConcentrationInput\n v-model=\"stockConcentration\"\n :disabled=\"disabled\"\n :show-conversion=\"false\"\n size=\"md\"\n />\n </div>\n\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">Final Concentration</label>\n <ConcentrationInput\n v-model=\"finalConcentration\"\n :disabled=\"disabled\"\n :show-conversion=\"false\"\n size=\"md\"\n />\n </div>\n\n <DoseCalculatorVolumeField\n v-model=\"finalVolume\"\n label=\"Final Volume\"\n :units=\"volumeUnits\"\n :disabled=\"disabled\"\n />\n\n <div class=\"mint-dose-calculator__divider\" />\n\n <!-- Dilution Result -->\n <div v-if=\"dilutionResult?.valid\" class=\"mint-dose-calculator__result\">\n <div class=\"mint-dose-calculator__result-title\">Result</div>\n <div class=\"mint-dose-calculator__result-row\">\n <span class=\"mint-dose-calculator__result-label\">Stock volume</span>\n <span class=\"mint-dose-calculator__result-value\">\n {{ formatVolume(dilutionResult.stockVolume) }}\n </span>\n </div>\n <div class=\"mint-dose-calculator__result-row\">\n <span class=\"mint-dose-calculator__result-label\">Diluent volume</span>\n <span class=\"mint-dose-calculator__result-value\">\n {{ formatVolume(dilutionResult.diluentVolume) }}\n </span>\n </div>\n <div class=\"mint-dose-calculator__result-row\">\n <span class=\"mint-dose-calculator__result-label\">Dilution factor</span>\n <span class=\"mint-dose-calculator__result-value\">\n {{ dilutionResult.dilutionFactor.toFixed(1) }}×\n </span>\n </div>\n </div>\n\n <div v-else-if=\"dilutionResult?.error\" class=\"mint-dose-calculator__error\">\n {{ dilutionResult.error }}\n </div>\n </div>\n\n <!-- Serial Dilution form -->\n <div\n v-else-if=\"activeMode === 'serial'\"\n class=\"mint-dose-calculator__form\"\n role=\"tabpanel\"\n >\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">Starting Concentration</label>\n <ConcentrationInput\n v-model=\"serialStartConcentration\"\n :disabled=\"disabled\"\n :show-conversion=\"false\"\n size=\"md\"\n />\n </div>\n\n <div class=\"mint-dose-calculator__field-row\">\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">Dilution Factor</label>\n <div class=\"mint-dose-calculator__input-wrapper\">\n <input\n v-model.number=\"serialDilutionFactor\"\n type=\"number\"\n :disabled=\"disabled\"\n class=\"mint-dose-calculator__input\"\n min=\"1.1\"\n step=\"0.1\"\n />\n <span class=\"mint-dose-calculator__unit-suffix\">×</span>\n </div>\n </div>\n\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">Dilutions</label>\n <input\n v-model.number=\"serialNumberOfDilutions\"\n type=\"number\"\n :disabled=\"disabled\"\n class=\"mint-dose-calculator__input\"\n min=\"1\"\n max=\"24\"\n />\n </div>\n </div>\n\n <DoseCalculatorVolumeField\n v-model=\"serialVolumePerWell\"\n label=\"Volume per Well\"\n :units=\"volumeUnits\"\n :disabled=\"disabled\"\n />\n\n <div class=\"mint-dose-calculator__divider\" />\n\n <!-- Serial Preview -->\n <div v-if=\"serialResult?.valid\" class=\"mint-dose-calculator__preview\">\n <div class=\"mint-dose-calculator__preview-title\">Preview</div>\n <div class=\"mint-dose-calculator__preview-steps\">\n <template v-for=\"(step, index) in serialResult.steps\" :key=\"step.stepNumber\">\n <div class=\"mint-dose-calculator__preview-step\">\n <span class=\"mint-dose-calculator__preview-step-num\">{{ step.stepNumber }}:</span>\n <span class=\"mint-dose-calculator__preview-step-conc\">\n {{ formatWithUnit(step.concentration, 3) }}\n </span>\n </div>\n <span\n v-if=\"index < serialResult.steps.length - 1\"\n class=\"mint-dose-calculator__preview-arrow\"\n >\n →\n </span>\n </template>\n </div>\n </div>\n\n <div v-else-if=\"serialResult?.error\" class=\"mint-dose-calculator__error\">\n {{ serialResult.error }}\n </div>\n </div>\n\n <!-- Conversion form -->\n <div\n v-else-if=\"activeMode === 'conversion'\"\n class=\"mint-dose-calculator__form\"\n role=\"tabpanel\"\n >\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">Input Concentration</label>\n <ConcentrationInput\n :disabled=\"disabled\"\n :molecular-weight=\"molecularWeight\"\n :show-conversion=\"true\"\n size=\"md\"\n />\n </div>\n\n <div\n v-if=\"!molecularWeight\"\n class=\"mint-dose-calculator__error\"\n >\n Provide molecular weight for mass ↔ molarity conversion\n </div>\n </div>\n\n <!-- Molecular weight section (shown if needed) -->\n <div\n v-if=\"molecularWeight\"\n class=\"mint-dose-calculator__mw-section\"\n >\n <span class=\"mint-dose-calculator__mw-label\">\n Molecular Weight: {{ molecularWeight }} g/mol\n </span>\n </div>\n\n <!-- Apply to wells button -->\n <div v-if=\"hasTargetWells\" class=\"mint-dose-calculator__form\">\n <button\n type=\"button\"\n class=\"mint-dose-calculator__apply-btn\"\n :disabled=\"!canApplyToWells || disabled\"\n @click=\"handleApplyToWells\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n Apply to {{ targetWells?.length }} selected well{{ targetWells?.length === 1 ? '' : 's' }}\n </button>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/dose-calculator.css';\n</style>\n","<script setup lang=\"ts\">\n/** Tabbed dosing calculator supporting single dilution, serial dilution, and unit conversion, with optional direct assignment to selected plate wells. */\nimport { ref, computed, watch } from 'vue'\nimport ConcentrationInput from './ConcentrationInput.vue'\nimport {\n useConcentrationUnits,\n type ConcentrationValue,\n} from '../composables/useConcentrationUnits'\nimport {\n useDoseCalculator,\n type VolumeValue,\n type DilutionResult,\n type SerialDilutionResult,\n type WellConcentration,\n} from '../composables/useDoseCalculator'\nimport DoseCalculatorVolumeField from './DoseCalculatorVolumeField.vue'\n\ntype CalculatorMode = 'dilution' | 'serial' | 'conversion'\n\ninterface Props {\n mode?: CalculatorMode | 'auto'\n molecularWeight?: number\n targetWells?: string[]\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n mode: 'auto',\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'apply-to-wells': [results: WellConcentration[]]\n 'calculate': [result: DilutionResult | SerialDilutionResult]\n}>()\n\nconst { formatWithUnit } = useConcentrationUnits()\nconst {\n volumeUnits,\n calculateDilution,\n calculateSerialDilution,\n formatVolume,\n generateWellConcentrations,\n} = useDoseCalculator()\n\n// Active mode (for tabbed interface)\nconst activeMode = ref<CalculatorMode>(props.mode === 'auto' ? 'dilution' : props.mode)\n\n// Dilution form state\nconst stockConcentration = ref<ConcentrationValue>({ value: 10, unit: 'mM' })\nconst finalConcentration = ref<ConcentrationValue>({ value: 100, unit: 'µM' })\nconst finalVolume = ref<VolumeValue>({ value: 1, unit: 'mL' })\n\n// Serial dilution form state\nconst serialStartConcentration = ref<ConcentrationValue>({ value: 100, unit: 'µM' })\nconst serialDilutionFactor = ref(3)\nconst serialNumberOfDilutions = ref(8)\nconst serialVolumePerWell = ref<VolumeValue>({ value: 100, unit: 'µL' })\n\n// Results\nconst dilutionResult = ref<DilutionResult | null>(null)\nconst serialResult = ref<SerialDilutionResult | null>(null)\n\n// Computed\nconst showTabs = computed(() => props.mode === 'auto')\n\nconst hasTargetWells = computed(() => {\n return props.targetWells && props.targetWells.length > 0\n})\n\nconst canApplyToWells = computed(() => {\n if (!hasTargetWells.value) return false\n if (activeMode.value === 'serial' && serialResult.value?.valid) return true\n if (activeMode.value === 'dilution' && dilutionResult.value?.valid) return true\n return false\n})\n\n// Calculate dilution result\nfunction recalculateDilution() {\n if (!stockConcentration.value || !finalConcentration.value || !finalVolume.value) {\n dilutionResult.value = null\n return\n }\n\n dilutionResult.value = calculateDilution({\n stockConcentration: stockConcentration.value,\n finalConcentration: finalConcentration.value,\n finalVolume: finalVolume.value,\n })\n\n if (dilutionResult.value.valid) {\n emit('calculate', dilutionResult.value)\n }\n}\n\n// Calculate serial dilution result\nfunction recalculateSerial() {\n if (\n !serialStartConcentration.value ||\n serialDilutionFactor.value <= 1 ||\n serialNumberOfDilutions.value < 1 ||\n !serialVolumePerWell.value\n ) {\n serialResult.value = null\n return\n }\n\n serialResult.value = calculateSerialDilution({\n startingConcentration: serialStartConcentration.value,\n dilutionFactor: serialDilutionFactor.value,\n numberOfDilutions: serialNumberOfDilutions.value,\n volumePerWell: serialVolumePerWell.value,\n })\n\n if (serialResult.value.valid) {\n emit('calculate', serialResult.value)\n }\n}\n\n// Apply to wells\nfunction handleApplyToWells() {\n if (!props.targetWells || props.targetWells.length === 0) return\n\n if (activeMode.value === 'serial' && serialResult.value?.valid) {\n const wellConcentrations = generateWellConcentrations(\n serialResult.value,\n props.targetWells\n )\n emit('apply-to-wells', wellConcentrations)\n }\n\n if (activeMode.value === 'dilution' && dilutionResult.value?.valid) {\n // For simple dilution, apply same concentration to all wells\n const wellConcentrations: WellConcentration[] = props.targetWells.map(wellId => ({\n wellId,\n concentration: finalConcentration.value,\n volume: finalVolume.value,\n }))\n emit('apply-to-wells', wellConcentrations)\n }\n}\n\n// Watch for changes and recalculate\nwatch(\n [stockConcentration, finalConcentration, finalVolume],\n () => {\n if (activeMode.value === 'dilution') {\n recalculateDilution()\n }\n },\n { deep: true, immediate: true }\n)\n\nwatch(\n [serialStartConcentration, serialDilutionFactor, serialNumberOfDilutions, serialVolumePerWell],\n () => {\n if (activeMode.value === 'serial') {\n recalculateSerial()\n }\n },\n { deep: true, immediate: true }\n)\n\nwatch(activeMode, (mode) => {\n if (mode === 'dilution') {\n recalculateDilution()\n } else if (mode === 'serial') {\n recalculateSerial()\n }\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-dose-calculator',\n disabled ? 'mint-dose-calculator--disabled' : '',\n ]\"\n >\n <!-- Tabs -->\n <div v-if=\"showTabs\" class=\"mint-dose-calculator__tabs\" role=\"tablist\">\n <button\n type=\"button\"\n :class=\"[\n 'mint-dose-calculator__tab',\n activeMode === 'dilution' ? 'mint-dose-calculator__tab--active' : '',\n ]\"\n role=\"tab\"\n :aria-selected=\"activeMode === 'dilution'\"\n @click=\"activeMode = 'dilution'\"\n >\n Dilution\n </button>\n <button\n type=\"button\"\n :class=\"[\n 'mint-dose-calculator__tab',\n activeMode === 'serial' ? 'mint-dose-calculator__tab--active' : '',\n ]\"\n role=\"tab\"\n :aria-selected=\"activeMode === 'serial'\"\n @click=\"activeMode = 'serial'\"\n >\n Serial\n </button>\n <button\n type=\"button\"\n :class=\"[\n 'mint-dose-calculator__tab',\n activeMode === 'conversion' ? 'mint-dose-calculator__tab--active' : '',\n ]\"\n role=\"tab\"\n :aria-selected=\"activeMode === 'conversion'\"\n @click=\"activeMode = 'conversion'\"\n >\n Conversion\n </button>\n </div>\n\n <!-- Dilution form -->\n <div\n v-if=\"activeMode === 'dilution'\"\n class=\"mint-dose-calculator__form\"\n role=\"tabpanel\"\n >\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">Stock Concentration</label>\n <ConcentrationInput\n v-model=\"stockConcentration\"\n :disabled=\"disabled\"\n :show-conversion=\"false\"\n size=\"md\"\n />\n </div>\n\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">Final Concentration</label>\n <ConcentrationInput\n v-model=\"finalConcentration\"\n :disabled=\"disabled\"\n :show-conversion=\"false\"\n size=\"md\"\n />\n </div>\n\n <DoseCalculatorVolumeField\n v-model=\"finalVolume\"\n label=\"Final Volume\"\n :units=\"volumeUnits\"\n :disabled=\"disabled\"\n />\n\n <div class=\"mint-dose-calculator__divider\" />\n\n <!-- Dilution Result -->\n <div v-if=\"dilutionResult?.valid\" class=\"mint-dose-calculator__result\">\n <div class=\"mint-dose-calculator__result-title\">Result</div>\n <div class=\"mint-dose-calculator__result-row\">\n <span class=\"mint-dose-calculator__result-label\">Stock volume</span>\n <span class=\"mint-dose-calculator__result-value\">\n {{ formatVolume(dilutionResult.stockVolume) }}\n </span>\n </div>\n <div class=\"mint-dose-calculator__result-row\">\n <span class=\"mint-dose-calculator__result-label\">Diluent volume</span>\n <span class=\"mint-dose-calculator__result-value\">\n {{ formatVolume(dilutionResult.diluentVolume) }}\n </span>\n </div>\n <div class=\"mint-dose-calculator__result-row\">\n <span class=\"mint-dose-calculator__result-label\">Dilution factor</span>\n <span class=\"mint-dose-calculator__result-value\">\n {{ dilutionResult.dilutionFactor.toFixed(1) }}×\n </span>\n </div>\n </div>\n\n <div v-else-if=\"dilutionResult?.error\" class=\"mint-dose-calculator__error\">\n {{ dilutionResult.error }}\n </div>\n </div>\n\n <!-- Serial Dilution form -->\n <div\n v-else-if=\"activeMode === 'serial'\"\n class=\"mint-dose-calculator__form\"\n role=\"tabpanel\"\n >\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">Starting Concentration</label>\n <ConcentrationInput\n v-model=\"serialStartConcentration\"\n :disabled=\"disabled\"\n :show-conversion=\"false\"\n size=\"md\"\n />\n </div>\n\n <div class=\"mint-dose-calculator__field-row\">\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">Dilution Factor</label>\n <div class=\"mint-dose-calculator__input-wrapper\">\n <input\n v-model.number=\"serialDilutionFactor\"\n type=\"number\"\n :disabled=\"disabled\"\n class=\"mint-dose-calculator__input\"\n min=\"1.1\"\n step=\"0.1\"\n />\n <span class=\"mint-dose-calculator__unit-suffix\">×</span>\n </div>\n </div>\n\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">Dilutions</label>\n <input\n v-model.number=\"serialNumberOfDilutions\"\n type=\"number\"\n :disabled=\"disabled\"\n class=\"mint-dose-calculator__input\"\n min=\"1\"\n max=\"24\"\n />\n </div>\n </div>\n\n <DoseCalculatorVolumeField\n v-model=\"serialVolumePerWell\"\n label=\"Volume per Well\"\n :units=\"volumeUnits\"\n :disabled=\"disabled\"\n />\n\n <div class=\"mint-dose-calculator__divider\" />\n\n <!-- Serial Preview -->\n <div v-if=\"serialResult?.valid\" class=\"mint-dose-calculator__preview\">\n <div class=\"mint-dose-calculator__preview-title\">Preview</div>\n <div class=\"mint-dose-calculator__preview-steps\">\n <template v-for=\"(step, index) in serialResult.steps\" :key=\"step.stepNumber\">\n <div class=\"mint-dose-calculator__preview-step\">\n <span class=\"mint-dose-calculator__preview-step-num\">{{ step.stepNumber }}:</span>\n <span class=\"mint-dose-calculator__preview-step-conc\">\n {{ formatWithUnit(step.concentration, 3) }}\n </span>\n </div>\n <span\n v-if=\"index < serialResult.steps.length - 1\"\n class=\"mint-dose-calculator__preview-arrow\"\n >\n →\n </span>\n </template>\n </div>\n </div>\n\n <div v-else-if=\"serialResult?.error\" class=\"mint-dose-calculator__error\">\n {{ serialResult.error }}\n </div>\n </div>\n\n <!-- Conversion form -->\n <div\n v-else-if=\"activeMode === 'conversion'\"\n class=\"mint-dose-calculator__form\"\n role=\"tabpanel\"\n >\n <div class=\"mint-dose-calculator__field\">\n <label class=\"mint-dose-calculator__field-label\">Input Concentration</label>\n <ConcentrationInput\n :disabled=\"disabled\"\n :molecular-weight=\"molecularWeight\"\n :show-conversion=\"true\"\n size=\"md\"\n />\n </div>\n\n <div\n v-if=\"!molecularWeight\"\n class=\"mint-dose-calculator__error\"\n >\n Provide molecular weight for mass ↔ molarity conversion\n </div>\n </div>\n\n <!-- Molecular weight section (shown if needed) -->\n <div\n v-if=\"molecularWeight\"\n class=\"mint-dose-calculator__mw-section\"\n >\n <span class=\"mint-dose-calculator__mw-label\">\n Molecular Weight: {{ molecularWeight }} g/mol\n </span>\n </div>\n\n <!-- Apply to wells button -->\n <div v-if=\"hasTargetWells\" class=\"mint-dose-calculator__form\">\n <button\n type=\"button\"\n class=\"mint-dose-calculator__apply-btn\"\n :disabled=\"!canApplyToWells || disabled\"\n @click=\"handleApplyToWells\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n Apply to {{ targetWells?.length }} selected well{{ targetWells?.length === 1 ? '' : 's' }}\n </button>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/dose-calculator.css';\n</style>\n","import type { ProtocolStepType } from '../types'\n\nexport const PROTOCOL_STEP_TYPE_ICONS: Record<ProtocolStepType, string> = {\n incubation: 'M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z',\n wash: 'M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z',\n addition: 'M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z',\n measurement: 'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z',\n transfer: 'M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4',\n centrifuge: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15',\n mix: 'M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z',\n custom: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z',\n}\n\nexport const PROTOCOL_STEP_TYPE_COLORS: Record<ProtocolStepType, string> = {\n incubation: '#F59E0B',\n wash: '#06B6D4',\n addition: '#10B981',\n measurement: '#8B5CF6',\n transfer: '#3B82F6',\n centrifuge: '#6B7280',\n mix: '#F97316',\n custom: '#EC4899',\n}\n\nexport function formatProtocolDuration(minutes: number | undefined): string {\n if (minutes === undefined) return ''\n if (minutes < 60) return `${minutes}m`\n const hours = Math.floor(minutes / 60)\n const mins = minutes % 60\n return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`\n}\n","<script setup lang=\"ts\">\n/** Horizontal or vertical timeline of ordered protocol steps with status indicators, drag-to-reorder, and step-type iconography. */\nimport { ref, computed } from 'vue'\nimport type { ProtocolStep, ProtocolStepStatus } from '../types'\nimport {\n PROTOCOL_STEP_TYPE_COLORS as stepTypeColors,\n PROTOCOL_STEP_TYPE_ICONS as stepTypeIcons,\n formatProtocolDuration as formatDuration,\n} from './ProtocolStep.presentation'\n\ninterface Props {\n modelValue?: ProtocolStep[]\n orientation?: 'horizontal' | 'vertical'\n showDuration?: boolean\n showTime?: boolean\n editable?: boolean\n collapsible?: boolean\n expandedStepId?: string\n size?: 'sm' | 'md' | 'lg'\n colorByStatus?: boolean\n colorByType?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: () => [],\n orientation: 'vertical',\n showDuration: true,\n showTime: false,\n editable: false,\n collapsible: true,\n expandedStepId: undefined,\n size: 'md',\n colorByStatus: true,\n colorByType: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [steps: ProtocolStep[]]\n 'step-click': [step: ProtocolStep]\n 'step-expand': [stepId: string | undefined]\n 'step-add': [afterStepId?: string]\n 'step-remove': [stepId: string]\n 'step-update': [step: ProtocolStep]\n 'step-reorder': [stepId: string, newOrder: number]\n 'step-status-change': [stepId: string, status: ProtocolStepStatus]\n}>()\n\nconst localExpandedId = ref<string | undefined>(props.expandedStepId)\nconst draggedStepId = ref<string | null>(null)\nconst dragOverStepId = ref<string | null>(null)\n\nconst expandedId = computed({\n get: () => props.expandedStepId ?? localExpandedId.value,\n set: (id) => {\n localExpandedId.value = id\n emit('step-expand', id)\n },\n})\n\nconst sortedSteps = computed(() => {\n if (props.modelValue.every((step, i, arr) => i === 0 || step.order >= arr[i - 1].order)) {\n return props.modelValue\n }\n return [...props.modelValue].sort((a, b) => a.order - b.order)\n})\n\nconst statusClasses: Record<ProtocolStepStatus, string> = {\n pending: 'mint-timeline__card--pending',\n in_progress: 'mint-timeline__card--in-progress',\n completed: 'mint-timeline__card--completed',\n failed: 'mint-timeline__card--failed',\n skipped: 'mint-timeline__card--skipped',\n}\n\nconst iconStatusClasses: Record<ProtocolStepStatus, string> = {\n pending: 'mint-timeline__icon--pending',\n in_progress: 'mint-timeline__icon--in-progress',\n completed: 'mint-timeline__icon--completed',\n failed: 'mint-timeline__icon--failed',\n skipped: 'mint-timeline__icon--skipped',\n}\n\nfunction getStepColor(step: ProtocolStep): string {\n return props.colorByType && !props.colorByStatus ? stepTypeColors[step.type] : ''\n}\n\nfunction getStatusClass(step: ProtocolStep): string {\n return props.colorByStatus ? statusClasses[step.status] : statusClasses.pending\n}\n\nfunction getIconStatusClass(step: ProtocolStep): string {\n return props.colorByStatus ? iconStatusClasses[step.status] : iconStatusClasses.pending\n}\n\nfunction toggleExpand(step: ProtocolStep) {\n expandedId.value = expandedId.value === step.id ? undefined : step.id\n}\n\nfunction handleStepClick(step: ProtocolStep) {\n emit('step-click', step)\n if (props.collapsible) {\n toggleExpand(step)\n }\n}\n\nfunction handleStatusChange(step: ProtocolStep, status: ProtocolStepStatus) {\n const updatedSteps = props.modelValue.map(s =>\n s.id === step.id ? { ...s, status } : s\n )\n emit('step-status-change', step.id, status)\n emit('update:modelValue', updatedSteps)\n}\n\nfunction handleRemoveStep(stepId: string) {\n const updatedSteps = props.modelValue\n .filter(s => s.id !== stepId)\n .map((s, i) => ({ ...s, order: i }))\n emit('step-remove', stepId)\n emit('update:modelValue', updatedSteps)\n}\n\nfunction handleAddStep(afterStepId?: string) {\n emit('step-add', afterStepId)\n}\n\nfunction handleDragStart(event: DragEvent, step: ProtocolStep) {\n if (!props.editable) return\n draggedStepId.value = step.id\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', step.id)\n }\n}\n\nfunction handleDragOver(event: DragEvent, step: ProtocolStep) {\n if (!props.editable || !draggedStepId.value) return\n event.preventDefault()\n dragOverStepId.value = step.id\n}\n\nfunction handleDragLeave() {\n dragOverStepId.value = null\n}\n\nfunction handleDrop(event: DragEvent, targetStep: ProtocolStep) {\n if (!props.editable || !draggedStepId.value) return\n event.preventDefault()\n\n const draggedStep = props.modelValue.find(s => s.id === draggedStepId.value)\n if (!draggedStep || draggedStep.id === targetStep.id) {\n draggedStepId.value = null\n dragOverStepId.value = null\n return\n }\n\n const steps = [...props.modelValue]\n const draggedIndex = steps.findIndex(s => s.id === draggedStepId.value)\n const targetIndex = steps.findIndex(s => s.id === targetStep.id)\n\n steps.splice(draggedIndex, 1)\n steps.splice(targetIndex, 0, draggedStep)\n\n const reordered = steps.map((s, i) => ({ ...s, order: i }))\n emit('step-reorder', draggedStepId.value, targetIndex)\n emit('update:modelValue', reordered)\n\n draggedStepId.value = null\n dragOverStepId.value = null\n}\n\nfunction handleDragEnd() {\n draggedStepId.value = null\n dragOverStepId.value = null\n}\n\n</script>\n\n<template>\n <div\n :class=\"['mint-timeline', orientation === 'horizontal' ? 'mint-timeline--horizontal' : 'mint-timeline--vertical']\"\n role=\"list\"\n :aria-label=\"`Protocol timeline with ${sortedSteps.length} steps`\"\n >\n <div\n v-for=\"(step, index) in sortedSteps\"\n :key=\"step.id\"\n class=\"mint-timeline__step\"\n role=\"listitem\"\n :draggable=\"editable\"\n @dragstart=\"handleDragStart($event, step)\"\n @dragover=\"handleDragOver($event, step)\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop($event, step)\"\n @dragend=\"handleDragEnd\"\n >\n <div\n v-if=\"index < sortedSteps.length - 1\"\n class=\"mint-timeline__connector\"\n />\n\n <div\n :class=\"[\n 'mint-timeline__card',\n `mint-timeline__card--${size}`,\n getStatusClass(step),\n dragOverStepId === step.id ? 'mint-timeline__card--drag-over' : '',\n draggedStepId === step.id ? 'mint-timeline__card--dragging' : '',\n editable ? 'mint-timeline__card--editable' : '',\n ]\"\n :style=\"colorByType ? { borderColor: getStepColor(step) } : {}\"\n @click=\"handleStepClick(step)\"\n >\n <div :class=\"['mint-timeline__header', `mint-timeline__header--${size}`]\">\n <div\n :class=\"[\n 'mint-timeline__icon',\n `mint-timeline__icon--${size}`,\n getIconStatusClass(step),\n ]\"\n :style=\"colorByType ? { backgroundColor: `${getStepColor(step)}20`, color: getStepColor(step) } : {}\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" :d=\"stepTypeIcons[step.type]\" />\n </svg>\n </div>\n\n <div class=\"mint-timeline__content\">\n <div class=\"mint-timeline__title-row\">\n <span :class=\"['mint-timeline__title', `mint-timeline__title--${size}`]\">\n {{ step.name }}\n </span>\n <span\n v-if=\"showDuration && step.duration\"\n :class=\"['mint-timeline__duration', size === 'sm' ? 'mint-timeline__duration--sm' : '']\"\n >\n {{ formatDuration(step.duration) }}\n </span>\n </div>\n <div :class=\"['mint-timeline__type', `mint-timeline__type--${size}`]\">\n {{ step.type.replace('_', ' ') }}\n </div>\n </div>\n\n <svg\n v-if=\"collapsible && (step.description || step.parameters)\"\n :class=\"[\n 'mint-timeline__expand-icon',\n expandedId === step.id ? 'mint-timeline__expand-icon--expanded' : '',\n ]\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n\n <button\n v-if=\"editable\"\n type=\"button\"\n class=\"mint-timeline__remove-btn\"\n :aria-label=\"`Remove ${step.name}`\"\n @click.stop=\"handleRemoveStep(step.id)\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div\n v-if=\"expandedId === step.id && (step.description || step.parameters)\"\n :aria-expanded=\"expandedId === step.id\"\n class=\"mint-timeline__expanded\"\n >\n <p v-if=\"step.description\" :class=\"['mint-timeline__description', `mint-timeline__description--${size}`]\">\n {{ step.description }}\n </p>\n\n <div v-if=\"step.parameters && Object.keys(step.parameters).length > 0\" class=\"mint-timeline__parameters\">\n <div\n v-for=\"(value, key) in step.parameters\"\n :key=\"String(key)\"\n :class=\"['mint-timeline__param', `mint-timeline__param--${size}`]\"\n >\n <span class=\"mint-timeline__param-key\">{{ key }}:</span>\n <span class=\"mint-timeline__param-value\">{{ value }}</span>\n </div>\n </div>\n\n <div v-if=\"editable\" class=\"mint-timeline__status-controls\">\n <button\n v-for=\"status in (['pending', 'in_progress', 'completed', 'failed', 'skipped'] as const)\"\n :key=\"status\"\n type=\"button\"\n :class=\"[\n 'mint-timeline__status-btn',\n `mint-timeline__status-btn--${status}`,\n step.status === status ? 'mint-timeline__status-btn--active' : '',\n ]\"\n @click.stop=\"handleStatusChange(step, status)\"\n >\n {{ status.replace('_', ' ') }}\n </button>\n </div>\n </div>\n </div>\n\n <button\n v-if=\"editable && orientation === 'vertical'\"\n type=\"button\"\n class=\"mint-timeline__add-inline\"\n :aria-label=\"`Add step after ${step.name}`\"\n @click.stop=\"handleAddStep(step.id)\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n </button>\n\n <div v-if=\"orientation === 'vertical' && index < sortedSteps.length - 1\" class=\"mint-timeline__spacer--vertical\" />\n <div v-if=\"orientation === 'horizontal' && index < sortedSteps.length - 1\" class=\"mint-timeline__spacer--horizontal\" />\n </div>\n\n <div\n v-if=\"sortedSteps.length === 0\"\n class=\"mint-timeline__empty\"\n >\n <div class=\"mint-timeline__empty-content\">\n <p class=\"mint-timeline__empty-text\">No protocol steps defined</p>\n <button\n v-if=\"editable\"\n type=\"button\"\n class=\"mint-timeline__empty-btn\"\n @click=\"handleAddStep()\"\n >\n Add First Step\n </button>\n </div>\n </div>\n\n <button\n v-if=\"editable && sortedSteps.length > 0\"\n type=\"button\"\n :class=\"['mint-timeline__add-end', `mint-timeline__add-end--${size}`]\"\n @click=\"handleAddStep()\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n <span :class=\"`mint-timeline__add-end-text--${size}`\">Add Step</span>\n </button>\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-timeline.css';\n</style>\n","<script setup lang=\"ts\">\n/** Horizontal or vertical timeline of ordered protocol steps with status indicators, drag-to-reorder, and step-type iconography. */\nimport { ref, computed } from 'vue'\nimport type { ProtocolStep, ProtocolStepStatus } from '../types'\nimport {\n PROTOCOL_STEP_TYPE_COLORS as stepTypeColors,\n PROTOCOL_STEP_TYPE_ICONS as stepTypeIcons,\n formatProtocolDuration as formatDuration,\n} from './ProtocolStep.presentation'\n\ninterface Props {\n modelValue?: ProtocolStep[]\n orientation?: 'horizontal' | 'vertical'\n showDuration?: boolean\n showTime?: boolean\n editable?: boolean\n collapsible?: boolean\n expandedStepId?: string\n size?: 'sm' | 'md' | 'lg'\n colorByStatus?: boolean\n colorByType?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: () => [],\n orientation: 'vertical',\n showDuration: true,\n showTime: false,\n editable: false,\n collapsible: true,\n expandedStepId: undefined,\n size: 'md',\n colorByStatus: true,\n colorByType: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [steps: ProtocolStep[]]\n 'step-click': [step: ProtocolStep]\n 'step-expand': [stepId: string | undefined]\n 'step-add': [afterStepId?: string]\n 'step-remove': [stepId: string]\n 'step-update': [step: ProtocolStep]\n 'step-reorder': [stepId: string, newOrder: number]\n 'step-status-change': [stepId: string, status: ProtocolStepStatus]\n}>()\n\nconst localExpandedId = ref<string | undefined>(props.expandedStepId)\nconst draggedStepId = ref<string | null>(null)\nconst dragOverStepId = ref<string | null>(null)\n\nconst expandedId = computed({\n get: () => props.expandedStepId ?? localExpandedId.value,\n set: (id) => {\n localExpandedId.value = id\n emit('step-expand', id)\n },\n})\n\nconst sortedSteps = computed(() => {\n if (props.modelValue.every((step, i, arr) => i === 0 || step.order >= arr[i - 1].order)) {\n return props.modelValue\n }\n return [...props.modelValue].sort((a, b) => a.order - b.order)\n})\n\nconst statusClasses: Record<ProtocolStepStatus, string> = {\n pending: 'mint-timeline__card--pending',\n in_progress: 'mint-timeline__card--in-progress',\n completed: 'mint-timeline__card--completed',\n failed: 'mint-timeline__card--failed',\n skipped: 'mint-timeline__card--skipped',\n}\n\nconst iconStatusClasses: Record<ProtocolStepStatus, string> = {\n pending: 'mint-timeline__icon--pending',\n in_progress: 'mint-timeline__icon--in-progress',\n completed: 'mint-timeline__icon--completed',\n failed: 'mint-timeline__icon--failed',\n skipped: 'mint-timeline__icon--skipped',\n}\n\nfunction getStepColor(step: ProtocolStep): string {\n return props.colorByType && !props.colorByStatus ? stepTypeColors[step.type] : ''\n}\n\nfunction getStatusClass(step: ProtocolStep): string {\n return props.colorByStatus ? statusClasses[step.status] : statusClasses.pending\n}\n\nfunction getIconStatusClass(step: ProtocolStep): string {\n return props.colorByStatus ? iconStatusClasses[step.status] : iconStatusClasses.pending\n}\n\nfunction toggleExpand(step: ProtocolStep) {\n expandedId.value = expandedId.value === step.id ? undefined : step.id\n}\n\nfunction handleStepClick(step: ProtocolStep) {\n emit('step-click', step)\n if (props.collapsible) {\n toggleExpand(step)\n }\n}\n\nfunction handleStatusChange(step: ProtocolStep, status: ProtocolStepStatus) {\n const updatedSteps = props.modelValue.map(s =>\n s.id === step.id ? { ...s, status } : s\n )\n emit('step-status-change', step.id, status)\n emit('update:modelValue', updatedSteps)\n}\n\nfunction handleRemoveStep(stepId: string) {\n const updatedSteps = props.modelValue\n .filter(s => s.id !== stepId)\n .map((s, i) => ({ ...s, order: i }))\n emit('step-remove', stepId)\n emit('update:modelValue', updatedSteps)\n}\n\nfunction handleAddStep(afterStepId?: string) {\n emit('step-add', afterStepId)\n}\n\nfunction handleDragStart(event: DragEvent, step: ProtocolStep) {\n if (!props.editable) return\n draggedStepId.value = step.id\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', step.id)\n }\n}\n\nfunction handleDragOver(event: DragEvent, step: ProtocolStep) {\n if (!props.editable || !draggedStepId.value) return\n event.preventDefault()\n dragOverStepId.value = step.id\n}\n\nfunction handleDragLeave() {\n dragOverStepId.value = null\n}\n\nfunction handleDrop(event: DragEvent, targetStep: ProtocolStep) {\n if (!props.editable || !draggedStepId.value) return\n event.preventDefault()\n\n const draggedStep = props.modelValue.find(s => s.id === draggedStepId.value)\n if (!draggedStep || draggedStep.id === targetStep.id) {\n draggedStepId.value = null\n dragOverStepId.value = null\n return\n }\n\n const steps = [...props.modelValue]\n const draggedIndex = steps.findIndex(s => s.id === draggedStepId.value)\n const targetIndex = steps.findIndex(s => s.id === targetStep.id)\n\n steps.splice(draggedIndex, 1)\n steps.splice(targetIndex, 0, draggedStep)\n\n const reordered = steps.map((s, i) => ({ ...s, order: i }))\n emit('step-reorder', draggedStepId.value, targetIndex)\n emit('update:modelValue', reordered)\n\n draggedStepId.value = null\n dragOverStepId.value = null\n}\n\nfunction handleDragEnd() {\n draggedStepId.value = null\n dragOverStepId.value = null\n}\n\n</script>\n\n<template>\n <div\n :class=\"['mint-timeline', orientation === 'horizontal' ? 'mint-timeline--horizontal' : 'mint-timeline--vertical']\"\n role=\"list\"\n :aria-label=\"`Protocol timeline with ${sortedSteps.length} steps`\"\n >\n <div\n v-for=\"(step, index) in sortedSteps\"\n :key=\"step.id\"\n class=\"mint-timeline__step\"\n role=\"listitem\"\n :draggable=\"editable\"\n @dragstart=\"handleDragStart($event, step)\"\n @dragover=\"handleDragOver($event, step)\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop($event, step)\"\n @dragend=\"handleDragEnd\"\n >\n <div\n v-if=\"index < sortedSteps.length - 1\"\n class=\"mint-timeline__connector\"\n />\n\n <div\n :class=\"[\n 'mint-timeline__card',\n `mint-timeline__card--${size}`,\n getStatusClass(step),\n dragOverStepId === step.id ? 'mint-timeline__card--drag-over' : '',\n draggedStepId === step.id ? 'mint-timeline__card--dragging' : '',\n editable ? 'mint-timeline__card--editable' : '',\n ]\"\n :style=\"colorByType ? { borderColor: getStepColor(step) } : {}\"\n @click=\"handleStepClick(step)\"\n >\n <div :class=\"['mint-timeline__header', `mint-timeline__header--${size}`]\">\n <div\n :class=\"[\n 'mint-timeline__icon',\n `mint-timeline__icon--${size}`,\n getIconStatusClass(step),\n ]\"\n :style=\"colorByType ? { backgroundColor: `${getStepColor(step)}20`, color: getStepColor(step) } : {}\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" :d=\"stepTypeIcons[step.type]\" />\n </svg>\n </div>\n\n <div class=\"mint-timeline__content\">\n <div class=\"mint-timeline__title-row\">\n <span :class=\"['mint-timeline__title', `mint-timeline__title--${size}`]\">\n {{ step.name }}\n </span>\n <span\n v-if=\"showDuration && step.duration\"\n :class=\"['mint-timeline__duration', size === 'sm' ? 'mint-timeline__duration--sm' : '']\"\n >\n {{ formatDuration(step.duration) }}\n </span>\n </div>\n <div :class=\"['mint-timeline__type', `mint-timeline__type--${size}`]\">\n {{ step.type.replace('_', ' ') }}\n </div>\n </div>\n\n <svg\n v-if=\"collapsible && (step.description || step.parameters)\"\n :class=\"[\n 'mint-timeline__expand-icon',\n expandedId === step.id ? 'mint-timeline__expand-icon--expanded' : '',\n ]\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n\n <button\n v-if=\"editable\"\n type=\"button\"\n class=\"mint-timeline__remove-btn\"\n :aria-label=\"`Remove ${step.name}`\"\n @click.stop=\"handleRemoveStep(step.id)\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div\n v-if=\"expandedId === step.id && (step.description || step.parameters)\"\n :aria-expanded=\"expandedId === step.id\"\n class=\"mint-timeline__expanded\"\n >\n <p v-if=\"step.description\" :class=\"['mint-timeline__description', `mint-timeline__description--${size}`]\">\n {{ step.description }}\n </p>\n\n <div v-if=\"step.parameters && Object.keys(step.parameters).length > 0\" class=\"mint-timeline__parameters\">\n <div\n v-for=\"(value, key) in step.parameters\"\n :key=\"String(key)\"\n :class=\"['mint-timeline__param', `mint-timeline__param--${size}`]\"\n >\n <span class=\"mint-timeline__param-key\">{{ key }}:</span>\n <span class=\"mint-timeline__param-value\">{{ value }}</span>\n </div>\n </div>\n\n <div v-if=\"editable\" class=\"mint-timeline__status-controls\">\n <button\n v-for=\"status in (['pending', 'in_progress', 'completed', 'failed', 'skipped'] as const)\"\n :key=\"status\"\n type=\"button\"\n :class=\"[\n 'mint-timeline__status-btn',\n `mint-timeline__status-btn--${status}`,\n step.status === status ? 'mint-timeline__status-btn--active' : '',\n ]\"\n @click.stop=\"handleStatusChange(step, status)\"\n >\n {{ status.replace('_', ' ') }}\n </button>\n </div>\n </div>\n </div>\n\n <button\n v-if=\"editable && orientation === 'vertical'\"\n type=\"button\"\n class=\"mint-timeline__add-inline\"\n :aria-label=\"`Add step after ${step.name}`\"\n @click.stop=\"handleAddStep(step.id)\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n </button>\n\n <div v-if=\"orientation === 'vertical' && index < sortedSteps.length - 1\" class=\"mint-timeline__spacer--vertical\" />\n <div v-if=\"orientation === 'horizontal' && index < sortedSteps.length - 1\" class=\"mint-timeline__spacer--horizontal\" />\n </div>\n\n <div\n v-if=\"sortedSteps.length === 0\"\n class=\"mint-timeline__empty\"\n >\n <div class=\"mint-timeline__empty-content\">\n <p class=\"mint-timeline__empty-text\">No protocol steps defined</p>\n <button\n v-if=\"editable\"\n type=\"button\"\n class=\"mint-timeline__empty-btn\"\n @click=\"handleAddStep()\"\n >\n Add First Step\n </button>\n </div>\n </div>\n\n <button\n v-if=\"editable && sortedSteps.length > 0\"\n type=\"button\"\n :class=\"['mint-timeline__add-end', `mint-timeline__add-end--${size}`]\"\n @click=\"handleAddStep()\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n <span :class=\"`mint-timeline__add-end-text--${size}`\">Add Step</span>\n </button>\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-timeline.css';\n</style>\n","<script setup lang=\"ts\">\nimport { ref, watch, computed } from 'vue'\nimport type { Well, WellEditData, WellEditField } from '../../types'\nimport { useEventListener } from '../../composables/useEventListener'\n\ninterface Props {\n wellId: string\n wellData?: Partial<Well>\n editFields?: WellEditField[]\n defaultInjectionVolume?: number\n position: { x: number; y: number }\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n wellData: () => ({}),\n editFields: () => ['label', 'sampleType', 'injectionVolume', 'injectionCount', 'customMethod'],\n defaultInjectionVolume: 5,\n})\n\nconst emit = defineEmits<{\n save: [data: WellEditData]\n clear: []\n close: []\n}>()\n\nconst showAdvanced = ref(false)\n\nconst formData = ref({\n label: '',\n sampleType: 'sample',\n injectionVolume: props.defaultInjectionVolume,\n injectionCount: 1,\n customMethod: '',\n})\n\nwatch(\n () => [props.wellId, props.wellData] as const,\n () => {\n const meta = props.wellData?.metadata as Record<string, unknown> | undefined\n formData.value = {\n label: (meta?.label as string) || '',\n sampleType: props.wellData?.sampleType || 'sample',\n injectionVolume: (meta?.injectionVolume as number) || props.defaultInjectionVolume,\n injectionCount: (meta?.injectionCount as number) || 1,\n customMethod: (meta?.customMethod as string) || '',\n }\n showAdvanced.value = (formData.value.injectionCount > 1) || !!formData.value.customMethod\n },\n { immediate: true },\n)\n\nconst hasField = (field: WellEditField) => props.editFields.includes(field)\n\nconst popupStyle = computed(() => {\n const x = props.position.x\n const y = props.position.y\n const style: Record<string, string> = {\n left: `${x}px`,\n top: `${y}px`,\n }\n return style\n})\n\nfunction save() {\n emit('save', {\n wellId: props.wellId,\n label: formData.value.label.trim(),\n sampleType: formData.value.sampleType,\n injectionVolume: formData.value.injectionVolume,\n injectionCount: formData.value.injectionCount || 1,\n customMethod: formData.value.customMethod?.trim() || '',\n })\n}\n\nfunction clear() {\n emit('clear')\n}\n\nfunction close() {\n emit('close')\n}\n\nfunction handleKeydown(e: KeyboardEvent) {\n if (e.key === 'Escape') close()\n}\n\nuseEventListener(() => document, 'keydown', handleKeydown)\n\nconst sampleTypeButtons = [\n { type: 'sample', label: 'S', tooltip: 'Sample' },\n { type: 'blank', label: 'B', tooltip: 'Blank' },\n { type: 'qc', label: 'Q', tooltip: 'QC' },\n { type: 'iqc', label: 'I', tooltip: 'IQC' },\n]\n</script>\n\n<template>\n <Teleport to=\"body\">\n <!-- Backdrop -->\n <div class=\"mint-well-edit-popup__backdrop\" @click=\"close\" />\n\n <!-- Popup -->\n <div\n class=\"mint-well-edit-popup\"\n :style=\"popupStyle\"\n @click.stop\n >\n <!-- Header -->\n <div class=\"mint-well-edit-popup__header\">\n <div class=\"mint-well-edit-popup__header-left\">\n <span class=\"mint-well-edit-popup__well-id\">{{ wellId }}</span>\n <span class=\"mint-well-edit-popup__title\">Edit Well</span>\n </div>\n <button class=\"mint-well-edit-popup__close\" @click=\"close\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- Body -->\n <div class=\"mint-well-edit-popup__body\">\n <!-- Sample Name -->\n <div v-if=\"hasField('label')\">\n <label class=\"mint-well-edit-popup__label\">Sample Name</label>\n <input\n v-model=\"formData.label\"\n type=\"text\"\n placeholder=\"e.g., Sample_001\"\n class=\"mint-well-edit-popup__input\"\n @keydown.enter=\"save\"\n autofocus\n />\n </div>\n\n <!-- Type + Volume row -->\n <div class=\"mint-well-edit-popup__row\">\n <div v-if=\"hasField('sampleType')\">\n <label class=\"mint-well-edit-popup__label\">Type</label>\n <div class=\"mint-well-edit-popup__type-grid\">\n <button\n v-for=\"btn in sampleTypeButtons\"\n :key=\"btn.type\"\n :title=\"btn.tooltip\"\n class=\"mint-well-edit-popup__type-btn\"\n :class=\"{\n [`mint-well-edit-popup__type-btn--${btn.type}`]: formData.sampleType === btn.type,\n }\"\n @click=\"formData.sampleType = btn.type\"\n >\n {{ btn.label }}\n </button>\n </div>\n </div>\n <div v-if=\"hasField('injectionVolume')\">\n <label class=\"mint-well-edit-popup__label\">Volume (uL)</label>\n <input\n v-model.number=\"formData.injectionVolume\"\n type=\"number\"\n min=\"0.1\"\n max=\"100\"\n step=\"0.5\"\n class=\"mint-well-edit-popup__input\"\n />\n </div>\n </div>\n\n <!-- Advanced toggle -->\n <label\n v-if=\"hasField('injectionCount') || hasField('customMethod')\"\n class=\"mint-well-edit-popup__advanced-toggle\"\n >\n <input type=\"checkbox\" v-model=\"showAdvanced\" />\n <span>Advanced options</span>\n </label>\n\n <!-- Advanced fields -->\n <template v-if=\"showAdvanced\">\n <!-- Injection count -->\n <div v-if=\"hasField('injectionCount')\">\n <label class=\"mint-well-edit-popup__label\">Repeat Injections</label>\n <div class=\"mint-well-edit-popup__count-grid\">\n <button\n v-for=\"n in [1, 2, 3, 4, 5]\"\n :key=\"n\"\n class=\"mint-well-edit-popup__count-btn\"\n :class=\"{ 'mint-well-edit-popup__count-btn--active': formData.injectionCount === n }\"\n @click=\"formData.injectionCount = n\"\n >\n {{ n }}×\n </button>\n <input\n v-model.number=\"formData.injectionCount\"\n type=\"number\"\n min=\"1\"\n max=\"10\"\n class=\"mint-well-edit-popup__count-input\"\n />\n </div>\n </div>\n\n <!-- Custom method -->\n <div v-if=\"hasField('customMethod')\">\n <label class=\"mint-well-edit-popup__label\">Custom Method Path</label>\n <input\n v-model=\"formData.customMethod\"\n type=\"text\"\n placeholder=\"Leave empty for default\"\n class=\"mint-well-edit-popup__input mint-well-edit-popup__input--mono\"\n />\n </div>\n </template>\n </div>\n\n <!-- Footer -->\n <div class=\"mint-well-edit-popup__footer\">\n <button class=\"mint-well-edit-popup__btn mint-well-edit-popup__btn--clear\" @click=\"clear\">\n Clear Well\n </button>\n <button class=\"mint-well-edit-popup__btn mint-well-edit-popup__btn--save\" @click=\"save\">\n Save\n </button>\n </div>\n </div>\n </Teleport>\n</template>\n\n<style>\n@import '../../styles/components/well-edit-popup.css';\n</style>\n","<script setup lang=\"ts\">\nimport { ref, watch, computed } from 'vue'\nimport type { Well, WellEditData, WellEditField } from '../../types'\nimport { useEventListener } from '../../composables/useEventListener'\n\ninterface Props {\n wellId: string\n wellData?: Partial<Well>\n editFields?: WellEditField[]\n defaultInjectionVolume?: number\n position: { x: number; y: number }\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n wellData: () => ({}),\n editFields: () => ['label', 'sampleType', 'injectionVolume', 'injectionCount', 'customMethod'],\n defaultInjectionVolume: 5,\n})\n\nconst emit = defineEmits<{\n save: [data: WellEditData]\n clear: []\n close: []\n}>()\n\nconst showAdvanced = ref(false)\n\nconst formData = ref({\n label: '',\n sampleType: 'sample',\n injectionVolume: props.defaultInjectionVolume,\n injectionCount: 1,\n customMethod: '',\n})\n\nwatch(\n () => [props.wellId, props.wellData] as const,\n () => {\n const meta = props.wellData?.metadata as Record<string, unknown> | undefined\n formData.value = {\n label: (meta?.label as string) || '',\n sampleType: props.wellData?.sampleType || 'sample',\n injectionVolume: (meta?.injectionVolume as number) || props.defaultInjectionVolume,\n injectionCount: (meta?.injectionCount as number) || 1,\n customMethod: (meta?.customMethod as string) || '',\n }\n showAdvanced.value = (formData.value.injectionCount > 1) || !!formData.value.customMethod\n },\n { immediate: true },\n)\n\nconst hasField = (field: WellEditField) => props.editFields.includes(field)\n\nconst popupStyle = computed(() => {\n const x = props.position.x\n const y = props.position.y\n const style: Record<string, string> = {\n left: `${x}px`,\n top: `${y}px`,\n }\n return style\n})\n\nfunction save() {\n emit('save', {\n wellId: props.wellId,\n label: formData.value.label.trim(),\n sampleType: formData.value.sampleType,\n injectionVolume: formData.value.injectionVolume,\n injectionCount: formData.value.injectionCount || 1,\n customMethod: formData.value.customMethod?.trim() || '',\n })\n}\n\nfunction clear() {\n emit('clear')\n}\n\nfunction close() {\n emit('close')\n}\n\nfunction handleKeydown(e: KeyboardEvent) {\n if (e.key === 'Escape') close()\n}\n\nuseEventListener(() => document, 'keydown', handleKeydown)\n\nconst sampleTypeButtons = [\n { type: 'sample', label: 'S', tooltip: 'Sample' },\n { type: 'blank', label: 'B', tooltip: 'Blank' },\n { type: 'qc', label: 'Q', tooltip: 'QC' },\n { type: 'iqc', label: 'I', tooltip: 'IQC' },\n]\n</script>\n\n<template>\n <Teleport to=\"body\">\n <!-- Backdrop -->\n <div class=\"mint-well-edit-popup__backdrop\" @click=\"close\" />\n\n <!-- Popup -->\n <div\n class=\"mint-well-edit-popup\"\n :style=\"popupStyle\"\n @click.stop\n >\n <!-- Header -->\n <div class=\"mint-well-edit-popup__header\">\n <div class=\"mint-well-edit-popup__header-left\">\n <span class=\"mint-well-edit-popup__well-id\">{{ wellId }}</span>\n <span class=\"mint-well-edit-popup__title\">Edit Well</span>\n </div>\n <button class=\"mint-well-edit-popup__close\" @click=\"close\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- Body -->\n <div class=\"mint-well-edit-popup__body\">\n <!-- Sample Name -->\n <div v-if=\"hasField('label')\">\n <label class=\"mint-well-edit-popup__label\">Sample Name</label>\n <input\n v-model=\"formData.label\"\n type=\"text\"\n placeholder=\"e.g., Sample_001\"\n class=\"mint-well-edit-popup__input\"\n @keydown.enter=\"save\"\n autofocus\n />\n </div>\n\n <!-- Type + Volume row -->\n <div class=\"mint-well-edit-popup__row\">\n <div v-if=\"hasField('sampleType')\">\n <label class=\"mint-well-edit-popup__label\">Type</label>\n <div class=\"mint-well-edit-popup__type-grid\">\n <button\n v-for=\"btn in sampleTypeButtons\"\n :key=\"btn.type\"\n :title=\"btn.tooltip\"\n class=\"mint-well-edit-popup__type-btn\"\n :class=\"{\n [`mint-well-edit-popup__type-btn--${btn.type}`]: formData.sampleType === btn.type,\n }\"\n @click=\"formData.sampleType = btn.type\"\n >\n {{ btn.label }}\n </button>\n </div>\n </div>\n <div v-if=\"hasField('injectionVolume')\">\n <label class=\"mint-well-edit-popup__label\">Volume (uL)</label>\n <input\n v-model.number=\"formData.injectionVolume\"\n type=\"number\"\n min=\"0.1\"\n max=\"100\"\n step=\"0.5\"\n class=\"mint-well-edit-popup__input\"\n />\n </div>\n </div>\n\n <!-- Advanced toggle -->\n <label\n v-if=\"hasField('injectionCount') || hasField('customMethod')\"\n class=\"mint-well-edit-popup__advanced-toggle\"\n >\n <input type=\"checkbox\" v-model=\"showAdvanced\" />\n <span>Advanced options</span>\n </label>\n\n <!-- Advanced fields -->\n <template v-if=\"showAdvanced\">\n <!-- Injection count -->\n <div v-if=\"hasField('injectionCount')\">\n <label class=\"mint-well-edit-popup__label\">Repeat Injections</label>\n <div class=\"mint-well-edit-popup__count-grid\">\n <button\n v-for=\"n in [1, 2, 3, 4, 5]\"\n :key=\"n\"\n class=\"mint-well-edit-popup__count-btn\"\n :class=\"{ 'mint-well-edit-popup__count-btn--active': formData.injectionCount === n }\"\n @click=\"formData.injectionCount = n\"\n >\n {{ n }}×\n </button>\n <input\n v-model.number=\"formData.injectionCount\"\n type=\"number\"\n min=\"1\"\n max=\"10\"\n class=\"mint-well-edit-popup__count-input\"\n />\n </div>\n </div>\n\n <!-- Custom method -->\n <div v-if=\"hasField('customMethod')\">\n <label class=\"mint-well-edit-popup__label\">Custom Method Path</label>\n <input\n v-model=\"formData.customMethod\"\n type=\"text\"\n placeholder=\"Leave empty for default\"\n class=\"mint-well-edit-popup__input mint-well-edit-popup__input--mono\"\n />\n </div>\n </template>\n </div>\n\n <!-- Footer -->\n <div class=\"mint-well-edit-popup__footer\">\n <button class=\"mint-well-edit-popup__btn mint-well-edit-popup__btn--clear\" @click=\"clear\">\n Clear Well\n </button>\n <button class=\"mint-well-edit-popup__btn mint-well-edit-popup__btn--save\" @click=\"save\">\n Save\n </button>\n </div>\n </div>\n </Teleport>\n</template>\n\n<style>\n@import '../../styles/components/well-edit-popup.css';\n</style>\n","import type { HeatmapConfig } from '../types'\n\nexport const DEFAULT_SAMPLE_TYPE_COLORS: Record<string, { bg: string; border: string }> = {\n sample: { bg: 'rgba(16, 185, 129, 0.15)', border: 'rgba(16, 185, 129, 0.4)' },\n control: { bg: 'rgba(59, 130, 246, 0.15)', border: 'rgba(59, 130, 246, 0.4)' },\n blank: { bg: 'rgba(249, 115, 22, 0.15)', border: 'rgba(249, 115, 22, 0.4)' },\n qc: { bg: 'rgba(139, 92, 246, 0.15)', border: 'rgba(139, 92, 246, 0.4)' },\n iqc: { bg: 'rgba(236, 72, 153, 0.15)', border: 'rgba(236, 72, 153, 0.4)' },\n}\n\nexport const HEATMAP_COLORS: Record<string, string[]> = {\n viridis: ['#440154', '#482878', '#3e4989', '#31688e', '#26828e', '#1f9e89', '#35b779', '#6ece58', '#b5de2b', '#fde725'],\n plasma: ['#0d0887', '#46039f', '#7201a8', '#9c179e', '#bd3786', '#d8576b', '#ed7953', '#fb9f3a', '#fdca26', '#f0f921'],\n turbo: ['#30123b', '#4145ab', '#4675ed', '#39a2fc', '#1bcfd4', '#24e79e', '#71f05f', '#c1f034', '#f1c83c', '#f99538', '#e45a31', '#ba2512', '#7a0403'],\n}\n\nexport function conditionGradientStyle(\n color: string,\n concentration: number,\n concentrations: number[],\n): Record<string, string> {\n const min = Math.min(...concentrations)\n const max = Math.max(...concentrations)\n const t = max <= min ? 1 : (concentration - min) / (max - min)\n const opacity = 0.25 + t * 0.50\n const r = parseInt(color.slice(1, 3), 16)\n const g = parseInt(color.slice(3, 5), 16)\n const b = parseInt(color.slice(5, 7), 16)\n const er = 255 * (1 - opacity) + r * opacity\n const eg = 255 * (1 - opacity) + g * opacity\n const eb = 255 * (1 - opacity) + b * opacity\n const luminance = (0.299 * er + 0.587 * eg + 0.114 * eb) / 255\n return {\n backgroundColor: `rgba(${r}, ${g}, ${b}, ${opacity})`,\n color: luminance > 0.55 ? '#1e293b' : '#ffffff',\n }\n}\n\nexport function formatConcentration(value: number): string {\n if (value >= 1000) return `${value / 1000}k`\n if (value < 0.01) return value.toExponential(0)\n return String(value)\n}\n\nexport function getHeatmapColor(config: HeatmapConfig | undefined, value: number | undefined): string | null {\n if (!config?.enabled || value === undefined) return null\n\n const min = config.min ?? 0\n const max = config.max ?? 1\n const normalized = Math.max(0, Math.min(1, (value - min) / (max - min)))\n const colors = config.colorScale === 'custom' && config.customColors?.length\n ? config.customColors\n : HEATMAP_COLORS[config.colorScale || 'viridis']\n const index = Math.min(Math.floor(normalized * (colors.length - 1)), colors.length - 1)\n return colors[index]\n}\n","import type { ColumnCondition, RowCondition } from '../types'\n\nexport interface ConditionEntry<TCondition> {\n condition: TCondition\n indexInGroup: number\n}\n\nexport type ColumnConditionSpan =\n | { condition: ColumnCondition; colspan: number }\n | { gap: true; colspan: number }\n\nexport type RowConditionSpan =\n | { condition: RowCondition; rowspan: number; startRow: number }\n | { gap: true; rowspan: number; startRow: number }\n\nexport function createColumnConditionMap(\n conditions: ColumnCondition[],\n): Map<number, ConditionEntry<ColumnCondition>> {\n const map = new Map<number, ConditionEntry<ColumnCondition>>()\n for (const condition of conditions) {\n condition.cols.forEach((col, indexInGroup) => {\n map.set(col, { condition, indexInGroup })\n })\n }\n return map\n}\n\nexport function createRowConditionMap(\n conditions: RowCondition[],\n): Map<string, ConditionEntry<RowCondition>> {\n const map = new Map<string, ConditionEntry<RowCondition>>()\n for (const condition of conditions) {\n condition.rows.forEach((row, indexInGroup) => {\n map.set(row, { condition, indexInGroup })\n })\n }\n return map\n}\n\nexport function createColumnConditionSpans(\n cols: number[],\n conditionMap: ReadonlyMap<number, ConditionEntry<ColumnCondition>>,\n): ColumnConditionSpan[] {\n const spans: ColumnConditionSpan[] = []\n let index = 0\n\n while (index < cols.length) {\n const entry = conditionMap.get(cols[index])\n if (entry?.indexInGroup === 0) {\n spans.push({ condition: entry.condition, colspan: entry.condition.cols.length })\n index += entry.condition.cols.length\n continue\n }\n\n let gapCount = 0\n while (index + gapCount < cols.length && !conditionMap.has(cols[index + gapCount])) {\n gapCount++\n }\n spans.push({ gap: true, colspan: gapCount || 1 })\n index += gapCount || 1\n }\n\n return spans\n}\n\nexport function createRowConditionSpans(\n rows: string[],\n conditionMap: ReadonlyMap<string, ConditionEntry<RowCondition>>,\n): RowConditionSpan[] {\n const spans: RowConditionSpan[] = []\n let index = 0\n\n while (index < rows.length) {\n const entry = conditionMap.get(rows[index])\n if (entry?.indexInGroup === 0) {\n spans.push({ condition: entry.condition, rowspan: entry.condition.rows.length, startRow: index })\n index += entry.condition.rows.length\n continue\n }\n\n let gapCount = 0\n while (index + gapCount < rows.length && !conditionMap.has(rows[index + gapCount])) {\n gapCount++\n }\n spans.push({ gap: true, rowspan: gapCount || 1, startRow: index })\n index += gapCount || 1\n }\n\n return spans\n}\n\nexport function createRowConditionSpanByRow(\n spans: RowConditionSpan[],\n): Map<number, RowConditionSpan> {\n const map = new Map<number, RowConditionSpan>()\n for (const span of spans) {\n map.set(span.startRow, span)\n }\n return map\n}\n","import type { Well, WellPlateFormat, WellPlateSize } from '../types'\n\nexport interface WellPlateConfig {\n rows: number\n cols: number\n}\n\nexport interface WellPlateCoordinate {\n row: number\n col: number\n}\n\nexport interface WellPlateSizeConfig {\n cellWidth: string\n cellHeight: string\n headerWidth: string\n headerHeight: string\n fontSize: string\n gap: string\n}\n\nexport const PLATE_CONFIGS: Record<WellPlateFormat, WellPlateConfig> = {\n 6: { rows: 2, cols: 3 },\n 12: { rows: 3, cols: 4 },\n 24: { rows: 4, cols: 6 },\n 48: { rows: 6, cols: 8 },\n 54: { rows: 6, cols: 9 },\n 96: { rows: 8, cols: 12 },\n 384: { rows: 16, cols: 24 },\n}\n\nexport const WELL_PLATE_SIZE_CONFIGS: Record<WellPlateSize, WellPlateSizeConfig> = {\n sm: { cellWidth: '40px', cellHeight: '40px', headerWidth: '32px', headerHeight: '32px', fontSize: '0.625rem', gap: '2px' },\n md: { cellWidth: '56px', cellHeight: '32px', headerWidth: '32px', headerHeight: '24px', fontSize: '0.75rem', gap: '3px' },\n lg: { cellWidth: '80px', cellHeight: '40px', headerWidth: '40px', headerHeight: '32px', fontSize: '0.875rem', gap: '4px' },\n xl: { cellWidth: '96px', cellHeight: '48px', headerWidth: '48px', headerHeight: '40px', fontSize: '1rem', gap: '4px' },\n fill: { cellWidth: '100%', cellHeight: '32px', headerWidth: '32px', headerHeight: '24px', fontSize: '0.75rem', gap: '2px' },\n}\n\nexport function createPlateRowLabels(config: WellPlateConfig): string[] {\n return Array.from({ length: config.rows }, (_, index) => String.fromCharCode(65 + index))\n}\n\nexport function createPlateColumnLabels(config: WellPlateConfig): number[] {\n return Array.from({ length: config.cols }, (_, index) => index + 1)\n}\n\nexport function createWellGrid(\n config: WellPlateConfig,\n rowLabels: string[],\n wells: Record<string, Partial<Well>>,\n): Well[][] {\n const grid: Well[][] = []\n for (let row = 0; row < config.rows; row++) {\n const rowWells: Well[] = []\n for (let col = 0; col < config.cols; col++) {\n const id = `${rowLabels[row]}${col + 1}`\n const wellData = wells[id] || {}\n rowWells.push({\n id,\n row,\n col,\n state: wellData.state || 'empty',\n sampleType: wellData.sampleType,\n value: wellData.value,\n metadata: wellData.metadata,\n })\n }\n grid.push(rowWells)\n }\n return grid\n}\n\nexport function getRectangleWellIds(\n start: WellPlateCoordinate,\n end: WellPlateCoordinate,\n rowLabels: string[],\n): Set<string> {\n const minRow = Math.min(start.row, end.row)\n const maxRow = Math.max(start.row, end.row)\n const minCol = Math.min(start.col, end.col)\n const maxCol = Math.max(start.col, end.col)\n\n const wells = new Set<string>()\n for (let row = minRow; row <= maxRow; row++) {\n for (let col = minCol; col <= maxCol; col++) {\n wells.add(`${rowLabels[row]}${col + 1}`)\n }\n }\n return wells\n}\n","import type { WellSampleDropData } from '../types'\n\ninterface DroppedSampleTransfer {\n getData(format: string): string\n}\n\nexport function parseDroppedSampleTransfer(transfer: DroppedSampleTransfer): WellSampleDropData | null {\n const json = transfer.getData('application/json') || transfer.getData('text/json')\n if (json) {\n try {\n return normalizeDroppedSample(JSON.parse(json))\n } catch {\n return null\n }\n }\n\n return createTextDroppedSample(transfer.getData('text/plain'))\n}\n\nexport function createTextDroppedSample(text: string | null | undefined): WellSampleDropData | null {\n const sampleName = text?.trim()\n if (!sampleName) return null\n\n return {\n sampleName,\n label: sampleName,\n raw: sampleName,\n }\n}\n\nexport function normalizeDroppedSample(raw: unknown): WellSampleDropData | null {\n if (!raw || typeof raw !== 'object') return null\n\n const record = raw as Record<string, unknown>\n const sampleName =\n getString(record.sampleName) ??\n getString(record.sample_name) ??\n getString(record.name) ??\n getString(record.label)\n const label = getString(record.label) ?? sampleName\n const sampleType =\n getString(record.sampleType) ??\n getString(record.sample_type) ??\n (record.type === 'sample' ? 'sample' : undefined)\n\n if (!sampleName && !label && !getString(record.id)) return null\n\n return {\n id: getString(record.id),\n sampleName,\n label,\n sampleType,\n injectionVolume: getNumber(record.injectionVolume ?? record.injection_volume),\n injectionCount: getNumber(record.injectionCount ?? record.injection_count),\n customMethod: getString(record.customMethod ?? record.custom_method) ?? null,\n metadata: getRecord(record.metadata),\n raw,\n }\n}\n\nfunction getString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value.trim() : undefined\n}\n\nfunction getNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined\n}\n\nfunction getRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? value as Record<string, unknown>\n : undefined\n}\n","import { computed, ref } from 'vue'\nimport type {\n Well,\n WellEditData,\n WellPlateSelectionMode,\n WellSampleDropData,\n WellSampleDropParser,\n} from '../types'\nimport {\n getRectangleWellIds,\n type WellPlateCoordinate,\n} from './WellPlate.geometry'\nimport { parseDroppedSampleTransfer } from './WellPlate.sampleDrop'\n\nexport interface WellPlateInteractionOptions {\n modelValue: () => string[]\n selectionMode: () => WellPlateSelectionMode\n disabled: () => boolean\n readonly: () => boolean\n editable: () => boolean\n allowSampleDrop: () => boolean\n sampleDropParser: () => WellSampleDropParser | undefined\n rowLabels: () => string[]\n wellGrid: () => Well[][]\n emitSelected: (wellIds: string[]) => void\n emitWellClick: (wellId: string, event: MouseEvent) => void\n emitWellHover: (wellId: string | null, event?: MouseEvent) => void\n emitContextMenu: (wellId: string, event: MouseEvent) => void\n emitWellMove: (sourceWellId: string, targetWellId: string) => void\n emitWellEdit: (wellId: string, data: WellEditData) => void\n emitWellClear: (wellId: string) => void\n emitSampleDrop: (wellId: string, data: WellSampleDropData, event: DragEvent) => void\n}\n\nexport function nextClickedWellSelection(\n selectedWellIds: string[],\n wellId: string,\n selectionMode: WellPlateSelectionMode,\n event: Pick<MouseEvent, 'shiftKey' | 'metaKey' | 'ctrlKey'>,\n): string[] {\n const selectedWellSet = new Set(selectedWellIds)\n const isCurrentlySelected = selectedWellSet.has(wellId)\n const isMultiSelect = event.shiftKey || event.metaKey || event.ctrlKey\n\n if (selectionMode === 'single') {\n return isCurrentlySelected ? [] : [wellId]\n }\n\n if (isMultiSelect) {\n return isCurrentlySelected\n ? selectedWellIds.filter(id => id !== wellId)\n : [...selectedWellIds, wellId]\n }\n\n return isCurrentlySelected && selectedWellIds.length === 1 ? [] : [wellId]\n}\n\nexport function useWellPlateInteraction(options: WellPlateInteractionOptions) {\n const dragSourceWell = ref<string | null>(null)\n const dragTargetWell = ref<string | null>(null)\n const isDragging = ref(false)\n const dragStart = ref<WellPlateCoordinate | null>(null)\n const dragEnd = ref<WellPlateCoordinate | null>(null)\n const hoveredWell = ref<string | null>(null)\n const editingWellId = ref<string | null>(null)\n const editPopupPosition = ref({ x: 0, y: 0 })\n\n const selectedWellSet = computed(() => new Set(options.modelValue()))\n\n const dragSelectedWells = computed(() => {\n if (!isDragging.value || !dragStart.value || !dragEnd.value) return new Set<string>()\n return getRectangleWellIds(dragStart.value, dragEnd.value, options.rowLabels())\n })\n\n function emitSelected(wellIds: string[]) {\n options.emitSelected(wellIds)\n }\n\n function isSelected(wellId: string): boolean {\n return selectedWellSet.value.has(wellId) || dragSelectedWells.value.has(wellId)\n }\n\n function openEditPopup(wellId: string, event: MouseEvent) {\n const target = event.currentTarget as HTMLElement\n const rect = target.getBoundingClientRect()\n editPopupPosition.value = {\n x: rect.right + 8,\n y: rect.top,\n }\n editingWellId.value = wellId\n }\n\n function handleWellClick(well: Well, event: MouseEvent) {\n if (options.disabled() || options.readonly()) return\n\n options.emitWellClick(well.id, event)\n\n if (options.editable()) {\n openEditPopup(well.id, event)\n return\n }\n\n const selectionMode = options.selectionMode()\n if (selectionMode === 'none') return\n\n emitSelected(nextClickedWellSelection(options.modelValue(), well.id, selectionMode, event))\n }\n\n function handleEditSave(data: WellEditData) {\n options.emitWellEdit(data.wellId, data)\n editingWellId.value = null\n }\n\n function handleEditClear() {\n if (editingWellId.value) options.emitWellClear(editingWellId.value)\n editingWellId.value = null\n }\n\n function handleEditClose() {\n editingWellId.value = null\n }\n\n function handleWellMouseDown(well: Well, event: MouseEvent) {\n if (options.disabled() || options.readonly() || options.selectionMode() !== 'rectangle') return\n if (options.editable()) return\n if (event.button !== 0) return\n\n isDragging.value = true\n dragStart.value = { row: well.row, col: well.col }\n dragEnd.value = { row: well.row, col: well.col }\n }\n\n function handleWellMouseEnter(well: Well, event: MouseEvent) {\n hoveredWell.value = well.id\n options.emitWellHover(well.id, event)\n\n if (isDragging.value && options.selectionMode() === 'rectangle') {\n dragEnd.value = { row: well.row, col: well.col }\n }\n }\n\n function handleWellMouseLeave() {\n hoveredWell.value = null\n options.emitWellHover(null)\n }\n\n function handleMouseUp() {\n if (!isDragging.value || options.selectionMode() !== 'rectangle') return\n\n const newSelection = Array.from(dragSelectedWells.value)\n isDragging.value = false\n dragStart.value = null\n dragEnd.value = null\n\n if (newSelection.length > 0) emitSelected(newSelection)\n }\n\n function handleContextMenu(well: Well, event: MouseEvent) {\n event.preventDefault()\n options.emitContextMenu(well.id, event)\n }\n\n function handleDragStart(well: Well, event: DragEvent) {\n if (options.disabled() || options.readonly() || options.selectionMode() !== 'drag') return\n if (!well.sampleType) return\n\n dragSourceWell.value = well.id\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', well.id)\n }\n }\n\n function handleDragOver(well: Well, event: DragEvent) {\n const isMovingWell = options.selectionMode() === 'drag' && !!dragSourceWell.value\n if (!isMovingWell && !options.allowSampleDrop()) return\n\n event.preventDefault()\n if (event.dataTransfer) {\n event.dataTransfer.dropEffect = isMovingWell ? 'move' : 'copy'\n }\n dragTargetWell.value = well.id\n }\n\n function handleDragLeave() {\n dragTargetWell.value = null\n }\n\n function handleDrop(well: Well, event: DragEvent) {\n const isMovingWell = options.selectionMode() === 'drag' && !!dragSourceWell.value\n if (!isMovingWell && !options.allowSampleDrop()) return\n\n event.preventDefault()\n\n if (isMovingWell && dragSourceWell.value) {\n const sourceId = dragSourceWell.value\n const targetId = well.id\n\n if (sourceId !== targetId) options.emitWellMove(sourceId, targetId)\n\n dragSourceWell.value = null\n dragTargetWell.value = null\n return\n }\n\n const droppedSample = parseDroppedSample(event, well.id)\n if (droppedSample) options.emitSampleDrop(well.id, droppedSample, event)\n\n dragSourceWell.value = null\n dragTargetWell.value = null\n }\n\n function handleDragEnd() {\n dragSourceWell.value = null\n dragTargetWell.value = null\n }\n\n function parseDroppedSample(event: DragEvent, wellId: string): WellSampleDropData | null {\n const customParser = options.sampleDropParser()\n if (customParser) return customParser(event, { wellId, event }) ?? null\n\n const transfer = event.dataTransfer\n if (!transfer) return null\n return parseDroppedSampleTransfer(transfer)\n }\n\n function handleKeyDown(event: KeyboardEvent) {\n if (options.disabled() || options.readonly()) return\n\n if (event.key === 'Escape') {\n if (editingWellId.value) {\n editingWellId.value = null\n return\n }\n emitSelected([])\n }\n\n if ((event.key === 'a' || event.key === 'A') && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n emitSelected(options.wellGrid().flat().map(w => w.id))\n }\n }\n\n return {\n dragSourceWell,\n dragTargetWell,\n isDragging,\n dragStart,\n dragEnd,\n hoveredWell,\n editingWellId,\n editPopupPosition,\n selectedWellSet,\n dragSelectedWells,\n isSelected,\n handleWellClick,\n handleEditSave,\n handleEditClear,\n handleEditClose,\n handleWellMouseDown,\n handleWellMouseEnter,\n handleWellMouseLeave,\n handleMouseUp,\n handleContextMenu,\n handleDragStart,\n handleDragOver,\n handleDragLeave,\n handleDrop,\n handleDragEnd,\n handleKeyDown,\n }\n}\n","import type { HeatmapConfig, Well, WellPlateSelectionMode, WellShape } from '../types'\nimport {\n DEFAULT_SAMPLE_TYPE_COLORS,\n getHeatmapColor,\n} from './WellPlate.colors'\n\nexport interface WellClassState {\n wellShape: WellShape\n selectionMode: WellPlateSelectionMode\n selected: boolean\n dragOver: boolean\n hovered: boolean\n disabled: boolean\n readonly: boolean\n dragSource: boolean\n dragTarget: boolean\n}\n\nexport interface WellStyleState {\n heatmap: HeatmapConfig\n sampleColors: Record<string, string>\n}\n\nexport function wellClasses(well: Well, state: WellClassState): string[] {\n const classes = [\n 'mint-well-plate__well',\n state.wellShape === 'circle' ? 'mint-well-plate__well--circle' : 'mint-well-plate__well--rounded',\n ]\n\n if (state.selectionMode === 'drag' && well.sampleType) {\n classes.push('mint-well-plate__well--draggable')\n }\n\n if (state.dragSource) classes.push('mint-well-plate__well--drag-source')\n else if (state.dragTarget) classes.push('mint-well-plate__well--drag-target')\n else if (state.selected) classes.push('mint-well-plate__well--selected')\n else if (state.dragOver) classes.push('mint-well-plate__well--drag-over')\n else if (state.hovered && !state.readonly) classes.push('mint-well-plate__well--hovered')\n\n if (state.disabled) classes.push('mint-well-plate__well--disabled')\n if (well.state === 'filled') classes.push('mint-well-plate__well--filled')\n\n return classes\n}\n\nexport function wellStyle(well: Well, state: WellStyleState): Record<string, string> {\n const heatmapColor = getHeatmapColor(state.heatmap, well.value)\n if (heatmapColor) {\n return { backgroundColor: heatmapColor, border: '1px solid transparent' }\n }\n\n if (well.sampleType && state.sampleColors[well.sampleType]) {\n const color = state.sampleColors[well.sampleType]\n return {\n backgroundColor: `${color}26`,\n border: `1px solid ${color}66`,\n }\n }\n\n if (well.sampleType && DEFAULT_SAMPLE_TYPE_COLORS[well.sampleType]) {\n const colors = DEFAULT_SAMPLE_TYPE_COLORS[well.sampleType]\n return {\n backgroundColor: colors.bg,\n border: `1px solid ${colors.border}`,\n }\n }\n\n const borderStyle = well.state === 'filled' ? 'solid' : 'dashed'\n return {\n backgroundColor: 'var(--bg-tertiary)',\n border: `1px ${borderStyle} var(--border-color)`,\n }\n}\n\nexport function sampleTypeIndicator(well: Well, showSampleTypeIndicator: boolean): string | null {\n if (!showSampleTypeIndicator || !well.sampleType) return null\n const typeMap: Record<string, string> = {\n sample: 'S',\n control: 'C',\n blank: 'B',\n qc: 'Q',\n iqc: 'I',\n }\n return typeMap[well.sampleType] || well.sampleType.charAt(0).toUpperCase()\n}\n\nexport function wellLabel(well: Well, showWellLabels: boolean): string | undefined {\n if (!showWellLabels) return undefined\n return well.metadata?.label as string | undefined\n}\n\nexport function wellBadge(\n well: Well,\n showBadges: boolean,\n): { text: string; color: string } | null {\n if (!showBadges || !well.metadata) return null\n const count = well.metadata.injectionCount as number | undefined\n if (count && count > 1) {\n return { text: String(count), color: '#6366f1' }\n }\n if (well.metadata.customMethod) {\n return { text: '+', color: '#ec4899' }\n }\n return null\n}\n","import type { WellLegendItem } from '../types'\n\nexport const DEFAULT_WELL_LEGEND_ITEMS: WellLegendItem[] = [\n { type: 'sample', label: 'Sample', color: '#10b981' },\n { type: 'blank', label: 'Blank', color: '#f97316' },\n { type: 'qc', label: 'QC', color: '#8b5cf6' },\n { type: 'iqc', label: 'IQC', color: '#ec4899' },\n]\n","<script setup lang=\"ts\">\n/** Interactive 96/384-well plate grid with drag-to-select, heatmap overlays, per-well editing, and sample-color mapping. */\nimport { ref, computed } from 'vue'\nimport type { WellPlateFormat, WellPlateSelectionMode, WellPlateSize, Well, HeatmapConfig, WellShape, WellEditField, WellEditData, WellLegendItem, ColumnCondition, RowCondition, WellSampleDropData, WellSampleDropParser } from '../types'\nimport { useEventListener } from '../composables/useEventListener'\nimport WellEditPopupInternal from './internal/WellEditPopupInternal.vue'\nimport {\n conditionGradientStyle,\n formatConcentration as formatConc,\n HEATMAP_COLORS,\n} from './WellPlate.colors'\nimport {\n createColumnConditionMap,\n createColumnConditionSpans,\n createRowConditionMap,\n createRowConditionSpanByRow,\n createRowConditionSpans,\n} from './WellPlate.conditions'\nimport {\n createPlateColumnLabels,\n createPlateRowLabels,\n createWellGrid,\n PLATE_CONFIGS,\n WELL_PLATE_SIZE_CONFIGS,\n} from './WellPlate.geometry'\nimport { useWellPlateInteraction } from './WellPlate.interaction'\nimport {\n sampleTypeIndicator,\n wellBadge,\n wellClasses,\n wellLabel,\n wellStyle,\n} from './WellPlate.rendering'\nimport { DEFAULT_WELL_LEGEND_ITEMS } from './WellPlate.legend'\n\ninterface Props {\n modelValue?: string[]\n format?: WellPlateFormat\n wells?: Record<string, Partial<Well>>\n selectionMode?: WellPlateSelectionMode\n showLabels?: boolean\n showWellIds?: boolean\n showSampleTypeIndicator?: boolean\n heatmap?: HeatmapConfig\n sampleColors?: Record<string, string>\n zoom?: number\n disabled?: boolean\n readonly?: boolean\n size?: WellPlateSize\n wellShape?: WellShape\n showWellLabels?: boolean\n showBadges?: boolean\n editable?: boolean\n editFields?: WellEditField[]\n defaultInjectionVolume?: number\n showLegend?: boolean\n legendItems?: WellLegendItem[]\n columnConditions?: ColumnCondition[]\n rowConditions?: RowCondition[]\n allowSampleDrop?: boolean\n sampleDropParser?: WellSampleDropParser\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: () => [],\n format: 96,\n wells: () => ({}),\n selectionMode: 'multiple',\n showLabels: true,\n showWellIds: false,\n showSampleTypeIndicator: false,\n heatmap: () => ({ enabled: false }),\n sampleColors: () => ({}),\n zoom: 1,\n disabled: false,\n readonly: false,\n size: 'md',\n wellShape: 'rounded',\n showWellLabels: false,\n showBadges: false,\n editable: false,\n editFields: () => ['label', 'sampleType', 'injectionVolume', 'injectionCount', 'customMethod'],\n defaultInjectionVolume: 5,\n showLegend: false,\n legendItems: undefined,\n columnConditions: () => [],\n rowConditions: () => [],\n allowSampleDrop: false,\n sampleDropParser: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [wellIds: string[]]\n 'well-click': [wellId: string, event: MouseEvent]\n 'well-hover': [wellId: string | null, event?: MouseEvent]\n 'selection-change': [wellIds: string[]]\n 'context-menu': [wellId: string, event: MouseEvent]\n 'well-move': [sourceWellId: string, targetWellId: string]\n 'well-edit': [wellId: string, data: WellEditData]\n 'well-clear': [wellId: string]\n 'sample-drop': [wellId: string, data: WellSampleDropData, event: DragEvent]\n}>()\n\ninterface WellEditorSlotProps {\n wellId: string\n wellData?: Partial<Well>\n editFields: WellEditField[]\n defaultInjectionVolume: number\n position: { x: number; y: number }\n save: (data: WellEditData) => void\n clear: () => void\n close: () => void\n}\n\ndefineSlots<{\n 'well-editor'?: (props: WellEditorSlotProps) => unknown\n}>()\n\nconst plateRef = ref<HTMLElement | null>(null)\nconst tableRef = ref<HTMLElement | null>(null)\n\nconst plateConfig = computed(() => PLATE_CONFIGS[props.format])\n\nconst rowLabels = computed(() => createPlateRowLabels(plateConfig.value))\n\nconst colLabels = computed(() => createPlateColumnLabels(plateConfig.value))\n\nconst wellGrid = computed(() => createWellGrid(plateConfig.value, rowLabels.value, props.wells))\n\nconst sizeConfig = computed(() => WELL_PLATE_SIZE_CONFIGS[props.size])\n\nconst isFillMode = computed(() => props.size === 'fill')\n\nconst activeLegendItems = computed(() => props.legendItems ?? DEFAULT_WELL_LEGEND_ITEMS)\n\n// --- Condition header helpers ---\n\nconst hasColumnConditions = computed(() => props.columnConditions.length > 0)\nconst hasRowConditions = computed(() => props.rowConditions.length > 0)\n\nconst colConditionMap = computed(() => createColumnConditionMap(props.columnConditions))\nconst rowConditionMap = computed(() => createRowConditionMap(props.rowConditions))\nconst colConditionSpans = computed(() => createColumnConditionSpans(colLabels.value, colConditionMap.value))\nconst rowConditionSpans = computed(() => createRowConditionSpans(rowLabels.value, rowConditionMap.value))\nconst rowConditionSpanByRow = computed(() => createRowConditionSpanByRow(rowConditionSpans.value))\n\nconst {\n dragSourceWell,\n dragTargetWell,\n hoveredWell,\n editingWellId,\n editPopupPosition,\n dragSelectedWells,\n isSelected,\n handleWellClick,\n handleEditSave,\n handleEditClear,\n handleEditClose,\n handleWellMouseDown,\n handleWellMouseEnter,\n handleWellMouseLeave,\n handleMouseUp,\n handleContextMenu,\n handleDragStart,\n handleDragOver,\n handleDragLeave,\n handleDrop,\n handleDragEnd,\n handleKeyDown,\n} = useWellPlateInteraction({\n modelValue: () => props.modelValue,\n selectionMode: () => props.selectionMode,\n disabled: () => props.disabled,\n readonly: () => props.readonly,\n editable: () => props.editable,\n allowSampleDrop: () => props.allowSampleDrop,\n sampleDropParser: () => props.sampleDropParser,\n rowLabels: () => rowLabels.value,\n wellGrid: () => wellGrid.value,\n emitSelected: (wellIds) => {\n emit('update:modelValue', wellIds)\n emit('selection-change', wellIds)\n },\n emitWellClick: (wellId, event) => emit('well-click', wellId, event),\n emitWellHover: (wellId, event) => emit('well-hover', wellId, event),\n emitContextMenu: (wellId, event) => emit('context-menu', wellId, event),\n emitWellMove: (sourceWellId, targetWellId) => emit('well-move', sourceWellId, targetWellId),\n emitWellEdit: (wellId, data) => emit('well-edit', wellId, data),\n emitWellClear: wellId => emit('well-clear', wellId),\n emitSampleDrop: (wellId, data, event) => emit('sample-drop', wellId, data, event),\n})\n\nfunction getWellClasses(well: Well): string[] {\n return wellClasses(well, {\n wellShape: props.wellShape,\n selectionMode: props.selectionMode,\n selected: isSelected(well.id),\n dragOver: dragSelectedWells.value.has(well.id),\n hovered: hoveredWell.value === well.id,\n disabled: props.disabled || well.state === 'disabled',\n readonly: props.readonly,\n dragSource: dragSourceWell.value === well.id,\n dragTarget: dragTargetWell.value === well.id,\n })\n}\n\nfunction getWellStyle(well: Well): Record<string, string> {\n return wellStyle(well, {\n heatmap: props.heatmap,\n sampleColors: props.sampleColors,\n })\n}\n\nfunction getSampleTypeIndicator(well: Well): string | null {\n return sampleTypeIndicator(well, props.showSampleTypeIndicator)\n}\n\nfunction getWellLabel(well: Well): string | undefined {\n return wellLabel(well, props.showWellLabels)\n}\n\nfunction getWellBadge(well: Well): { text: string; color: string } | null {\n return wellBadge(well, props.showBadges)\n}\n\nuseEventListener(() => document, 'mouseup', handleMouseUp)\nuseEventListener(() => document, 'keydown', handleKeyDown)\n\nconst containerStyle = computed(() =>\n isFillMode.value ? {} : {\n transform: `scale(${props.zoom})`,\n transformOrigin: 'top left',\n }\n)\n\nconst tableStyle = computed(() => ({\n borderCollapse: 'separate' as const,\n borderSpacing: sizeConfig.value.gap,\n ...(isFillMode.value ? { width: '100%', tableLayout: 'fixed' as const } : {}),\n}))\n</script>\n\n<template>\n <div\n ref=\"plateRef\"\n :class=\"['mint-well-plate', isFillMode ? 'mint-well-plate--fill' : 'mint-well-plate--inline']\"\n :style=\"containerStyle\"\n >\n <div class=\"mint-well-plate__scroll\">\n <div class=\"mint-well-plate__content\">\n <table\n ref=\"tableRef\"\n class=\"mint-well-plate__table\"\n role=\"grid\"\n :aria-label=\"`${props.format}-well plate`\"\n :style=\"tableStyle\"\n >\n <!-- Column condition label row (drug name + unit) -->\n <thead v-if=\"hasColumnConditions\">\n <tr>\n <!-- Spacer for row header column -->\n <th :style=\"{ width: sizeConfig.headerWidth }\"></th>\n <!-- Spacer for row condition column -->\n <th v-if=\"hasRowConditions\" :style=\"{ width: sizeConfig.headerWidth }\"></th>\n <template v-for=\"(span, idx) in colConditionSpans\" :key=\"'clabel-' + idx\">\n <th\n v-if=\"'condition' in span\"\n :colspan=\"span.colspan\"\n class=\"mint-well-plate__condition-label\"\n :style=\"{\n height: sizeConfig.headerHeight,\n fontSize: sizeConfig.fontSize,\n color: span.condition.color,\n }\"\n >\n {{ span.condition.label }}<template v-if=\"span.condition.unit\"> ({{ span.condition.unit }})</template>\n </th>\n <th v-else :colspan=\"span.colspan\"></th>\n </template>\n </tr>\n </thead>\n <!-- Column headers (with concentration overlay when conditions present) -->\n <thead v-if=\"props.showLabels\">\n <tr>\n <th :style=\"{ width: sizeConfig.headerWidth, height: sizeConfig.headerHeight }\"></th>\n <!-- Spacer for row condition column in column header row -->\n <th v-if=\"hasRowConditions\" :style=\"{ width: sizeConfig.headerWidth, height: sizeConfig.headerHeight }\"></th>\n <th\n v-for=\"col in colLabels\"\n :key=\"col\"\n class=\"mint-well-plate__col-header\"\n :style=\"{ height: sizeConfig.headerHeight, fontSize: sizeConfig.fontSize }\"\n >\n <template v-for=\"entry in [colConditionMap.get(col)]\" :key=\"col\">\n <span\n v-if=\"entry\"\n class=\"mint-well-plate__condition-cell\"\n :style=\"conditionGradientStyle(\n entry.condition.color,\n entry.condition.concentrations[entry.indexInGroup],\n entry.condition.concentrations,\n )\"\n >\n {{ formatConc(entry.condition.concentrations[entry.indexInGroup]) }}\n </span>\n <template v-else>{{ col }}</template>\n </template>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"(row, rowIndex) in wellGrid\" :key=\"rowIndex\" role=\"row\">\n <!-- Row condition label cell (drug name, spans multiple rows) -->\n <template v-if=\"hasRowConditions && rowConditionSpanByRow.has(rowIndex)\">\n <td\n v-for=\"span in [rowConditionSpanByRow.get(rowIndex)!]\"\n :key=\"rowIndex\"\n :rowspan=\"span.rowspan\"\n :class=\"[\n 'mint-well-plate__row-condition-label',\n 'gap' in span ? 'mint-well-plate__row-condition-label--empty' : '',\n ]\"\n :style=\"{\n width: sizeConfig.headerWidth,\n minWidth: sizeConfig.headerWidth,\n fontSize: sizeConfig.fontSize,\n ...('condition' in span ? { color: span.condition.color } : {}),\n }\"\n >\n <div v-if=\"'condition' in span\" class=\"mint-well-plate__row-condition-wrap\">\n <span class=\"mint-well-plate__row-condition-text\">{{ span.condition.label }}</span>\n </div>\n </td>\n </template>\n <!-- Row header (with concentration overlay when conditions present) -->\n <td\n v-if=\"props.showLabels\"\n class=\"mint-well-plate__row-header\"\n :style=\"{\n width: sizeConfig.headerWidth,\n height: sizeConfig.cellHeight,\n minWidth: sizeConfig.headerWidth,\n minHeight: sizeConfig.cellHeight,\n fontSize: sizeConfig.fontSize,\n }\"\n >\n <template v-for=\"entry in [rowConditionMap.get(rowLabels[rowIndex])]\" :key=\"rowIndex\">\n <span\n v-if=\"entry\"\n class=\"mint-well-plate__condition-cell\"\n :style=\"conditionGradientStyle(\n entry.condition.color,\n entry.condition.concentrations[entry.indexInGroup],\n entry.condition.concentrations,\n )\"\n >\n {{ formatConc(entry.condition.concentrations[entry.indexInGroup]) }}\n </span>\n <template v-else>{{ rowLabels[rowIndex] }}</template>\n </template>\n </td>\n\n <!-- Wells -->\n <td v-for=\"well in row\" :key=\"well.id\" class=\"mint-well-plate__cell\">\n <div\n role=\"gridcell\"\n tabindex=\"0\"\n :aria-label=\"`Well ${well.id}${well.sampleType ? `, Sample type: ${well.sampleType}` : ''}${well.value !== undefined ? `, Value: ${well.value}` : ''}`\"\n :aria-selected=\"isSelected(well.id)\"\n :aria-disabled=\"props.disabled || well.state === 'disabled'\"\n :draggable=\"props.selectionMode === 'drag' && !!well.sampleType\"\n :class=\"getWellClasses(well)\"\n :style=\"{\n width: isFillMode ? '100%' : sizeConfig.cellWidth,\n height: sizeConfig.cellHeight,\n minWidth: isFillMode ? '0' : sizeConfig.cellWidth,\n minHeight: sizeConfig.cellHeight,\n boxSizing: 'border-box',\n fontSize: sizeConfig.fontSize,\n ...getWellStyle(well),\n }\"\n :title=\"`${well.id}${well.sampleType ? `: ${well.sampleType}` : ''}${getWellLabel(well) ? ` - ${getWellLabel(well)}` : ''}`\"\n @click=\"handleWellClick(well, $event)\"\n @mousedown=\"handleWellMouseDown(well, $event)\"\n @mouseenter=\"handleWellMouseEnter(well, $event)\"\n @mouseleave=\"handleWellMouseLeave\"\n @contextmenu=\"handleContextMenu(well, $event)\"\n @dragstart=\"handleDragStart(well, $event)\"\n @dragover=\"handleDragOver(well, $event)\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop(well, $event)\"\n @dragend=\"handleDragEnd\"\n @keydown.enter=\"handleWellClick(well, $event as unknown as MouseEvent)\"\n @keydown.space.prevent=\"handleWellClick(well, $event as unknown as MouseEvent)\"\n >\n <!-- Well label text (from metadata.label) -->\n <span\n v-if=\"getWellLabel(well)\"\n class=\"mint-well-plate__label\"\n >\n {{ getWellLabel(well) }}\n </span>\n <!-- Sample type indicator (S/B/Q/C) -->\n <span\n v-else-if=\"getSampleTypeIndicator(well)\"\n class=\"mint-well-plate__indicator\"\n >\n {{ getSampleTypeIndicator(well) }}\n </span>\n <!-- Well ID -->\n <span\n v-else-if=\"props.showWellIds\"\n class=\"mint-well-plate__well-id\"\n >\n {{ well.id }}\n </span>\n\n <!-- Badge (injection count or custom method) -->\n <span\n v-if=\"getWellBadge(well)\"\n class=\"mint-well-plate__badge\"\n :style=\"{ backgroundColor: getWellBadge(well)?.color }\"\n :title=\"getWellBadge(well)?.text === '+' ? 'Custom method' : `${getWellBadge(well)?.text ?? ''}x injections`\"\n >\n {{ getWellBadge(well)?.text }}\n </span>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!-- Heatmap legend (inside content wrapper to match table width) -->\n <div\n v-if=\"props.heatmap?.enabled && props.heatmap.showLegend\"\n class=\"mint-well-plate__legend\"\n >\n <span class=\"mint-well-plate__legend-label\">{{ props.heatmap.min ?? 0 }}</span>\n <div class=\"mint-well-plate__legend-bar\">\n <div\n v-for=\"(color, index) in (props.heatmap.colorScale === 'custom' && props.heatmap.customColors?.length ? props.heatmap.customColors : HEATMAP_COLORS[props.heatmap.colorScale || 'viridis'])\"\n :key=\"index\"\n class=\"mint-well-plate__legend-segment\"\n :style=\"{ backgroundColor: color }\"\n />\n </div>\n <span class=\"mint-well-plate__legend-label\">{{ props.heatmap.max ?? 1 }}</span>\n </div>\n\n <!-- Sample type legend bar -->\n <div v-if=\"props.showLegend\" class=\"mint-well-plate__sample-legend\">\n <div\n v-for=\"item in activeLegendItems\"\n :key=\"item.type\"\n class=\"mint-well-plate__sample-legend-item\"\n >\n <span\n class=\"mint-well-plate__sample-legend-dot\"\n :style=\"{ backgroundColor: `${item.color}26`, border: `1px solid ${item.color}66` }\"\n />\n <span class=\"mint-well-plate__sample-legend-text\">{{ item.label }}</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Edit popup -->\n <template v-if=\"editable && editingWellId\">\n <slot\n name=\"well-editor\"\n :well-id=\"editingWellId\"\n :well-data=\"wells[editingWellId]\"\n :edit-fields=\"editFields\"\n :default-injection-volume=\"defaultInjectionVolume\"\n :position=\"editPopupPosition\"\n :save=\"handleEditSave\"\n :clear=\"handleEditClear\"\n :close=\"handleEditClose\"\n >\n <WellEditPopupInternal\n :well-id=\"editingWellId\"\n :well-data=\"wells[editingWellId]\"\n :edit-fields=\"editFields\"\n :default-injection-volume=\"defaultInjectionVolume\"\n :position=\"editPopupPosition\"\n @save=\"handleEditSave\"\n @clear=\"handleEditClear\"\n @close=\"handleEditClose\"\n />\n </slot>\n </template>\n </div>\n</template>\n\n<style>\n@import '../styles/components/well-plate.css';\n</style>\n","<script setup lang=\"ts\">\n/** Interactive 96/384-well plate grid with drag-to-select, heatmap overlays, per-well editing, and sample-color mapping. */\nimport { ref, computed } from 'vue'\nimport type { WellPlateFormat, WellPlateSelectionMode, WellPlateSize, Well, HeatmapConfig, WellShape, WellEditField, WellEditData, WellLegendItem, ColumnCondition, RowCondition, WellSampleDropData, WellSampleDropParser } from '../types'\nimport { useEventListener } from '../composables/useEventListener'\nimport WellEditPopupInternal from './internal/WellEditPopupInternal.vue'\nimport {\n conditionGradientStyle,\n formatConcentration as formatConc,\n HEATMAP_COLORS,\n} from './WellPlate.colors'\nimport {\n createColumnConditionMap,\n createColumnConditionSpans,\n createRowConditionMap,\n createRowConditionSpanByRow,\n createRowConditionSpans,\n} from './WellPlate.conditions'\nimport {\n createPlateColumnLabels,\n createPlateRowLabels,\n createWellGrid,\n PLATE_CONFIGS,\n WELL_PLATE_SIZE_CONFIGS,\n} from './WellPlate.geometry'\nimport { useWellPlateInteraction } from './WellPlate.interaction'\nimport {\n sampleTypeIndicator,\n wellBadge,\n wellClasses,\n wellLabel,\n wellStyle,\n} from './WellPlate.rendering'\nimport { DEFAULT_WELL_LEGEND_ITEMS } from './WellPlate.legend'\n\ninterface Props {\n modelValue?: string[]\n format?: WellPlateFormat\n wells?: Record<string, Partial<Well>>\n selectionMode?: WellPlateSelectionMode\n showLabels?: boolean\n showWellIds?: boolean\n showSampleTypeIndicator?: boolean\n heatmap?: HeatmapConfig\n sampleColors?: Record<string, string>\n zoom?: number\n disabled?: boolean\n readonly?: boolean\n size?: WellPlateSize\n wellShape?: WellShape\n showWellLabels?: boolean\n showBadges?: boolean\n editable?: boolean\n editFields?: WellEditField[]\n defaultInjectionVolume?: number\n showLegend?: boolean\n legendItems?: WellLegendItem[]\n columnConditions?: ColumnCondition[]\n rowConditions?: RowCondition[]\n allowSampleDrop?: boolean\n sampleDropParser?: WellSampleDropParser\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: () => [],\n format: 96,\n wells: () => ({}),\n selectionMode: 'multiple',\n showLabels: true,\n showWellIds: false,\n showSampleTypeIndicator: false,\n heatmap: () => ({ enabled: false }),\n sampleColors: () => ({}),\n zoom: 1,\n disabled: false,\n readonly: false,\n size: 'md',\n wellShape: 'rounded',\n showWellLabels: false,\n showBadges: false,\n editable: false,\n editFields: () => ['label', 'sampleType', 'injectionVolume', 'injectionCount', 'customMethod'],\n defaultInjectionVolume: 5,\n showLegend: false,\n legendItems: undefined,\n columnConditions: () => [],\n rowConditions: () => [],\n allowSampleDrop: false,\n sampleDropParser: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [wellIds: string[]]\n 'well-click': [wellId: string, event: MouseEvent]\n 'well-hover': [wellId: string | null, event?: MouseEvent]\n 'selection-change': [wellIds: string[]]\n 'context-menu': [wellId: string, event: MouseEvent]\n 'well-move': [sourceWellId: string, targetWellId: string]\n 'well-edit': [wellId: string, data: WellEditData]\n 'well-clear': [wellId: string]\n 'sample-drop': [wellId: string, data: WellSampleDropData, event: DragEvent]\n}>()\n\ninterface WellEditorSlotProps {\n wellId: string\n wellData?: Partial<Well>\n editFields: WellEditField[]\n defaultInjectionVolume: number\n position: { x: number; y: number }\n save: (data: WellEditData) => void\n clear: () => void\n close: () => void\n}\n\ndefineSlots<{\n 'well-editor'?: (props: WellEditorSlotProps) => unknown\n}>()\n\nconst plateRef = ref<HTMLElement | null>(null)\nconst tableRef = ref<HTMLElement | null>(null)\n\nconst plateConfig = computed(() => PLATE_CONFIGS[props.format])\n\nconst rowLabels = computed(() => createPlateRowLabels(plateConfig.value))\n\nconst colLabels = computed(() => createPlateColumnLabels(plateConfig.value))\n\nconst wellGrid = computed(() => createWellGrid(plateConfig.value, rowLabels.value, props.wells))\n\nconst sizeConfig = computed(() => WELL_PLATE_SIZE_CONFIGS[props.size])\n\nconst isFillMode = computed(() => props.size === 'fill')\n\nconst activeLegendItems = computed(() => props.legendItems ?? DEFAULT_WELL_LEGEND_ITEMS)\n\n// --- Condition header helpers ---\n\nconst hasColumnConditions = computed(() => props.columnConditions.length > 0)\nconst hasRowConditions = computed(() => props.rowConditions.length > 0)\n\nconst colConditionMap = computed(() => createColumnConditionMap(props.columnConditions))\nconst rowConditionMap = computed(() => createRowConditionMap(props.rowConditions))\nconst colConditionSpans = computed(() => createColumnConditionSpans(colLabels.value, colConditionMap.value))\nconst rowConditionSpans = computed(() => createRowConditionSpans(rowLabels.value, rowConditionMap.value))\nconst rowConditionSpanByRow = computed(() => createRowConditionSpanByRow(rowConditionSpans.value))\n\nconst {\n dragSourceWell,\n dragTargetWell,\n hoveredWell,\n editingWellId,\n editPopupPosition,\n dragSelectedWells,\n isSelected,\n handleWellClick,\n handleEditSave,\n handleEditClear,\n handleEditClose,\n handleWellMouseDown,\n handleWellMouseEnter,\n handleWellMouseLeave,\n handleMouseUp,\n handleContextMenu,\n handleDragStart,\n handleDragOver,\n handleDragLeave,\n handleDrop,\n handleDragEnd,\n handleKeyDown,\n} = useWellPlateInteraction({\n modelValue: () => props.modelValue,\n selectionMode: () => props.selectionMode,\n disabled: () => props.disabled,\n readonly: () => props.readonly,\n editable: () => props.editable,\n allowSampleDrop: () => props.allowSampleDrop,\n sampleDropParser: () => props.sampleDropParser,\n rowLabels: () => rowLabels.value,\n wellGrid: () => wellGrid.value,\n emitSelected: (wellIds) => {\n emit('update:modelValue', wellIds)\n emit('selection-change', wellIds)\n },\n emitWellClick: (wellId, event) => emit('well-click', wellId, event),\n emitWellHover: (wellId, event) => emit('well-hover', wellId, event),\n emitContextMenu: (wellId, event) => emit('context-menu', wellId, event),\n emitWellMove: (sourceWellId, targetWellId) => emit('well-move', sourceWellId, targetWellId),\n emitWellEdit: (wellId, data) => emit('well-edit', wellId, data),\n emitWellClear: wellId => emit('well-clear', wellId),\n emitSampleDrop: (wellId, data, event) => emit('sample-drop', wellId, data, event),\n})\n\nfunction getWellClasses(well: Well): string[] {\n return wellClasses(well, {\n wellShape: props.wellShape,\n selectionMode: props.selectionMode,\n selected: isSelected(well.id),\n dragOver: dragSelectedWells.value.has(well.id),\n hovered: hoveredWell.value === well.id,\n disabled: props.disabled || well.state === 'disabled',\n readonly: props.readonly,\n dragSource: dragSourceWell.value === well.id,\n dragTarget: dragTargetWell.value === well.id,\n })\n}\n\nfunction getWellStyle(well: Well): Record<string, string> {\n return wellStyle(well, {\n heatmap: props.heatmap,\n sampleColors: props.sampleColors,\n })\n}\n\nfunction getSampleTypeIndicator(well: Well): string | null {\n return sampleTypeIndicator(well, props.showSampleTypeIndicator)\n}\n\nfunction getWellLabel(well: Well): string | undefined {\n return wellLabel(well, props.showWellLabels)\n}\n\nfunction getWellBadge(well: Well): { text: string; color: string } | null {\n return wellBadge(well, props.showBadges)\n}\n\nuseEventListener(() => document, 'mouseup', handleMouseUp)\nuseEventListener(() => document, 'keydown', handleKeyDown)\n\nconst containerStyle = computed(() =>\n isFillMode.value ? {} : {\n transform: `scale(${props.zoom})`,\n transformOrigin: 'top left',\n }\n)\n\nconst tableStyle = computed(() => ({\n borderCollapse: 'separate' as const,\n borderSpacing: sizeConfig.value.gap,\n ...(isFillMode.value ? { width: '100%', tableLayout: 'fixed' as const } : {}),\n}))\n</script>\n\n<template>\n <div\n ref=\"plateRef\"\n :class=\"['mint-well-plate', isFillMode ? 'mint-well-plate--fill' : 'mint-well-plate--inline']\"\n :style=\"containerStyle\"\n >\n <div class=\"mint-well-plate__scroll\">\n <div class=\"mint-well-plate__content\">\n <table\n ref=\"tableRef\"\n class=\"mint-well-plate__table\"\n role=\"grid\"\n :aria-label=\"`${props.format}-well plate`\"\n :style=\"tableStyle\"\n >\n <!-- Column condition label row (drug name + unit) -->\n <thead v-if=\"hasColumnConditions\">\n <tr>\n <!-- Spacer for row header column -->\n <th :style=\"{ width: sizeConfig.headerWidth }\"></th>\n <!-- Spacer for row condition column -->\n <th v-if=\"hasRowConditions\" :style=\"{ width: sizeConfig.headerWidth }\"></th>\n <template v-for=\"(span, idx) in colConditionSpans\" :key=\"'clabel-' + idx\">\n <th\n v-if=\"'condition' in span\"\n :colspan=\"span.colspan\"\n class=\"mint-well-plate__condition-label\"\n :style=\"{\n height: sizeConfig.headerHeight,\n fontSize: sizeConfig.fontSize,\n color: span.condition.color,\n }\"\n >\n {{ span.condition.label }}<template v-if=\"span.condition.unit\"> ({{ span.condition.unit }})</template>\n </th>\n <th v-else :colspan=\"span.colspan\"></th>\n </template>\n </tr>\n </thead>\n <!-- Column headers (with concentration overlay when conditions present) -->\n <thead v-if=\"props.showLabels\">\n <tr>\n <th :style=\"{ width: sizeConfig.headerWidth, height: sizeConfig.headerHeight }\"></th>\n <!-- Spacer for row condition column in column header row -->\n <th v-if=\"hasRowConditions\" :style=\"{ width: sizeConfig.headerWidth, height: sizeConfig.headerHeight }\"></th>\n <th\n v-for=\"col in colLabels\"\n :key=\"col\"\n class=\"mint-well-plate__col-header\"\n :style=\"{ height: sizeConfig.headerHeight, fontSize: sizeConfig.fontSize }\"\n >\n <template v-for=\"entry in [colConditionMap.get(col)]\" :key=\"col\">\n <span\n v-if=\"entry\"\n class=\"mint-well-plate__condition-cell\"\n :style=\"conditionGradientStyle(\n entry.condition.color,\n entry.condition.concentrations[entry.indexInGroup],\n entry.condition.concentrations,\n )\"\n >\n {{ formatConc(entry.condition.concentrations[entry.indexInGroup]) }}\n </span>\n <template v-else>{{ col }}</template>\n </template>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"(row, rowIndex) in wellGrid\" :key=\"rowIndex\" role=\"row\">\n <!-- Row condition label cell (drug name, spans multiple rows) -->\n <template v-if=\"hasRowConditions && rowConditionSpanByRow.has(rowIndex)\">\n <td\n v-for=\"span in [rowConditionSpanByRow.get(rowIndex)!]\"\n :key=\"rowIndex\"\n :rowspan=\"span.rowspan\"\n :class=\"[\n 'mint-well-plate__row-condition-label',\n 'gap' in span ? 'mint-well-plate__row-condition-label--empty' : '',\n ]\"\n :style=\"{\n width: sizeConfig.headerWidth,\n minWidth: sizeConfig.headerWidth,\n fontSize: sizeConfig.fontSize,\n ...('condition' in span ? { color: span.condition.color } : {}),\n }\"\n >\n <div v-if=\"'condition' in span\" class=\"mint-well-plate__row-condition-wrap\">\n <span class=\"mint-well-plate__row-condition-text\">{{ span.condition.label }}</span>\n </div>\n </td>\n </template>\n <!-- Row header (with concentration overlay when conditions present) -->\n <td\n v-if=\"props.showLabels\"\n class=\"mint-well-plate__row-header\"\n :style=\"{\n width: sizeConfig.headerWidth,\n height: sizeConfig.cellHeight,\n minWidth: sizeConfig.headerWidth,\n minHeight: sizeConfig.cellHeight,\n fontSize: sizeConfig.fontSize,\n }\"\n >\n <template v-for=\"entry in [rowConditionMap.get(rowLabels[rowIndex])]\" :key=\"rowIndex\">\n <span\n v-if=\"entry\"\n class=\"mint-well-plate__condition-cell\"\n :style=\"conditionGradientStyle(\n entry.condition.color,\n entry.condition.concentrations[entry.indexInGroup],\n entry.condition.concentrations,\n )\"\n >\n {{ formatConc(entry.condition.concentrations[entry.indexInGroup]) }}\n </span>\n <template v-else>{{ rowLabels[rowIndex] }}</template>\n </template>\n </td>\n\n <!-- Wells -->\n <td v-for=\"well in row\" :key=\"well.id\" class=\"mint-well-plate__cell\">\n <div\n role=\"gridcell\"\n tabindex=\"0\"\n :aria-label=\"`Well ${well.id}${well.sampleType ? `, Sample type: ${well.sampleType}` : ''}${well.value !== undefined ? `, Value: ${well.value}` : ''}`\"\n :aria-selected=\"isSelected(well.id)\"\n :aria-disabled=\"props.disabled || well.state === 'disabled'\"\n :draggable=\"props.selectionMode === 'drag' && !!well.sampleType\"\n :class=\"getWellClasses(well)\"\n :style=\"{\n width: isFillMode ? '100%' : sizeConfig.cellWidth,\n height: sizeConfig.cellHeight,\n minWidth: isFillMode ? '0' : sizeConfig.cellWidth,\n minHeight: sizeConfig.cellHeight,\n boxSizing: 'border-box',\n fontSize: sizeConfig.fontSize,\n ...getWellStyle(well),\n }\"\n :title=\"`${well.id}${well.sampleType ? `: ${well.sampleType}` : ''}${getWellLabel(well) ? ` - ${getWellLabel(well)}` : ''}`\"\n @click=\"handleWellClick(well, $event)\"\n @mousedown=\"handleWellMouseDown(well, $event)\"\n @mouseenter=\"handleWellMouseEnter(well, $event)\"\n @mouseleave=\"handleWellMouseLeave\"\n @contextmenu=\"handleContextMenu(well, $event)\"\n @dragstart=\"handleDragStart(well, $event)\"\n @dragover=\"handleDragOver(well, $event)\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop(well, $event)\"\n @dragend=\"handleDragEnd\"\n @keydown.enter=\"handleWellClick(well, $event as unknown as MouseEvent)\"\n @keydown.space.prevent=\"handleWellClick(well, $event as unknown as MouseEvent)\"\n >\n <!-- Well label text (from metadata.label) -->\n <span\n v-if=\"getWellLabel(well)\"\n class=\"mint-well-plate__label\"\n >\n {{ getWellLabel(well) }}\n </span>\n <!-- Sample type indicator (S/B/Q/C) -->\n <span\n v-else-if=\"getSampleTypeIndicator(well)\"\n class=\"mint-well-plate__indicator\"\n >\n {{ getSampleTypeIndicator(well) }}\n </span>\n <!-- Well ID -->\n <span\n v-else-if=\"props.showWellIds\"\n class=\"mint-well-plate__well-id\"\n >\n {{ well.id }}\n </span>\n\n <!-- Badge (injection count or custom method) -->\n <span\n v-if=\"getWellBadge(well)\"\n class=\"mint-well-plate__badge\"\n :style=\"{ backgroundColor: getWellBadge(well)?.color }\"\n :title=\"getWellBadge(well)?.text === '+' ? 'Custom method' : `${getWellBadge(well)?.text ?? ''}x injections`\"\n >\n {{ getWellBadge(well)?.text }}\n </span>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!-- Heatmap legend (inside content wrapper to match table width) -->\n <div\n v-if=\"props.heatmap?.enabled && props.heatmap.showLegend\"\n class=\"mint-well-plate__legend\"\n >\n <span class=\"mint-well-plate__legend-label\">{{ props.heatmap.min ?? 0 }}</span>\n <div class=\"mint-well-plate__legend-bar\">\n <div\n v-for=\"(color, index) in (props.heatmap.colorScale === 'custom' && props.heatmap.customColors?.length ? props.heatmap.customColors : HEATMAP_COLORS[props.heatmap.colorScale || 'viridis'])\"\n :key=\"index\"\n class=\"mint-well-plate__legend-segment\"\n :style=\"{ backgroundColor: color }\"\n />\n </div>\n <span class=\"mint-well-plate__legend-label\">{{ props.heatmap.max ?? 1 }}</span>\n </div>\n\n <!-- Sample type legend bar -->\n <div v-if=\"props.showLegend\" class=\"mint-well-plate__sample-legend\">\n <div\n v-for=\"item in activeLegendItems\"\n :key=\"item.type\"\n class=\"mint-well-plate__sample-legend-item\"\n >\n <span\n class=\"mint-well-plate__sample-legend-dot\"\n :style=\"{ backgroundColor: `${item.color}26`, border: `1px solid ${item.color}66` }\"\n />\n <span class=\"mint-well-plate__sample-legend-text\">{{ item.label }}</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Edit popup -->\n <template v-if=\"editable && editingWellId\">\n <slot\n name=\"well-editor\"\n :well-id=\"editingWellId\"\n :well-data=\"wells[editingWellId]\"\n :edit-fields=\"editFields\"\n :default-injection-volume=\"defaultInjectionVolume\"\n :position=\"editPopupPosition\"\n :save=\"handleEditSave\"\n :clear=\"handleEditClear\"\n :close=\"handleEditClose\"\n >\n <WellEditPopupInternal\n :well-id=\"editingWellId\"\n :well-data=\"wells[editingWellId]\"\n :edit-fields=\"editFields\"\n :default-injection-volume=\"defaultInjectionVolume\"\n :position=\"editPopupPosition\"\n @save=\"handleEditSave\"\n @clear=\"handleEditClear\"\n @close=\"handleEditClose\"\n />\n </slot>\n </template>\n </div>\n</template>\n\n<style>\n@import '../styles/components/well-plate.css';\n</style>\n","<script setup lang=\"ts\">\n/** Clickable color-swatch legend for plate sample types, supporting selection, count display, add/remove, and custom color assignment. */\nimport type { SampleType } from '../types'\n\ninterface Props {\n modelValue?: string\n samples: SampleType[]\n showCounts?: boolean\n editable?: boolean\n colorPalette?: string[]\n size?: 'sm' | 'md' | 'lg'\n orientation?: 'vertical' | 'horizontal'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: undefined,\n samples: () => [],\n showCounts: true,\n editable: false,\n colorPalette: () => [\n '#3B82F6', '#10B981', '#EF4444', '#F59E0B', '#8B5CF6',\n '#F97316', '#06B6D4', '#14B8A6', '#6B7280',\n ],\n size: 'md',\n orientation: 'vertical',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [sampleId: string | undefined]\n 'sample-click': [sample: SampleType]\n 'sample-add': []\n 'sample-remove': [sampleId: string]\n 'color-change': [sampleId: string, color: string]\n}>()\n\n\nfunction getSampleColor(sample: SampleType, index: number): string {\n return sample.color || props.colorPalette[index % props.colorPalette.length]\n}\n\nfunction handleSampleClick(sample: SampleType) {\n const newValue = props.modelValue === sample.id ? undefined : sample.id\n emit('sample-click', sample)\n emit('update:modelValue', newValue)\n}\n\nfunction handleRemove(event: Event, sampleId: string) {\n event.stopPropagation()\n emit('sample-remove', sampleId)\n}\n\nfunction handleAdd() {\n emit('sample-add')\n}\n</script>\n\n<template>\n <div :class=\"['mint-sample-legend', `mint-sample-legend--${orientation}`]\" role=\"list\" aria-label=\"Sample types\">\n <button\n v-for=\"(sample, index) in samples\"\n :key=\"sample.id\"\n type=\"button\"\n role=\"listitem\"\n :aria-pressed=\"modelValue === sample.id\"\n :class=\"[\n 'mint-sample-legend__item',\n `mint-sample-legend__item--${size}`,\n modelValue === sample.id ? 'mint-sample-legend__item--selected' : '',\n ]\"\n @click=\"handleSampleClick(sample)\"\n >\n <span\n :class=\"['mint-sample-legend__swatch', `mint-sample-legend__swatch--${size}`]\"\n :style=\"{ backgroundColor: getSampleColor(sample, index) }\"\n :aria-hidden=\"true\"\n />\n\n <span :class=\"['mint-sample-legend__name', `mint-sample-legend__name--${size}`]\">\n {{ sample.name }}\n </span>\n\n <span\n v-if=\"showCounts && sample.count !== undefined\"\n :class=\"['mint-sample-legend__count', `mint-sample-legend__count--${size}`]\"\n >\n {{ sample.count }}\n </span>\n\n <button\n v-if=\"editable\"\n type=\"button\"\n class=\"mint-sample-legend__remove\"\n :aria-label=\"`Remove ${sample.name}`\"\n @click=\"handleRemove($event, sample.id)\"\n >\n <svg class=\"mint-sample-legend__remove-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </button>\n\n <button\n v-if=\"editable\"\n type=\"button\"\n :class=\"['mint-sample-legend__add', `mint-sample-legend__add--${size}`]\"\n aria-label=\"Add sample type\"\n @click=\"handleAdd\"\n >\n <svg :class=\"`mint-sample-legend__add-icon--${size}`\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n <span :class=\"`mint-sample-legend__add-text--${size}`\">Add</span>\n </button>\n </div>\n</template>\n\n<style>\n@import '../styles/components/sample-legend.css';\n</style>\n","<script setup lang=\"ts\">\n/** Clickable color-swatch legend for plate sample types, supporting selection, count display, add/remove, and custom color assignment. */\nimport type { SampleType } from '../types'\n\ninterface Props {\n modelValue?: string\n samples: SampleType[]\n showCounts?: boolean\n editable?: boolean\n colorPalette?: string[]\n size?: 'sm' | 'md' | 'lg'\n orientation?: 'vertical' | 'horizontal'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: undefined,\n samples: () => [],\n showCounts: true,\n editable: false,\n colorPalette: () => [\n '#3B82F6', '#10B981', '#EF4444', '#F59E0B', '#8B5CF6',\n '#F97316', '#06B6D4', '#14B8A6', '#6B7280',\n ],\n size: 'md',\n orientation: 'vertical',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [sampleId: string | undefined]\n 'sample-click': [sample: SampleType]\n 'sample-add': []\n 'sample-remove': [sampleId: string]\n 'color-change': [sampleId: string, color: string]\n}>()\n\n\nfunction getSampleColor(sample: SampleType, index: number): string {\n return sample.color || props.colorPalette[index % props.colorPalette.length]\n}\n\nfunction handleSampleClick(sample: SampleType) {\n const newValue = props.modelValue === sample.id ? undefined : sample.id\n emit('sample-click', sample)\n emit('update:modelValue', newValue)\n}\n\nfunction handleRemove(event: Event, sampleId: string) {\n event.stopPropagation()\n emit('sample-remove', sampleId)\n}\n\nfunction handleAdd() {\n emit('sample-add')\n}\n</script>\n\n<template>\n <div :class=\"['mint-sample-legend', `mint-sample-legend--${orientation}`]\" role=\"list\" aria-label=\"Sample types\">\n <button\n v-for=\"(sample, index) in samples\"\n :key=\"sample.id\"\n type=\"button\"\n role=\"listitem\"\n :aria-pressed=\"modelValue === sample.id\"\n :class=\"[\n 'mint-sample-legend__item',\n `mint-sample-legend__item--${size}`,\n modelValue === sample.id ? 'mint-sample-legend__item--selected' : '',\n ]\"\n @click=\"handleSampleClick(sample)\"\n >\n <span\n :class=\"['mint-sample-legend__swatch', `mint-sample-legend__swatch--${size}`]\"\n :style=\"{ backgroundColor: getSampleColor(sample, index) }\"\n :aria-hidden=\"true\"\n />\n\n <span :class=\"['mint-sample-legend__name', `mint-sample-legend__name--${size}`]\">\n {{ sample.name }}\n </span>\n\n <span\n v-if=\"showCounts && sample.count !== undefined\"\n :class=\"['mint-sample-legend__count', `mint-sample-legend__count--${size}`]\"\n >\n {{ sample.count }}\n </span>\n\n <button\n v-if=\"editable\"\n type=\"button\"\n class=\"mint-sample-legend__remove\"\n :aria-label=\"`Remove ${sample.name}`\"\n @click=\"handleRemove($event, sample.id)\"\n >\n <svg class=\"mint-sample-legend__remove-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </button>\n\n <button\n v-if=\"editable\"\n type=\"button\"\n :class=\"['mint-sample-legend__add', `mint-sample-legend__add--${size}`]\"\n aria-label=\"Add sample type\"\n @click=\"handleAdd\"\n >\n <svg :class=\"`mint-sample-legend__add-icon--${size}`\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n <span :class=\"`mint-sample-legend__add-text--${size}`\">Add</span>\n </button>\n </div>\n</template>\n\n<style>\n@import '../styles/components/sample-legend.css';\n</style>\n","<script setup lang=\"ts\">\n/** Toolbar for PlateMapEditor plate tabs and import/export history actions. */\nimport type { PlateMap } from '../../types'\n\ninterface Props {\n plates: PlateMap[]\n activePlateId?: string\n plateWellCounts: Record<string, number>\n canAddPlate: boolean\n canUndo: boolean\n canRedo: boolean\n slotColorForPlate: (plateId: string, plateIndex: number) => string\n}\n\nconst props = defineProps<Props>()\n\nconst emit = defineEmits<{\n 'select-plate': [plateId: string]\n 'remove-plate': [plateId: string]\n 'add-plate': []\n undo: []\n redo: []\n import: []\n 'export-json': []\n}>()\n</script>\n\n<template>\n <div class=\"mint-plate-editor__toolbar\">\n <div class=\"mint-plate-editor__tabs\">\n <button\n v-for=\"(plate, index) in plates\"\n :key=\"plate.id\"\n type=\"button\"\n :class=\"['mint-plate-editor__tab', { 'mint-plate-editor__tab--active': plate.id === activePlateId }]\"\n @click=\"emit('select-plate', plate.id)\"\n >\n <span\n class=\"mint-plate-editor__tab-slot\"\n :style=\"{ backgroundColor: slotColorForPlate(plate.id, index) }\"\n />\n <span class=\"mint-plate-editor__tab-name\">{{ plate.name }}</span>\n <span\n v-if=\"plateWellCounts[plate.id] > 0\"\n class=\"mint-plate-editor__tab-count\"\n >\n {{ plateWellCounts[plate.id] }}\n </span>\n <button\n v-if=\"props.plates.length > 1\"\n type=\"button\"\n class=\"mint-plate-editor__tab-remove\"\n :aria-label=\"`Remove ${plate.name}`\"\n @click.stop=\"emit('remove-plate', plate.id)\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </button>\n\n <button\n v-if=\"canAddPlate\"\n type=\"button\"\n class=\"mint-plate-editor__add-plate\"\n aria-label=\"Add plate\"\n @click=\"emit('add-plate')\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M5 12h14\" /><path d=\"M12 5v14\" />\n </svg>\n <span>Add</span>\n </button>\n </div>\n\n <div class=\"mint-plate-editor__spacer\" />\n\n <div class=\"mint-plate-editor__actions\">\n <button\n type=\"button\"\n :disabled=\"!canUndo\"\n class=\"mint-plate-editor__action-btn\"\n title=\"Undo (Ctrl+Z)\"\n @click=\"emit('undo')\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 14 4 9l5-5\" /><path d=\"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11\" />\n </svg>\n </button>\n\n <button\n type=\"button\"\n :disabled=\"!canRedo\"\n class=\"mint-plate-editor__action-btn\"\n title=\"Redo (Ctrl+Shift+Z)\"\n @click=\"emit('redo')\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m15 14 5-5-5-5\" /><path d=\"M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13\" />\n </svg>\n </button>\n\n <div class=\"mint-plate-editor__divider\" />\n\n <button\n type=\"button\"\n class=\"mint-plate-editor__action-btn\"\n title=\"Import\"\n @click=\"emit('import')\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 3v12\" /><path d=\"m17 8-5-5-5 5\" /><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\n </svg>\n </button>\n\n <button\n type=\"button\"\n class=\"mint-plate-editor__action-btn\"\n title=\"Export JSON\"\n @click=\"emit('export-json')\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 15V3\" /><path d=\"m7 10 5 5 5-5\" /><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\n </svg>\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Toolbar for PlateMapEditor plate tabs and import/export history actions. */\nimport type { PlateMap } from '../../types'\n\ninterface Props {\n plates: PlateMap[]\n activePlateId?: string\n plateWellCounts: Record<string, number>\n canAddPlate: boolean\n canUndo: boolean\n canRedo: boolean\n slotColorForPlate: (plateId: string, plateIndex: number) => string\n}\n\nconst props = defineProps<Props>()\n\nconst emit = defineEmits<{\n 'select-plate': [plateId: string]\n 'remove-plate': [plateId: string]\n 'add-plate': []\n undo: []\n redo: []\n import: []\n 'export-json': []\n}>()\n</script>\n\n<template>\n <div class=\"mint-plate-editor__toolbar\">\n <div class=\"mint-plate-editor__tabs\">\n <button\n v-for=\"(plate, index) in plates\"\n :key=\"plate.id\"\n type=\"button\"\n :class=\"['mint-plate-editor__tab', { 'mint-plate-editor__tab--active': plate.id === activePlateId }]\"\n @click=\"emit('select-plate', plate.id)\"\n >\n <span\n class=\"mint-plate-editor__tab-slot\"\n :style=\"{ backgroundColor: slotColorForPlate(plate.id, index) }\"\n />\n <span class=\"mint-plate-editor__tab-name\">{{ plate.name }}</span>\n <span\n v-if=\"plateWellCounts[plate.id] > 0\"\n class=\"mint-plate-editor__tab-count\"\n >\n {{ plateWellCounts[plate.id] }}\n </span>\n <button\n v-if=\"props.plates.length > 1\"\n type=\"button\"\n class=\"mint-plate-editor__tab-remove\"\n :aria-label=\"`Remove ${plate.name}`\"\n @click.stop=\"emit('remove-plate', plate.id)\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </button>\n\n <button\n v-if=\"canAddPlate\"\n type=\"button\"\n class=\"mint-plate-editor__add-plate\"\n aria-label=\"Add plate\"\n @click=\"emit('add-plate')\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M5 12h14\" /><path d=\"M12 5v14\" />\n </svg>\n <span>Add</span>\n </button>\n </div>\n\n <div class=\"mint-plate-editor__spacer\" />\n\n <div class=\"mint-plate-editor__actions\">\n <button\n type=\"button\"\n :disabled=\"!canUndo\"\n class=\"mint-plate-editor__action-btn\"\n title=\"Undo (Ctrl+Z)\"\n @click=\"emit('undo')\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 14 4 9l5-5\" /><path d=\"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11\" />\n </svg>\n </button>\n\n <button\n type=\"button\"\n :disabled=\"!canRedo\"\n class=\"mint-plate-editor__action-btn\"\n title=\"Redo (Ctrl+Shift+Z)\"\n @click=\"emit('redo')\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m15 14 5-5-5-5\" /><path d=\"M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13\" />\n </svg>\n </button>\n\n <div class=\"mint-plate-editor__divider\" />\n\n <button\n type=\"button\"\n class=\"mint-plate-editor__action-btn\"\n title=\"Import\"\n @click=\"emit('import')\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 3v12\" /><path d=\"m17 8-5-5-5 5\" /><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\n </svg>\n </button>\n\n <button\n type=\"button\"\n class=\"mint-plate-editor__action-btn\"\n title=\"Export JSON\"\n @click=\"emit('export-json')\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 15V3\" /><path d=\"m7 10 5 5 5-5\" /><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\n </svg>\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Full plate-map design environment for 96/384-well plates with sample assignment, slot-color coding, undo/redo, and CSV/JSON import-export. */\nimport { ref, computed, watch } from 'vue'\nimport type { PlateMapEditorState, WellPlateFormat, SampleType, Well, PlateMap } from '../types'\nimport { useWellPlateEditor } from '../composables/useWellPlateEditor'\nimport { useEventListener } from '../composables/useEventListener'\nimport WellPlate from './WellPlate.vue'\nimport SampleLegend from './SampleLegend.vue'\nimport PlateMapEditorToolbarInternal from './internal/PlateMapEditorToolbarInternal.vue'\n\n// Slot colors matching MSExpDesigner\ntype SlotPosition = 'R' | 'G' | 'B' | 'Y'\nconst SLOT_COLORS: Record<SlotPosition, string> = {\n R: '#ef4444', // red\n G: '#22c55e', // green\n B: '#3b82f6', // blue\n Y: '#eab308', // yellow\n}\nconst SLOT_ORDER: SlotPosition[] = ['R', 'G', 'B', 'Y']\n\ninterface Props {\n modelValue?: PlateMapEditorState\n format?: WellPlateFormat\n maxPlates?: number\n samples?: SampleType[]\n showToolbar?: boolean\n showSidebar?: boolean\n allowAddPlates?: boolean\n allowAddSamples?: boolean\n size?: 'sm' | 'md' | 'lg' | 'xl' | 'fill'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: undefined,\n format: 96,\n maxPlates: 10,\n samples: () => [],\n showToolbar: true,\n showSidebar: true,\n allowAddPlates: true,\n allowAddSamples: true,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [state: PlateMapEditorState]\n 'plate-add': [plate: { id: string; name: string }]\n 'plate-remove': [plateId: string]\n 'sample-assign': [wellIds: string[], sampleId: string | undefined]\n 'wells-clear': [wellIds: string[]]\n 'undo': []\n 'redo': []\n 'export': [data: string, format: 'json' | 'csv']\n 'import': [success: boolean]\n}>()\n\nconst editor = useWellPlateEditor(props.modelValue, {\n defaultFormat: props.format,\n})\n\nconst newSampleName = ref('')\nconst showImportModal = ref(false)\nconst importText = ref('')\nconst importFormat = ref<'json' | 'csv'>('json')\n\n// Track slot assignment for each plate\nconst plateSlots = ref<Map<string, SlotPosition>>(new Map())\n\n// Assign slots to plates on creation\nfunction getPlateSlot(plateId: string, plateIndex: number): SlotPosition {\n if (!plateSlots.value.has(plateId)) {\n plateSlots.value.set(plateId, SLOT_ORDER[plateIndex % SLOT_ORDER.length])\n }\n return plateSlots.value.get(plateId)!\n}\n\nfunction getPlateSlotColor(plateId: string, plateIndex: number): string {\n return SLOT_COLORS[getPlateSlot(plateId, plateIndex)]\n}\n\nconst sampleColors = computed(() => {\n const colors: Record<string, string> = {}\n for (const sample of editor.samples.value) {\n if (sample.color) {\n colors[sample.id] = sample.color\n }\n }\n return colors\n})\n\nconst wellsData = computed(() => {\n const plate = editor.activePlate.value\n if (!plate) return {}\n\n const wells: Record<string, Partial<Well>> = {}\n for (const [wellId, well] of Object.entries(plate.wells)) {\n wells[wellId] = {\n state: well.state,\n sampleType: well.sampleType,\n value: well.value,\n }\n }\n return wells\n})\n\nfunction countPlateAssignedWells(plate: PlateMap): number {\n return Object.values(plate.wells).filter(w => w.sampleType).length\n}\n\nconst plateWellCounts = computed<Record<string, number>>(() => {\n const counts: Record<string, number> = {}\n for (const plate of editor.plates.value) {\n counts[plate.id] = countPlateAssignedWells(plate)\n }\n return counts\n})\n\nwatch(\n () => editor.state.value,\n (newState) => emit('update:modelValue', { ...newState }),\n { deep: true }\n)\n\nwatch(\n () => props.modelValue,\n (newValue) => {\n if (newValue) editor.loadState(newValue)\n }\n)\n\nfunction handleSelectionChange(wellIds: string[]) {\n editor.setSelectedWells(wellIds)\n}\n\nfunction handleSampleClick(sample: SampleType) {\n const newSampleId = editor.activeSampleId.value === sample.id ? undefined : sample.id\n editor.setActiveSample(newSampleId)\n}\n\nfunction handleAssignSample() {\n const wells = editor.selectedWells.value\n if (wells.length === 0) return\n\n editor.assignSample(wells, editor.activeSampleId.value)\n emit('sample-assign', wells, editor.activeSampleId.value)\n}\n\nfunction handleClearWells() {\n const wells = editor.selectedWells.value\n if (wells.length === 0) return\n\n editor.clearWells(wells)\n emit('wells-clear', wells)\n}\n\nfunction handleAddSample() {\n if (!newSampleName.value.trim()) return\n editor.addSample(newSampleName.value.trim())\n newSampleName.value = ''\n}\n\nfunction handleRemoveSample(sampleId: string) {\n editor.removeSample(sampleId)\n}\n\nfunction handleAddPlate() {\n if (editor.plates.value.length >= props.maxPlates) return\n const plate = editor.addPlate()\n emit('plate-add', { id: plate.id, name: plate.name })\n}\n\nfunction handleRemovePlate(plateId: string) {\n editor.removePlate(plateId)\n plateSlots.value.delete(plateId)\n emit('plate-remove', plateId)\n}\n\nfunction handleUndo() {\n editor.undo()\n emit('undo')\n}\n\nfunction handleRedo() {\n editor.redo()\n emit('redo')\n}\n\nfunction handleExport(format: 'json' | 'csv') {\n const data = editor.exportData(format)\n emit('export', data, format)\n\n const blob = new Blob([data], { type: format === 'json' ? 'application/json' : 'text/csv' })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = `plate-map.${format}`\n a.click()\n URL.revokeObjectURL(url)\n}\n\nfunction handleImport() {\n const success = editor.importData(importText.value, importFormat.value)\n emit('import', success)\n if (success) {\n showImportModal.value = false\n importText.value = ''\n }\n}\n\nfunction handleKeyDown(event: KeyboardEvent) {\n const isUndo = (event.metaKey || event.ctrlKey) && event.key === 'z'\n if (isUndo) {\n event.preventDefault()\n event.shiftKey ? handleRedo() : handleUndo()\n return\n }\n\n const isDelete = event.key === 'Delete' || event.key === 'Backspace'\n if (isDelete && editor.selectedWells.value.length > 0) {\n event.preventDefault()\n handleClearWells()\n return\n }\n\n const num = parseInt(event.key)\n const isValidSampleKey = num >= 1 && num <= 9 && editor.samples.value.length >= num\n if (isValidSampleKey) {\n editor.setActiveSample(editor.samples.value[num - 1].id)\n if (editor.selectedWells.value.length > 0) {\n handleAssignSample()\n }\n }\n}\n\nuseEventListener(() => document, 'keydown', handleKeyDown)\n</script>\n\n<template>\n <div :class=\"['mint-plate-editor', { 'mint-plate-editor--with-sidebar': showSidebar }]\">\n <!-- Main plate area -->\n <div class=\"mint-plate-editor__main\">\n <!-- Toolbar -->\n <PlateMapEditorToolbarInternal\n v-if=\"showToolbar\"\n :plates=\"editor.plates.value\"\n :active-plate-id=\"editor.activePlate.value?.id\"\n :plate-well-counts=\"plateWellCounts\"\n :can-add-plate=\"allowAddPlates && editor.plates.value.length < maxPlates\"\n :can-undo=\"editor.canUndo.value\"\n :can-redo=\"editor.canRedo.value\"\n :slot-color-for-plate=\"getPlateSlotColor\"\n @select-plate=\"editor.setActivePlate\"\n @remove-plate=\"handleRemovePlate\"\n @add-plate=\"handleAddPlate\"\n @undo=\"handleUndo\"\n @redo=\"handleRedo\"\n @import=\"showImportModal = true\"\n @export-json=\"handleExport('json')\"\n />\n\n <!-- Well plate -->\n <WellPlate\n v-if=\"editor.activePlate.value\"\n :model-value=\"editor.selectedWells.value\"\n :format=\"editor.activePlate.value.format\"\n :wells=\"wellsData\"\n :sample-colors=\"sampleColors\"\n :size=\"size\"\n selection-mode=\"rectangle\"\n show-sample-type-indicator\n @update:model-value=\"handleSelectionChange\"\n />\n\n <!-- Selection info bar -->\n <div\n v-if=\"editor.selectedWells.value.length > 0\"\n class=\"mint-plate-editor__selection-bar\"\n >\n <span class=\"mint-plate-editor__selection-count\">\n <strong>{{ editor.selectedWells.value.length }}</strong> wells selected\n </span>\n <div class=\"mint-plate-editor__spacer\" />\n <button\n v-if=\"editor.activeSampleId.value\"\n type=\"button\"\n class=\"mint-plate-editor__assign-btn\"\n @click=\"handleAssignSample\"\n >\n Assign {{ editor.samples.value.find(s => s.id === editor.activeSampleId.value)?.name }}\n </button>\n <button\n type=\"button\"\n class=\"mint-plate-editor__clear-btn\"\n @click=\"handleClearWells\"\n >\n Clear\n </button>\n </div>\n\n <!-- Legend -->\n <div class=\"mint-plate-editor__legend\">\n <div class=\"mint-plate-editor__legend-items\">\n <div class=\"mint-plate-editor__legend-item\">\n <div class=\"mint-plate-editor__legend-swatch\" style=\"background-color: rgba(16, 185, 129, 0.15); border: 1px solid rgba(16, 185, 129, 0.4)\" />\n <span class=\"mint-plate-editor__legend-label\">Sample</span>\n </div>\n <div class=\"mint-plate-editor__legend-item\">\n <div class=\"mint-plate-editor__legend-swatch\" style=\"background-color: rgba(59, 130, 246, 0.15); border: 1px solid rgba(59, 130, 246, 0.4)\" />\n <span class=\"mint-plate-editor__legend-label\">Control</span>\n </div>\n <div class=\"mint-plate-editor__legend-item\">\n <div class=\"mint-plate-editor__legend-swatch\" style=\"background-color: rgba(249, 115, 22, 0.15); border: 1px solid rgba(249, 115, 22, 0.4)\" />\n <span class=\"mint-plate-editor__legend-label\">Blank</span>\n </div>\n <div class=\"mint-plate-editor__legend-item\">\n <div class=\"mint-plate-editor__legend-swatch\" style=\"background-color: rgba(139, 92, 246, 0.15); border: 1px solid rgba(139, 92, 246, 0.4)\" />\n <span class=\"mint-plate-editor__legend-label\">QC</span>\n </div>\n </div>\n <span class=\"mint-plate-editor__legend-hint\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72\" /><path d=\"m14 7 3 3\" /><path d=\"M5 6v4\" /><path d=\"M19 14v4\" /><path d=\"M10 2v2\" /><path d=\"M7 8H3\" /><path d=\"M21 16h-4\" /><path d=\"M11 3H9\" />\n </svg>\n Drag to select\n </span>\n </div>\n </div>\n\n <!-- Sidebar -->\n <div v-if=\"showSidebar\" class=\"mint-plate-editor__sidebar\">\n <div class=\"mint-plate-editor__sidebar-panel\">\n <h3 class=\"mint-plate-editor__sidebar-title\">Sample Types</h3>\n\n <SampleLegend\n :model-value=\"editor.activeSampleId.value\"\n :samples=\"editor.samples.value\"\n :editable=\"allowAddSamples\"\n :size=\"size === 'lg' ? 'md' : 'sm'\"\n @update:model-value=\"editor.setActiveSample($event)\"\n @sample-click=\"handleSampleClick\"\n @sample-remove=\"handleRemoveSample\"\n />\n\n <!-- Add sample -->\n <div v-if=\"allowAddSamples\" class=\"mint-plate-editor__add-sample\">\n <div class=\"mint-plate-editor__add-sample-form\">\n <input\n v-model=\"newSampleName\"\n type=\"text\"\n placeholder=\"New sample...\"\n class=\"mint-plate-editor__add-sample-input\"\n @keyup.enter=\"handleAddSample\"\n />\n <button\n type=\"button\"\n :disabled=\"!newSampleName.trim()\"\n class=\"mint-plate-editor__add-sample-btn\"\n @click=\"handleAddSample\"\n >\n Add\n </button>\n </div>\n </div>\n\n <!-- Keyboard shortcuts -->\n <div class=\"mint-plate-editor__shortcuts\">\n <h4 class=\"mint-plate-editor__shortcuts-title\">Shortcuts</h4>\n <div class=\"mint-plate-editor__shortcuts-list\">\n <div><kbd class=\"mint-plate-editor__shortcut-key\">1-9</kbd> Quick assign</div>\n <div><kbd class=\"mint-plate-editor__shortcut-key\">Del</kbd> Clear wells</div>\n <div><kbd class=\"mint-plate-editor__shortcut-key\">Ctrl+Z</kbd> Undo</div>\n <div><kbd class=\"mint-plate-editor__shortcut-key\">Ctrl+A</kbd> Select all</div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Import modal -->\n <Teleport to=\"body\">\n <div\n v-if=\"showImportModal\"\n class=\"mint-plate-editor__modal-overlay\"\n @click.self=\"showImportModal = false\"\n >\n <div class=\"mint-plate-editor__modal\">\n <h3 class=\"mint-plate-editor__modal-title\">Import Plate Map</h3>\n\n <div class=\"mint-plate-editor__modal-field\">\n <label class=\"mint-plate-editor__modal-label\">Format</label>\n <select\n v-model=\"importFormat\"\n class=\"mint-plate-editor__modal-select\"\n >\n <option value=\"json\">JSON</option>\n <option value=\"csv\">CSV</option>\n </select>\n </div>\n\n <div class=\"mint-plate-editor__modal-field\">\n <label class=\"mint-plate-editor__modal-label\">Data</label>\n <textarea\n v-model=\"importText\"\n rows=\"8\"\n class=\"mint-plate-editor__modal-textarea\"\n placeholder=\"Paste your data here...\"\n />\n </div>\n\n <div class=\"mint-plate-editor__modal-actions\">\n <button\n type=\"button\"\n class=\"mint-plate-editor__modal-cancel\"\n @click=\"showImportModal = false\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n :disabled=\"!importText.trim()\"\n class=\"mint-plate-editor__modal-submit\"\n @click=\"handleImport\"\n >\n Import\n </button>\n </div>\n </div>\n </div>\n </Teleport>\n </div>\n</template>\n\n<style>\n@import '../styles/components/plate-map-editor.css';\n</style>\n","<script setup lang=\"ts\">\n/** Full plate-map design environment for 96/384-well plates with sample assignment, slot-color coding, undo/redo, and CSV/JSON import-export. */\nimport { ref, computed, watch } from 'vue'\nimport type { PlateMapEditorState, WellPlateFormat, SampleType, Well, PlateMap } from '../types'\nimport { useWellPlateEditor } from '../composables/useWellPlateEditor'\nimport { useEventListener } from '../composables/useEventListener'\nimport WellPlate from './WellPlate.vue'\nimport SampleLegend from './SampleLegend.vue'\nimport PlateMapEditorToolbarInternal from './internal/PlateMapEditorToolbarInternal.vue'\n\n// Slot colors matching MSExpDesigner\ntype SlotPosition = 'R' | 'G' | 'B' | 'Y'\nconst SLOT_COLORS: Record<SlotPosition, string> = {\n R: '#ef4444', // red\n G: '#22c55e', // green\n B: '#3b82f6', // blue\n Y: '#eab308', // yellow\n}\nconst SLOT_ORDER: SlotPosition[] = ['R', 'G', 'B', 'Y']\n\ninterface Props {\n modelValue?: PlateMapEditorState\n format?: WellPlateFormat\n maxPlates?: number\n samples?: SampleType[]\n showToolbar?: boolean\n showSidebar?: boolean\n allowAddPlates?: boolean\n allowAddSamples?: boolean\n size?: 'sm' | 'md' | 'lg' | 'xl' | 'fill'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: undefined,\n format: 96,\n maxPlates: 10,\n samples: () => [],\n showToolbar: true,\n showSidebar: true,\n allowAddPlates: true,\n allowAddSamples: true,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [state: PlateMapEditorState]\n 'plate-add': [plate: { id: string; name: string }]\n 'plate-remove': [plateId: string]\n 'sample-assign': [wellIds: string[], sampleId: string | undefined]\n 'wells-clear': [wellIds: string[]]\n 'undo': []\n 'redo': []\n 'export': [data: string, format: 'json' | 'csv']\n 'import': [success: boolean]\n}>()\n\nconst editor = useWellPlateEditor(props.modelValue, {\n defaultFormat: props.format,\n})\n\nconst newSampleName = ref('')\nconst showImportModal = ref(false)\nconst importText = ref('')\nconst importFormat = ref<'json' | 'csv'>('json')\n\n// Track slot assignment for each plate\nconst plateSlots = ref<Map<string, SlotPosition>>(new Map())\n\n// Assign slots to plates on creation\nfunction getPlateSlot(plateId: string, plateIndex: number): SlotPosition {\n if (!plateSlots.value.has(plateId)) {\n plateSlots.value.set(plateId, SLOT_ORDER[plateIndex % SLOT_ORDER.length])\n }\n return plateSlots.value.get(plateId)!\n}\n\nfunction getPlateSlotColor(plateId: string, plateIndex: number): string {\n return SLOT_COLORS[getPlateSlot(plateId, plateIndex)]\n}\n\nconst sampleColors = computed(() => {\n const colors: Record<string, string> = {}\n for (const sample of editor.samples.value) {\n if (sample.color) {\n colors[sample.id] = sample.color\n }\n }\n return colors\n})\n\nconst wellsData = computed(() => {\n const plate = editor.activePlate.value\n if (!plate) return {}\n\n const wells: Record<string, Partial<Well>> = {}\n for (const [wellId, well] of Object.entries(plate.wells)) {\n wells[wellId] = {\n state: well.state,\n sampleType: well.sampleType,\n value: well.value,\n }\n }\n return wells\n})\n\nfunction countPlateAssignedWells(plate: PlateMap): number {\n return Object.values(plate.wells).filter(w => w.sampleType).length\n}\n\nconst plateWellCounts = computed<Record<string, number>>(() => {\n const counts: Record<string, number> = {}\n for (const plate of editor.plates.value) {\n counts[plate.id] = countPlateAssignedWells(plate)\n }\n return counts\n})\n\nwatch(\n () => editor.state.value,\n (newState) => emit('update:modelValue', { ...newState }),\n { deep: true }\n)\n\nwatch(\n () => props.modelValue,\n (newValue) => {\n if (newValue) editor.loadState(newValue)\n }\n)\n\nfunction handleSelectionChange(wellIds: string[]) {\n editor.setSelectedWells(wellIds)\n}\n\nfunction handleSampleClick(sample: SampleType) {\n const newSampleId = editor.activeSampleId.value === sample.id ? undefined : sample.id\n editor.setActiveSample(newSampleId)\n}\n\nfunction handleAssignSample() {\n const wells = editor.selectedWells.value\n if (wells.length === 0) return\n\n editor.assignSample(wells, editor.activeSampleId.value)\n emit('sample-assign', wells, editor.activeSampleId.value)\n}\n\nfunction handleClearWells() {\n const wells = editor.selectedWells.value\n if (wells.length === 0) return\n\n editor.clearWells(wells)\n emit('wells-clear', wells)\n}\n\nfunction handleAddSample() {\n if (!newSampleName.value.trim()) return\n editor.addSample(newSampleName.value.trim())\n newSampleName.value = ''\n}\n\nfunction handleRemoveSample(sampleId: string) {\n editor.removeSample(sampleId)\n}\n\nfunction handleAddPlate() {\n if (editor.plates.value.length >= props.maxPlates) return\n const plate = editor.addPlate()\n emit('plate-add', { id: plate.id, name: plate.name })\n}\n\nfunction handleRemovePlate(plateId: string) {\n editor.removePlate(plateId)\n plateSlots.value.delete(plateId)\n emit('plate-remove', plateId)\n}\n\nfunction handleUndo() {\n editor.undo()\n emit('undo')\n}\n\nfunction handleRedo() {\n editor.redo()\n emit('redo')\n}\n\nfunction handleExport(format: 'json' | 'csv') {\n const data = editor.exportData(format)\n emit('export', data, format)\n\n const blob = new Blob([data], { type: format === 'json' ? 'application/json' : 'text/csv' })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = `plate-map.${format}`\n a.click()\n URL.revokeObjectURL(url)\n}\n\nfunction handleImport() {\n const success = editor.importData(importText.value, importFormat.value)\n emit('import', success)\n if (success) {\n showImportModal.value = false\n importText.value = ''\n }\n}\n\nfunction handleKeyDown(event: KeyboardEvent) {\n const isUndo = (event.metaKey || event.ctrlKey) && event.key === 'z'\n if (isUndo) {\n event.preventDefault()\n event.shiftKey ? handleRedo() : handleUndo()\n return\n }\n\n const isDelete = event.key === 'Delete' || event.key === 'Backspace'\n if (isDelete && editor.selectedWells.value.length > 0) {\n event.preventDefault()\n handleClearWells()\n return\n }\n\n const num = parseInt(event.key)\n const isValidSampleKey = num >= 1 && num <= 9 && editor.samples.value.length >= num\n if (isValidSampleKey) {\n editor.setActiveSample(editor.samples.value[num - 1].id)\n if (editor.selectedWells.value.length > 0) {\n handleAssignSample()\n }\n }\n}\n\nuseEventListener(() => document, 'keydown', handleKeyDown)\n</script>\n\n<template>\n <div :class=\"['mint-plate-editor', { 'mint-plate-editor--with-sidebar': showSidebar }]\">\n <!-- Main plate area -->\n <div class=\"mint-plate-editor__main\">\n <!-- Toolbar -->\n <PlateMapEditorToolbarInternal\n v-if=\"showToolbar\"\n :plates=\"editor.plates.value\"\n :active-plate-id=\"editor.activePlate.value?.id\"\n :plate-well-counts=\"plateWellCounts\"\n :can-add-plate=\"allowAddPlates && editor.plates.value.length < maxPlates\"\n :can-undo=\"editor.canUndo.value\"\n :can-redo=\"editor.canRedo.value\"\n :slot-color-for-plate=\"getPlateSlotColor\"\n @select-plate=\"editor.setActivePlate\"\n @remove-plate=\"handleRemovePlate\"\n @add-plate=\"handleAddPlate\"\n @undo=\"handleUndo\"\n @redo=\"handleRedo\"\n @import=\"showImportModal = true\"\n @export-json=\"handleExport('json')\"\n />\n\n <!-- Well plate -->\n <WellPlate\n v-if=\"editor.activePlate.value\"\n :model-value=\"editor.selectedWells.value\"\n :format=\"editor.activePlate.value.format\"\n :wells=\"wellsData\"\n :sample-colors=\"sampleColors\"\n :size=\"size\"\n selection-mode=\"rectangle\"\n show-sample-type-indicator\n @update:model-value=\"handleSelectionChange\"\n />\n\n <!-- Selection info bar -->\n <div\n v-if=\"editor.selectedWells.value.length > 0\"\n class=\"mint-plate-editor__selection-bar\"\n >\n <span class=\"mint-plate-editor__selection-count\">\n <strong>{{ editor.selectedWells.value.length }}</strong> wells selected\n </span>\n <div class=\"mint-plate-editor__spacer\" />\n <button\n v-if=\"editor.activeSampleId.value\"\n type=\"button\"\n class=\"mint-plate-editor__assign-btn\"\n @click=\"handleAssignSample\"\n >\n Assign {{ editor.samples.value.find(s => s.id === editor.activeSampleId.value)?.name }}\n </button>\n <button\n type=\"button\"\n class=\"mint-plate-editor__clear-btn\"\n @click=\"handleClearWells\"\n >\n Clear\n </button>\n </div>\n\n <!-- Legend -->\n <div class=\"mint-plate-editor__legend\">\n <div class=\"mint-plate-editor__legend-items\">\n <div class=\"mint-plate-editor__legend-item\">\n <div class=\"mint-plate-editor__legend-swatch\" style=\"background-color: rgba(16, 185, 129, 0.15); border: 1px solid rgba(16, 185, 129, 0.4)\" />\n <span class=\"mint-plate-editor__legend-label\">Sample</span>\n </div>\n <div class=\"mint-plate-editor__legend-item\">\n <div class=\"mint-plate-editor__legend-swatch\" style=\"background-color: rgba(59, 130, 246, 0.15); border: 1px solid rgba(59, 130, 246, 0.4)\" />\n <span class=\"mint-plate-editor__legend-label\">Control</span>\n </div>\n <div class=\"mint-plate-editor__legend-item\">\n <div class=\"mint-plate-editor__legend-swatch\" style=\"background-color: rgba(249, 115, 22, 0.15); border: 1px solid rgba(249, 115, 22, 0.4)\" />\n <span class=\"mint-plate-editor__legend-label\">Blank</span>\n </div>\n <div class=\"mint-plate-editor__legend-item\">\n <div class=\"mint-plate-editor__legend-swatch\" style=\"background-color: rgba(139, 92, 246, 0.15); border: 1px solid rgba(139, 92, 246, 0.4)\" />\n <span class=\"mint-plate-editor__legend-label\">QC</span>\n </div>\n </div>\n <span class=\"mint-plate-editor__legend-hint\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72\" /><path d=\"m14 7 3 3\" /><path d=\"M5 6v4\" /><path d=\"M19 14v4\" /><path d=\"M10 2v2\" /><path d=\"M7 8H3\" /><path d=\"M21 16h-4\" /><path d=\"M11 3H9\" />\n </svg>\n Drag to select\n </span>\n </div>\n </div>\n\n <!-- Sidebar -->\n <div v-if=\"showSidebar\" class=\"mint-plate-editor__sidebar\">\n <div class=\"mint-plate-editor__sidebar-panel\">\n <h3 class=\"mint-plate-editor__sidebar-title\">Sample Types</h3>\n\n <SampleLegend\n :model-value=\"editor.activeSampleId.value\"\n :samples=\"editor.samples.value\"\n :editable=\"allowAddSamples\"\n :size=\"size === 'lg' ? 'md' : 'sm'\"\n @update:model-value=\"editor.setActiveSample($event)\"\n @sample-click=\"handleSampleClick\"\n @sample-remove=\"handleRemoveSample\"\n />\n\n <!-- Add sample -->\n <div v-if=\"allowAddSamples\" class=\"mint-plate-editor__add-sample\">\n <div class=\"mint-plate-editor__add-sample-form\">\n <input\n v-model=\"newSampleName\"\n type=\"text\"\n placeholder=\"New sample...\"\n class=\"mint-plate-editor__add-sample-input\"\n @keyup.enter=\"handleAddSample\"\n />\n <button\n type=\"button\"\n :disabled=\"!newSampleName.trim()\"\n class=\"mint-plate-editor__add-sample-btn\"\n @click=\"handleAddSample\"\n >\n Add\n </button>\n </div>\n </div>\n\n <!-- Keyboard shortcuts -->\n <div class=\"mint-plate-editor__shortcuts\">\n <h4 class=\"mint-plate-editor__shortcuts-title\">Shortcuts</h4>\n <div class=\"mint-plate-editor__shortcuts-list\">\n <div><kbd class=\"mint-plate-editor__shortcut-key\">1-9</kbd> Quick assign</div>\n <div><kbd class=\"mint-plate-editor__shortcut-key\">Del</kbd> Clear wells</div>\n <div><kbd class=\"mint-plate-editor__shortcut-key\">Ctrl+Z</kbd> Undo</div>\n <div><kbd class=\"mint-plate-editor__shortcut-key\">Ctrl+A</kbd> Select all</div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Import modal -->\n <Teleport to=\"body\">\n <div\n v-if=\"showImportModal\"\n class=\"mint-plate-editor__modal-overlay\"\n @click.self=\"showImportModal = false\"\n >\n <div class=\"mint-plate-editor__modal\">\n <h3 class=\"mint-plate-editor__modal-title\">Import Plate Map</h3>\n\n <div class=\"mint-plate-editor__modal-field\">\n <label class=\"mint-plate-editor__modal-label\">Format</label>\n <select\n v-model=\"importFormat\"\n class=\"mint-plate-editor__modal-select\"\n >\n <option value=\"json\">JSON</option>\n <option value=\"csv\">CSV</option>\n </select>\n </div>\n\n <div class=\"mint-plate-editor__modal-field\">\n <label class=\"mint-plate-editor__modal-label\">Data</label>\n <textarea\n v-model=\"importText\"\n rows=\"8\"\n class=\"mint-plate-editor__modal-textarea\"\n placeholder=\"Paste your data here...\"\n />\n </div>\n\n <div class=\"mint-plate-editor__modal-actions\">\n <button\n type=\"button\"\n class=\"mint-plate-editor__modal-cancel\"\n @click=\"showImportModal = false\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n :disabled=\"!importText.trim()\"\n class=\"mint-plate-editor__modal-submit\"\n @click=\"handleImport\"\n >\n Import\n </button>\n </div>\n </div>\n </div>\n </Teleport>\n </div>\n</template>\n\n<style>\n@import '../styles/components/plate-map-editor.css';\n</style>\n","import type { Reagent, ReagentColumn } from '../types'\n\nexport const REAGENT_COLUMN_LABELS: Record<ReagentColumn, string> = {\n name: 'Name',\n catalog: 'Catalog #',\n lot: 'Lot #',\n expiry: 'Expiry',\n storage: 'Storage',\n location: 'Location',\n stock: 'Stock',\n supplier: 'Supplier',\n}\n\nexport interface ReagentRowClassOptions {\n lowStockThreshold: number\n draggedId?: string | null\n dragOverId?: string | null\n now?: Date\n}\n\nexport function getReagentColumnValue(reagent: Reagent, column: ReagentColumn): unknown {\n switch (column) {\n case 'name':\n return reagent.name\n case 'catalog':\n return reagent.catalogNumber\n case 'lot':\n return reagent.lotNumber\n case 'expiry':\n return reagent.expiryDate ? new Date(reagent.expiryDate).getTime() : null\n case 'storage':\n return reagent.storageCondition\n case 'location':\n return reagent.location\n case 'stock':\n return reagent.stockLevel\n case 'supplier':\n return reagent.supplier\n default:\n return null\n }\n}\n\nexport function isReagentExpired(reagent: Reagent, now = new Date()): boolean {\n if (!reagent.expiryDate) return false\n const expiry = new Date(reagent.expiryDate)\n return expiry < now\n}\n\nexport function isReagentExpiringSoon(\n reagent: Reagent,\n daysThreshold = 30,\n now = new Date()\n): boolean {\n if (!reagent.expiryDate) return false\n const expiry = new Date(reagent.expiryDate)\n const daysUntilExpiry = (expiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)\n return daysUntilExpiry > 0 && daysUntilExpiry <= daysThreshold\n}\n\nexport function isReagentLowStock(reagent: Reagent, lowStockThreshold: number): boolean {\n if (reagent.stockLevel === undefined) return false\n return reagent.stockLevel <= lowStockThreshold\n}\n\nexport function formatReagentExpiryDate(date: Date | string | undefined): string {\n if (!date) return '-'\n const parsed = new Date(date)\n return parsed.toLocaleDateString('en-US', { year: 'numeric', month: 'short' })\n}\n\nexport function getReagentStockLevel(reagent: Reagent): number {\n return reagent.stockLevel ?? 0\n}\n\nexport function getReagentStockFillClass(level: number): string {\n if (level >= 50) return 'mint-reagent-list__stock-fill--good'\n if (level >= 20) return 'mint-reagent-list__stock-fill--warning'\n return 'mint-reagent-list__stock-fill--low'\n}\n\nexport function getReagentRowClasses(\n reagent: Reagent,\n options: ReagentRowClassOptions\n): string[] {\n const classes: string[] = ['mint-reagent-list__row']\n\n if (isReagentExpired(reagent, options.now)) {\n classes.push('mint-reagent-list__row--expired')\n } else if (isReagentExpiringSoon(reagent, 30, options.now)) {\n classes.push('mint-reagent-list__row--expiring-soon')\n }\n\n if (isReagentLowStock(reagent, options.lowStockThreshold)) {\n classes.push('mint-reagent-list__row--low-stock')\n }\n if (options.draggedId === reagent.id) {\n classes.push('mint-reagent-list__row--dragging')\n }\n if (options.dragOverId === reagent.id) {\n classes.push('mint-reagent-list__row--drag-over')\n }\n\n return classes\n}\n","<script setup lang=\"ts\">\n/** Sortable, searchable table of experiment reagents showing lot number, expiry, storage conditions, and stock level with low-stock warnings. */\nimport { ref, computed } from 'vue'\nimport type { ReagentColumn, Reagent } from '../types'\nimport { useSortedItems } from '../composables/useSortedItems'\nimport { useTextSearch } from '../composables/useTextSearch'\nimport {\n REAGENT_COLUMN_LABELS as columnLabels,\n formatReagentExpiryDate as formatExpiryDate,\n getReagentColumnValue,\n getReagentRowClasses,\n getReagentStockFillClass as getStockFillClass,\n getReagentStockLevel as getStockLevel,\n isReagentExpired as isExpired,\n isReagentExpiringSoon as isExpiringSoon,\n} from './ReagentList.presentation'\n\ninterface Props {\n modelValue?: Reagent[]\n readonly?: boolean\n showStockLevel?: boolean\n lowStockThreshold?: number\n columns?: ReagentColumn[]\n sortable?: boolean\n searchable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: () => [],\n readonly: false,\n showStockLevel: true,\n lowStockThreshold: 10,\n columns: () => ['name', 'lot', 'expiry', 'storage', 'stock'],\n sortable: true,\n searchable: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [reagents: Reagent[]]\n 'add': []\n 'edit': [reagent: Reagent]\n 'remove': [reagentId: string]\n 'reorder': [reagentId: string, fromIndex: number, toIndex: number]\n}>()\n\n// State\nconst searchQuery = ref('')\nconst sortColumn = ref<ReagentColumn | null>(null)\nconst sortDirection = ref<'asc' | 'desc'>('asc')\nconst draggedId = ref<string | null>(null)\nconst dragOverId = ref<string | null>(null)\n\nconst reagentSearch = useTextSearch({\n items: () => props.modelValue || [],\n query: searchQuery,\n enabled: () => props.searchable,\n getText: (reagent) => [\n reagent.name,\n reagent.catalogNumber,\n reagent.lotNumber,\n reagent.supplier,\n ],\n})\n\nconst reagentSort = computed(() => {\n if (!sortColumn.value) return null\n return {\n key: sortColumn.value,\n direction: sortDirection.value,\n }\n})\n\nconst sortedReagents = useSortedItems<Reagent, ReagentColumn>({\n items: reagentSearch.filteredItems,\n sort: reagentSort,\n caseSensitive: false,\n getValue: (reagent, column) => getReagentColumnValue(reagent, column),\n})\nconst filteredReagents = sortedReagents.sortedItems\n\nfunction getRowClass(reagent: Reagent): string[] {\n return getReagentRowClasses(reagent, {\n lowStockThreshold: props.lowStockThreshold,\n draggedId: draggedId.value,\n dragOverId: dragOverId.value,\n })\n}\n\n// Actions\nfunction handleSort(column: ReagentColumn) {\n if (!props.sortable) return\n if (sortColumn.value === column) {\n sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc'\n } else {\n sortColumn.value = column\n sortDirection.value = 'asc'\n }\n}\n\nfunction handleEdit(reagent: Reagent) {\n emit('edit', reagent)\n}\n\nfunction handleRemove(reagentId: string) {\n emit('remove', reagentId)\n const updated = (props.modelValue || []).filter((r) => r.id !== reagentId)\n emit('update:modelValue', updated)\n}\n\nfunction handleAdd() {\n emit('add')\n}\n\n// Drag and drop\nfunction handleDragStart(event: DragEvent, reagent: Reagent) {\n if (props.readonly || !props.sortable) return\n draggedId.value = reagent.id\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', reagent.id)\n }\n}\n\nfunction handleDragOver(event: DragEvent, reagent: Reagent) {\n if (props.readonly || !props.sortable || !draggedId.value) return\n event.preventDefault()\n dragOverId.value = reagent.id\n}\n\nfunction handleDragLeave() {\n dragOverId.value = null\n}\n\nfunction handleDrop(event: DragEvent, targetReagent: Reagent) {\n if (props.readonly || !props.sortable || !draggedId.value) return\n event.preventDefault()\n\n const reagents = [...(props.modelValue || [])]\n const fromIndex = reagents.findIndex((r) => r.id === draggedId.value)\n const toIndex = reagents.findIndex((r) => r.id === targetReagent.id)\n\n if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) {\n draggedId.value = null\n dragOverId.value = null\n return\n }\n\n const [removed] = reagents.splice(fromIndex, 1)\n reagents.splice(toIndex, 0, removed)\n\n emit('reorder', draggedId.value, fromIndex, toIndex)\n emit('update:modelValue', reagents)\n\n draggedId.value = null\n dragOverId.value = null\n}\n\nfunction handleDragEnd() {\n draggedId.value = null\n dragOverId.value = null\n}\n</script>\n\n<template>\n <div class=\"mint-reagent-list\">\n <!-- Header -->\n <div class=\"mint-reagent-list__header\">\n <!-- Search -->\n <div v-if=\"searchable\" class=\"mint-reagent-list__search\">\n <svg class=\"mint-reagent-list__search-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\n </svg>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n class=\"mint-reagent-list__search-input\"\n placeholder=\"Search reagents...\"\n />\n </div>\n\n <!-- Add button -->\n <button\n v-if=\"!readonly\"\n type=\"button\"\n class=\"mint-reagent-list__add-btn\"\n @click=\"handleAdd\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n Add\n </button>\n </div>\n\n <!-- Table -->\n <table v-if=\"filteredReagents.length > 0\" class=\"mint-reagent-list__table\">\n <thead>\n <tr>\n <th v-if=\"sortable && !readonly\" style=\"width: 24px\" />\n <th\n v-for=\"col in columns\"\n :key=\"col\"\n :class=\"[sortable ? 'mint-reagent-list__col--sortable' : '']\"\n @click=\"handleSort(col)\"\n >\n {{ columnLabels[col] }}\n <svg\n v-if=\"sortable\"\n :class=\"[\n 'mint-reagent-list__sort-icon',\n sortColumn === col ? 'mint-reagent-list__sort-icon--active' : '',\n ]\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n v-if=\"sortColumn === col && sortDirection === 'desc'\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M19 9l-7 7-7-7\"\n />\n <path\n v-else\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M5 15l7-7 7 7\"\n />\n </svg>\n </th>\n <th v-if=\"!readonly\" style=\"width: 80px\" />\n </tr>\n </thead>\n <tbody>\n <tr\n v-for=\"reagent in filteredReagents\"\n :key=\"reagent.id\"\n :class=\"getRowClass(reagent)\"\n :draggable=\"sortable && !readonly\"\n @dragstart=\"handleDragStart($event, reagent)\"\n @dragover=\"handleDragOver($event, reagent)\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop($event, reagent)\"\n @dragend=\"handleDragEnd\"\n >\n <!-- Drag handle -->\n <td v-if=\"sortable && !readonly\">\n <svg class=\"mint-reagent-list__drag-handle\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8h16M4 16h16\" />\n </svg>\n </td>\n\n <!-- Name -->\n <td v-if=\"columns.includes('name')\" class=\"mint-reagent-list__cell mint-reagent-list__cell--name\">\n <a\n v-if=\"reagent.url\"\n :href=\"reagent.url\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"mint-reagent-list__link\"\n >\n {{ reagent.name }}\n </a>\n <span v-else>{{ reagent.name }}</span>\n </td>\n\n <!-- Catalog number -->\n <td v-if=\"columns.includes('catalog')\" class=\"mint-reagent-list__cell mint-reagent-list__cell--muted\">\n {{ reagent.catalogNumber || '-' }}\n </td>\n\n <!-- Lot number -->\n <td v-if=\"columns.includes('lot')\" class=\"mint-reagent-list__cell mint-reagent-list__cell--muted\">\n {{ reagent.lotNumber || '-' }}\n </td>\n\n <!-- Expiry -->\n <td v-if=\"columns.includes('expiry')\" class=\"mint-reagent-list__cell\">\n <span v-if=\"isExpired(reagent)\" class=\"mint-reagent-list__expired-text\">\n {{ formatExpiryDate(reagent.expiryDate) }}\n </span>\n <span v-else-if=\"isExpiringSoon(reagent)\" class=\"mint-reagent-list__expiring-text\">\n {{ formatExpiryDate(reagent.expiryDate) }}\n </span>\n <span v-else>\n {{ formatExpiryDate(reagent.expiryDate) }}\n </span>\n\n <!-- Expiry badge -->\n <span v-if=\"isExpired(reagent)\" class=\"mint-reagent-list__badge mint-reagent-list__badge--error\">\n <svg class=\"mint-reagent-list__badge-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n Expired\n </span>\n <span v-else-if=\"isExpiringSoon(reagent)\" class=\"mint-reagent-list__badge mint-reagent-list__badge--warning\">\n <svg class=\"mint-reagent-list__badge-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\" />\n </svg>\n Soon\n </span>\n </td>\n\n <!-- Storage -->\n <td v-if=\"columns.includes('storage')\" class=\"mint-reagent-list__cell\">\n <span\n v-if=\"reagent.storageCondition\"\n :class=\"['mint-reagent-list__storage', `mint-reagent-list__storage--${reagent.storageCondition}`]\"\n >\n {{ reagent.storageCondition }}\n </span>\n <span v-else class=\"mint-reagent-list__cell--muted\">-</span>\n </td>\n\n <!-- Location -->\n <td v-if=\"columns.includes('location')\" class=\"mint-reagent-list__cell mint-reagent-list__cell--muted\">\n {{ reagent.location || '-' }}\n </td>\n\n <!-- Stock -->\n <td v-if=\"columns.includes('stock') && showStockLevel\" class=\"mint-reagent-list__cell\">\n <div v-if=\"reagent.stockLevel !== undefined\" class=\"mint-reagent-list__stock\">\n <div class=\"mint-reagent-list__stock-bar\">\n <div\n :class=\"['mint-reagent-list__stock-fill', getStockFillClass(getStockLevel(reagent))]\"\n :style=\"{ width: `${Math.min(100, getStockLevel(reagent))}%` }\"\n />\n </div>\n <span class=\"mint-reagent-list__stock-text\">\n {{ getStockLevel(reagent) }}%\n </span>\n </div>\n <span v-else class=\"mint-reagent-list__cell--muted\">-</span>\n </td>\n\n <!-- Supplier -->\n <td v-if=\"columns.includes('supplier')\" class=\"mint-reagent-list__cell mint-reagent-list__cell--muted\">\n {{ reagent.supplier || '-' }}\n </td>\n\n <!-- Actions -->\n <td v-if=\"!readonly\" class=\"mint-reagent-list__cell mint-reagent-list__cell--actions\">\n <button\n type=\"button\"\n class=\"mint-reagent-list__action-btn\"\n aria-label=\"Edit\"\n @click=\"handleEdit(reagent)\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"mint-reagent-list__action-btn mint-reagent-list__action-btn--danger\"\n aria-label=\"Remove\"\n @click=\"handleRemove(reagent.id)\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!-- Empty state -->\n <div v-else class=\"mint-reagent-list__empty\">\n <svg class=\"mint-reagent-list__empty-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\" />\n </svg>\n <p class=\"mint-reagent-list__empty-text\">\n {{ searchQuery ? 'No reagents found' : 'No reagents added yet' }}\n </p>\n <button\n v-if=\"!readonly && !searchQuery\"\n type=\"button\"\n class=\"mint-reagent-list__empty-btn\"\n @click=\"handleAdd\"\n >\n Add Reagent\n </button>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/reagent-list.css';\n</style>\n","<script setup lang=\"ts\">\n/** Sortable, searchable table of experiment reagents showing lot number, expiry, storage conditions, and stock level with low-stock warnings. */\nimport { ref, computed } from 'vue'\nimport type { ReagentColumn, Reagent } from '../types'\nimport { useSortedItems } from '../composables/useSortedItems'\nimport { useTextSearch } from '../composables/useTextSearch'\nimport {\n REAGENT_COLUMN_LABELS as columnLabels,\n formatReagentExpiryDate as formatExpiryDate,\n getReagentColumnValue,\n getReagentRowClasses,\n getReagentStockFillClass as getStockFillClass,\n getReagentStockLevel as getStockLevel,\n isReagentExpired as isExpired,\n isReagentExpiringSoon as isExpiringSoon,\n} from './ReagentList.presentation'\n\ninterface Props {\n modelValue?: Reagent[]\n readonly?: boolean\n showStockLevel?: boolean\n lowStockThreshold?: number\n columns?: ReagentColumn[]\n sortable?: boolean\n searchable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: () => [],\n readonly: false,\n showStockLevel: true,\n lowStockThreshold: 10,\n columns: () => ['name', 'lot', 'expiry', 'storage', 'stock'],\n sortable: true,\n searchable: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [reagents: Reagent[]]\n 'add': []\n 'edit': [reagent: Reagent]\n 'remove': [reagentId: string]\n 'reorder': [reagentId: string, fromIndex: number, toIndex: number]\n}>()\n\n// State\nconst searchQuery = ref('')\nconst sortColumn = ref<ReagentColumn | null>(null)\nconst sortDirection = ref<'asc' | 'desc'>('asc')\nconst draggedId = ref<string | null>(null)\nconst dragOverId = ref<string | null>(null)\n\nconst reagentSearch = useTextSearch({\n items: () => props.modelValue || [],\n query: searchQuery,\n enabled: () => props.searchable,\n getText: (reagent) => [\n reagent.name,\n reagent.catalogNumber,\n reagent.lotNumber,\n reagent.supplier,\n ],\n})\n\nconst reagentSort = computed(() => {\n if (!sortColumn.value) return null\n return {\n key: sortColumn.value,\n direction: sortDirection.value,\n }\n})\n\nconst sortedReagents = useSortedItems<Reagent, ReagentColumn>({\n items: reagentSearch.filteredItems,\n sort: reagentSort,\n caseSensitive: false,\n getValue: (reagent, column) => getReagentColumnValue(reagent, column),\n})\nconst filteredReagents = sortedReagents.sortedItems\n\nfunction getRowClass(reagent: Reagent): string[] {\n return getReagentRowClasses(reagent, {\n lowStockThreshold: props.lowStockThreshold,\n draggedId: draggedId.value,\n dragOverId: dragOverId.value,\n })\n}\n\n// Actions\nfunction handleSort(column: ReagentColumn) {\n if (!props.sortable) return\n if (sortColumn.value === column) {\n sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc'\n } else {\n sortColumn.value = column\n sortDirection.value = 'asc'\n }\n}\n\nfunction handleEdit(reagent: Reagent) {\n emit('edit', reagent)\n}\n\nfunction handleRemove(reagentId: string) {\n emit('remove', reagentId)\n const updated = (props.modelValue || []).filter((r) => r.id !== reagentId)\n emit('update:modelValue', updated)\n}\n\nfunction handleAdd() {\n emit('add')\n}\n\n// Drag and drop\nfunction handleDragStart(event: DragEvent, reagent: Reagent) {\n if (props.readonly || !props.sortable) return\n draggedId.value = reagent.id\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', reagent.id)\n }\n}\n\nfunction handleDragOver(event: DragEvent, reagent: Reagent) {\n if (props.readonly || !props.sortable || !draggedId.value) return\n event.preventDefault()\n dragOverId.value = reagent.id\n}\n\nfunction handleDragLeave() {\n dragOverId.value = null\n}\n\nfunction handleDrop(event: DragEvent, targetReagent: Reagent) {\n if (props.readonly || !props.sortable || !draggedId.value) return\n event.preventDefault()\n\n const reagents = [...(props.modelValue || [])]\n const fromIndex = reagents.findIndex((r) => r.id === draggedId.value)\n const toIndex = reagents.findIndex((r) => r.id === targetReagent.id)\n\n if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) {\n draggedId.value = null\n dragOverId.value = null\n return\n }\n\n const [removed] = reagents.splice(fromIndex, 1)\n reagents.splice(toIndex, 0, removed)\n\n emit('reorder', draggedId.value, fromIndex, toIndex)\n emit('update:modelValue', reagents)\n\n draggedId.value = null\n dragOverId.value = null\n}\n\nfunction handleDragEnd() {\n draggedId.value = null\n dragOverId.value = null\n}\n</script>\n\n<template>\n <div class=\"mint-reagent-list\">\n <!-- Header -->\n <div class=\"mint-reagent-list__header\">\n <!-- Search -->\n <div v-if=\"searchable\" class=\"mint-reagent-list__search\">\n <svg class=\"mint-reagent-list__search-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\n </svg>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n class=\"mint-reagent-list__search-input\"\n placeholder=\"Search reagents...\"\n />\n </div>\n\n <!-- Add button -->\n <button\n v-if=\"!readonly\"\n type=\"button\"\n class=\"mint-reagent-list__add-btn\"\n @click=\"handleAdd\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n Add\n </button>\n </div>\n\n <!-- Table -->\n <table v-if=\"filteredReagents.length > 0\" class=\"mint-reagent-list__table\">\n <thead>\n <tr>\n <th v-if=\"sortable && !readonly\" style=\"width: 24px\" />\n <th\n v-for=\"col in columns\"\n :key=\"col\"\n :class=\"[sortable ? 'mint-reagent-list__col--sortable' : '']\"\n @click=\"handleSort(col)\"\n >\n {{ columnLabels[col] }}\n <svg\n v-if=\"sortable\"\n :class=\"[\n 'mint-reagent-list__sort-icon',\n sortColumn === col ? 'mint-reagent-list__sort-icon--active' : '',\n ]\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n v-if=\"sortColumn === col && sortDirection === 'desc'\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M19 9l-7 7-7-7\"\n />\n <path\n v-else\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M5 15l7-7 7 7\"\n />\n </svg>\n </th>\n <th v-if=\"!readonly\" style=\"width: 80px\" />\n </tr>\n </thead>\n <tbody>\n <tr\n v-for=\"reagent in filteredReagents\"\n :key=\"reagent.id\"\n :class=\"getRowClass(reagent)\"\n :draggable=\"sortable && !readonly\"\n @dragstart=\"handleDragStart($event, reagent)\"\n @dragover=\"handleDragOver($event, reagent)\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop($event, reagent)\"\n @dragend=\"handleDragEnd\"\n >\n <!-- Drag handle -->\n <td v-if=\"sortable && !readonly\">\n <svg class=\"mint-reagent-list__drag-handle\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8h16M4 16h16\" />\n </svg>\n </td>\n\n <!-- Name -->\n <td v-if=\"columns.includes('name')\" class=\"mint-reagent-list__cell mint-reagent-list__cell--name\">\n <a\n v-if=\"reagent.url\"\n :href=\"reagent.url\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"mint-reagent-list__link\"\n >\n {{ reagent.name }}\n </a>\n <span v-else>{{ reagent.name }}</span>\n </td>\n\n <!-- Catalog number -->\n <td v-if=\"columns.includes('catalog')\" class=\"mint-reagent-list__cell mint-reagent-list__cell--muted\">\n {{ reagent.catalogNumber || '-' }}\n </td>\n\n <!-- Lot number -->\n <td v-if=\"columns.includes('lot')\" class=\"mint-reagent-list__cell mint-reagent-list__cell--muted\">\n {{ reagent.lotNumber || '-' }}\n </td>\n\n <!-- Expiry -->\n <td v-if=\"columns.includes('expiry')\" class=\"mint-reagent-list__cell\">\n <span v-if=\"isExpired(reagent)\" class=\"mint-reagent-list__expired-text\">\n {{ formatExpiryDate(reagent.expiryDate) }}\n </span>\n <span v-else-if=\"isExpiringSoon(reagent)\" class=\"mint-reagent-list__expiring-text\">\n {{ formatExpiryDate(reagent.expiryDate) }}\n </span>\n <span v-else>\n {{ formatExpiryDate(reagent.expiryDate) }}\n </span>\n\n <!-- Expiry badge -->\n <span v-if=\"isExpired(reagent)\" class=\"mint-reagent-list__badge mint-reagent-list__badge--error\">\n <svg class=\"mint-reagent-list__badge-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n Expired\n </span>\n <span v-else-if=\"isExpiringSoon(reagent)\" class=\"mint-reagent-list__badge mint-reagent-list__badge--warning\">\n <svg class=\"mint-reagent-list__badge-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\" />\n </svg>\n Soon\n </span>\n </td>\n\n <!-- Storage -->\n <td v-if=\"columns.includes('storage')\" class=\"mint-reagent-list__cell\">\n <span\n v-if=\"reagent.storageCondition\"\n :class=\"['mint-reagent-list__storage', `mint-reagent-list__storage--${reagent.storageCondition}`]\"\n >\n {{ reagent.storageCondition }}\n </span>\n <span v-else class=\"mint-reagent-list__cell--muted\">-</span>\n </td>\n\n <!-- Location -->\n <td v-if=\"columns.includes('location')\" class=\"mint-reagent-list__cell mint-reagent-list__cell--muted\">\n {{ reagent.location || '-' }}\n </td>\n\n <!-- Stock -->\n <td v-if=\"columns.includes('stock') && showStockLevel\" class=\"mint-reagent-list__cell\">\n <div v-if=\"reagent.stockLevel !== undefined\" class=\"mint-reagent-list__stock\">\n <div class=\"mint-reagent-list__stock-bar\">\n <div\n :class=\"['mint-reagent-list__stock-fill', getStockFillClass(getStockLevel(reagent))]\"\n :style=\"{ width: `${Math.min(100, getStockLevel(reagent))}%` }\"\n />\n </div>\n <span class=\"mint-reagent-list__stock-text\">\n {{ getStockLevel(reagent) }}%\n </span>\n </div>\n <span v-else class=\"mint-reagent-list__cell--muted\">-</span>\n </td>\n\n <!-- Supplier -->\n <td v-if=\"columns.includes('supplier')\" class=\"mint-reagent-list__cell mint-reagent-list__cell--muted\">\n {{ reagent.supplier || '-' }}\n </td>\n\n <!-- Actions -->\n <td v-if=\"!readonly\" class=\"mint-reagent-list__cell mint-reagent-list__cell--actions\">\n <button\n type=\"button\"\n class=\"mint-reagent-list__action-btn\"\n aria-label=\"Edit\"\n @click=\"handleEdit(reagent)\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"mint-reagent-list__action-btn mint-reagent-list__action-btn--danger\"\n aria-label=\"Remove\"\n @click=\"handleRemove(reagent.id)\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!-- Empty state -->\n <div v-else class=\"mint-reagent-list__empty\">\n <svg class=\"mint-reagent-list__empty-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\" />\n </svg>\n <p class=\"mint-reagent-list__empty-text\">\n {{ searchQuery ? 'No reagents found' : 'No reagents added yet' }}\n </p>\n <button\n v-if=\"!readonly && !searchQuery\"\n type=\"button\"\n class=\"mint-reagent-list__empty-btn\"\n @click=\"handleAdd\"\n >\n Add Reagent\n </button>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/reagent-list.css';\n</style>\n","<script setup lang=\"ts\">\n/** Animated SVG spinner with size and color variants, and a visually hidden status label for screen readers. */\ninterface Props {\n size?: 'xs' | 'sm' | 'md' | 'lg'\n variant?: 'primary' | 'cta' | 'muted'\n label?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n size: 'md',\n variant: 'primary',\n label: 'Loading',\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-spinner',\n `mint-spinner--${size}`,\n `mint-spinner--${variant}`,\n ]\"\n role=\"status\"\n >\n <svg\n class=\"mint-spinner__svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n style=\"opacity: 0.25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"4\"\n />\n <path\n style=\"opacity: 0.75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n <span class=\"mint-spinner__label\">{{ label }}</span>\n </div>\n</template>\n\n<style>\n@import '../styles/components/loading-spinner.css';\n</style>\n","<script setup lang=\"ts\">\n/** Animated SVG spinner with size and color variants, and a visually hidden status label for screen readers. */\ninterface Props {\n size?: 'xs' | 'sm' | 'md' | 'lg'\n variant?: 'primary' | 'cta' | 'muted'\n label?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n size: 'md',\n variant: 'primary',\n label: 'Loading',\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-spinner',\n `mint-spinner--${size}`,\n `mint-spinner--${variant}`,\n ]\"\n role=\"status\"\n >\n <svg\n class=\"mint-spinner__svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n style=\"opacity: 0.25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"4\"\n />\n <path\n style=\"opacity: 0.75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n <span class=\"mint-spinner__label\">{{ label }}</span>\n </div>\n</template>\n\n<style>\n@import '../styles/components/loading-spinner.css';\n</style>\n","<script setup lang=\"ts\">\n/** Wizard modal that auto-groups samples by parsing naming patterns or an uploaded CSV file. */\nimport { ref, computed, watch, nextTick } from 'vue'\nimport BaseModal from './BaseModal.vue'\nimport BaseButton from './BaseButton.vue'\nimport BaseInput from './BaseInput.vue'\nimport StepWizard from './StepWizard.vue'\nimport LoadingSpinner from './LoadingSpinner.vue'\nimport AlertBox from './AlertBox.vue'\nimport { useAutoGroup, parseCSV } from '../composables/useAutoGroup'\nimport { classKey } from '../composables/autoGroup'\nimport { useApi } from '../composables/useApi'\nimport type { WizardStep } from '../types/components'\nimport type { AutoGroupResult, MergeSuggestion, ColumnRole } from '../types/auto-group'\n\nexport interface Props {\n modelValue: boolean\n samples?: string[]\n experimentId?: number\n /** Pre-fetched design data — bypasses API fetch when provided */\n designData?: Record<string, unknown>\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n samples: () => [],\n experimentId: undefined,\n designData: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n 'apply': [result: AutoGroupResult]\n}>()\n\nconst autoGroup = useAutoGroup()\nconst api = useApi()\nconst wizardRef = ref<InstanceType<typeof StepWizard> | null>(null)\nconst currentStep = ref(0)\nconst isDragOver = ref(false)\nconst csvFileName = ref('')\nconst experimentLoading = ref(false)\nconst experimentError = ref<string | null>(null)\nconst csvError = ref<string | null>(null)\n\n// Workspace step refs\nconst openPopoverIdx = ref<number | null>(null)\nconst qcAsOverlay = ref(true)\nconst excludeQc = ref(false)\n\nfunction roleLabel(role?: ColumnRole): string {\n if (!role) return 'Factor'\n const labels: Record<ColumnRole, string> = {\n 'constant': 'Constant',\n 'factor': 'Factor',\n 'replicate': 'Replicate',\n 'run-order': 'Run',\n 'numeric': 'Numeric',\n 'class-tag': 'Class tag',\n 'ignore': 'Ignore',\n }\n return labels[role]\n}\n\nconst activeSchemaLabel = computed(() => {\n const cls = autoGroup.classes.value.find(c => classKey(c) === autoGroup.activeClassKey.value)\n return cls?.label ?? '—'\n})\n\nconst activeMemberCount = computed(() => {\n const cls = autoGroup.classes.value.find(c => classKey(c) === autoGroup.activeClassKey.value)\n return cls?.members.length ?? 0\n})\n\nconst activeColumnCount = computed(() => autoGroup.activeSchema.value?.columns.length ?? 0)\n\nconst groupKeyNames = computed<string[]>(() => {\n const schema = autoGroup.activeSchema.value\n if (!schema) return []\n return schema.groupBy\n .map(idx => {\n const col = schema.columns.find(c => c.index === idx)\n return col?.displayName ?? col?.name ?? ''\n })\n .filter((s): s is string => s.length > 0)\n})\n\nconst totalGrouped = computed(() =>\n (autoGroup.result.value.experimentalGroups ?? []).reduce((acc, g) => acc + g.samples.length, 0),\n)\nconst totalQc = computed(() =>\n (autoGroup.qcGroups.value ?? []).reduce((acc, g) => acc + g.samples.length, 0),\n)\n\nfunction acceptSuggestion(s: MergeSuggestion) {\n autoGroup.mergeColumns(autoGroup.activeClassKey.value, s.columnIndices)\n}\n\nfunction dismissSuggestion(_: MergeSuggestion) {\n // Suggestions are derived; \"dismiss\" is a no-op in MVP (it will re-appear on schema reset).\n}\n\n// QC routing: a 3-way state replaces the two former booleans. Internally we\n// still store qcAsOverlay + excludeQc (so downstream watchers stay stable),\n// but the UI presents the choice as one radio group: overlay / exclude / mix.\ntype QcRouting = 'overlay' | 'exclude' | 'mix'\nconst qcRouting = computed<QcRouting>(() => {\n if (excludeQc.value) return 'exclude'\n if (qcAsOverlay.value) return 'overlay'\n return 'mix'\n})\n\nfunction setQcRouting(value: QcRouting) {\n qcAsOverlay.value = value === 'overlay'\n excludeQc.value = value === 'exclude'\n autoGroup.setClassDispositions(\n c => c.kind !== 'biological' && c.kind !== 'unknown',\n value === 'exclude' ? 'exclude' : value === 'overlay' ? 'overlay' : 'group',\n )\n}\n\n// Group statistics for the 3-stat strip — scoped to the ACTIVE class so the\n// numbers correspond to what the user sees in the schema panel below.\n// (The Preview step shows global totals across all classes.)\nconst groupStats = computed(() => {\n const cls = autoGroup.classes.value.find(c => classKey(c) === autoGroup.activeClassKey.value)\n if (!cls) return { count: 0, samples: 0, avg: 0, min: 0, max: 0 }\n const schema = autoGroup.activeSchema.value\n const total = cls.members.length\n const buckets = new Map<string, number>()\n if (!schema || schema.groupBy.length === 0) {\n // No factor columns selected → one group containing everyone.\n if (total > 0) buckets.set('__all__', total)\n } else {\n for (const m of cls.members) {\n const tokens = autoGroup.tokenized.value[m]\n const key = schema.groupBy.map(idx => tokens[idx] ?? '').join(' / ')\n buckets.set(key, (buckets.get(key) ?? 0) + 1)\n }\n }\n const sizes = [...buckets.values()]\n return {\n count: sizes.length,\n samples: total,\n avg: sizes.length > 0 ? Math.round((total / sizes.length) * 10) / 10 : 0,\n min: sizes.length > 0 ? Math.min(...sizes) : 0,\n max: sizes.length > 0 ? Math.max(...sizes) : 0,\n }\n})\n\nconst activeColumn = computed(() => {\n if (openPopoverIdx.value === null) return null\n return autoGroup.activeSchema.value?.columns.find(c => c.index === openPopoverIdx.value) ?? null\n})\n\nfunction renameActive(name: string) {\n const col = activeColumn.value\n if (!col) return\n autoGroup.setColumnName(autoGroup.activeClassKey.value, col.index, name)\n}\n\nfunction setAlias(src: string, dst: string) {\n const col = activeColumn.value\n if (!col) return\n const next = { ...(col.ops?.alias ?? {}) }\n if (dst === src) delete next[src]\n else next[src] = dst\n autoGroup.setValueOps(autoGroup.activeClassKey.value, col.index, {\n ...col.ops,\n alias: next,\n })\n}\n\nfunction toggleExclude(value: string) {\n const col = activeColumn.value\n if (!col) return\n const list = new Set(col.ops?.exclude ?? [])\n if (list.has(value)) list.delete(value)\n else list.add(value)\n autoGroup.setValueOps(autoGroup.activeClassKey.value, col.index, {\n ...col.ops,\n exclude: [...list],\n })\n}\n\nfunction mergeWithNext() {\n const col = activeColumn.value\n if (!col) return\n const schema = autoGroup.activeSchema.value\n const next = schema?.columns.find(c => c.index === col.index + 1)\n if (!next) return\n autoGroup.mergeColumns(autoGroup.activeClassKey.value, [col.index, next.index])\n openPopoverIdx.value = null\n}\n\n// Pre-fill from props.\n// When samples or experiment data are already known we auto-skip the Input\n// step entirely and land on Workspace — there's nothing for the user to\n// paste or upload. The wizard becomes a clean 2-step (Workspace → Preview)\n// in the common case. Input still exists for cold opens (no samples, no\n// experiment) where users paste their own sample list or upload a CSV.\nwatch(() => props.modelValue, (open) => {\n if (open) {\n autoGroup.reset()\n currentStep.value = 0\n csvFileName.value = ''\n experimentError.value = null\n csvError.value = null\n if (props.experimentId || props.designData) {\n fetchExperimentData()\n currentStep.value = 1\n } else if (props.samples.length > 0) {\n autoGroup.rawText.value = props.samples.join('\\n')\n autoGroup.inputMode.value = 'paste'\n autoGroup.parseInput()\n currentStep.value = 1\n } else {\n // Cold open: default to Paste so users can type their own sample\n // list immediately. CSV upload is one click away in the mode toggle.\n autoGroup.inputMode.value = 'paste'\n }\n }\n}, { immediate: true })\n\n// Wizard steps: three steps, unconditional\nconst allSteps: WizardStep[] = [\n { id: 'input', label: 'Input' },\n { id: 'workspace', label: 'Workspace' },\n { id: 'preview', label: 'Preview' },\n]\n\nconst dynamicSteps = computed<WizardStep[]>(() => allSteps)\n\n// Map current step index to step ID\nconst currentStepId = computed(() => {\n const step = dynamicSteps.value[currentStep.value]\n return step?.id ?? 'input'\n})\n\n// Clamp step when dynamic steps change\nwatch(() => dynamicSteps.value.length, (newLen) => {\n if (currentStep.value >= newLen) {\n currentStep.value = newLen - 1\n }\n})\n\n// Step validity\nconst inputValid = computed(() => autoGroup.samples.value.length > 0)\nconst workspaceValid = computed(() => true) // empty groupBy is allowed; warning is shown in Preview.\n\nwatch([inputValid, workspaceValid, dynamicSteps], () => {\n if (!wizardRef.value) return\n const steps = dynamicSteps.value\n for (let i = 0; i < steps.length; i++) {\n const id = steps[i].id\n if (id === 'input') wizardRef.value.setStepValid(i, inputValid.value)\n else if (id === 'workspace') wizardRef.value.setStepValid(i, workspaceValid.value)\n else wizardRef.value.setStepValid(i, true)\n }\n}, { immediate: true })\n\n// Navigation\nfunction handleNext() {\n if (currentStepId.value === 'input') {\n autoGroup.parseInput()\n }\n // StepWizard handles the actual navigation via v-model\n}\n\nfunction handleApply() {\n emit('apply', autoGroup.result.value)\n emit('update:modelValue', false)\n}\n\nfunction handleCancel() {\n emit('update:modelValue', false)\n}\n\n// Experiment data fetching\nasync function fetchExperimentData() {\n if (!props.experimentId && !props.designData) return\n\n autoGroup.inputMode.value = 'experiment'\n experimentLoading.value = true\n experimentError.value = null\n\n try {\n let pluginData: Record<string, unknown> | undefined\n\n if (props.designData) {\n pluginData = props.designData\n } else {\n const result = await api.get<Record<string, unknown>>(\n `/experiments/${props.experimentId}/data`,\n )\n pluginData = result.data as Record<string, unknown> | undefined\n }\n\n if (!pluginData || typeof pluginData !== 'object') {\n experimentError.value = 'No design data found for this experiment'\n return\n }\n\n const success = autoGroup.loadExperimentData(pluginData)\n if (!success) {\n experimentError.value = 'No sample metadata found in this experiment'\n return\n }\n\n // Auto-advance to Workspace step\n await nextTick()\n const workspaceIdx = dynamicSteps.value.findIndex(s => s.id === 'workspace')\n if (workspaceIdx >= 0) {\n currentStep.value = workspaceIdx\n }\n } catch (e) {\n experimentError.value = e instanceof Error ? e.message : 'Failed to fetch experiment data'\n } finally {\n experimentLoading.value = false\n }\n}\n\n// CSV file handling\nfunction handleFileDrop(event: DragEvent) {\n event.preventDefault()\n isDragOver.value = false\n const files = event.dataTransfer?.files\n if (files && files.length > 0) {\n processFile(files[0])\n }\n}\n\nfunction handleFileInput(event: Event) {\n const target = event.target as HTMLInputElement\n if (target.files && target.files.length > 0) {\n processFile(target.files[0])\n }\n}\n\nasync function processFile(file: File) {\n if (!file.name.endsWith('.csv') && !file.name.endsWith('.tsv')) return\n\n csvError.value = null\n try {\n const text = await file.text()\n const parsed = parseCSV(text)\n autoGroup.inputMode.value = 'csv'\n autoGroup.csvData.value = parsed\n csvFileName.value = file.name\n } catch (e) {\n csvError.value = e instanceof Error ? e.message : 'Failed to parse file'\n }\n}\n\nfunction clearCsvFile() {\n autoGroup.csvData.value = null\n // Stay in CSV mode after a clear so the user can pick another file\n // without re-clicking the CSV tab. They can switch to Paste manually.\n autoGroup.inputMode.value = 'csv'\n csvFileName.value = ''\n}\n\nfunction switchToPasteMode() {\n autoGroup.inputMode.value = 'paste'\n autoGroup.csvData.value = null\n autoGroup.sampleTypeHints.value = []\n csvFileName.value = ''\n csvError.value = null\n}\n\n// Experiment metadata field names (for display in experiment mode)\nconst experimentFieldNames = computed(() =>\n autoGroup.csvData.value?.columns.filter(c => c !== 'sample_name') ?? [],\n)\n\n// Last step check\nconst isLastStep = computed(() => currentStep.value === dynamicSteps.value.length - 1)\nconst isFirstStep = computed(() => currentStep.value === 0)\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n title=\"Smart Group\"\n size=\"lg\"\n :close-on-overlay=\"false\"\n :close-on-escape=\"false\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n @close=\"handleCancel\"\n >\n <div class=\"mint-auto-group\">\n <StepWizard\n ref=\"wizardRef\"\n v-model=\"currentStep\"\n :steps=\"dynamicSteps\"\n :linear=\"true\"\n size=\"sm\"\n >\n <!-- Step: Input -->\n <template #step-input>\n <div class=\"mint-auto-group__input-step\">\n <!-- Mode toggle. Paste is the self-designed path (type your\n own sample list); CSV is the structured-upload path; the\n Experiment tab only appears when the parent component\n owns an experimentId / designData. -->\n <div class=\"mint-auto-group__mode-toggle\">\n <BaseButton\n v-if=\"experimentId || designData\"\n :variant=\"autoGroup.inputMode.value === 'experiment' ? 'primary' : 'secondary'\"\n size=\"sm\"\n :disabled=\"experimentLoading\"\n @click=\"fetchExperimentData\"\n >\n Experiment\n </BaseButton>\n <BaseButton\n :variant=\"autoGroup.inputMode.value === 'paste' ? 'primary' : 'secondary'\"\n size=\"sm\"\n @click=\"switchToPasteMode\"\n >\n Paste\n </BaseButton>\n <BaseButton\n :variant=\"autoGroup.inputMode.value === 'csv' ? 'primary' : 'secondary'\"\n size=\"sm\"\n @click=\"autoGroup.inputMode.value = 'csv'\"\n >\n CSV\n </BaseButton>\n </div>\n\n <!-- Experiment mode -->\n <div v-if=\"autoGroup.inputMode.value === 'experiment'\" class=\"mint-auto-group__experiment\">\n <div v-if=\"experimentLoading\" class=\"mint-auto-group__experiment-loading\">\n <LoadingSpinner size=\"sm\" />\n <span>Loading experiment metadata…</span>\n </div>\n <div v-else-if=\"experimentError\" class=\"mint-auto-group__experiment-error\">\n <p>{{ experimentError }}</p>\n <p>\n Switch to <button type=\"button\" class=\"mint-auto-group__link-btn\" @click=\"switchToPasteMode\">Paste</button>\n or <button type=\"button\" class=\"mint-auto-group__link-btn\" @click=\"autoGroup.inputMode.value = 'csv'\">CSV</button> mode instead.\n </p>\n </div>\n <div v-else-if=\"autoGroup.csvData.value\" class=\"mint-auto-group__experiment-loaded\">\n <div class=\"mint-auto-group__experiment-summary\">\n <span class=\"mint-auto-group__experiment-stat\">\n <strong>{{ autoGroup.csvData.value.rows.length }}</strong> samples\n </span>\n <span class=\"mint-auto-group__experiment-stat\">\n <strong>{{ experimentFieldNames.length }}</strong> metadata fields\n </span>\n </div>\n <div class=\"mint-auto-group__experiment-fields\">\n <span\n v-for=\"col in experimentFieldNames\"\n :key=\"col\"\n class=\"mint-auto-group__field-value\"\n >{{ col }}</span>\n </div>\n </div>\n </div>\n\n <!-- Paste mode: free-form textarea for users to type or paste\n their own sample list (the \"self-designed data\" path). -->\n <div v-if=\"autoGroup.inputMode.value === 'paste'\" class=\"mint-auto-group__paste\">\n <textarea\n v-model=\"autoGroup.rawText.value\"\n class=\"mint-auto-group__textarea\"\n rows=\"12\"\n placeholder=\"Paste sample names, one per line...\"\n />\n <div v-if=\"autoGroup.samples.value.length > 0\" class=\"mint-auto-group__sample-count\">\n {{ autoGroup.samples.value.length }} samples\n </div>\n </div>\n\n <!-- CSV mode -->\n <div v-if=\"autoGroup.inputMode.value === 'csv'\" class=\"mint-auto-group__csv\">\n <div v-if=\"!autoGroup.csvData.value\"\n :class=\"[\n 'mint-auto-group__dropzone',\n isDragOver ? 'mint-auto-group__dropzone--dragover' : '',\n ]\"\n @drop=\"handleFileDrop\"\n @dragover.prevent=\"isDragOver = true\"\n @dragleave=\"isDragOver = false\"\n >\n <input\n type=\"file\"\n accept=\".csv,.tsv\"\n class=\"mint-auto-group__file-input\"\n @change=\"handleFileInput\"\n />\n <svg class=\"mint-auto-group__upload-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n <p class=\"mint-auto-group__upload-text\">\n <span class=\"mint-auto-group__upload-highlight\">Click to upload</span>\n or drag and drop a CSV file\n </p>\n <p class=\"mint-auto-group__upload-hint\">\n CSV or TSV with a header row. First column or column named\n \"Sample\" / \"File Name\" is used as sample identifier.\n Other columns become grouping fields.\n </p>\n </div>\n\n <div v-if=\"csvError && !autoGroup.csvData.value\" class=\"mint-auto-group__csv-error\">\n {{ csvError }}\n </div>\n\n <div v-if=\"autoGroup.csvData.value\" class=\"mint-auto-group__file-info\">\n <svg class=\"mint-auto-group__file-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n <span class=\"mint-auto-group__file-name\">{{ csvFileName }}</span>\n <span class=\"mint-auto-group__file-rows\">{{ autoGroup.csvData.value.rows.length }} rows</span>\n <button type=\"button\" class=\"mint-auto-group__file-clear\" @click=\"clearCsvFile\">\n Change file\n </button>\n </div>\n </div>\n\n <div class=\"mint-auto-group__template\" v-if=\"autoGroup.canDownloadTemplate.value\">\n <div class=\"mint-auto-group__template-label\">Or hand-edit in Excel:</div>\n <div class=\"mint-auto-group__template-actions\">\n <BaseButton size=\"sm\" variant=\"ghost\" @click=\"autoGroup.downloadTemplate('prefilled', 'csv')\">\n Download pre-filled CSV\n </BaseButton>\n <BaseButton size=\"sm\" variant=\"ghost\" @click=\"autoGroup.downloadTemplate('blank', 'csv')\">\n Download blank CSV\n </BaseButton>\n </div>\n </div>\n </div>\n </template>\n\n <!-- Step: Workspace -->\n <template #step-workspace>\n <div class=\"mint-auto-group__workspace\">\n <aside class=\"mint-auto-group__classes\" aria-label=\"Detected sample classes\">\n <h3>Detected classes</h3>\n <button\n v-for=\"cls in autoGroup.classes.value\"\n :key=\"classKey(cls)\"\n type=\"button\"\n :class=\"[\n 'mint-auto-group__class',\n autoGroup.activeClassKey.value === classKey(cls) ? 'mint-auto-group__class--active' : '',\n cls.kind !== 'biological' && cls.kind !== 'unknown' ? 'mint-auto-group__class--qc' : '',\n ]\"\n @click=\"autoGroup.activeClassKey.value = classKey(cls)\"\n >\n <span class=\"mint-auto-group__class-dot\" />\n <span class=\"mint-auto-group__class-label\">{{ cls.label }}</span>\n <span class=\"mint-auto-group__class-count\">{{ cls.members.length }}</span>\n </button>\n\n <div class=\"mint-auto-group__routing\">\n <h4 class=\"mint-auto-group__routing-head\">QC routing</h4>\n <label class=\"mint-auto-group__routing-radio\">\n <input\n type=\"radio\"\n name=\"qc-routing\"\n :checked=\"qcRouting === 'overlay'\"\n @change=\"setQcRouting('overlay')\"\n />\n <span>Overlay <small>preview only</small></span>\n </label>\n <label class=\"mint-auto-group__routing-radio\">\n <input\n type=\"radio\"\n name=\"qc-routing\"\n :checked=\"qcRouting === 'exclude'\"\n @change=\"setQcRouting('exclude')\"\n />\n <span>Exclude entirely</span>\n </label>\n <label class=\"mint-auto-group__routing-radio\">\n <input\n type=\"radio\"\n name=\"qc-routing\"\n :checked=\"qcRouting === 'mix'\"\n @change=\"setQcRouting('mix')\"\n />\n <span>Mix into groups</span>\n </label>\n </div>\n </aside>\n\n <section class=\"mint-auto-group__schema\" aria-label=\"Schema editor\">\n <div class=\"mint-auto-group__stats\">\n <div class=\"mint-auto-group__stat mint-auto-group__stat--primary\">\n <div class=\"mint-auto-group__stat-v\">{{ groupStats.count }}</div>\n <div class=\"mint-auto-group__stat-l\">Groups</div>\n <div class=\"mint-auto-group__stat-sub\">\n <span v-if=\"groupKeyNames.length === 0\" class=\"mint-auto-group__stat-empty\">Toggle <strong>Use</strong> on a column to start splitting</span>\n <template v-else>\n <span\n v-for=\"name in groupKeyNames\"\n :key=\"name\"\n class=\"mint-auto-group__group-chip\"\n >{{ name }}</span>\n </template>\n </div>\n </div>\n <div class=\"mint-auto-group__stat\">\n <div class=\"mint-auto-group__stat-v\">{{ activeMemberCount }}</div>\n <div class=\"mint-auto-group__stat-l\">Samples</div>\n <div class=\"mint-auto-group__stat-sub mint-auto-group__stat-sub--muted\">{{ activeSchemaLabel }} · {{ activeColumnCount }} token positions</div>\n </div>\n <div class=\"mint-auto-group__stat\">\n <div class=\"mint-auto-group__stat-v\">{{ groupStats.avg }}</div>\n <div class=\"mint-auto-group__stat-l\">Avg / group</div>\n <div class=\"mint-auto-group__stat-sub mint-auto-group__stat-sub--muted\">\n <template v-if=\"groupStats.count > 0\">Min {{ groupStats.min }} · Max {{ groupStats.max }}</template>\n <template v-else>—</template>\n </div>\n </div>\n </div>\n\n <div v-if=\"autoGroup.suggestions.value.length > 0\" class=\"mint-auto-group__suggest\">\n <strong>Suggested merge:</strong>\n columns {{ autoGroup.suggestions.value[0].columnIndices.map((i: number) => i + 1).join(' + ') }}\n always co-occur — combine into <code>{{ autoGroup.suggestions.value[0].proposedName }}</code>?\n <button type=\"button\" class=\"mint-auto-group__suggest-btn\" @click=\"acceptSuggestion(autoGroup.suggestions.value[0])\">\n Accept · merge\n </button>\n <button type=\"button\" class=\"mint-auto-group__suggest-btn mint-auto-group__suggest-btn--ghost\" @click=\"dismissSuggestion(autoGroup.suggestions.value[0])\">\n Dismiss\n </button>\n </div>\n\n <div class=\"mint-auto-group__token-table\" role=\"table\" aria-label=\"Token positions\">\n <div class=\"mint-auto-group__token-row mint-auto-group__token-row--head\" role=\"row\">\n <span role=\"columnheader\" aria-label=\"Token number\">#</span>\n <span role=\"columnheader\">Name</span>\n <span role=\"columnheader\">Role</span>\n <span role=\"columnheader\" class=\"mint-auto-group__token-num\" aria-label=\"Unique value count\">Unique</span>\n <span role=\"columnheader\">Preview</span>\n <span role=\"columnheader\" class=\"mint-auto-group__token-use-head\">Use</span>\n <span role=\"columnheader\" aria-label=\"More actions\" />\n </div>\n <button\n v-for=\"col in (autoGroup.activeSchema.value?.columns ?? [])\"\n :key=\"col.index\"\n type=\"button\"\n role=\"row\"\n :class=\"[\n 'mint-auto-group__token-row',\n openPopoverIdx === col.index ? 'mint-auto-group__token-row--active' : '',\n ]\"\n @click=\"openPopoverIdx = col.index\"\n >\n <span class=\"mint-auto-group__token-ix\" role=\"cell\">{{ col.index + 1 }}</span>\n <span class=\"mint-auto-group__token-nm\" role=\"cell\">{{ col.displayName ?? col.name }}</span>\n <span\n :class=\"['mint-auto-group__role', `mint-auto-group__role--${col.role}`]\"\n role=\"cell\"\n >{{ roleLabel(col.role) }}</span>\n <span class=\"mint-auto-group__token-unique\" role=\"cell\">{{ col.cardinality }}</span>\n <span class=\"mint-auto-group__token-preview\" role=\"cell\">\n {{ col.uniqueValues.slice(0, 4).join(', ') }}<span\n v-if=\"col.uniqueValues.length > 4\"\n class=\"mint-auto-group__token-more\"\n > +{{ col.uniqueValues.length - 4 }} more</span>\n </span>\n <label\n class=\"mint-auto-group__token-use\"\n role=\"cell\"\n @click.stop\n >\n <input\n type=\"checkbox\"\n :checked=\"(autoGroup.activeSchema.value?.groupBy ?? []).includes(col.index)\"\n @change=\"autoGroup.toggleGroupBy(autoGroup.activeClassKey.value, col.index)\"\n />\n </label>\n <span class=\"mint-auto-group__token-trigger\" role=\"cell\" aria-hidden=\"true\">⋯</span>\n </button>\n </div>\n </section>\n\n <div\n v-if=\"openPopoverIdx !== null && activeColumn\"\n class=\"mint-auto-group__popover\"\n @click.stop\n >\n <div class=\"mint-auto-group__popover-head\">\n <strong>{{ activeColumn.displayName ?? activeColumn.name }}</strong>\n <span class=\"mono\">{{ activeColumn.cardinality }} unique</span>\n <button type=\"button\" class=\"mint-auto-group__popover-close\" @click=\"openPopoverIdx = null\">×</button>\n </div>\n\n <div class=\"mint-auto-group__popover-section\">\n <div class=\"mint-auto-group__popover-label\">Role</div>\n <div class=\"mint-auto-group__pills\">\n <button\n v-for=\"r in (['factor','replicate','run-order','numeric','constant','ignore'] as ColumnRole[])\"\n :key=\"r\"\n type=\"button\"\n :class=\"['mint-auto-group__pill-btn', activeColumn.role === r ? 'mint-auto-group__pill-btn--on' : '']\"\n @click=\"autoGroup.setRole(autoGroup.activeClassKey.value, activeColumn.index, r)\"\n >{{ roleLabel(r) }}</button>\n </div>\n </div>\n\n <div class=\"mint-auto-group__popover-section\">\n <div class=\"mint-auto-group__popover-label\">Rename</div>\n <BaseInput\n :model-value=\"activeColumn.displayName ?? activeColumn.name\"\n @update:model-value=\"renameActive(String($event ?? ''))\"\n />\n </div>\n\n <div class=\"mint-auto-group__popover-section\">\n <div class=\"mint-auto-group__popover-label\">Values ({{ activeColumn.uniqueValues.length }})</div>\n <div class=\"mint-auto-group__values-list\">\n <div\n v-for=\"v in activeColumn.uniqueValues\"\n :key=\"v\"\n class=\"mint-auto-group__value-row\"\n >\n <span class=\"mint-auto-group__value-src\">{{ v }} →</span>\n <input\n class=\"mint-auto-group__value-input\"\n :value=\"activeColumn.ops?.alias?.[v] ?? v\"\n @change=\"setAlias(v, ($event.target as HTMLInputElement).value)\"\n />\n <label class=\"mint-auto-group__value-excl\">\n <input\n type=\"checkbox\"\n :checked=\"activeColumn.ops?.exclude?.includes(v) ?? false\"\n @change=\"toggleExclude(v)\"\n />\n excl\n </label>\n </div>\n </div>\n </div>\n\n <div class=\"mint-auto-group__popover-section\">\n <button type=\"button\" class=\"mint-auto-group__link\" @click=\"mergeWithNext\">▸ Merge with next column</button>\n </div>\n </div>\n </div>\n </template>\n\n <!-- Step: Preview -->\n <template #step-preview>\n <div class=\"mint-auto-group__preview\">\n <div class=\"mint-auto-group__preview-summary\">\n <strong>{{ autoGroup.groups.value.length }}</strong> groups ·\n <strong>{{ totalGrouped }}</strong> samples ·\n <strong>{{ totalQc }}</strong> QC kept aside ·\n <strong>{{ autoGroup.excludedSamples.value.length }}</strong> excluded\n </div>\n\n <AlertBox\n v-if=\"autoGroup.allSingletons.value\"\n type=\"warning\"\n title=\"Each group has only one sample\"\n >\n Group key produced a unique key per row. Go back and disable any column whose\n values are unique per sample.\n </AlertBox>\n\n <div class=\"mint-auto-group__preview-grid\">\n <div class=\"mint-auto-group__preview-panel\">\n <h4>Experimental groups</h4>\n <details\n v-for=\"group in (autoGroup.result.value.experimentalGroups ?? [])\"\n :key=\"group.name\"\n class=\"mint-auto-group__preview-group\"\n >\n <summary>\n <span class=\"mint-auto-group__preview-dot\" :style=\"{ background: group.color }\" />\n <span>{{ group.name }}</span>\n <span class=\"mint-auto-group__preview-count\">{{ group.samples.length }}</span>\n </summary>\n <div class=\"mint-auto-group__preview-samples\">\n <span\n v-for=\"s in group.samples\"\n :key=\"s\"\n class=\"mint-auto-group__preview-sample\"\n >{{ s }}</span>\n </div>\n </details>\n </div>\n\n <div class=\"mint-auto-group__preview-panel\">\n <h4>QC kept aside (overlay)</h4>\n <div class=\"mint-auto-group__qc-chips\">\n <span\n v-for=\"g in (autoGroup.qcGroups.value ?? [])\"\n :key=\"g.name\"\n class=\"mint-auto-group__qc-chip\"\n >\n {{ g.name }} <code>×{{ g.samples.length }}</code>\n </span>\n </div>\n <details class=\"mint-auto-group__fingerprint\">\n <summary>Schema fingerprint</summary>\n <pre>{{ JSON.stringify(autoGroup.fingerprint.value, null, 2) }}</pre>\n </details>\n </div>\n </div>\n </div>\n </template>\n\n <!-- Custom navigation -->\n <template #navigation=\"{ goBack, goNext, canProceed }\">\n <div class=\"mint-auto-group__nav\">\n <BaseButton variant=\"secondary\" @click=\"handleCancel\">\n Cancel\n </BaseButton>\n <div class=\"mint-auto-group__nav-spacer\" />\n <BaseButton\n v-if=\"!isFirstStep\"\n variant=\"secondary\"\n @click=\"goBack\"\n >\n Back\n </BaseButton>\n <BaseButton\n v-if=\"!isLastStep\"\n variant=\"primary\"\n :disabled=\"!canProceed\"\n @click=\"() => { handleNext(); goNext() }\"\n >\n Next\n </BaseButton>\n <BaseButton\n v-if=\"isLastStep\"\n variant=\"primary\"\n @click=\"handleApply\"\n >\n Apply\n </BaseButton>\n </div>\n </template>\n </StepWizard>\n </div>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/auto-group-modal.css';\n</style>\n","<script setup lang=\"ts\">\n/** Wizard modal that auto-groups samples by parsing naming patterns or an uploaded CSV file. */\nimport { ref, computed, watch, nextTick } from 'vue'\nimport BaseModal from './BaseModal.vue'\nimport BaseButton from './BaseButton.vue'\nimport BaseInput from './BaseInput.vue'\nimport StepWizard from './StepWizard.vue'\nimport LoadingSpinner from './LoadingSpinner.vue'\nimport AlertBox from './AlertBox.vue'\nimport { useAutoGroup, parseCSV } from '../composables/useAutoGroup'\nimport { classKey } from '../composables/autoGroup'\nimport { useApi } from '../composables/useApi'\nimport type { WizardStep } from '../types/components'\nimport type { AutoGroupResult, MergeSuggestion, ColumnRole } from '../types/auto-group'\n\nexport interface Props {\n modelValue: boolean\n samples?: string[]\n experimentId?: number\n /** Pre-fetched design data — bypasses API fetch when provided */\n designData?: Record<string, unknown>\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n samples: () => [],\n experimentId: undefined,\n designData: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n 'apply': [result: AutoGroupResult]\n}>()\n\nconst autoGroup = useAutoGroup()\nconst api = useApi()\nconst wizardRef = ref<InstanceType<typeof StepWizard> | null>(null)\nconst currentStep = ref(0)\nconst isDragOver = ref(false)\nconst csvFileName = ref('')\nconst experimentLoading = ref(false)\nconst experimentError = ref<string | null>(null)\nconst csvError = ref<string | null>(null)\n\n// Workspace step refs\nconst openPopoverIdx = ref<number | null>(null)\nconst qcAsOverlay = ref(true)\nconst excludeQc = ref(false)\n\nfunction roleLabel(role?: ColumnRole): string {\n if (!role) return 'Factor'\n const labels: Record<ColumnRole, string> = {\n 'constant': 'Constant',\n 'factor': 'Factor',\n 'replicate': 'Replicate',\n 'run-order': 'Run',\n 'numeric': 'Numeric',\n 'class-tag': 'Class tag',\n 'ignore': 'Ignore',\n }\n return labels[role]\n}\n\nconst activeSchemaLabel = computed(() => {\n const cls = autoGroup.classes.value.find(c => classKey(c) === autoGroup.activeClassKey.value)\n return cls?.label ?? '—'\n})\n\nconst activeMemberCount = computed(() => {\n const cls = autoGroup.classes.value.find(c => classKey(c) === autoGroup.activeClassKey.value)\n return cls?.members.length ?? 0\n})\n\nconst activeColumnCount = computed(() => autoGroup.activeSchema.value?.columns.length ?? 0)\n\nconst groupKeyNames = computed<string[]>(() => {\n const schema = autoGroup.activeSchema.value\n if (!schema) return []\n return schema.groupBy\n .map(idx => {\n const col = schema.columns.find(c => c.index === idx)\n return col?.displayName ?? col?.name ?? ''\n })\n .filter((s): s is string => s.length > 0)\n})\n\nconst totalGrouped = computed(() =>\n (autoGroup.result.value.experimentalGroups ?? []).reduce((acc, g) => acc + g.samples.length, 0),\n)\nconst totalQc = computed(() =>\n (autoGroup.qcGroups.value ?? []).reduce((acc, g) => acc + g.samples.length, 0),\n)\n\nfunction acceptSuggestion(s: MergeSuggestion) {\n autoGroup.mergeColumns(autoGroup.activeClassKey.value, s.columnIndices)\n}\n\nfunction dismissSuggestion(_: MergeSuggestion) {\n // Suggestions are derived; \"dismiss\" is a no-op in MVP (it will re-appear on schema reset).\n}\n\n// QC routing: a 3-way state replaces the two former booleans. Internally we\n// still store qcAsOverlay + excludeQc (so downstream watchers stay stable),\n// but the UI presents the choice as one radio group: overlay / exclude / mix.\ntype QcRouting = 'overlay' | 'exclude' | 'mix'\nconst qcRouting = computed<QcRouting>(() => {\n if (excludeQc.value) return 'exclude'\n if (qcAsOverlay.value) return 'overlay'\n return 'mix'\n})\n\nfunction setQcRouting(value: QcRouting) {\n qcAsOverlay.value = value === 'overlay'\n excludeQc.value = value === 'exclude'\n autoGroup.setClassDispositions(\n c => c.kind !== 'biological' && c.kind !== 'unknown',\n value === 'exclude' ? 'exclude' : value === 'overlay' ? 'overlay' : 'group',\n )\n}\n\n// Group statistics for the 3-stat strip — scoped to the ACTIVE class so the\n// numbers correspond to what the user sees in the schema panel below.\n// (The Preview step shows global totals across all classes.)\nconst groupStats = computed(() => {\n const cls = autoGroup.classes.value.find(c => classKey(c) === autoGroup.activeClassKey.value)\n if (!cls) return { count: 0, samples: 0, avg: 0, min: 0, max: 0 }\n const schema = autoGroup.activeSchema.value\n const total = cls.members.length\n const buckets = new Map<string, number>()\n if (!schema || schema.groupBy.length === 0) {\n // No factor columns selected → one group containing everyone.\n if (total > 0) buckets.set('__all__', total)\n } else {\n for (const m of cls.members) {\n const tokens = autoGroup.tokenized.value[m]\n const key = schema.groupBy.map(idx => tokens[idx] ?? '').join(' / ')\n buckets.set(key, (buckets.get(key) ?? 0) + 1)\n }\n }\n const sizes = [...buckets.values()]\n return {\n count: sizes.length,\n samples: total,\n avg: sizes.length > 0 ? Math.round((total / sizes.length) * 10) / 10 : 0,\n min: sizes.length > 0 ? Math.min(...sizes) : 0,\n max: sizes.length > 0 ? Math.max(...sizes) : 0,\n }\n})\n\nconst activeColumn = computed(() => {\n if (openPopoverIdx.value === null) return null\n return autoGroup.activeSchema.value?.columns.find(c => c.index === openPopoverIdx.value) ?? null\n})\n\nfunction renameActive(name: string) {\n const col = activeColumn.value\n if (!col) return\n autoGroup.setColumnName(autoGroup.activeClassKey.value, col.index, name)\n}\n\nfunction setAlias(src: string, dst: string) {\n const col = activeColumn.value\n if (!col) return\n const next = { ...(col.ops?.alias ?? {}) }\n if (dst === src) delete next[src]\n else next[src] = dst\n autoGroup.setValueOps(autoGroup.activeClassKey.value, col.index, {\n ...col.ops,\n alias: next,\n })\n}\n\nfunction toggleExclude(value: string) {\n const col = activeColumn.value\n if (!col) return\n const list = new Set(col.ops?.exclude ?? [])\n if (list.has(value)) list.delete(value)\n else list.add(value)\n autoGroup.setValueOps(autoGroup.activeClassKey.value, col.index, {\n ...col.ops,\n exclude: [...list],\n })\n}\n\nfunction mergeWithNext() {\n const col = activeColumn.value\n if (!col) return\n const schema = autoGroup.activeSchema.value\n const next = schema?.columns.find(c => c.index === col.index + 1)\n if (!next) return\n autoGroup.mergeColumns(autoGroup.activeClassKey.value, [col.index, next.index])\n openPopoverIdx.value = null\n}\n\n// Pre-fill from props.\n// When samples or experiment data are already known we auto-skip the Input\n// step entirely and land on Workspace — there's nothing for the user to\n// paste or upload. The wizard becomes a clean 2-step (Workspace → Preview)\n// in the common case. Input still exists for cold opens (no samples, no\n// experiment) where users paste their own sample list or upload a CSV.\nwatch(() => props.modelValue, (open) => {\n if (open) {\n autoGroup.reset()\n currentStep.value = 0\n csvFileName.value = ''\n experimentError.value = null\n csvError.value = null\n if (props.experimentId || props.designData) {\n fetchExperimentData()\n currentStep.value = 1\n } else if (props.samples.length > 0) {\n autoGroup.rawText.value = props.samples.join('\\n')\n autoGroup.inputMode.value = 'paste'\n autoGroup.parseInput()\n currentStep.value = 1\n } else {\n // Cold open: default to Paste so users can type their own sample\n // list immediately. CSV upload is one click away in the mode toggle.\n autoGroup.inputMode.value = 'paste'\n }\n }\n}, { immediate: true })\n\n// Wizard steps: three steps, unconditional\nconst allSteps: WizardStep[] = [\n { id: 'input', label: 'Input' },\n { id: 'workspace', label: 'Workspace' },\n { id: 'preview', label: 'Preview' },\n]\n\nconst dynamicSteps = computed<WizardStep[]>(() => allSteps)\n\n// Map current step index to step ID\nconst currentStepId = computed(() => {\n const step = dynamicSteps.value[currentStep.value]\n return step?.id ?? 'input'\n})\n\n// Clamp step when dynamic steps change\nwatch(() => dynamicSteps.value.length, (newLen) => {\n if (currentStep.value >= newLen) {\n currentStep.value = newLen - 1\n }\n})\n\n// Step validity\nconst inputValid = computed(() => autoGroup.samples.value.length > 0)\nconst workspaceValid = computed(() => true) // empty groupBy is allowed; warning is shown in Preview.\n\nwatch([inputValid, workspaceValid, dynamicSteps], () => {\n if (!wizardRef.value) return\n const steps = dynamicSteps.value\n for (let i = 0; i < steps.length; i++) {\n const id = steps[i].id\n if (id === 'input') wizardRef.value.setStepValid(i, inputValid.value)\n else if (id === 'workspace') wizardRef.value.setStepValid(i, workspaceValid.value)\n else wizardRef.value.setStepValid(i, true)\n }\n}, { immediate: true })\n\n// Navigation\nfunction handleNext() {\n if (currentStepId.value === 'input') {\n autoGroup.parseInput()\n }\n // StepWizard handles the actual navigation via v-model\n}\n\nfunction handleApply() {\n emit('apply', autoGroup.result.value)\n emit('update:modelValue', false)\n}\n\nfunction handleCancel() {\n emit('update:modelValue', false)\n}\n\n// Experiment data fetching\nasync function fetchExperimentData() {\n if (!props.experimentId && !props.designData) return\n\n autoGroup.inputMode.value = 'experiment'\n experimentLoading.value = true\n experimentError.value = null\n\n try {\n let pluginData: Record<string, unknown> | undefined\n\n if (props.designData) {\n pluginData = props.designData\n } else {\n const result = await api.get<Record<string, unknown>>(\n `/experiments/${props.experimentId}/data`,\n )\n pluginData = result.data as Record<string, unknown> | undefined\n }\n\n if (!pluginData || typeof pluginData !== 'object') {\n experimentError.value = 'No design data found for this experiment'\n return\n }\n\n const success = autoGroup.loadExperimentData(pluginData)\n if (!success) {\n experimentError.value = 'No sample metadata found in this experiment'\n return\n }\n\n // Auto-advance to Workspace step\n await nextTick()\n const workspaceIdx = dynamicSteps.value.findIndex(s => s.id === 'workspace')\n if (workspaceIdx >= 0) {\n currentStep.value = workspaceIdx\n }\n } catch (e) {\n experimentError.value = e instanceof Error ? e.message : 'Failed to fetch experiment data'\n } finally {\n experimentLoading.value = false\n }\n}\n\n// CSV file handling\nfunction handleFileDrop(event: DragEvent) {\n event.preventDefault()\n isDragOver.value = false\n const files = event.dataTransfer?.files\n if (files && files.length > 0) {\n processFile(files[0])\n }\n}\n\nfunction handleFileInput(event: Event) {\n const target = event.target as HTMLInputElement\n if (target.files && target.files.length > 0) {\n processFile(target.files[0])\n }\n}\n\nasync function processFile(file: File) {\n if (!file.name.endsWith('.csv') && !file.name.endsWith('.tsv')) return\n\n csvError.value = null\n try {\n const text = await file.text()\n const parsed = parseCSV(text)\n autoGroup.inputMode.value = 'csv'\n autoGroup.csvData.value = parsed\n csvFileName.value = file.name\n } catch (e) {\n csvError.value = e instanceof Error ? e.message : 'Failed to parse file'\n }\n}\n\nfunction clearCsvFile() {\n autoGroup.csvData.value = null\n // Stay in CSV mode after a clear so the user can pick another file\n // without re-clicking the CSV tab. They can switch to Paste manually.\n autoGroup.inputMode.value = 'csv'\n csvFileName.value = ''\n}\n\nfunction switchToPasteMode() {\n autoGroup.inputMode.value = 'paste'\n autoGroup.csvData.value = null\n autoGroup.sampleTypeHints.value = []\n csvFileName.value = ''\n csvError.value = null\n}\n\n// Experiment metadata field names (for display in experiment mode)\nconst experimentFieldNames = computed(() =>\n autoGroup.csvData.value?.columns.filter(c => c !== 'sample_name') ?? [],\n)\n\n// Last step check\nconst isLastStep = computed(() => currentStep.value === dynamicSteps.value.length - 1)\nconst isFirstStep = computed(() => currentStep.value === 0)\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n title=\"Smart Group\"\n size=\"lg\"\n :close-on-overlay=\"false\"\n :close-on-escape=\"false\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n @close=\"handleCancel\"\n >\n <div class=\"mint-auto-group\">\n <StepWizard\n ref=\"wizardRef\"\n v-model=\"currentStep\"\n :steps=\"dynamicSteps\"\n :linear=\"true\"\n size=\"sm\"\n >\n <!-- Step: Input -->\n <template #step-input>\n <div class=\"mint-auto-group__input-step\">\n <!-- Mode toggle. Paste is the self-designed path (type your\n own sample list); CSV is the structured-upload path; the\n Experiment tab only appears when the parent component\n owns an experimentId / designData. -->\n <div class=\"mint-auto-group__mode-toggle\">\n <BaseButton\n v-if=\"experimentId || designData\"\n :variant=\"autoGroup.inputMode.value === 'experiment' ? 'primary' : 'secondary'\"\n size=\"sm\"\n :disabled=\"experimentLoading\"\n @click=\"fetchExperimentData\"\n >\n Experiment\n </BaseButton>\n <BaseButton\n :variant=\"autoGroup.inputMode.value === 'paste' ? 'primary' : 'secondary'\"\n size=\"sm\"\n @click=\"switchToPasteMode\"\n >\n Paste\n </BaseButton>\n <BaseButton\n :variant=\"autoGroup.inputMode.value === 'csv' ? 'primary' : 'secondary'\"\n size=\"sm\"\n @click=\"autoGroup.inputMode.value = 'csv'\"\n >\n CSV\n </BaseButton>\n </div>\n\n <!-- Experiment mode -->\n <div v-if=\"autoGroup.inputMode.value === 'experiment'\" class=\"mint-auto-group__experiment\">\n <div v-if=\"experimentLoading\" class=\"mint-auto-group__experiment-loading\">\n <LoadingSpinner size=\"sm\" />\n <span>Loading experiment metadata…</span>\n </div>\n <div v-else-if=\"experimentError\" class=\"mint-auto-group__experiment-error\">\n <p>{{ experimentError }}</p>\n <p>\n Switch to <button type=\"button\" class=\"mint-auto-group__link-btn\" @click=\"switchToPasteMode\">Paste</button>\n or <button type=\"button\" class=\"mint-auto-group__link-btn\" @click=\"autoGroup.inputMode.value = 'csv'\">CSV</button> mode instead.\n </p>\n </div>\n <div v-else-if=\"autoGroup.csvData.value\" class=\"mint-auto-group__experiment-loaded\">\n <div class=\"mint-auto-group__experiment-summary\">\n <span class=\"mint-auto-group__experiment-stat\">\n <strong>{{ autoGroup.csvData.value.rows.length }}</strong> samples\n </span>\n <span class=\"mint-auto-group__experiment-stat\">\n <strong>{{ experimentFieldNames.length }}</strong> metadata fields\n </span>\n </div>\n <div class=\"mint-auto-group__experiment-fields\">\n <span\n v-for=\"col in experimentFieldNames\"\n :key=\"col\"\n class=\"mint-auto-group__field-value\"\n >{{ col }}</span>\n </div>\n </div>\n </div>\n\n <!-- Paste mode: free-form textarea for users to type or paste\n their own sample list (the \"self-designed data\" path). -->\n <div v-if=\"autoGroup.inputMode.value === 'paste'\" class=\"mint-auto-group__paste\">\n <textarea\n v-model=\"autoGroup.rawText.value\"\n class=\"mint-auto-group__textarea\"\n rows=\"12\"\n placeholder=\"Paste sample names, one per line...\"\n />\n <div v-if=\"autoGroup.samples.value.length > 0\" class=\"mint-auto-group__sample-count\">\n {{ autoGroup.samples.value.length }} samples\n </div>\n </div>\n\n <!-- CSV mode -->\n <div v-if=\"autoGroup.inputMode.value === 'csv'\" class=\"mint-auto-group__csv\">\n <div v-if=\"!autoGroup.csvData.value\"\n :class=\"[\n 'mint-auto-group__dropzone',\n isDragOver ? 'mint-auto-group__dropzone--dragover' : '',\n ]\"\n @drop=\"handleFileDrop\"\n @dragover.prevent=\"isDragOver = true\"\n @dragleave=\"isDragOver = false\"\n >\n <input\n type=\"file\"\n accept=\".csv,.tsv\"\n class=\"mint-auto-group__file-input\"\n @change=\"handleFileInput\"\n />\n <svg class=\"mint-auto-group__upload-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n <p class=\"mint-auto-group__upload-text\">\n <span class=\"mint-auto-group__upload-highlight\">Click to upload</span>\n or drag and drop a CSV file\n </p>\n <p class=\"mint-auto-group__upload-hint\">\n CSV or TSV with a header row. First column or column named\n \"Sample\" / \"File Name\" is used as sample identifier.\n Other columns become grouping fields.\n </p>\n </div>\n\n <div v-if=\"csvError && !autoGroup.csvData.value\" class=\"mint-auto-group__csv-error\">\n {{ csvError }}\n </div>\n\n <div v-if=\"autoGroup.csvData.value\" class=\"mint-auto-group__file-info\">\n <svg class=\"mint-auto-group__file-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n <span class=\"mint-auto-group__file-name\">{{ csvFileName }}</span>\n <span class=\"mint-auto-group__file-rows\">{{ autoGroup.csvData.value.rows.length }} rows</span>\n <button type=\"button\" class=\"mint-auto-group__file-clear\" @click=\"clearCsvFile\">\n Change file\n </button>\n </div>\n </div>\n\n <div class=\"mint-auto-group__template\" v-if=\"autoGroup.canDownloadTemplate.value\">\n <div class=\"mint-auto-group__template-label\">Or hand-edit in Excel:</div>\n <div class=\"mint-auto-group__template-actions\">\n <BaseButton size=\"sm\" variant=\"ghost\" @click=\"autoGroup.downloadTemplate('prefilled', 'csv')\">\n Download pre-filled CSV\n </BaseButton>\n <BaseButton size=\"sm\" variant=\"ghost\" @click=\"autoGroup.downloadTemplate('blank', 'csv')\">\n Download blank CSV\n </BaseButton>\n </div>\n </div>\n </div>\n </template>\n\n <!-- Step: Workspace -->\n <template #step-workspace>\n <div class=\"mint-auto-group__workspace\">\n <aside class=\"mint-auto-group__classes\" aria-label=\"Detected sample classes\">\n <h3>Detected classes</h3>\n <button\n v-for=\"cls in autoGroup.classes.value\"\n :key=\"classKey(cls)\"\n type=\"button\"\n :class=\"[\n 'mint-auto-group__class',\n autoGroup.activeClassKey.value === classKey(cls) ? 'mint-auto-group__class--active' : '',\n cls.kind !== 'biological' && cls.kind !== 'unknown' ? 'mint-auto-group__class--qc' : '',\n ]\"\n @click=\"autoGroup.activeClassKey.value = classKey(cls)\"\n >\n <span class=\"mint-auto-group__class-dot\" />\n <span class=\"mint-auto-group__class-label\">{{ cls.label }}</span>\n <span class=\"mint-auto-group__class-count\">{{ cls.members.length }}</span>\n </button>\n\n <div class=\"mint-auto-group__routing\">\n <h4 class=\"mint-auto-group__routing-head\">QC routing</h4>\n <label class=\"mint-auto-group__routing-radio\">\n <input\n type=\"radio\"\n name=\"qc-routing\"\n :checked=\"qcRouting === 'overlay'\"\n @change=\"setQcRouting('overlay')\"\n />\n <span>Overlay <small>preview only</small></span>\n </label>\n <label class=\"mint-auto-group__routing-radio\">\n <input\n type=\"radio\"\n name=\"qc-routing\"\n :checked=\"qcRouting === 'exclude'\"\n @change=\"setQcRouting('exclude')\"\n />\n <span>Exclude entirely</span>\n </label>\n <label class=\"mint-auto-group__routing-radio\">\n <input\n type=\"radio\"\n name=\"qc-routing\"\n :checked=\"qcRouting === 'mix'\"\n @change=\"setQcRouting('mix')\"\n />\n <span>Mix into groups</span>\n </label>\n </div>\n </aside>\n\n <section class=\"mint-auto-group__schema\" aria-label=\"Schema editor\">\n <div class=\"mint-auto-group__stats\">\n <div class=\"mint-auto-group__stat mint-auto-group__stat--primary\">\n <div class=\"mint-auto-group__stat-v\">{{ groupStats.count }}</div>\n <div class=\"mint-auto-group__stat-l\">Groups</div>\n <div class=\"mint-auto-group__stat-sub\">\n <span v-if=\"groupKeyNames.length === 0\" class=\"mint-auto-group__stat-empty\">Toggle <strong>Use</strong> on a column to start splitting</span>\n <template v-else>\n <span\n v-for=\"name in groupKeyNames\"\n :key=\"name\"\n class=\"mint-auto-group__group-chip\"\n >{{ name }}</span>\n </template>\n </div>\n </div>\n <div class=\"mint-auto-group__stat\">\n <div class=\"mint-auto-group__stat-v\">{{ activeMemberCount }}</div>\n <div class=\"mint-auto-group__stat-l\">Samples</div>\n <div class=\"mint-auto-group__stat-sub mint-auto-group__stat-sub--muted\">{{ activeSchemaLabel }} · {{ activeColumnCount }} token positions</div>\n </div>\n <div class=\"mint-auto-group__stat\">\n <div class=\"mint-auto-group__stat-v\">{{ groupStats.avg }}</div>\n <div class=\"mint-auto-group__stat-l\">Avg / group</div>\n <div class=\"mint-auto-group__stat-sub mint-auto-group__stat-sub--muted\">\n <template v-if=\"groupStats.count > 0\">Min {{ groupStats.min }} · Max {{ groupStats.max }}</template>\n <template v-else>—</template>\n </div>\n </div>\n </div>\n\n <div v-if=\"autoGroup.suggestions.value.length > 0\" class=\"mint-auto-group__suggest\">\n <strong>Suggested merge:</strong>\n columns {{ autoGroup.suggestions.value[0].columnIndices.map((i: number) => i + 1).join(' + ') }}\n always co-occur — combine into <code>{{ autoGroup.suggestions.value[0].proposedName }}</code>?\n <button type=\"button\" class=\"mint-auto-group__suggest-btn\" @click=\"acceptSuggestion(autoGroup.suggestions.value[0])\">\n Accept · merge\n </button>\n <button type=\"button\" class=\"mint-auto-group__suggest-btn mint-auto-group__suggest-btn--ghost\" @click=\"dismissSuggestion(autoGroup.suggestions.value[0])\">\n Dismiss\n </button>\n </div>\n\n <div class=\"mint-auto-group__token-table\" role=\"table\" aria-label=\"Token positions\">\n <div class=\"mint-auto-group__token-row mint-auto-group__token-row--head\" role=\"row\">\n <span role=\"columnheader\" aria-label=\"Token number\">#</span>\n <span role=\"columnheader\">Name</span>\n <span role=\"columnheader\">Role</span>\n <span role=\"columnheader\" class=\"mint-auto-group__token-num\" aria-label=\"Unique value count\">Unique</span>\n <span role=\"columnheader\">Preview</span>\n <span role=\"columnheader\" class=\"mint-auto-group__token-use-head\">Use</span>\n <span role=\"columnheader\" aria-label=\"More actions\" />\n </div>\n <button\n v-for=\"col in (autoGroup.activeSchema.value?.columns ?? [])\"\n :key=\"col.index\"\n type=\"button\"\n role=\"row\"\n :class=\"[\n 'mint-auto-group__token-row',\n openPopoverIdx === col.index ? 'mint-auto-group__token-row--active' : '',\n ]\"\n @click=\"openPopoverIdx = col.index\"\n >\n <span class=\"mint-auto-group__token-ix\" role=\"cell\">{{ col.index + 1 }}</span>\n <span class=\"mint-auto-group__token-nm\" role=\"cell\">{{ col.displayName ?? col.name }}</span>\n <span\n :class=\"['mint-auto-group__role', `mint-auto-group__role--${col.role}`]\"\n role=\"cell\"\n >{{ roleLabel(col.role) }}</span>\n <span class=\"mint-auto-group__token-unique\" role=\"cell\">{{ col.cardinality }}</span>\n <span class=\"mint-auto-group__token-preview\" role=\"cell\">\n {{ col.uniqueValues.slice(0, 4).join(', ') }}<span\n v-if=\"col.uniqueValues.length > 4\"\n class=\"mint-auto-group__token-more\"\n > +{{ col.uniqueValues.length - 4 }} more</span>\n </span>\n <label\n class=\"mint-auto-group__token-use\"\n role=\"cell\"\n @click.stop\n >\n <input\n type=\"checkbox\"\n :checked=\"(autoGroup.activeSchema.value?.groupBy ?? []).includes(col.index)\"\n @change=\"autoGroup.toggleGroupBy(autoGroup.activeClassKey.value, col.index)\"\n />\n </label>\n <span class=\"mint-auto-group__token-trigger\" role=\"cell\" aria-hidden=\"true\">⋯</span>\n </button>\n </div>\n </section>\n\n <div\n v-if=\"openPopoverIdx !== null && activeColumn\"\n class=\"mint-auto-group__popover\"\n @click.stop\n >\n <div class=\"mint-auto-group__popover-head\">\n <strong>{{ activeColumn.displayName ?? activeColumn.name }}</strong>\n <span class=\"mono\">{{ activeColumn.cardinality }} unique</span>\n <button type=\"button\" class=\"mint-auto-group__popover-close\" @click=\"openPopoverIdx = null\">×</button>\n </div>\n\n <div class=\"mint-auto-group__popover-section\">\n <div class=\"mint-auto-group__popover-label\">Role</div>\n <div class=\"mint-auto-group__pills\">\n <button\n v-for=\"r in (['factor','replicate','run-order','numeric','constant','ignore'] as ColumnRole[])\"\n :key=\"r\"\n type=\"button\"\n :class=\"['mint-auto-group__pill-btn', activeColumn.role === r ? 'mint-auto-group__pill-btn--on' : '']\"\n @click=\"autoGroup.setRole(autoGroup.activeClassKey.value, activeColumn.index, r)\"\n >{{ roleLabel(r) }}</button>\n </div>\n </div>\n\n <div class=\"mint-auto-group__popover-section\">\n <div class=\"mint-auto-group__popover-label\">Rename</div>\n <BaseInput\n :model-value=\"activeColumn.displayName ?? activeColumn.name\"\n @update:model-value=\"renameActive(String($event ?? ''))\"\n />\n </div>\n\n <div class=\"mint-auto-group__popover-section\">\n <div class=\"mint-auto-group__popover-label\">Values ({{ activeColumn.uniqueValues.length }})</div>\n <div class=\"mint-auto-group__values-list\">\n <div\n v-for=\"v in activeColumn.uniqueValues\"\n :key=\"v\"\n class=\"mint-auto-group__value-row\"\n >\n <span class=\"mint-auto-group__value-src\">{{ v }} →</span>\n <input\n class=\"mint-auto-group__value-input\"\n :value=\"activeColumn.ops?.alias?.[v] ?? v\"\n @change=\"setAlias(v, ($event.target as HTMLInputElement).value)\"\n />\n <label class=\"mint-auto-group__value-excl\">\n <input\n type=\"checkbox\"\n :checked=\"activeColumn.ops?.exclude?.includes(v) ?? false\"\n @change=\"toggleExclude(v)\"\n />\n excl\n </label>\n </div>\n </div>\n </div>\n\n <div class=\"mint-auto-group__popover-section\">\n <button type=\"button\" class=\"mint-auto-group__link\" @click=\"mergeWithNext\">▸ Merge with next column</button>\n </div>\n </div>\n </div>\n </template>\n\n <!-- Step: Preview -->\n <template #step-preview>\n <div class=\"mint-auto-group__preview\">\n <div class=\"mint-auto-group__preview-summary\">\n <strong>{{ autoGroup.groups.value.length }}</strong> groups ·\n <strong>{{ totalGrouped }}</strong> samples ·\n <strong>{{ totalQc }}</strong> QC kept aside ·\n <strong>{{ autoGroup.excludedSamples.value.length }}</strong> excluded\n </div>\n\n <AlertBox\n v-if=\"autoGroup.allSingletons.value\"\n type=\"warning\"\n title=\"Each group has only one sample\"\n >\n Group key produced a unique key per row. Go back and disable any column whose\n values are unique per sample.\n </AlertBox>\n\n <div class=\"mint-auto-group__preview-grid\">\n <div class=\"mint-auto-group__preview-panel\">\n <h4>Experimental groups</h4>\n <details\n v-for=\"group in (autoGroup.result.value.experimentalGroups ?? [])\"\n :key=\"group.name\"\n class=\"mint-auto-group__preview-group\"\n >\n <summary>\n <span class=\"mint-auto-group__preview-dot\" :style=\"{ background: group.color }\" />\n <span>{{ group.name }}</span>\n <span class=\"mint-auto-group__preview-count\">{{ group.samples.length }}</span>\n </summary>\n <div class=\"mint-auto-group__preview-samples\">\n <span\n v-for=\"s in group.samples\"\n :key=\"s\"\n class=\"mint-auto-group__preview-sample\"\n >{{ s }}</span>\n </div>\n </details>\n </div>\n\n <div class=\"mint-auto-group__preview-panel\">\n <h4>QC kept aside (overlay)</h4>\n <div class=\"mint-auto-group__qc-chips\">\n <span\n v-for=\"g in (autoGroup.qcGroups.value ?? [])\"\n :key=\"g.name\"\n class=\"mint-auto-group__qc-chip\"\n >\n {{ g.name }} <code>×{{ g.samples.length }}</code>\n </span>\n </div>\n <details class=\"mint-auto-group__fingerprint\">\n <summary>Schema fingerprint</summary>\n <pre>{{ JSON.stringify(autoGroup.fingerprint.value, null, 2) }}</pre>\n </details>\n </div>\n </div>\n </div>\n </template>\n\n <!-- Custom navigation -->\n <template #navigation=\"{ goBack, goNext, canProceed }\">\n <div class=\"mint-auto-group__nav\">\n <BaseButton variant=\"secondary\" @click=\"handleCancel\">\n Cancel\n </BaseButton>\n <div class=\"mint-auto-group__nav-spacer\" />\n <BaseButton\n v-if=\"!isFirstStep\"\n variant=\"secondary\"\n @click=\"goBack\"\n >\n Back\n </BaseButton>\n <BaseButton\n v-if=\"!isLastStep\"\n variant=\"primary\"\n :disabled=\"!canProceed\"\n @click=\"() => { handleNext(); goNext() }\"\n >\n Next\n </BaseButton>\n <BaseButton\n v-if=\"isLastStep\"\n variant=\"primary\"\n @click=\"handleApply\"\n >\n Apply\n </BaseButton>\n </div>\n </template>\n </StepWizard>\n </div>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/auto-group-modal.css';\n</style>\n","<script setup lang=\"ts\">\n/** Single draggable sample row used by grouped and ungrouped SampleSelector lists. */\nimport { computed } from 'vue'\n\ntype CheckboxSize = 'small' | 'tiny'\n\ninterface Props {\n sample: string\n selected: boolean\n dragging?: boolean\n accentColor?: string\n checkboxSize?: CheckboxSize\n removable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n dragging: false,\n checkboxSize: 'tiny',\n removable: false,\n})\n\nconst emit = defineEmits<{\n toggle: []\n remove: []\n}>()\n\nconst checkboxClasses = computed(() => [\n 'mint-sample-selector__checkbox',\n `mint-sample-selector__checkbox--${props.checkboxSize}`,\n])\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-sample-selector__sample',\n dragging ? 'mint-sample-selector__sample--dragging' : '',\n ]\"\n draggable=\"true\"\n >\n <svg class=\"mint-sample-selector__drag-handle\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8h16M4 16h16\" />\n </svg>\n <input\n type=\"checkbox\"\n :checked=\"selected\"\n :class=\"checkboxClasses\"\n :style=\"accentColor ? { accentColor } : undefined\"\n @change=\"emit('toggle')\"\n />\n <span class=\"mint-sample-selector__sample-name\">{{ sample }}</span>\n <button\n v-if=\"removable\"\n type=\"button\"\n class=\"mint-sample-selector__remove-btn\"\n title=\"Remove from group\"\n @click=\"emit('remove')\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 12H4\" />\n </svg>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Single draggable sample row used by grouped and ungrouped SampleSelector lists. */\nimport { computed } from 'vue'\n\ntype CheckboxSize = 'small' | 'tiny'\n\ninterface Props {\n sample: string\n selected: boolean\n dragging?: boolean\n accentColor?: string\n checkboxSize?: CheckboxSize\n removable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n dragging: false,\n checkboxSize: 'tiny',\n removable: false,\n})\n\nconst emit = defineEmits<{\n toggle: []\n remove: []\n}>()\n\nconst checkboxClasses = computed(() => [\n 'mint-sample-selector__checkbox',\n `mint-sample-selector__checkbox--${props.checkboxSize}`,\n])\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-sample-selector__sample',\n dragging ? 'mint-sample-selector__sample--dragging' : '',\n ]\"\n draggable=\"true\"\n >\n <svg class=\"mint-sample-selector__drag-handle\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8h16M4 16h16\" />\n </svg>\n <input\n type=\"checkbox\"\n :checked=\"selected\"\n :class=\"checkboxClasses\"\n :style=\"accentColor ? { accentColor } : undefined\"\n @change=\"emit('toggle')\"\n />\n <span class=\"mint-sample-selector__sample-name\">{{ sample }}</span>\n <button\n v-if=\"removable\"\n type=\"button\"\n class=\"mint-sample-selector__remove-btn\"\n title=\"Remove from group\"\n @click=\"emit('remove')\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 12H4\" />\n </svg>\n </button>\n </div>\n</template>\n","import type { SampleGroup } from '../types'\n\nexport type GroupDragKind = 'major' | 'sub' | 'flat'\nexport type GroupReorderPosition = 'before' | 'after'\n\nexport function detectSampleGroupSeparator(groups: SampleGroup[]): string {\n return groups.some(group => group.name.includes('/')) ? '/' : '_'\n}\n\nexport function getSampleGroupMajorPrefix(groupName: string, separator: string): string {\n const parts = groupName.split(separator)\n return parts.length > 1 ? parts[0] : groupName\n}\n\nexport function moveSampleToGroup(\n groups: SampleGroup[],\n sample: string,\n sourceGroup: string | null,\n targetGroup: string,\n): SampleGroup[] {\n if (sourceGroup === targetGroup) return groups\n\n return groups.map(group => {\n if (sourceGroup && group.name === sourceGroup) {\n return { ...group, samples: group.samples.filter(item => item !== sample) }\n }\n if (group.name === targetGroup && !group.samples.includes(sample)) {\n return { ...group, samples: [...group.samples, sample] }\n }\n return group\n })\n}\n\nexport function removeSampleGroup(groups: SampleGroup[], groupName: string): SampleGroup[] {\n return groups.filter(group => group.name !== groupName)\n}\n\nexport function removeSampleMajorGroup(\n groups: SampleGroup[],\n majorGroup: { subGroups: Array<{ name: string }> },\n): SampleGroup[] {\n const groupNames = new Set(majorGroup.subGroups.map(group => group.name))\n return groups.filter(group => !groupNames.has(group.name))\n}\n\nexport function removeSampleFromGroup(\n groups: SampleGroup[],\n sample: string,\n groupName: string,\n): SampleGroup[] {\n return groups.map(group =>\n group.name === groupName\n ? { ...group, samples: group.samples.filter(item => item !== sample) }\n : group,\n )\n}\n\nexport function reorderSampleGroup(\n groups: SampleGroup[],\n source: string,\n target: string,\n position: GroupReorderPosition,\n): SampleGroup[] {\n if (source === target) return groups\n\n const nextGroups = [...groups]\n const sourceIndex = nextGroups.findIndex(group => group.name === source)\n if (sourceIndex === -1) return groups\n\n const [item] = nextGroups.splice(sourceIndex, 1)\n const targetIndex = nextGroups.findIndex(group => group.name === target)\n if (targetIndex === -1) return groups\n\n nextGroups.splice(position === 'before' ? targetIndex : targetIndex + 1, 0, item)\n return nextGroups\n}\n\nexport function reorderSampleMajorGroup(\n groups: SampleGroup[],\n source: string,\n target: string,\n position: GroupReorderPosition,\n): SampleGroup[] {\n if (source === target) return groups\n\n const separator = detectSampleGroupSeparator(groups)\n const isSource = (group: SampleGroup) => getSampleGroupMajorPrefix(group.name, separator) === source\n const isTarget = (group: SampleGroup) => getSampleGroupMajorPrefix(group.name, separator) === target\n\n const sourceGroups = groups.filter(isSource)\n if (sourceGroups.length === 0) return groups\n\n const remainingGroups = groups.filter(group => !isSource(group))\n let firstTargetIndex = -1\n let lastTargetIndex = -1\n\n remainingGroups.forEach((group, index) => {\n if (isTarget(group)) {\n if (firstTargetIndex === -1) firstTargetIndex = index\n lastTargetIndex = index\n }\n })\n\n if (firstTargetIndex === -1) return groups\n\n const insertIndex = position === 'before' ? firstTargetIndex : lastTargetIndex + 1\n remainingGroups.splice(insertIndex, 0, ...sourceGroups)\n return remainingGroups\n}\n","import { ref } from 'vue'\nimport type { SampleGroup } from '../types'\nimport {\n detectSampleGroupSeparator,\n getSampleGroupMajorPrefix,\n moveSampleToGroup,\n reorderSampleGroup,\n reorderSampleMajorGroup,\n type GroupDragKind,\n type GroupReorderPosition,\n} from './SampleSelector.groups'\n\ninterface MutableSampleGroups {\n value: SampleGroup[]\n}\n\nexport function useSampleSelectorDrag(groups: MutableSampleGroups) {\n const draggingSample = ref<string | null>(null)\n const dragSourceGroup = ref<string | null>(null)\n const dragOverGroup = ref<string | null>(null)\n\n const draggingGroup = ref<string | null>(null)\n const draggingGroupKind = ref<GroupDragKind | null>(null)\n const reorderTarget = ref<string | null>(null)\n const reorderPosition = ref<GroupReorderPosition | null>(null)\n\n function handleDragStart(sample: string, sourceGroup: string | null, event: DragEvent) {\n draggingSample.value = sample\n dragSourceGroup.value = sourceGroup\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', sample)\n }\n }\n\n function resetDragState() {\n draggingSample.value = null\n dragSourceGroup.value = null\n dragOverGroup.value = null\n }\n\n function handleDragEnd() {\n resetDragState()\n }\n\n function handleDragOver(groupName: string, event: DragEvent) {\n // Only accept this drop target when a sample is being dragged; group drags are handled separately.\n if (!draggingSample.value) return\n event.preventDefault()\n if (event.dataTransfer) {\n event.dataTransfer.dropEffect = 'move'\n }\n dragOverGroup.value = groupName\n }\n\n function handleDragLeave() {\n dragOverGroup.value = null\n }\n\n function handleDrop(targetGroupName: string, event: DragEvent) {\n if (!draggingSample.value) return\n event.preventDefault()\n\n const sample = draggingSample.value\n const sourceGroup = dragSourceGroup.value\n\n if (sourceGroup === targetGroupName) {\n resetDragState()\n return\n }\n\n groups.value = moveSampleToGroup(groups.value, sample, sourceGroup, targetGroupName)\n resetDragState()\n }\n\n function handleGroupDragStart(name: string, kind: GroupDragKind, event: DragEvent) {\n draggingGroup.value = name\n draggingGroupKind.value = kind\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', `mint-group:${name}`)\n }\n }\n\n function handleGroupDragEnd() {\n draggingGroup.value = null\n draggingGroupKind.value = null\n reorderTarget.value = null\n reorderPosition.value = null\n }\n\n function handleGroupDragOver(name: string, kind: GroupDragKind, event: DragEvent) {\n if (!draggingGroup.value || draggingGroupKind.value !== kind) return\n if (draggingGroup.value === name) return\n\n // Sub-group reorder is restricted to siblings under the same major prefix;\n // crossing majors would silently rename the group, which is too magical.\n if (kind === 'sub') {\n const separator = detectSampleGroupSeparator(groups.value)\n if (\n getSampleGroupMajorPrefix(draggingGroup.value, separator) !==\n getSampleGroupMajorPrefix(name, separator)\n ) return\n }\n\n event.preventDefault()\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'\n\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()\n reorderTarget.value = name\n reorderPosition.value = event.clientY < rect.top + rect.height / 2 ? 'before' : 'after'\n }\n\n function handleGroupDragLeave(event: DragEvent) {\n const cur = event.currentTarget as HTMLElement\n const rel = event.relatedTarget as Node | null\n if (rel && cur.contains(rel)) return\n reorderTarget.value = null\n reorderPosition.value = null\n }\n\n function handleGroupDrop(name: string, kind: GroupDragKind, event: DragEvent) {\n if (!draggingGroup.value || draggingGroupKind.value !== kind) {\n handleGroupDragEnd()\n return\n }\n const position = reorderPosition.value\n if (!position || draggingGroup.value === name) {\n handleGroupDragEnd()\n return\n }\n\n event.preventDefault()\n\n if (kind === 'major') {\n const nextGroups = reorderSampleMajorGroup(groups.value, draggingGroup.value, name, position)\n if (nextGroups !== groups.value) groups.value = nextGroups\n } else {\n const nextGroups = reorderSampleGroup(groups.value, draggingGroup.value, name, position)\n if (nextGroups !== groups.value) groups.value = nextGroups\n }\n\n handleGroupDragEnd()\n }\n\n return {\n draggingSample,\n dragOverGroup,\n draggingGroup,\n draggingGroupKind,\n reorderTarget,\n reorderPosition,\n handleDragStart,\n handleDragEnd,\n handleDragOver,\n handleDragLeave,\n handleDrop,\n handleGroupDragStart,\n handleGroupDragEnd,\n handleGroupDragOver,\n handleGroupDragLeave,\n handleGroupDrop,\n }\n}\n","import type { ComputedRef } from 'vue'\nimport { useListSelection } from '../composables/useListSelection'\nimport type { SampleMajorGroup } from '../composables/useSampleGroups'\n\ninterface SampleGroupSelectionSource {\n samples: string[]\n}\n\nexport interface UseSampleSelectorSelectionOptions {\n selected: () => string[]\n samples: () => string[]\n findGroup: (groupName: string) => SampleGroupSelectionSource | undefined\n emitSelected: (samples: string[]) => void\n}\n\nexport interface UseSampleSelectorSelectionReturn {\n isAllSelected: ComputedRef<boolean>\n toggleSelectAll: () => void\n toggleSample: (sample: string) => void\n toggleGroupSamples: (groupName: string) => void\n toggleMajorGroupSamples: (majorGroup: SampleMajorGroup) => void\n isFullySelected: (samples: string[]) => boolean\n isPartiallySelected: (samples: string[]) => boolean\n isGroupFullySelected: (groupName: string) => boolean\n isGroupPartiallySelected: (groupName: string) => boolean\n isMajorGroupFullySelected: (majorGroup: SampleMajorGroup) => boolean\n isMajorGroupPartiallySelected: (majorGroup: SampleMajorGroup) => boolean\n}\n\nexport function useSampleSelectorSelection(\n options: UseSampleSelectorSelectionOptions,\n): UseSampleSelectorSelectionReturn {\n const sampleSelection = useListSelection({\n selected: options.selected,\n items: options.samples,\n })\n\n function emitSelection(samples: string[]) {\n options.emitSelected(samples)\n }\n\n function toggleSelectAll() {\n emitSelection(sampleSelection.toggleAll())\n }\n\n function toggleSample(sample: string) {\n emitSelection(sampleSelection.toggleValue(sample))\n }\n\n function toggleSamplesSelection(samples: string[]) {\n emitSelection(sampleSelection.toggleValues(samples))\n }\n\n function toggleGroupSamples(groupName: string) {\n const group = options.findGroup(groupName)\n if (!group) return\n toggleSamplesSelection(group.samples)\n }\n\n function toggleMajorGroupSamples(majorGroup: SampleMajorGroup) {\n toggleSamplesSelection(majorGroup.allSamples)\n }\n\n function isFullySelected(samples: string[]): boolean {\n return sampleSelection.isFullySelected(samples)\n }\n\n function isPartiallySelected(samples: string[]): boolean {\n return sampleSelection.isPartiallySelected(samples)\n }\n\n function isGroupFullySelected(groupName: string): boolean {\n const group = options.findGroup(groupName)\n return group ? isFullySelected(group.samples) : false\n }\n\n function isGroupPartiallySelected(groupName: string): boolean {\n const group = options.findGroup(groupName)\n return group ? isPartiallySelected(group.samples) : false\n }\n\n function isMajorGroupFullySelected(majorGroup: SampleMajorGroup): boolean {\n return isFullySelected(majorGroup.allSamples)\n }\n\n function isMajorGroupPartiallySelected(majorGroup: SampleMajorGroup): boolean {\n return isPartiallySelected(majorGroup.allSamples)\n }\n\n return {\n isAllSelected: sampleSelection.isAllSelected,\n toggleSelectAll,\n toggleSample,\n toggleGroupSamples,\n toggleMajorGroupSamples,\n isFullySelected,\n isPartiallySelected,\n isGroupFullySelected,\n isGroupPartiallySelected,\n isMajorGroupFullySelected,\n isMajorGroupPartiallySelected,\n }\n}\n","import { DEFAULT_COLORS } from '../composables/useAutoGroup'\nimport type { SampleGroup } from '../types'\n\nexport type ColorEdit =\n | { kind: 'single'; name: string }\n | { kind: 'family'; names: string[] }\n\nexport const DEFAULT_COLOR_PICKER_SEED = '#3B82F6'\n\nexport function applySampleGroupColorEdit(\n groups: SampleGroup[],\n edit: ColorEdit,\n color: string,\n): SampleGroup[] {\n const targets = edit.kind === 'family' ? new Set(edit.names) : new Set([edit.name])\n return groups.map(group =>\n targets.has(group.name) ? { ...group, color } : group,\n )\n}\n\nexport function getSampleGroupColorEditSeed(\n edit: ColorEdit | null,\n getGroupColor: (groupName: string) => string,\n): string {\n if (!edit) return DEFAULT_COLOR_PICKER_SEED\n return getGroupColor(edit.kind === 'family' ? edit.names[0] : edit.name)\n}\n\nexport function pickUnusedSampleGroupColor(groups: SampleGroup[]): string {\n const usedColors = new Set(groups.map(group => group.color))\n return DEFAULT_COLORS.find(color => !usedColors.has(color)) || DEFAULT_COLORS[0]\n}\n\nexport function createSampleGroup(name: string, existingGroups: SampleGroup[]): SampleGroup | null {\n const trimmedName = name.trim()\n if (!trimmedName) return null\n\n return {\n name: trimmedName,\n color: pickUnusedSampleGroupColor(existingGroups),\n samples: [],\n }\n}\n","<script setup lang=\"ts\">\n/** Interactive sample list with drag-and-drop group assignment, color labeling, and smart-group support. */\nimport { ref, computed } from 'vue'\nimport BaseButton from './BaseButton.vue'\nimport BaseInput from './BaseInput.vue'\nimport AutoGroupModal from './AutoGroupModal.vue'\nimport SampleSelectorSampleRow from './SampleSelectorSampleRow.vue'\nimport type { SampleGroup } from '../types'\nimport type { AutoGroupResult } from '../types/auto-group'\nimport { useTextSearch } from '../composables/useTextSearch'\nimport { useSampleGroups, type SampleMajorGroup } from '../composables/useSampleGroups'\nimport { useExpansionSet } from '../composables/useExpansionSet'\nimport { useExperimentSamples } from '../composables/useExperimentSamples'\nimport { useSampleSelectorDrag } from './SampleSelector.drag'\nimport { useSampleSelectorSelection } from './SampleSelector.selection'\nimport {\n applySampleGroupColorEdit,\n createSampleGroup,\n getSampleGroupColorEditSeed,\n type ColorEdit,\n} from './SampleSelector.colors'\nimport {\n removeSampleFromGroup as removeSampleFromSampleGroup,\n removeSampleGroup,\n removeSampleMajorGroup,\n} from './SampleSelector.groups'\n\ninterface Props {\n samples?: string[]\n modelValue: string[]\n groups?: SampleGroup[]\n enableGrouping?: boolean\n enableSmartGroup?: boolean\n experimentId?: number\n designData?: Record<string, unknown>\n autoloadExperimentData?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n samples: () => [],\n groups: () => [],\n enableGrouping: true,\n enableSmartGroup: true,\n experimentId: undefined,\n designData: undefined,\n autoloadExperimentData: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [samples: string[]]\n 'update:groups': [groups: SampleGroup[]]\n smartGroup: [result: AutoGroupResult]\n}>()\n\n// UI State\nconst showSmartGroupModal = ref(false)\nconst newGroupName = ref('')\nconst editingColor = ref<ColorEdit | null>(null)\nconst colorPickerInput = ref<HTMLInputElement | null>(null)\nconst searchQuery = ref('')\nconst groupExpansion = useExpansionSet()\n\n// Computed: groups from props\nconst internalGroups = computed({\n get: () => props.groups,\n set: (value) => emit('update:groups', value),\n})\n\nconst {\n draggingSample,\n dragOverGroup,\n draggingGroup,\n draggingGroupKind,\n reorderTarget,\n reorderPosition,\n handleDragStart,\n handleDragEnd,\n handleDragOver,\n handleDragLeave,\n handleDrop,\n handleGroupDragStart,\n handleGroupDragEnd,\n handleGroupDragOver,\n handleGroupDragLeave,\n handleGroupDrop,\n} = useSampleSelectorDrag(internalGroups)\n\nconst providedSamples = computed(() => props.samples)\nconst shouldLoadExperimentSamples = computed(() =>\n props.autoloadExperimentData && providedSamples.value.length === 0,\n)\nconst experimentSamples = useExperimentSamples({\n experimentId: () => props.experimentId,\n designData: () => props.designData,\n enabled: shouldLoadExperimentSamples,\n})\nconst resolvedSamples = computed(() =>\n providedSamples.value.length > 0 ? providedSamples.value : experimentSamples.samples.value,\n)\nconst resolvedExperimentId = computed(() => props.experimentId ?? experimentSamples.experimentId.value)\nconst resolvedDesignData = computed(() => props.designData ?? experimentSamples.designData.value ?? undefined)\n\nconst sampleGroups = useSampleGroups({\n samples: () => resolvedSamples.value,\n groups: internalGroups,\n})\nconst hierarchicalGroups = sampleGroups.hierarchicalGroups\nconst showHierarchy = sampleGroups.showHierarchy\n\nconst groupingEnabled = computed(() => internalGroups.value.length > 0)\nconst ungroupedSamples = sampleGroups.ungroupedSamples\n\nconst sampleSearch = useTextSearch({\n items: () => resolvedSamples.value,\n query: searchQuery,\n getText: sample => sample,\n})\nconst filteredSamples = sampleSearch.filteredItems\n\nconst {\n isAllSelected,\n toggleSelectAll,\n toggleSample,\n toggleGroupSamples,\n toggleMajorGroupSamples,\n isGroupFullySelected,\n isGroupPartiallySelected,\n isMajorGroupFullySelected,\n isMajorGroupPartiallySelected,\n} = useSampleSelectorSelection({\n selected: () => props.modelValue,\n samples: () => resolvedSamples.value,\n findGroup: sampleGroups.findGroup,\n emitSelected: samples => emit('update:modelValue', samples),\n})\n\n// Expand/collapse\nfunction toggleGroupExpanded(groupName: string) {\n groupExpansion.toggle(groupName)\n}\n\nfunction isGroupExpanded(groupName: string): boolean {\n return groupExpansion.isExpanded(groupName)\n}\n\nfunction expandAllGroups() {\n const expanded: string[] = []\n for (const major of hierarchicalGroups.value) {\n expanded.push(`major:${major.name}`)\n for (const sub of major.subGroups) {\n expanded.push(sub.name)\n }\n }\n expanded.push('__ungrouped__')\n groupExpansion.setExpanded(expanded)\n}\n\nfunction collapseAllGroups() {\n groupExpansion.collapseAll()\n}\n\n// Smart group\nfunction handleSmartGroupApply(result: AutoGroupResult) {\n emit('smartGroup', result)\n emit('update:groups', result.groups)\n}\n\n// Group management\nfunction clearGroups() {\n internalGroups.value = []\n}\n\nfunction deleteMajorGroup(majorGroup: SampleMajorGroup) {\n internalGroups.value = removeSampleMajorGroup(internalGroups.value, majorGroup)\n}\n\nfunction deleteGroup(groupName: string) {\n internalGroups.value = removeSampleGroup(internalGroups.value, groupName)\n}\n\nfunction removeSampleFromGroup(sample: string, groupName: string) {\n internalGroups.value = removeSampleFromSampleGroup(internalGroups.value, sample, groupName)\n}\n\n// Color picker\nfunction openColorPicker(groupName: string, event: Event) {\n event.stopPropagation()\n editingColor.value = { kind: 'single', name: groupName }\n colorPickerInput.value?.click()\n}\n\nfunction openMajorGroupColorPicker(majorGroup: SampleMajorGroup, event: Event) {\n event.stopPropagation()\n editingColor.value = { kind: 'family', names: majorGroup.subGroups.map(sg => sg.name) }\n colorPickerInput.value?.click()\n}\n\nfunction handleColorChange(event: Event) {\n const edit = editingColor.value\n if (!edit) return\n\n const newColor = (event.target as HTMLInputElement).value\n internalGroups.value = applySampleGroupColorEdit(internalGroups.value, edit, newColor)\n editingColor.value = null\n}\n\nfunction getGroupColor(groupName: string): string {\n return sampleGroups.getGroupColor(groupName)\n}\n\nconst colorPickerSeed = computed(() => {\n return getSampleGroupColorEditSeed(editingColor.value, getGroupColor)\n})\n\n// New group\nfunction addNewGroup() {\n const newGroup = createSampleGroup(newGroupName.value, internalGroups.value)\n if (!newGroup) return\n internalGroups.value = [...internalGroups.value, newGroup]\n newGroupName.value = ''\n}\n\ndefineExpose({ handleSmartGroupApply })\n</script>\n\n<template>\n <div class=\"mint-sample-selector\">\n <!-- Select All Row -->\n <label class=\"mint-sample-selector__select-all\">\n <input\n type=\"checkbox\"\n :checked=\"isAllSelected\"\n @change=\"toggleSelectAll\"\n class=\"mint-sample-selector__checkbox\"\n />\n <span class=\"mint-sample-selector__select-all-label\">Select All</span>\n <span class=\"mint-sample-selector__select-all-count\">{{ resolvedSamples.length }} samples</span>\n </label>\n\n <!-- Action Buttons Row -->\n <div v-if=\"enableGrouping\" class=\"mint-sample-selector__actions\">\n <div class=\"mint-sample-selector__actions-row\">\n <!-- Smart Group Button -->\n <BaseButton\n v-if=\"enableSmartGroup\"\n :variant=\"groupingEnabled ? 'primary' : 'secondary'\"\n size=\"sm\"\n :disabled=\"resolvedSamples.length === 0\"\n class=\"mint-sample-selector__action-btn\"\n @click=\"showSmartGroupModal = true\"\n >\n <svg class=\"mint-sample-selector__action-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n <span>Smart Group</span>\n </BaseButton>\n\n <!-- Reset Button -->\n <BaseButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"internalGroups.length === 0\"\n class=\"mint-sample-selector__action-btn mint-sample-selector__action-btn--reset\"\n @click=\"clearGroups\"\n title=\"Clear all groups\"\n >\n <svg class=\"mint-sample-selector__action-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n </BaseButton>\n </div>\n </div>\n\n <!-- Grouped View -->\n <div v-if=\"groupingEnabled\" class=\"mint-sample-selector__grouped\">\n <!-- Groups Header -->\n <div class=\"mint-sample-selector__groups-header\">\n <span class=\"mint-sample-selector__groups-title\">Groups ({{ internalGroups.length }})</span>\n <div class=\"mint-sample-selector__groups-controls\">\n <button type=\"button\" class=\"mint-sample-selector__expand-btn\" @click=\"expandAllGroups\" title=\"Expand all\">\n <svg class=\"mint-sample-selector__expand-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n <button type=\"button\" class=\"mint-sample-selector__expand-btn\" @click=\"collapseAllGroups\" title=\"Collapse all\">\n <svg class=\"mint-sample-selector__expand-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n </div>\n\n <!-- Hierarchical Tree -->\n <div class=\"mint-sample-selector__tree\">\n <!-- Major Groups (when hierarchy is meaningful) -->\n <template v-if=\"showHierarchy\">\n <div\n v-for=\"majorGroup in hierarchicalGroups\"\n :key=\"majorGroup.name\"\n class=\"mint-sample-selector__major-group\"\n >\n <!-- Major Group Header -->\n <div\n :class=\"[\n 'mint-sample-selector__major-header',\n draggingGroup === majorGroup.name && draggingGroupKind === 'major' ? 'mint-sample-selector__header--dragging' : '',\n reorderTarget === majorGroup.name && draggingGroupKind === 'major' && reorderPosition === 'before' ? 'mint-sample-selector__header--drag-over-before' : '',\n reorderTarget === majorGroup.name && draggingGroupKind === 'major' && reorderPosition === 'after' ? 'mint-sample-selector__header--drag-over-after' : '',\n ]\"\n draggable=\"true\"\n @click=\"toggleGroupExpanded(`major:${majorGroup.name}`)\"\n @dragstart=\"handleGroupDragStart(majorGroup.name, 'major', $event)\"\n @dragend=\"handleGroupDragEnd\"\n @dragover=\"handleGroupDragOver(majorGroup.name, 'major', $event)\"\n @dragleave=\"handleGroupDragLeave($event)\"\n @drop=\"handleGroupDrop(majorGroup.name, 'major', $event)\"\n >\n <svg\n :class=\"[\n 'mint-sample-selector__chevron',\n isGroupExpanded(`major:${majorGroup.name}`) ? 'mint-sample-selector__chevron--open' : '',\n ]\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n\n <input\n type=\"checkbox\"\n :checked=\"isMajorGroupFullySelected(majorGroup)\"\n :indeterminate=\"isMajorGroupPartiallySelected(majorGroup)\"\n class=\"mint-sample-selector__checkbox\"\n :style=\"{ accentColor: majorGroup.color }\"\n @click.stop\n @change=\"toggleMajorGroupSamples(majorGroup)\"\n />\n\n <button\n type=\"button\"\n class=\"mint-sample-selector__color-dot mint-sample-selector__color-dot--large mint-sample-selector__color-dot--clickable\"\n :style=\"{ backgroundColor: majorGroup.color }\"\n @click.stop=\"openMajorGroupColorPicker(majorGroup, $event)\"\n title=\"Click to change color family\"\n />\n\n <span class=\"mint-sample-selector__major-name\">{{ majorGroup.name }}</span>\n\n <span\n class=\"mint-sample-selector__count-badge\"\n :style=\"{ backgroundColor: majorGroup.color + '20', color: majorGroup.color }\"\n >\n {{ majorGroup.allSamples.length }}\n </span>\n\n <button\n type=\"button\"\n class=\"mint-sample-selector__delete-btn mint-sample-selector__delete-btn--hidden\"\n @click.stop=\"deleteMajorGroup(majorGroup)\"\n title=\"Remove major group\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- Sub Groups (collapsible) -->\n <Transition name=\"mint-collapse\">\n <div\n v-if=\"isGroupExpanded(`major:${majorGroup.name}`)\"\n class=\"mint-sample-selector__sub-groups\"\n :style=\"{ borderColor: majorGroup.color + '30' }\"\n >\n <div\n v-for=\"subGroup in majorGroup.subGroups\"\n :key=\"subGroup.name\"\n :class=\"[\n 'mint-sample-selector__sub-group',\n dragOverGroup === subGroup.name ? 'mint-sample-selector__sub-group--drag-over' : '',\n ]\"\n @dragover=\"handleDragOver(subGroup.name, $event)\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop(subGroup.name, $event)\"\n >\n <!-- Sub Group Header -->\n <div\n :class=\"[\n 'mint-sample-selector__sub-header',\n draggingGroup === subGroup.name && draggingGroupKind === 'sub' ? 'mint-sample-selector__header--dragging' : '',\n reorderTarget === subGroup.name && draggingGroupKind === 'sub' && reorderPosition === 'before' ? 'mint-sample-selector__header--drag-over-before' : '',\n reorderTarget === subGroup.name && draggingGroupKind === 'sub' && reorderPosition === 'after' ? 'mint-sample-selector__header--drag-over-after' : '',\n ]\"\n draggable=\"true\"\n @click=\"toggleGroupExpanded(subGroup.name)\"\n @dragstart=\"handleGroupDragStart(subGroup.name, 'sub', $event)\"\n @dragend=\"handleGroupDragEnd\"\n @dragover=\"handleGroupDragOver(subGroup.name, 'sub', $event)\"\n @dragleave=\"handleGroupDragLeave($event)\"\n @drop=\"handleGroupDrop(subGroup.name, 'sub', $event)\"\n >\n <svg\n :class=\"[\n 'mint-sample-selector__chevron mint-sample-selector__chevron--small',\n isGroupExpanded(subGroup.name) ? 'mint-sample-selector__chevron--open' : '',\n ]\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n\n <input\n type=\"checkbox\"\n :checked=\"isGroupFullySelected(subGroup.name)\"\n :indeterminate=\"isGroupPartiallySelected(subGroup.name)\"\n class=\"mint-sample-selector__checkbox mint-sample-selector__checkbox--small\"\n :style=\"{ accentColor: subGroup.displayColor }\"\n @click.stop\n @change=\"toggleGroupSamples(subGroup.name)\"\n />\n\n <div\n class=\"mint-sample-selector__color-dot\"\n :style=\"{ backgroundColor: subGroup.displayColor }\"\n aria-hidden=\"true\"\n />\n\n <span class=\"mint-sample-selector__sub-name\">{{ subGroup.name }}</span>\n\n <span\n class=\"mint-sample-selector__count-badge mint-sample-selector__count-badge--small\"\n :style=\"{ backgroundColor: subGroup.displayBg, color: subGroup.displayColor }\"\n >\n {{ subGroup.samples.length }}\n </span>\n\n <button\n type=\"button\"\n class=\"mint-sample-selector__delete-btn mint-sample-selector__delete-btn--hidden\"\n @click.stop=\"deleteGroup(subGroup.name)\"\n title=\"Remove sub group\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- Samples (collapsible) -->\n <Transition name=\"mint-collapse\">\n <div\n v-if=\"isGroupExpanded(subGroup.name)\"\n class=\"mint-sample-selector__samples\"\n :style=\"{ borderColor: subGroup.displayBorder }\"\n >\n <SampleSelectorSampleRow\n v-for=\"sample in subGroup.samples\"\n :key=\"sample\"\n :sample=\"sample\"\n :selected=\"modelValue.includes(sample)\"\n :dragging=\"draggingSample === sample\"\n :accent-color=\"subGroup.displayColor\"\n checkbox-size=\"tiny\"\n removable\n @dragstart=\"handleDragStart(sample, subGroup.name, $event)\"\n @dragend=\"handleDragEnd\"\n @toggle=\"toggleSample(sample)\"\n @remove=\"removeSampleFromGroup(sample, subGroup.name)\"\n />\n </div>\n </Transition>\n </div>\n </div>\n </Transition>\n </div>\n </template>\n\n <!-- Flat Groups (when no meaningful hierarchy - e.g., level 1) -->\n <template v-else>\n <div\n v-for=\"group in internalGroups\"\n :key=\"group.name\"\n :class=\"[\n 'mint-sample-selector__sub-group',\n dragOverGroup === group.name ? 'mint-sample-selector__sub-group--drag-over' : '',\n ]\"\n @dragover=\"handleDragOver(group.name, $event)\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop(group.name, $event)\"\n >\n <!-- Group Header -->\n <div\n :class=\"[\n 'mint-sample-selector__major-header',\n draggingGroup === group.name && draggingGroupKind === 'flat' ? 'mint-sample-selector__header--dragging' : '',\n reorderTarget === group.name && draggingGroupKind === 'flat' && reorderPosition === 'before' ? 'mint-sample-selector__header--drag-over-before' : '',\n reorderTarget === group.name && draggingGroupKind === 'flat' && reorderPosition === 'after' ? 'mint-sample-selector__header--drag-over-after' : '',\n ]\"\n draggable=\"true\"\n @click=\"toggleGroupExpanded(group.name)\"\n @dragstart=\"handleGroupDragStart(group.name, 'flat', $event)\"\n @dragend=\"handleGroupDragEnd\"\n @dragover=\"handleGroupDragOver(group.name, 'flat', $event)\"\n @dragleave=\"handleGroupDragLeave($event)\"\n @drop=\"handleGroupDrop(group.name, 'flat', $event)\"\n >\n <svg\n :class=\"[\n 'mint-sample-selector__chevron',\n isGroupExpanded(group.name) ? 'mint-sample-selector__chevron--open' : '',\n ]\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n\n <input\n type=\"checkbox\"\n :checked=\"isGroupFullySelected(group.name)\"\n :indeterminate=\"isGroupPartiallySelected(group.name)\"\n class=\"mint-sample-selector__checkbox\"\n :style=\"{ accentColor: group.color }\"\n @click.stop\n @change=\"toggleGroupSamples(group.name)\"\n />\n\n <button\n type=\"button\"\n class=\"mint-sample-selector__color-dot mint-sample-selector__color-dot--large mint-sample-selector__color-dot--clickable\"\n :style=\"{ backgroundColor: group.color }\"\n @click.stop=\"openColorPicker(group.name, $event)\"\n title=\"Click to change color\"\n />\n\n <span class=\"mint-sample-selector__major-name\">{{ group.name }}</span>\n\n <span\n class=\"mint-sample-selector__count-badge\"\n :style=\"{ backgroundColor: group.color + '20', color: group.color }\"\n >\n {{ group.samples.length }}\n </span>\n\n <button\n type=\"button\"\n class=\"mint-sample-selector__delete-btn mint-sample-selector__delete-btn--hidden\"\n @click.stop=\"deleteGroup(group.name)\"\n title=\"Remove group\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- Samples (collapsible) -->\n <Transition name=\"mint-collapse\">\n <div\n v-if=\"isGroupExpanded(group.name)\"\n class=\"mint-sample-selector__samples\"\n :style=\"{ borderColor: group.color + '40' }\"\n >\n <SampleSelectorSampleRow\n v-for=\"sample in group.samples\"\n :key=\"sample\"\n :sample=\"sample\"\n :selected=\"modelValue.includes(sample)\"\n :dragging=\"draggingSample === sample\"\n :accent-color=\"group.color\"\n checkbox-size=\"tiny\"\n removable\n @dragstart=\"handleDragStart(sample, group.name, $event)\"\n @dragend=\"handleDragEnd\"\n @toggle=\"toggleSample(sample)\"\n @remove=\"removeSampleFromGroup(sample, group.name)\"\n />\n </div>\n </Transition>\n </div>\n </template>\n\n <!-- Empty state -->\n <div v-if=\"internalGroups.length === 0\" class=\"mint-sample-selector__empty\">\n Click Smart Group to auto-group samples\n </div>\n </div>\n\n <!-- Ungrouped Samples Section -->\n <div v-if=\"ungroupedSamples.length > 0\" class=\"mint-sample-selector__ungrouped\">\n <div\n class=\"mint-sample-selector__ungrouped-header\"\n @click=\"toggleGroupExpanded('__ungrouped__')\"\n >\n <svg\n :class=\"[\n 'mint-sample-selector__chevron',\n isGroupExpanded('__ungrouped__') ? 'mint-sample-selector__chevron--open' : '',\n ]\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n <span class=\"mint-sample-selector__ungrouped-label\">Ungrouped</span>\n <span class=\"mint-sample-selector__ungrouped-count\">{{ ungroupedSamples.length }}</span>\n </div>\n\n <Transition name=\"mint-collapse\">\n <div\n v-if=\"isGroupExpanded('__ungrouped__')\"\n class=\"mint-sample-selector__ungrouped-list\"\n >\n <SampleSelectorSampleRow\n v-for=\"sample in ungroupedSamples\"\n :key=\"sample\"\n :sample=\"sample\"\n :selected=\"modelValue.includes(sample)\"\n :dragging=\"draggingSample === sample\"\n checkbox-size=\"small\"\n @dragstart=\"handleDragStart(sample, null, $event)\"\n @dragend=\"handleDragEnd\"\n @toggle=\"toggleSample(sample)\"\n />\n </div>\n </Transition>\n </div>\n\n <!-- New Group Input -->\n <div class=\"mint-sample-selector__new-group\">\n <BaseInput\n v-model=\"newGroupName\"\n placeholder=\"New group name...\"\n class=\"mint-sample-selector__new-group-input\"\n @keyup.enter=\"addNewGroup\"\n />\n <BaseButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"!newGroupName.trim()\"\n class=\"mint-sample-selector__new-group-btn\"\n @click=\"addNewGroup\"\n >\n Add\n </BaseButton>\n </div>\n\n <!-- Hidden color picker input -->\n <input\n ref=\"colorPickerInput\"\n type=\"color\"\n class=\"mint-sample-selector__color-input\"\n :value=\"colorPickerSeed\"\n @change=\"handleColorChange\"\n />\n </div>\n\n <!-- Flat View (when no groups) -->\n <div v-if=\"!groupingEnabled\" class=\"mint-sample-selector__flat\">\n <!-- Search -->\n <div class=\"mint-sample-selector__search\">\n <svg class=\"mint-sample-selector__search-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\n </svg>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search samples...\"\n class=\"mint-sample-selector__search-input\"\n />\n </div>\n\n <!-- Flat samples list -->\n <div class=\"mint-sample-selector__flat-list\">\n <div\n v-for=\"sample in filteredSamples\"\n :key=\"sample\"\n class=\"mint-sample-selector__flat-item\"\n >\n <input\n type=\"checkbox\"\n :checked=\"modelValue.includes(sample)\"\n class=\"mint-sample-selector__checkbox\"\n @change=\"toggleSample(sample)\"\n />\n <span class=\"mint-sample-selector__flat-name\">{{ sample }}</span>\n </div>\n\n <div v-if=\"filteredSamples.length === 0 && searchQuery.trim()\" class=\"mint-sample-selector__empty\">\n No samples match \"{{ searchQuery }}\"\n </div>\n </div>\n </div>\n\n <!-- Smart Grouping Modal -->\n <AutoGroupModal\n v-model=\"showSmartGroupModal\"\n :samples=\"resolvedSamples\"\n :experiment-id=\"resolvedExperimentId\"\n :design-data=\"resolvedDesignData\"\n @apply=\"handleSmartGroupApply\"\n />\n </div>\n</template>\n\n<style>\n@import '../styles/components/sample-selector.css';\n</style>\n","<script setup lang=\"ts\">\n/** Interactive sample list with drag-and-drop group assignment, color labeling, and smart-group support. */\nimport { ref, computed } from 'vue'\nimport BaseButton from './BaseButton.vue'\nimport BaseInput from './BaseInput.vue'\nimport AutoGroupModal from './AutoGroupModal.vue'\nimport SampleSelectorSampleRow from './SampleSelectorSampleRow.vue'\nimport type { SampleGroup } from '../types'\nimport type { AutoGroupResult } from '../types/auto-group'\nimport { useTextSearch } from '../composables/useTextSearch'\nimport { useSampleGroups, type SampleMajorGroup } from '../composables/useSampleGroups'\nimport { useExpansionSet } from '../composables/useExpansionSet'\nimport { useExperimentSamples } from '../composables/useExperimentSamples'\nimport { useSampleSelectorDrag } from './SampleSelector.drag'\nimport { useSampleSelectorSelection } from './SampleSelector.selection'\nimport {\n applySampleGroupColorEdit,\n createSampleGroup,\n getSampleGroupColorEditSeed,\n type ColorEdit,\n} from './SampleSelector.colors'\nimport {\n removeSampleFromGroup as removeSampleFromSampleGroup,\n removeSampleGroup,\n removeSampleMajorGroup,\n} from './SampleSelector.groups'\n\ninterface Props {\n samples?: string[]\n modelValue: string[]\n groups?: SampleGroup[]\n enableGrouping?: boolean\n enableSmartGroup?: boolean\n experimentId?: number\n designData?: Record<string, unknown>\n autoloadExperimentData?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n samples: () => [],\n groups: () => [],\n enableGrouping: true,\n enableSmartGroup: true,\n experimentId: undefined,\n designData: undefined,\n autoloadExperimentData: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [samples: string[]]\n 'update:groups': [groups: SampleGroup[]]\n smartGroup: [result: AutoGroupResult]\n}>()\n\n// UI State\nconst showSmartGroupModal = ref(false)\nconst newGroupName = ref('')\nconst editingColor = ref<ColorEdit | null>(null)\nconst colorPickerInput = ref<HTMLInputElement | null>(null)\nconst searchQuery = ref('')\nconst groupExpansion = useExpansionSet()\n\n// Computed: groups from props\nconst internalGroups = computed({\n get: () => props.groups,\n set: (value) => emit('update:groups', value),\n})\n\nconst {\n draggingSample,\n dragOverGroup,\n draggingGroup,\n draggingGroupKind,\n reorderTarget,\n reorderPosition,\n handleDragStart,\n handleDragEnd,\n handleDragOver,\n handleDragLeave,\n handleDrop,\n handleGroupDragStart,\n handleGroupDragEnd,\n handleGroupDragOver,\n handleGroupDragLeave,\n handleGroupDrop,\n} = useSampleSelectorDrag(internalGroups)\n\nconst providedSamples = computed(() => props.samples)\nconst shouldLoadExperimentSamples = computed(() =>\n props.autoloadExperimentData && providedSamples.value.length === 0,\n)\nconst experimentSamples = useExperimentSamples({\n experimentId: () => props.experimentId,\n designData: () => props.designData,\n enabled: shouldLoadExperimentSamples,\n})\nconst resolvedSamples = computed(() =>\n providedSamples.value.length > 0 ? providedSamples.value : experimentSamples.samples.value,\n)\nconst resolvedExperimentId = computed(() => props.experimentId ?? experimentSamples.experimentId.value)\nconst resolvedDesignData = computed(() => props.designData ?? experimentSamples.designData.value ?? undefined)\n\nconst sampleGroups = useSampleGroups({\n samples: () => resolvedSamples.value,\n groups: internalGroups,\n})\nconst hierarchicalGroups = sampleGroups.hierarchicalGroups\nconst showHierarchy = sampleGroups.showHierarchy\n\nconst groupingEnabled = computed(() => internalGroups.value.length > 0)\nconst ungroupedSamples = sampleGroups.ungroupedSamples\n\nconst sampleSearch = useTextSearch({\n items: () => resolvedSamples.value,\n query: searchQuery,\n getText: sample => sample,\n})\nconst filteredSamples = sampleSearch.filteredItems\n\nconst {\n isAllSelected,\n toggleSelectAll,\n toggleSample,\n toggleGroupSamples,\n toggleMajorGroupSamples,\n isGroupFullySelected,\n isGroupPartiallySelected,\n isMajorGroupFullySelected,\n isMajorGroupPartiallySelected,\n} = useSampleSelectorSelection({\n selected: () => props.modelValue,\n samples: () => resolvedSamples.value,\n findGroup: sampleGroups.findGroup,\n emitSelected: samples => emit('update:modelValue', samples),\n})\n\n// Expand/collapse\nfunction toggleGroupExpanded(groupName: string) {\n groupExpansion.toggle(groupName)\n}\n\nfunction isGroupExpanded(groupName: string): boolean {\n return groupExpansion.isExpanded(groupName)\n}\n\nfunction expandAllGroups() {\n const expanded: string[] = []\n for (const major of hierarchicalGroups.value) {\n expanded.push(`major:${major.name}`)\n for (const sub of major.subGroups) {\n expanded.push(sub.name)\n }\n }\n expanded.push('__ungrouped__')\n groupExpansion.setExpanded(expanded)\n}\n\nfunction collapseAllGroups() {\n groupExpansion.collapseAll()\n}\n\n// Smart group\nfunction handleSmartGroupApply(result: AutoGroupResult) {\n emit('smartGroup', result)\n emit('update:groups', result.groups)\n}\n\n// Group management\nfunction clearGroups() {\n internalGroups.value = []\n}\n\nfunction deleteMajorGroup(majorGroup: SampleMajorGroup) {\n internalGroups.value = removeSampleMajorGroup(internalGroups.value, majorGroup)\n}\n\nfunction deleteGroup(groupName: string) {\n internalGroups.value = removeSampleGroup(internalGroups.value, groupName)\n}\n\nfunction removeSampleFromGroup(sample: string, groupName: string) {\n internalGroups.value = removeSampleFromSampleGroup(internalGroups.value, sample, groupName)\n}\n\n// Color picker\nfunction openColorPicker(groupName: string, event: Event) {\n event.stopPropagation()\n editingColor.value = { kind: 'single', name: groupName }\n colorPickerInput.value?.click()\n}\n\nfunction openMajorGroupColorPicker(majorGroup: SampleMajorGroup, event: Event) {\n event.stopPropagation()\n editingColor.value = { kind: 'family', names: majorGroup.subGroups.map(sg => sg.name) }\n colorPickerInput.value?.click()\n}\n\nfunction handleColorChange(event: Event) {\n const edit = editingColor.value\n if (!edit) return\n\n const newColor = (event.target as HTMLInputElement).value\n internalGroups.value = applySampleGroupColorEdit(internalGroups.value, edit, newColor)\n editingColor.value = null\n}\n\nfunction getGroupColor(groupName: string): string {\n return sampleGroups.getGroupColor(groupName)\n}\n\nconst colorPickerSeed = computed(() => {\n return getSampleGroupColorEditSeed(editingColor.value, getGroupColor)\n})\n\n// New group\nfunction addNewGroup() {\n const newGroup = createSampleGroup(newGroupName.value, internalGroups.value)\n if (!newGroup) return\n internalGroups.value = [...internalGroups.value, newGroup]\n newGroupName.value = ''\n}\n\ndefineExpose({ handleSmartGroupApply })\n</script>\n\n<template>\n <div class=\"mint-sample-selector\">\n <!-- Select All Row -->\n <label class=\"mint-sample-selector__select-all\">\n <input\n type=\"checkbox\"\n :checked=\"isAllSelected\"\n @change=\"toggleSelectAll\"\n class=\"mint-sample-selector__checkbox\"\n />\n <span class=\"mint-sample-selector__select-all-label\">Select All</span>\n <span class=\"mint-sample-selector__select-all-count\">{{ resolvedSamples.length }} samples</span>\n </label>\n\n <!-- Action Buttons Row -->\n <div v-if=\"enableGrouping\" class=\"mint-sample-selector__actions\">\n <div class=\"mint-sample-selector__actions-row\">\n <!-- Smart Group Button -->\n <BaseButton\n v-if=\"enableSmartGroup\"\n :variant=\"groupingEnabled ? 'primary' : 'secondary'\"\n size=\"sm\"\n :disabled=\"resolvedSamples.length === 0\"\n class=\"mint-sample-selector__action-btn\"\n @click=\"showSmartGroupModal = true\"\n >\n <svg class=\"mint-sample-selector__action-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n <span>Smart Group</span>\n </BaseButton>\n\n <!-- Reset Button -->\n <BaseButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"internalGroups.length === 0\"\n class=\"mint-sample-selector__action-btn mint-sample-selector__action-btn--reset\"\n @click=\"clearGroups\"\n title=\"Clear all groups\"\n >\n <svg class=\"mint-sample-selector__action-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n </BaseButton>\n </div>\n </div>\n\n <!-- Grouped View -->\n <div v-if=\"groupingEnabled\" class=\"mint-sample-selector__grouped\">\n <!-- Groups Header -->\n <div class=\"mint-sample-selector__groups-header\">\n <span class=\"mint-sample-selector__groups-title\">Groups ({{ internalGroups.length }})</span>\n <div class=\"mint-sample-selector__groups-controls\">\n <button type=\"button\" class=\"mint-sample-selector__expand-btn\" @click=\"expandAllGroups\" title=\"Expand all\">\n <svg class=\"mint-sample-selector__expand-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n <button type=\"button\" class=\"mint-sample-selector__expand-btn\" @click=\"collapseAllGroups\" title=\"Collapse all\">\n <svg class=\"mint-sample-selector__expand-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n </div>\n\n <!-- Hierarchical Tree -->\n <div class=\"mint-sample-selector__tree\">\n <!-- Major Groups (when hierarchy is meaningful) -->\n <template v-if=\"showHierarchy\">\n <div\n v-for=\"majorGroup in hierarchicalGroups\"\n :key=\"majorGroup.name\"\n class=\"mint-sample-selector__major-group\"\n >\n <!-- Major Group Header -->\n <div\n :class=\"[\n 'mint-sample-selector__major-header',\n draggingGroup === majorGroup.name && draggingGroupKind === 'major' ? 'mint-sample-selector__header--dragging' : '',\n reorderTarget === majorGroup.name && draggingGroupKind === 'major' && reorderPosition === 'before' ? 'mint-sample-selector__header--drag-over-before' : '',\n reorderTarget === majorGroup.name && draggingGroupKind === 'major' && reorderPosition === 'after' ? 'mint-sample-selector__header--drag-over-after' : '',\n ]\"\n draggable=\"true\"\n @click=\"toggleGroupExpanded(`major:${majorGroup.name}`)\"\n @dragstart=\"handleGroupDragStart(majorGroup.name, 'major', $event)\"\n @dragend=\"handleGroupDragEnd\"\n @dragover=\"handleGroupDragOver(majorGroup.name, 'major', $event)\"\n @dragleave=\"handleGroupDragLeave($event)\"\n @drop=\"handleGroupDrop(majorGroup.name, 'major', $event)\"\n >\n <svg\n :class=\"[\n 'mint-sample-selector__chevron',\n isGroupExpanded(`major:${majorGroup.name}`) ? 'mint-sample-selector__chevron--open' : '',\n ]\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n\n <input\n type=\"checkbox\"\n :checked=\"isMajorGroupFullySelected(majorGroup)\"\n :indeterminate=\"isMajorGroupPartiallySelected(majorGroup)\"\n class=\"mint-sample-selector__checkbox\"\n :style=\"{ accentColor: majorGroup.color }\"\n @click.stop\n @change=\"toggleMajorGroupSamples(majorGroup)\"\n />\n\n <button\n type=\"button\"\n class=\"mint-sample-selector__color-dot mint-sample-selector__color-dot--large mint-sample-selector__color-dot--clickable\"\n :style=\"{ backgroundColor: majorGroup.color }\"\n @click.stop=\"openMajorGroupColorPicker(majorGroup, $event)\"\n title=\"Click to change color family\"\n />\n\n <span class=\"mint-sample-selector__major-name\">{{ majorGroup.name }}</span>\n\n <span\n class=\"mint-sample-selector__count-badge\"\n :style=\"{ backgroundColor: majorGroup.color + '20', color: majorGroup.color }\"\n >\n {{ majorGroup.allSamples.length }}\n </span>\n\n <button\n type=\"button\"\n class=\"mint-sample-selector__delete-btn mint-sample-selector__delete-btn--hidden\"\n @click.stop=\"deleteMajorGroup(majorGroup)\"\n title=\"Remove major group\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- Sub Groups (collapsible) -->\n <Transition name=\"mint-collapse\">\n <div\n v-if=\"isGroupExpanded(`major:${majorGroup.name}`)\"\n class=\"mint-sample-selector__sub-groups\"\n :style=\"{ borderColor: majorGroup.color + '30' }\"\n >\n <div\n v-for=\"subGroup in majorGroup.subGroups\"\n :key=\"subGroup.name\"\n :class=\"[\n 'mint-sample-selector__sub-group',\n dragOverGroup === subGroup.name ? 'mint-sample-selector__sub-group--drag-over' : '',\n ]\"\n @dragover=\"handleDragOver(subGroup.name, $event)\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop(subGroup.name, $event)\"\n >\n <!-- Sub Group Header -->\n <div\n :class=\"[\n 'mint-sample-selector__sub-header',\n draggingGroup === subGroup.name && draggingGroupKind === 'sub' ? 'mint-sample-selector__header--dragging' : '',\n reorderTarget === subGroup.name && draggingGroupKind === 'sub' && reorderPosition === 'before' ? 'mint-sample-selector__header--drag-over-before' : '',\n reorderTarget === subGroup.name && draggingGroupKind === 'sub' && reorderPosition === 'after' ? 'mint-sample-selector__header--drag-over-after' : '',\n ]\"\n draggable=\"true\"\n @click=\"toggleGroupExpanded(subGroup.name)\"\n @dragstart=\"handleGroupDragStart(subGroup.name, 'sub', $event)\"\n @dragend=\"handleGroupDragEnd\"\n @dragover=\"handleGroupDragOver(subGroup.name, 'sub', $event)\"\n @dragleave=\"handleGroupDragLeave($event)\"\n @drop=\"handleGroupDrop(subGroup.name, 'sub', $event)\"\n >\n <svg\n :class=\"[\n 'mint-sample-selector__chevron mint-sample-selector__chevron--small',\n isGroupExpanded(subGroup.name) ? 'mint-sample-selector__chevron--open' : '',\n ]\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n\n <input\n type=\"checkbox\"\n :checked=\"isGroupFullySelected(subGroup.name)\"\n :indeterminate=\"isGroupPartiallySelected(subGroup.name)\"\n class=\"mint-sample-selector__checkbox mint-sample-selector__checkbox--small\"\n :style=\"{ accentColor: subGroup.displayColor }\"\n @click.stop\n @change=\"toggleGroupSamples(subGroup.name)\"\n />\n\n <div\n class=\"mint-sample-selector__color-dot\"\n :style=\"{ backgroundColor: subGroup.displayColor }\"\n aria-hidden=\"true\"\n />\n\n <span class=\"mint-sample-selector__sub-name\">{{ subGroup.name }}</span>\n\n <span\n class=\"mint-sample-selector__count-badge mint-sample-selector__count-badge--small\"\n :style=\"{ backgroundColor: subGroup.displayBg, color: subGroup.displayColor }\"\n >\n {{ subGroup.samples.length }}\n </span>\n\n <button\n type=\"button\"\n class=\"mint-sample-selector__delete-btn mint-sample-selector__delete-btn--hidden\"\n @click.stop=\"deleteGroup(subGroup.name)\"\n title=\"Remove sub group\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- Samples (collapsible) -->\n <Transition name=\"mint-collapse\">\n <div\n v-if=\"isGroupExpanded(subGroup.name)\"\n class=\"mint-sample-selector__samples\"\n :style=\"{ borderColor: subGroup.displayBorder }\"\n >\n <SampleSelectorSampleRow\n v-for=\"sample in subGroup.samples\"\n :key=\"sample\"\n :sample=\"sample\"\n :selected=\"modelValue.includes(sample)\"\n :dragging=\"draggingSample === sample\"\n :accent-color=\"subGroup.displayColor\"\n checkbox-size=\"tiny\"\n removable\n @dragstart=\"handleDragStart(sample, subGroup.name, $event)\"\n @dragend=\"handleDragEnd\"\n @toggle=\"toggleSample(sample)\"\n @remove=\"removeSampleFromGroup(sample, subGroup.name)\"\n />\n </div>\n </Transition>\n </div>\n </div>\n </Transition>\n </div>\n </template>\n\n <!-- Flat Groups (when no meaningful hierarchy - e.g., level 1) -->\n <template v-else>\n <div\n v-for=\"group in internalGroups\"\n :key=\"group.name\"\n :class=\"[\n 'mint-sample-selector__sub-group',\n dragOverGroup === group.name ? 'mint-sample-selector__sub-group--drag-over' : '',\n ]\"\n @dragover=\"handleDragOver(group.name, $event)\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop(group.name, $event)\"\n >\n <!-- Group Header -->\n <div\n :class=\"[\n 'mint-sample-selector__major-header',\n draggingGroup === group.name && draggingGroupKind === 'flat' ? 'mint-sample-selector__header--dragging' : '',\n reorderTarget === group.name && draggingGroupKind === 'flat' && reorderPosition === 'before' ? 'mint-sample-selector__header--drag-over-before' : '',\n reorderTarget === group.name && draggingGroupKind === 'flat' && reorderPosition === 'after' ? 'mint-sample-selector__header--drag-over-after' : '',\n ]\"\n draggable=\"true\"\n @click=\"toggleGroupExpanded(group.name)\"\n @dragstart=\"handleGroupDragStart(group.name, 'flat', $event)\"\n @dragend=\"handleGroupDragEnd\"\n @dragover=\"handleGroupDragOver(group.name, 'flat', $event)\"\n @dragleave=\"handleGroupDragLeave($event)\"\n @drop=\"handleGroupDrop(group.name, 'flat', $event)\"\n >\n <svg\n :class=\"[\n 'mint-sample-selector__chevron',\n isGroupExpanded(group.name) ? 'mint-sample-selector__chevron--open' : '',\n ]\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n\n <input\n type=\"checkbox\"\n :checked=\"isGroupFullySelected(group.name)\"\n :indeterminate=\"isGroupPartiallySelected(group.name)\"\n class=\"mint-sample-selector__checkbox\"\n :style=\"{ accentColor: group.color }\"\n @click.stop\n @change=\"toggleGroupSamples(group.name)\"\n />\n\n <button\n type=\"button\"\n class=\"mint-sample-selector__color-dot mint-sample-selector__color-dot--large mint-sample-selector__color-dot--clickable\"\n :style=\"{ backgroundColor: group.color }\"\n @click.stop=\"openColorPicker(group.name, $event)\"\n title=\"Click to change color\"\n />\n\n <span class=\"mint-sample-selector__major-name\">{{ group.name }}</span>\n\n <span\n class=\"mint-sample-selector__count-badge\"\n :style=\"{ backgroundColor: group.color + '20', color: group.color }\"\n >\n {{ group.samples.length }}\n </span>\n\n <button\n type=\"button\"\n class=\"mint-sample-selector__delete-btn mint-sample-selector__delete-btn--hidden\"\n @click.stop=\"deleteGroup(group.name)\"\n title=\"Remove group\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- Samples (collapsible) -->\n <Transition name=\"mint-collapse\">\n <div\n v-if=\"isGroupExpanded(group.name)\"\n class=\"mint-sample-selector__samples\"\n :style=\"{ borderColor: group.color + '40' }\"\n >\n <SampleSelectorSampleRow\n v-for=\"sample in group.samples\"\n :key=\"sample\"\n :sample=\"sample\"\n :selected=\"modelValue.includes(sample)\"\n :dragging=\"draggingSample === sample\"\n :accent-color=\"group.color\"\n checkbox-size=\"tiny\"\n removable\n @dragstart=\"handleDragStart(sample, group.name, $event)\"\n @dragend=\"handleDragEnd\"\n @toggle=\"toggleSample(sample)\"\n @remove=\"removeSampleFromGroup(sample, group.name)\"\n />\n </div>\n </Transition>\n </div>\n </template>\n\n <!-- Empty state -->\n <div v-if=\"internalGroups.length === 0\" class=\"mint-sample-selector__empty\">\n Click Smart Group to auto-group samples\n </div>\n </div>\n\n <!-- Ungrouped Samples Section -->\n <div v-if=\"ungroupedSamples.length > 0\" class=\"mint-sample-selector__ungrouped\">\n <div\n class=\"mint-sample-selector__ungrouped-header\"\n @click=\"toggleGroupExpanded('__ungrouped__')\"\n >\n <svg\n :class=\"[\n 'mint-sample-selector__chevron',\n isGroupExpanded('__ungrouped__') ? 'mint-sample-selector__chevron--open' : '',\n ]\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n <span class=\"mint-sample-selector__ungrouped-label\">Ungrouped</span>\n <span class=\"mint-sample-selector__ungrouped-count\">{{ ungroupedSamples.length }}</span>\n </div>\n\n <Transition name=\"mint-collapse\">\n <div\n v-if=\"isGroupExpanded('__ungrouped__')\"\n class=\"mint-sample-selector__ungrouped-list\"\n >\n <SampleSelectorSampleRow\n v-for=\"sample in ungroupedSamples\"\n :key=\"sample\"\n :sample=\"sample\"\n :selected=\"modelValue.includes(sample)\"\n :dragging=\"draggingSample === sample\"\n checkbox-size=\"small\"\n @dragstart=\"handleDragStart(sample, null, $event)\"\n @dragend=\"handleDragEnd\"\n @toggle=\"toggleSample(sample)\"\n />\n </div>\n </Transition>\n </div>\n\n <!-- New Group Input -->\n <div class=\"mint-sample-selector__new-group\">\n <BaseInput\n v-model=\"newGroupName\"\n placeholder=\"New group name...\"\n class=\"mint-sample-selector__new-group-input\"\n @keyup.enter=\"addNewGroup\"\n />\n <BaseButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"!newGroupName.trim()\"\n class=\"mint-sample-selector__new-group-btn\"\n @click=\"addNewGroup\"\n >\n Add\n </BaseButton>\n </div>\n\n <!-- Hidden color picker input -->\n <input\n ref=\"colorPickerInput\"\n type=\"color\"\n class=\"mint-sample-selector__color-input\"\n :value=\"colorPickerSeed\"\n @change=\"handleColorChange\"\n />\n </div>\n\n <!-- Flat View (when no groups) -->\n <div v-if=\"!groupingEnabled\" class=\"mint-sample-selector__flat\">\n <!-- Search -->\n <div class=\"mint-sample-selector__search\">\n <svg class=\"mint-sample-selector__search-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\n </svg>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search samples...\"\n class=\"mint-sample-selector__search-input\"\n />\n </div>\n\n <!-- Flat samples list -->\n <div class=\"mint-sample-selector__flat-list\">\n <div\n v-for=\"sample in filteredSamples\"\n :key=\"sample\"\n class=\"mint-sample-selector__flat-item\"\n >\n <input\n type=\"checkbox\"\n :checked=\"modelValue.includes(sample)\"\n class=\"mint-sample-selector__checkbox\"\n @change=\"toggleSample(sample)\"\n />\n <span class=\"mint-sample-selector__flat-name\">{{ sample }}</span>\n </div>\n\n <div v-if=\"filteredSamples.length === 0 && searchQuery.trim()\" class=\"mint-sample-selector__empty\">\n No samples match \"{{ searchQuery }}\"\n </div>\n </div>\n </div>\n\n <!-- Smart Grouping Modal -->\n <AutoGroupModal\n v-model=\"showSmartGroupModal\"\n :samples=\"resolvedSamples\"\n :experiment-id=\"resolvedExperimentId\"\n :design-data=\"resolvedDesignData\"\n @apply=\"handleSmartGroupApply\"\n />\n </div>\n</template>\n\n<style>\n@import '../styles/components/sample-selector.css';\n</style>\n","import { computed, type Ref } from 'vue'\nimport type {\n ScheduleView,\n ScheduleEvent,\n ScheduleBlockedSlot,\n ScheduleEventStatus,\n} from '../types/components'\nimport { formatTime } from './useTimeUtils'\n\ntype ReadableRef<T> = Readonly<Ref<T>>\n\nexport const SCHEDULE_SLOT_HEIGHT = 40\n\nexport interface UseScheduleCalendarLayoutOptions {\n currentView: ReadableRef<ScheduleView>\n currentDate: ReadableRef<Date>\n now: ReadableRef<Date>\n events: ReadableRef<readonly ScheduleEvent[]>\n blockedSlots: ReadableRef<readonly ScheduleBlockedSlot[]>\n dayStartHour: ReadableRef<number>\n dayEndHour: ReadableRef<number>\n slotDuration: ReadableRef<number>\n weekStartsOn: ReadableRef<0 | 1>\n locale: ReadableRef<string>\n showNowIndicator: ReadableRef<boolean>\n slotHeight?: ReadableRef<number>\n}\n\n/** Builds ScheduleCalendar date ranges, event layout styles, and display labels. */\nexport function useScheduleCalendarLayout(options: UseScheduleCalendarLayoutOptions) {\n const slotHeight = computed(() => options.slotHeight?.value ?? SCHEDULE_SLOT_HEIGHT)\n const totalSlots = computed(() => {\n return ((options.dayEndHour.value - options.dayStartHour.value) * 60) / options.slotDuration.value\n })\n\n const timeLabels = computed(() => buildTimeLabels(\n options.dayStartHour.value,\n options.slotDuration.value,\n totalSlots.value,\n ))\n const weekDays = computed(() => buildWeekDays(options.currentDate.value, options.weekStartsOn.value))\n const visibleDays = computed(() => {\n if (options.currentView.value === 'day') return [options.currentDate.value]\n if (options.currentView.value === 'week') return weekDays.value\n return []\n })\n const headerTitle = computed(() => buildHeaderTitle(\n options.currentView.value,\n options.currentDate.value,\n weekDays.value,\n options.locale.value,\n ))\n const monthDays = computed(() => buildMonthDays(options.currentDate.value, options.weekStartsOn.value))\n const monthWeekdayLabels = computed(() => buildMonthWeekdayLabels(\n options.locale.value,\n options.weekStartsOn.value,\n ))\n\n function isToday(date: Date): boolean {\n return isSameDay(date, options.now.value)\n }\n\n function getEventStyle(event: ScheduleEvent, dayDate: Date): Record<string, string> | null {\n return getTimedItemStyle(\n event.start,\n event.end,\n dayDate,\n options.dayStartHour.value,\n options.dayEndHour.value,\n options.slotDuration.value,\n slotHeight.value,\n true,\n slotHeight.value / 2,\n )\n }\n\n function getBlockedStyle(slot: ScheduleBlockedSlot): Record<string, string> {\n return getTimedItemStyle(\n slot.start,\n slot.end,\n new Date(slot.start),\n options.dayStartHour.value,\n options.dayEndHour.value,\n options.slotDuration.value,\n slotHeight.value,\n false,\n 0,\n ) ?? { top: '0px', height: '0px' }\n }\n\n function getNowIndicatorStyle(): Record<string, string> | null {\n if (!options.showNowIndicator.value) return null\n const nowMin = dateToMinutes(options.now.value)\n const dayStartMin = options.dayStartHour.value * 60\n const dayEndMin = options.dayEndHour.value * 60\n if (nowMin < dayStartMin || nowMin > dayEndMin) return null\n\n const pixelsPerMin = slotHeight.value / options.slotDuration.value\n return { top: `${(nowMin - dayStartMin) * pixelsPerMin}px` }\n }\n\n function getEventsForDay(day: Date): ScheduleEvent[] {\n return eventsForDate(options.events.value, day)\n }\n\n function getEventsForDate(date: Date): ScheduleEvent[] {\n return eventsForDate(options.events.value, date)\n }\n\n function getBlockedForDay(day: Date): ScheduleBlockedSlot[] {\n return options.blockedSlots.value.filter((slot) => isSameDay(new Date(slot.start), day))\n }\n\n return {\n totalSlots,\n timeLabels,\n weekDays,\n visibleDays,\n headerTitle,\n monthDays,\n monthWeekdayLabels,\n isToday,\n isSameDay,\n getEventStyle,\n getEventsForDay,\n getEventsForDate,\n getBlockedForDay,\n getBlockedStyle,\n getNowIndicatorStyle,\n getStatusClass,\n getMonthStatusClass,\n formatEventTime,\n }\n}\n\nexport function parseScheduleModelDate(value?: Date | string): Date {\n if (!value) return new Date()\n if (value instanceof Date) return value\n const d = new Date(value)\n return isNaN(d.getTime()) ? new Date() : d\n}\n\nexport function getWeekStart(date: Date, weekStartsOn: 0 | 1): Date {\n const d = new Date(date)\n const day = d.getDay()\n const diff = (day - weekStartsOn + 7) % 7\n d.setDate(d.getDate() - diff)\n return d\n}\n\nexport function isSameDay(a: Date, b: Date): boolean {\n return a.toDateString() === b.toDateString()\n}\n\nfunction buildTimeLabels(dayStartHour: number, slotDuration: number, totalSlots: number): string[] {\n const labels: string[] = []\n for (let i = 0; i <= totalSlots; i++) {\n const totalMinutes = dayStartHour * 60 + i * slotDuration\n const hour = Math.floor(totalMinutes / 60)\n const minute = totalMinutes % 60\n labels.push(formatTime(hour, minute, '24h'))\n }\n return labels\n}\n\nfunction buildWeekDays(currentDate: Date, weekStartsOn: 0 | 1): Date[] {\n const days: Date[] = []\n const start = getWeekStart(currentDate, weekStartsOn)\n for (let i = 0; i < 7; i++) {\n const d = new Date(start)\n d.setDate(d.getDate() + i)\n days.push(d)\n }\n return days\n}\n\nfunction buildHeaderTitle(\n view: ScheduleView,\n currentDate: Date,\n weekDays: Date[],\n locale: string,\n): string {\n if (view === 'day') {\n return currentDate.toLocaleDateString(locale, {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n })\n }\n if (view === 'week') {\n const first = weekDays[0]\n const last = weekDays[6]\n const sameMonth = first.getMonth() === last.getMonth()\n if (sameMonth) {\n return `${first.toLocaleDateString(locale, { month: 'short', day: 'numeric' })} - ${last.getDate()}, ${last.getFullYear()}`\n }\n return `${first.toLocaleDateString(locale, { month: 'short', day: 'numeric' })} - ${last.toLocaleDateString(locale, { month: 'short', day: 'numeric' })}, ${last.getFullYear()}`\n }\n return currentDate.toLocaleDateString(locale, { year: 'numeric', month: 'long' })\n}\n\nfunction buildMonthDays(currentDate: Date, weekStartsOn: 0 | 1): { date: Date; isCurrentMonth: boolean }[] {\n const year = currentDate.getFullYear()\n const month = currentDate.getMonth()\n const firstDay = new Date(year, month, 1)\n const lastDay = new Date(year, month + 1, 0)\n const days: { date: Date; isCurrentMonth: boolean }[] = []\n\n let startPadding = firstDay.getDay() - weekStartsOn\n if (startPadding < 0) startPadding += 7\n\n for (let i = startPadding - 1; i >= 0; i--) {\n days.push({ date: new Date(year, month, -i), isCurrentMonth: false })\n }\n\n for (let i = 1; i <= lastDay.getDate(); i++) {\n days.push({ date: new Date(year, month, i), isCurrentMonth: true })\n }\n\n const remaining = 42 - days.length\n for (let i = 1; i <= remaining; i++) {\n days.push({ date: new Date(year, month + 1, i), isCurrentMonth: false })\n }\n\n return days\n}\n\nfunction buildMonthWeekdayLabels(locale: string, weekStartsOn: 0 | 1): string[] {\n const labels: string[] = []\n const base = new Date(2024, 0, weekStartsOn === 1 ? 1 : 7)\n for (let i = 0; i < 7; i++) {\n const d = new Date(base)\n d.setDate(d.getDate() + i)\n labels.push(d.toLocaleDateString(locale, { weekday: 'short' }))\n }\n return labels\n}\n\nfunction getTimedItemStyle(\n startValue: string,\n endValue: string,\n dayDate: Date,\n dayStartHour: number,\n dayEndHour: number,\n slotDuration: number,\n slotHeight: number,\n requireSameDayEdge: boolean,\n minimumHeight: number,\n): Record<string, string> | null {\n const start = new Date(startValue)\n const end = new Date(endValue)\n if (requireSameDayEdge && !isSameDay(start, dayDate) && !isSameDay(end, dayDate)) return null\n\n const dayStartMin = dayStartHour * 60\n const startMin = Math.max(dateToMinutes(start), dayStartMin) - dayStartMin\n const endMin = Math.min(dateToMinutes(end), dayEndHour * 60) - dayStartMin\n const pixelsPerMin = slotHeight / slotDuration\n return {\n top: `${startMin * pixelsPerMin}px`,\n height: `${Math.max((endMin - startMin) * pixelsPerMin, minimumHeight)}px`,\n }\n}\n\nfunction eventsForDate(events: readonly ScheduleEvent[], date: Date): ScheduleEvent[] {\n return events.filter((event) => isSameDay(new Date(event.start), date))\n}\n\nfunction dateToMinutes(date: Date): number {\n return date.getHours() * 60 + date.getMinutes()\n}\n\nfunction getStatusClass(status?: ScheduleEventStatus): string {\n if (!status) return 'mint-schedule__event--confirmed'\n return `mint-schedule__event--${status}`\n}\n\nfunction getMonthStatusClass(status?: ScheduleEventStatus): string {\n if (!status) return 'mint-schedule__month-event--confirmed'\n return `mint-schedule__month-event--${status}`\n}\n\nfunction formatEventTime(event: ScheduleEvent): string {\n const start = new Date(event.start)\n const end = new Date(event.end)\n return `${formatTime(start.getHours(), start.getMinutes())} - ${formatTime(end.getHours(), end.getMinutes())}`\n}\n","<script setup lang=\"ts\">\n/** Drag-and-drop week/day calendar for scheduling lab events in configurable time slots. */\nimport { ref, computed, watch, onMounted, onUnmounted } from 'vue'\nimport type {\n ScheduleView,\n ScheduleEvent,\n ScheduleBlockedSlot,\n} from '../types/components'\nimport { useScheduleDrag } from '../composables/useScheduleDrag'\nimport {\n SCHEDULE_SLOT_HEIGHT,\n parseScheduleModelDate,\n useScheduleCalendarLayout,\n} from '../composables/useScheduleCalendarLayout'\n\ninterface Props {\n modelValue?: Date | string\n view?: ScheduleView\n events?: ScheduleEvent[]\n dayStartHour?: number\n dayEndHour?: number\n slotDuration?: number\n showNowIndicator?: boolean\n readonly?: boolean\n blockedSlots?: ScheduleBlockedSlot[]\n weekStartsOn?: 0 | 1\n showViewToggle?: boolean\n showNavigation?: boolean\n statusColors?: Record<string, string>\n locale?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n view: 'week',\n events: () => [],\n dayStartHour: 6,\n dayEndHour: 22,\n slotDuration: 30,\n showNowIndicator: true,\n readonly: false,\n blockedSlots: () => [],\n weekStartsOn: 1,\n showViewToggle: true,\n showNavigation: true,\n locale: 'en-US',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: Date]\n 'update:view': [value: ScheduleView]\n 'event-click': [event: ScheduleEvent]\n 'event-create': [context: { start: Date; end: Date }]\n 'event-update': [context: { event: ScheduleEvent; newStart: Date; newEnd: Date }]\n 'slot-click': [context: { date: Date; hour: number; minute: number }]\n 'navigate': [context: { direction: string; date: Date }]\n}>()\n\nconst SLOT_HEIGHT = SCHEDULE_SLOT_HEIGHT\nconst currentView = ref<ScheduleView>(props.view)\nconst currentDate = ref<Date>(parseScheduleModelDate(props.modelValue))\nconst nowTimer = ref<ReturnType<typeof setInterval>>()\nconst now = ref(new Date())\nconst bodyRef = ref<HTMLDivElement>()\n\nwatch(() => props.view, (v) => { currentView.value = v })\nwatch(() => props.modelValue, (v) => { if (v) currentDate.value = parseScheduleModelDate(v) })\n\nconst slotHeightRef = computed(() => SLOT_HEIGHT)\nconst slotDurationRef = computed(() => props.slotDuration)\nconst dayStartRef = computed(() => props.dayStartHour)\nconst dayEndRef = computed(() => props.dayEndHour)\nconst readonlyRef = computed(() => props.readonly)\nconst eventsRef = computed(() => props.events)\nconst blockedSlotsRef = computed(() => props.blockedSlots)\nconst weekStartsOnRef = computed(() => props.weekStartsOn)\nconst localeRef = computed(() => props.locale)\nconst showNowIndicatorRef = computed(() => props.showNowIndicator)\n\nconst { isDragging, ghost, startCreate, startMove, startResize } = useScheduleDrag({\n slotDuration: slotDurationRef,\n dayStartHour: dayStartRef,\n dayEndHour: dayEndRef,\n readonly: readonlyRef,\n slotHeight: slotHeightRef,\n onCreateComplete(start, end) {\n emit('event-create', { start, end })\n },\n onMoveComplete(event, newStart, newEnd) {\n emit('event-update', { event, newStart, newEnd })\n },\n onResizeComplete(event, newStart, newEnd) {\n emit('event-update', { event, newStart, newEnd })\n },\n})\n\nconst {\n totalSlots,\n timeLabels,\n visibleDays,\n headerTitle,\n monthDays,\n monthWeekdayLabels,\n isToday,\n isSameDay,\n getEventStyle,\n getEventsForDay,\n getEventsForDate,\n getBlockedForDay,\n getBlockedStyle,\n getNowIndicatorStyle,\n getStatusClass,\n getMonthStatusClass,\n formatEventTime,\n} = useScheduleCalendarLayout({\n currentView,\n currentDate,\n now,\n events: eventsRef,\n blockedSlots: blockedSlotsRef,\n dayStartHour: dayStartRef,\n dayEndHour: dayEndRef,\n slotDuration: slotDurationRef,\n weekStartsOn: weekStartsOnRef,\n locale: localeRef,\n showNowIndicator: showNowIndicatorRef,\n slotHeight: slotHeightRef,\n})\n\nfunction navigate(direction: 'prev' | 'next' | 'today') {\n const d = new Date(currentDate.value)\n if (direction === 'today') {\n currentDate.value = new Date()\n } else {\n const delta = direction === 'prev' ? -1 : 1\n if (currentView.value === 'day') d.setDate(d.getDate() + delta)\n else if (currentView.value === 'week') d.setDate(d.getDate() + delta * 7)\n else d.setMonth(d.getMonth() + delta)\n currentDate.value = d\n }\n emit('update:modelValue', currentDate.value)\n emit('navigate', { direction, date: currentDate.value })\n}\n\nfunction setView(view: ScheduleView) {\n currentView.value = view\n emit('update:view', view)\n}\n\nfunction onEventClick(event: ScheduleEvent, e: MouseEvent) {\n e.stopPropagation()\n emit('event-click', event)\n}\n\nfunction onSlotClick(day: Date, slotIndex: number) {\n if (props.readonly || isDragging.value) return\n const totalMinutes = props.dayStartHour * 60 + slotIndex * props.slotDuration\n const hour = Math.floor(totalMinutes / 60)\n const minute = totalMinutes % 60\n const date = new Date(day)\n date.setHours(hour, minute, 0, 0)\n emit('slot-click', { date, hour, minute })\n}\n\nfunction onSlotPointerDown(day: Date, slotIndex: number, e: PointerEvent) {\n if (props.readonly) return\n const date = new Date(day)\n const totalMinutes = props.dayStartHour * 60 + slotIndex * props.slotDuration\n date.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0)\n const dayIndex = visibleDays.value.findIndex((d) => isSameDay(d, day))\n startCreate(date, e.clientY, dayIndex)\n}\n\nfunction onEventPointerDown(event: ScheduleEvent, dayIndex: number, e: PointerEvent) {\n e.stopPropagation()\n startMove(event, e.clientY, dayIndex)\n}\n\nfunction onResizePointerDown(event: ScheduleEvent, edge: 'top' | 'bottom', dayIndex: number, e: PointerEvent) {\n e.stopPropagation()\n startResize(event, edge, e.clientY, dayIndex)\n}\n\nfunction onMonthDayClick(date: Date) {\n currentDate.value = date\n emit('update:modelValue', date)\n setView('day')\n}\n\nonMounted(() => {\n nowTimer.value = setInterval(() => { now.value = new Date() }, 60000)\n\n // Scroll to current time or 8am\n if (bodyRef.value) {\n const targetHour = now.value.getHours() >= props.dayStartHour && now.value.getHours() <= props.dayEndHour\n ? now.value.getHours()\n : 8\n const offsetMin = (targetHour - props.dayStartHour) * 60\n const pixelsPerMin = SLOT_HEIGHT / props.slotDuration\n bodyRef.value.scrollTop = Math.max(0, offsetMin * pixelsPerMin - 100)\n }\n})\n\nonUnmounted(() => {\n if (nowTimer.value) clearInterval(nowTimer.value)\n})\n</script>\n\n<template>\n <div class=\"mint-schedule\" :style=\"({ '--slot-height': `${SLOT_HEIGHT}px` } as Record<string, string>)\">\n <!-- Header -->\n <div v-if=\"showNavigation || showViewToggle\" class=\"mint-schedule__header\">\n <div v-if=\"showNavigation\" class=\"mint-schedule__nav\">\n <button\n type=\"button\"\n class=\"mint-schedule__nav-btn\"\n aria-label=\"Previous\"\n @click=\"navigate('prev')\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"mint-schedule__nav-btn\"\n aria-label=\"Next\"\n @click=\"navigate('next')\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"mint-schedule__today-btn\"\n @click=\"navigate('today')\"\n >\n Today\n </button>\n </div>\n\n <span class=\"mint-schedule__title\">{{ headerTitle }}</span>\n\n <div v-if=\"showViewToggle\" class=\"mint-schedule__view-toggle\">\n <button\n v-for=\"v in (['day', 'week', 'month'] as ScheduleView[])\"\n :key=\"v\"\n type=\"button\"\n :class=\"['mint-schedule__view-btn', currentView === v ? 'mint-schedule__view-btn--active' : '']\"\n @click=\"setView(v)\"\n >\n {{ v.charAt(0).toUpperCase() + v.slice(1) }}\n </button>\n </div>\n </div>\n\n <!-- Day / Week View -->\n <template v-if=\"currentView === 'day' || currentView === 'week'\">\n <!-- Day headers -->\n <div v-if=\"currentView === 'week'\" class=\"mint-schedule__day-headers\">\n <div class=\"mint-schedule__day-header-gutter\" />\n <div\n v-for=\"(day, i) in visibleDays\"\n :key=\"i\"\n :class=\"['mint-schedule__day-header', isToday(day) ? 'mint-schedule__day-header--today' : '']\"\n >\n <slot name=\"day-header\" :date=\"day\" :isToday=\"isToday(day)\">\n <span class=\"mint-schedule__day-header-name\">\n {{ day.toLocaleDateString(locale, { weekday: 'short' }) }}\n </span>\n <span class=\"mint-schedule__day-header-number\">\n {{ day.getDate() }}\n </span>\n </slot>\n </div>\n </div>\n\n <!-- Time grid body -->\n <div ref=\"bodyRef\" class=\"mint-schedule__body\">\n <!-- Time gutter -->\n <div class=\"mint-schedule__time-gutter\">\n <div\n v-for=\"(label, i) in timeLabels\"\n :key=\"i\"\n class=\"mint-schedule__time-label\"\n >\n <slot name=\"time-label\" :label=\"label\" :index=\"i\">\n {{ i < timeLabels.length - 1 ? label : '' }}\n </slot>\n </div>\n </div>\n\n <!-- Day columns -->\n <div class=\"mint-schedule__day-columns\">\n <div\n v-for=\"(day, dayIdx) in visibleDays\"\n :key=\"dayIdx\"\n :class=\"['mint-schedule__day-column', isToday(day) ? 'mint-schedule__day-column--today' : '']\"\n >\n <!-- Slot grid -->\n <div\n v-for=\"slotIdx in totalSlots\"\n :key=\"slotIdx\"\n :class=\"['mint-schedule__slot', !readonly ? 'mint-schedule__slot--interactive' : '']\"\n @click=\"onSlotClick(day, slotIdx - 1)\"\n @pointerdown=\"onSlotPointerDown(day, slotIdx - 1, $event)\"\n />\n\n <!-- Events -->\n <div\n v-for=\"event in getEventsForDay(day)\"\n :key=\"event.id\"\n :class=\"['mint-schedule__event', getStatusClass(event.status)]\"\n :style=\"getEventStyle(event, day) || undefined\"\n @click=\"onEventClick(event, $event)\"\n @pointerdown=\"onEventPointerDown(event, dayIdx, $event)\"\n >\n <slot name=\"event\" :event=\"event\">\n <div\n v-if=\"!readonly && event.resizable !== false\"\n class=\"mint-schedule__event-resize-handle mint-schedule__event-resize-handle--top\"\n @pointerdown=\"onResizePointerDown(event, 'top', dayIdx, $event)\"\n />\n <div class=\"mint-schedule__event-title\">{{ event.title }}</div>\n <div class=\"mint-schedule__event-time\">{{ formatEventTime(event) }}</div>\n <div\n v-if=\"!readonly && event.resizable !== false\"\n class=\"mint-schedule__event-resize-handle mint-schedule__event-resize-handle--bottom\"\n @pointerdown=\"onResizePointerDown(event, 'bottom', dayIdx, $event)\"\n />\n </slot>\n </div>\n\n <!-- Blocked slots -->\n <div\n v-for=\"(blocked, bIdx) in getBlockedForDay(day)\"\n :key=\"`b-${bIdx}`\"\n class=\"mint-schedule__blocked\"\n :style=\"getBlockedStyle(blocked)\"\n >\n <span v-if=\"blocked.label\" class=\"mint-schedule__blocked-label\">\n {{ blocked.label }}\n </span>\n </div>\n\n <!-- Now indicator -->\n <div\n v-if=\"showNowIndicator && isToday(day) && getNowIndicatorStyle()\"\n class=\"mint-schedule__now-indicator\"\n :style=\"getNowIndicatorStyle()!\"\n >\n <div class=\"mint-schedule__now-dot\" />\n </div>\n\n <!-- Drag ghost -->\n <div\n v-if=\"isDragging && ghost && ghost.dayIndex === dayIdx\"\n class=\"mint-schedule__ghost\"\n :style=\"ghost.style\"\n />\n </div>\n </div>\n </div>\n </template>\n\n <!-- Month View -->\n <template v-if=\"currentView === 'month'\">\n <div class=\"mint-schedule__month-grid\">\n <!-- Weekday headers -->\n <div\n v-for=\"label in monthWeekdayLabels\"\n :key=\"label\"\n class=\"mint-schedule__month-weekday\"\n >\n {{ label }}\n </div>\n\n <!-- Day cells -->\n <div\n v-for=\"(day, i) in monthDays\"\n :key=\"i\"\n :class=\"[\n 'mint-schedule__month-cell',\n isToday(day.date) ? 'mint-schedule__month-cell--today' : '',\n !day.isCurrentMonth ? 'mint-schedule__month-cell--outside' : '',\n ]\"\n @click=\"onMonthDayClick(day.date)\"\n >\n <slot name=\"month-day\" :date=\"day.date\" :isToday=\"isToday(day.date)\" :events=\"getEventsForDate(day.date)\">\n <div class=\"mint-schedule__month-date\">{{ day.date.getDate() }}</div>\n <div\n v-for=\"event in getEventsForDate(day.date).slice(0, 3)\"\n :key=\"event.id\"\n :class=\"['mint-schedule__month-event', getMonthStatusClass(event.status)]\"\n @click.stop=\"onEventClick(event, $event)\"\n >\n {{ event.title }}\n </div>\n <div\n v-if=\"getEventsForDate(day.date).length > 3\"\n class=\"mint-schedule__month-more\"\n >\n +{{ getEventsForDate(day.date).length - 3 }} more\n </div>\n </slot>\n </div>\n </div>\n </template>\n </div>\n</template>\n\n<style>\n@import '../styles/components/schedule-calendar.css';\n</style>\n","<script setup lang=\"ts\">\n/** Drag-and-drop week/day calendar for scheduling lab events in configurable time slots. */\nimport { ref, computed, watch, onMounted, onUnmounted } from 'vue'\nimport type {\n ScheduleView,\n ScheduleEvent,\n ScheduleBlockedSlot,\n} from '../types/components'\nimport { useScheduleDrag } from '../composables/useScheduleDrag'\nimport {\n SCHEDULE_SLOT_HEIGHT,\n parseScheduleModelDate,\n useScheduleCalendarLayout,\n} from '../composables/useScheduleCalendarLayout'\n\ninterface Props {\n modelValue?: Date | string\n view?: ScheduleView\n events?: ScheduleEvent[]\n dayStartHour?: number\n dayEndHour?: number\n slotDuration?: number\n showNowIndicator?: boolean\n readonly?: boolean\n blockedSlots?: ScheduleBlockedSlot[]\n weekStartsOn?: 0 | 1\n showViewToggle?: boolean\n showNavigation?: boolean\n statusColors?: Record<string, string>\n locale?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n view: 'week',\n events: () => [],\n dayStartHour: 6,\n dayEndHour: 22,\n slotDuration: 30,\n showNowIndicator: true,\n readonly: false,\n blockedSlots: () => [],\n weekStartsOn: 1,\n showViewToggle: true,\n showNavigation: true,\n locale: 'en-US',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: Date]\n 'update:view': [value: ScheduleView]\n 'event-click': [event: ScheduleEvent]\n 'event-create': [context: { start: Date; end: Date }]\n 'event-update': [context: { event: ScheduleEvent; newStart: Date; newEnd: Date }]\n 'slot-click': [context: { date: Date; hour: number; minute: number }]\n 'navigate': [context: { direction: string; date: Date }]\n}>()\n\nconst SLOT_HEIGHT = SCHEDULE_SLOT_HEIGHT\nconst currentView = ref<ScheduleView>(props.view)\nconst currentDate = ref<Date>(parseScheduleModelDate(props.modelValue))\nconst nowTimer = ref<ReturnType<typeof setInterval>>()\nconst now = ref(new Date())\nconst bodyRef = ref<HTMLDivElement>()\n\nwatch(() => props.view, (v) => { currentView.value = v })\nwatch(() => props.modelValue, (v) => { if (v) currentDate.value = parseScheduleModelDate(v) })\n\nconst slotHeightRef = computed(() => SLOT_HEIGHT)\nconst slotDurationRef = computed(() => props.slotDuration)\nconst dayStartRef = computed(() => props.dayStartHour)\nconst dayEndRef = computed(() => props.dayEndHour)\nconst readonlyRef = computed(() => props.readonly)\nconst eventsRef = computed(() => props.events)\nconst blockedSlotsRef = computed(() => props.blockedSlots)\nconst weekStartsOnRef = computed(() => props.weekStartsOn)\nconst localeRef = computed(() => props.locale)\nconst showNowIndicatorRef = computed(() => props.showNowIndicator)\n\nconst { isDragging, ghost, startCreate, startMove, startResize } = useScheduleDrag({\n slotDuration: slotDurationRef,\n dayStartHour: dayStartRef,\n dayEndHour: dayEndRef,\n readonly: readonlyRef,\n slotHeight: slotHeightRef,\n onCreateComplete(start, end) {\n emit('event-create', { start, end })\n },\n onMoveComplete(event, newStart, newEnd) {\n emit('event-update', { event, newStart, newEnd })\n },\n onResizeComplete(event, newStart, newEnd) {\n emit('event-update', { event, newStart, newEnd })\n },\n})\n\nconst {\n totalSlots,\n timeLabels,\n visibleDays,\n headerTitle,\n monthDays,\n monthWeekdayLabels,\n isToday,\n isSameDay,\n getEventStyle,\n getEventsForDay,\n getEventsForDate,\n getBlockedForDay,\n getBlockedStyle,\n getNowIndicatorStyle,\n getStatusClass,\n getMonthStatusClass,\n formatEventTime,\n} = useScheduleCalendarLayout({\n currentView,\n currentDate,\n now,\n events: eventsRef,\n blockedSlots: blockedSlotsRef,\n dayStartHour: dayStartRef,\n dayEndHour: dayEndRef,\n slotDuration: slotDurationRef,\n weekStartsOn: weekStartsOnRef,\n locale: localeRef,\n showNowIndicator: showNowIndicatorRef,\n slotHeight: slotHeightRef,\n})\n\nfunction navigate(direction: 'prev' | 'next' | 'today') {\n const d = new Date(currentDate.value)\n if (direction === 'today') {\n currentDate.value = new Date()\n } else {\n const delta = direction === 'prev' ? -1 : 1\n if (currentView.value === 'day') d.setDate(d.getDate() + delta)\n else if (currentView.value === 'week') d.setDate(d.getDate() + delta * 7)\n else d.setMonth(d.getMonth() + delta)\n currentDate.value = d\n }\n emit('update:modelValue', currentDate.value)\n emit('navigate', { direction, date: currentDate.value })\n}\n\nfunction setView(view: ScheduleView) {\n currentView.value = view\n emit('update:view', view)\n}\n\nfunction onEventClick(event: ScheduleEvent, e: MouseEvent) {\n e.stopPropagation()\n emit('event-click', event)\n}\n\nfunction onSlotClick(day: Date, slotIndex: number) {\n if (props.readonly || isDragging.value) return\n const totalMinutes = props.dayStartHour * 60 + slotIndex * props.slotDuration\n const hour = Math.floor(totalMinutes / 60)\n const minute = totalMinutes % 60\n const date = new Date(day)\n date.setHours(hour, minute, 0, 0)\n emit('slot-click', { date, hour, minute })\n}\n\nfunction onSlotPointerDown(day: Date, slotIndex: number, e: PointerEvent) {\n if (props.readonly) return\n const date = new Date(day)\n const totalMinutes = props.dayStartHour * 60 + slotIndex * props.slotDuration\n date.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0)\n const dayIndex = visibleDays.value.findIndex((d) => isSameDay(d, day))\n startCreate(date, e.clientY, dayIndex)\n}\n\nfunction onEventPointerDown(event: ScheduleEvent, dayIndex: number, e: PointerEvent) {\n e.stopPropagation()\n startMove(event, e.clientY, dayIndex)\n}\n\nfunction onResizePointerDown(event: ScheduleEvent, edge: 'top' | 'bottom', dayIndex: number, e: PointerEvent) {\n e.stopPropagation()\n startResize(event, edge, e.clientY, dayIndex)\n}\n\nfunction onMonthDayClick(date: Date) {\n currentDate.value = date\n emit('update:modelValue', date)\n setView('day')\n}\n\nonMounted(() => {\n nowTimer.value = setInterval(() => { now.value = new Date() }, 60000)\n\n // Scroll to current time or 8am\n if (bodyRef.value) {\n const targetHour = now.value.getHours() >= props.dayStartHour && now.value.getHours() <= props.dayEndHour\n ? now.value.getHours()\n : 8\n const offsetMin = (targetHour - props.dayStartHour) * 60\n const pixelsPerMin = SLOT_HEIGHT / props.slotDuration\n bodyRef.value.scrollTop = Math.max(0, offsetMin * pixelsPerMin - 100)\n }\n})\n\nonUnmounted(() => {\n if (nowTimer.value) clearInterval(nowTimer.value)\n})\n</script>\n\n<template>\n <div class=\"mint-schedule\" :style=\"({ '--slot-height': `${SLOT_HEIGHT}px` } as Record<string, string>)\">\n <!-- Header -->\n <div v-if=\"showNavigation || showViewToggle\" class=\"mint-schedule__header\">\n <div v-if=\"showNavigation\" class=\"mint-schedule__nav\">\n <button\n type=\"button\"\n class=\"mint-schedule__nav-btn\"\n aria-label=\"Previous\"\n @click=\"navigate('prev')\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"mint-schedule__nav-btn\"\n aria-label=\"Next\"\n @click=\"navigate('next')\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"mint-schedule__today-btn\"\n @click=\"navigate('today')\"\n >\n Today\n </button>\n </div>\n\n <span class=\"mint-schedule__title\">{{ headerTitle }}</span>\n\n <div v-if=\"showViewToggle\" class=\"mint-schedule__view-toggle\">\n <button\n v-for=\"v in (['day', 'week', 'month'] as ScheduleView[])\"\n :key=\"v\"\n type=\"button\"\n :class=\"['mint-schedule__view-btn', currentView === v ? 'mint-schedule__view-btn--active' : '']\"\n @click=\"setView(v)\"\n >\n {{ v.charAt(0).toUpperCase() + v.slice(1) }}\n </button>\n </div>\n </div>\n\n <!-- Day / Week View -->\n <template v-if=\"currentView === 'day' || currentView === 'week'\">\n <!-- Day headers -->\n <div v-if=\"currentView === 'week'\" class=\"mint-schedule__day-headers\">\n <div class=\"mint-schedule__day-header-gutter\" />\n <div\n v-for=\"(day, i) in visibleDays\"\n :key=\"i\"\n :class=\"['mint-schedule__day-header', isToday(day) ? 'mint-schedule__day-header--today' : '']\"\n >\n <slot name=\"day-header\" :date=\"day\" :isToday=\"isToday(day)\">\n <span class=\"mint-schedule__day-header-name\">\n {{ day.toLocaleDateString(locale, { weekday: 'short' }) }}\n </span>\n <span class=\"mint-schedule__day-header-number\">\n {{ day.getDate() }}\n </span>\n </slot>\n </div>\n </div>\n\n <!-- Time grid body -->\n <div ref=\"bodyRef\" class=\"mint-schedule__body\">\n <!-- Time gutter -->\n <div class=\"mint-schedule__time-gutter\">\n <div\n v-for=\"(label, i) in timeLabels\"\n :key=\"i\"\n class=\"mint-schedule__time-label\"\n >\n <slot name=\"time-label\" :label=\"label\" :index=\"i\">\n {{ i < timeLabels.length - 1 ? label : '' }}\n </slot>\n </div>\n </div>\n\n <!-- Day columns -->\n <div class=\"mint-schedule__day-columns\">\n <div\n v-for=\"(day, dayIdx) in visibleDays\"\n :key=\"dayIdx\"\n :class=\"['mint-schedule__day-column', isToday(day) ? 'mint-schedule__day-column--today' : '']\"\n >\n <!-- Slot grid -->\n <div\n v-for=\"slotIdx in totalSlots\"\n :key=\"slotIdx\"\n :class=\"['mint-schedule__slot', !readonly ? 'mint-schedule__slot--interactive' : '']\"\n @click=\"onSlotClick(day, slotIdx - 1)\"\n @pointerdown=\"onSlotPointerDown(day, slotIdx - 1, $event)\"\n />\n\n <!-- Events -->\n <div\n v-for=\"event in getEventsForDay(day)\"\n :key=\"event.id\"\n :class=\"['mint-schedule__event', getStatusClass(event.status)]\"\n :style=\"getEventStyle(event, day) || undefined\"\n @click=\"onEventClick(event, $event)\"\n @pointerdown=\"onEventPointerDown(event, dayIdx, $event)\"\n >\n <slot name=\"event\" :event=\"event\">\n <div\n v-if=\"!readonly && event.resizable !== false\"\n class=\"mint-schedule__event-resize-handle mint-schedule__event-resize-handle--top\"\n @pointerdown=\"onResizePointerDown(event, 'top', dayIdx, $event)\"\n />\n <div class=\"mint-schedule__event-title\">{{ event.title }}</div>\n <div class=\"mint-schedule__event-time\">{{ formatEventTime(event) }}</div>\n <div\n v-if=\"!readonly && event.resizable !== false\"\n class=\"mint-schedule__event-resize-handle mint-schedule__event-resize-handle--bottom\"\n @pointerdown=\"onResizePointerDown(event, 'bottom', dayIdx, $event)\"\n />\n </slot>\n </div>\n\n <!-- Blocked slots -->\n <div\n v-for=\"(blocked, bIdx) in getBlockedForDay(day)\"\n :key=\"`b-${bIdx}`\"\n class=\"mint-schedule__blocked\"\n :style=\"getBlockedStyle(blocked)\"\n >\n <span v-if=\"blocked.label\" class=\"mint-schedule__blocked-label\">\n {{ blocked.label }}\n </span>\n </div>\n\n <!-- Now indicator -->\n <div\n v-if=\"showNowIndicator && isToday(day) && getNowIndicatorStyle()\"\n class=\"mint-schedule__now-indicator\"\n :style=\"getNowIndicatorStyle()!\"\n >\n <div class=\"mint-schedule__now-dot\" />\n </div>\n\n <!-- Drag ghost -->\n <div\n v-if=\"isDragging && ghost && ghost.dayIndex === dayIdx\"\n class=\"mint-schedule__ghost\"\n :style=\"ghost.style\"\n />\n </div>\n </div>\n </div>\n </template>\n\n <!-- Month View -->\n <template v-if=\"currentView === 'month'\">\n <div class=\"mint-schedule__month-grid\">\n <!-- Weekday headers -->\n <div\n v-for=\"label in monthWeekdayLabels\"\n :key=\"label\"\n class=\"mint-schedule__month-weekday\"\n >\n {{ label }}\n </div>\n\n <!-- Day cells -->\n <div\n v-for=\"(day, i) in monthDays\"\n :key=\"i\"\n :class=\"[\n 'mint-schedule__month-cell',\n isToday(day.date) ? 'mint-schedule__month-cell--today' : '',\n !day.isCurrentMonth ? 'mint-schedule__month-cell--outside' : '',\n ]\"\n @click=\"onMonthDayClick(day.date)\"\n >\n <slot name=\"month-day\" :date=\"day.date\" :isToday=\"isToday(day.date)\" :events=\"getEventsForDate(day.date)\">\n <div class=\"mint-schedule__month-date\">{{ day.date.getDate() }}</div>\n <div\n v-for=\"event in getEventsForDate(day.date).slice(0, 3)\"\n :key=\"event.id\"\n :class=\"['mint-schedule__month-event', getMonthStatusClass(event.status)]\"\n @click.stop=\"onEventClick(event, $event)\"\n >\n {{ event.title }}\n </div>\n <div\n v-if=\"getEventsForDate(day.date).length > 3\"\n class=\"mint-schedule__month-more\"\n >\n +{{ getEventsForDate(day.date).length - 3 }} more\n </div>\n </slot>\n </div>\n </div>\n </template>\n </div>\n</template>\n\n<style>\n@import '../styles/components/schedule-calendar.css';\n</style>\n","<script setup lang=\"ts\">\n/** Render generated SDK component bindings such as WellPlate, DoseCalculator, DataFrame, and PlateMapEditor. */\nimport { computed, type Component } from 'vue'\nimport DataFrame from './DataFrame.vue'\nimport DoseCalculator from './DoseCalculator.vue'\nimport ExperimentTimeline from './ExperimentTimeline.vue'\nimport PlateMapEditor from './PlateMapEditor.vue'\nimport ReagentList from './ReagentList.vue'\nimport SampleSelector from './SampleSelector.vue'\nimport ScheduleCalendar from './ScheduleCalendar.vue'\nimport WellPlate from './WellPlate.vue'\n\nexport interface ComponentBindingRendererBinding {\n id?: string\n component: string\n props?: Record<string, unknown> | readonly string[]\n propsObject?: Record<string, unknown>\n description?: string\n template_id?: string\n}\n\ntype ComponentBindingRendererLayout = 'grid' | 'stack'\n\ninterface Props {\n /** Single generated SDK component binding to render. */\n binding?: ComponentBindingRendererBinding\n /** Generated SDK component bindings to render. */\n bindings?: ComponentBindingRendererBinding[]\n /** Optional allow-list of component names, for example ['WellPlate', 'DataFrame']. */\n include?: string[]\n /** Optional deny-list of component names. */\n exclude?: string[]\n /** Compact child component sizing. */\n dense?: boolean\n /** Prefer preview-safe props for editable components. */\n readonly?: boolean\n /** Show component/template labels above each rendered component. */\n showHeaders?: boolean\n /** Show binding descriptions in each header. */\n showDescriptions?: boolean\n /** Grid or vertical stack layout. */\n layout?: ComponentBindingRendererLayout\n /** Message shown when no binding can be rendered. */\n emptyText?: string\n}\n\ntype RenderableBinding = ComponentBindingRendererBinding & {\n id: string\n componentImpl: Component\n normalizedProps: Record<string, unknown>\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n binding: undefined,\n bindings: () => [],\n include: () => [],\n exclude: () => [],\n dense: false,\n readonly: true,\n showHeaders: true,\n showDescriptions: true,\n layout: 'grid',\n emptyText: 'No renderable SDK component bindings.',\n})\n\nconst componentRegistry: Record<string, Component> = {\n DataFrame,\n DoseCalculator,\n ExperimentTimeline,\n PlateMapEditor,\n ReagentList,\n SampleSelector,\n ScheduleCalendar,\n WellPlate,\n}\n\nconst inputBindings = computed<ComponentBindingRendererBinding[]>(() => [\n ...(props.binding ? [props.binding] : []),\n ...props.bindings,\n])\n\nconst renderableBindings = computed<RenderableBinding[]>(() =>\n inputBindings.value\n .filter(binding => isIncluded(binding.component))\n .map((binding, index) => {\n const componentImpl = componentRegistry[binding.component]\n if (!componentImpl) return null\n return {\n ...binding,\n id: binding.id ?? `${binding.component}-${index + 1}`,\n componentImpl,\n normalizedProps: normalizeProps(binding),\n }\n })\n .filter((binding): binding is RenderableBinding => binding !== null)\n)\n\nconst rendererClasses = computed(() => [\n 'mint-component-binding-renderer',\n `mint-component-binding-renderer--${props.layout}`,\n props.dense ? 'mint-component-binding-renderer--dense' : '',\n])\n\nfunction isIncluded(componentName: string): boolean {\n if (props.include.length > 0 && !props.include.includes(componentName)) return false\n return !props.exclude.includes(componentName)\n}\n\nfunction baseProps(binding: ComponentBindingRendererBinding): Record<string, unknown> {\n if (binding.propsObject) return { ...binding.propsObject }\n if (isRecord(binding.props)) return { ...binding.props }\n return {}\n}\n\nfunction normalizeProps(binding: ComponentBindingRendererBinding): Record<string, unknown> {\n const base = baseProps(binding)\n\n switch (binding.component) {\n case 'WellPlate':\n return {\n ...base,\n readonly: props.readonly || Boolean(base.readonly),\n size: props.dense ? 'md' : (base.size ?? 'fill'),\n }\n case 'PlateMapEditor':\n return {\n ...base,\n size: props.dense ? 'md' : (base.size ?? 'fill'),\n showToolbar: props.readonly ? false : (base.showToolbar ?? true),\n showSidebar: props.readonly ? false : (base.showSidebar ?? true),\n allowAddPlates: props.readonly ? false : (base.allowAddPlates ?? true),\n allowAddSamples: props.readonly ? false : (base.allowAddSamples ?? true),\n }\n case 'DataFrame':\n return {\n ...base,\n size: props.dense ? 'sm' : (base.size ?? 'md'),\n maxHeight: base.maxHeight ?? (props.dense ? '280px' : undefined),\n }\n case 'ExperimentTimeline':\n return {\n ...base,\n editable: props.readonly ? false : base.editable,\n size: props.dense ? 'sm' : (base.size ?? 'md'),\n }\n case 'ReagentList':\n return {\n ...base,\n readonly: props.readonly || Boolean(base.readonly),\n }\n case 'SampleSelector':\n return {\n ...base,\n enableGrouping: props.readonly ? false : (base.enableGrouping ?? true),\n enableSmartGroup: props.readonly ? false : (base.enableSmartGroup ?? true),\n }\n case 'ScheduleCalendar':\n return {\n ...base,\n readonly: props.readonly || Boolean(base.readonly),\n showNavigation: props.dense ? false : (base.showNavigation ?? true),\n showViewToggle: props.dense ? false : (base.showViewToggle ?? true),\n }\n default:\n return base\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n</script>\n\n<template>\n <div :class=\"rendererClasses\">\n <section\n v-for=\"renderableBinding in renderableBindings\"\n :key=\"renderableBinding.id\"\n class=\"mint-component-binding-renderer__item\"\n :data-component-binding-id=\"renderableBinding.id\"\n :data-template-id=\"renderableBinding.template_id\"\n :data-template-component=\"renderableBinding.component\"\n >\n <header\n v-if=\"showHeaders\"\n class=\"mint-component-binding-renderer__header\"\n >\n <div class=\"mint-component-binding-renderer__title-group\">\n <p class=\"mint-component-binding-renderer__component\">\n {{ renderableBinding.component }}\n </p>\n <p\n v-if=\"renderableBinding.template_id || renderableBinding.id\"\n class=\"mint-component-binding-renderer__source\"\n >\n {{ renderableBinding.template_id ?? renderableBinding.id }}\n </p>\n </div>\n <p\n v-if=\"showDescriptions && renderableBinding.description\"\n class=\"mint-component-binding-renderer__description\"\n >\n {{ renderableBinding.description }}\n </p>\n </header>\n\n <div class=\"mint-component-binding-renderer__body\">\n <component\n :is=\"renderableBinding.componentImpl\"\n v-bind=\"renderableBinding.normalizedProps\"\n />\n </div>\n </section>\n\n <p\n v-if=\"renderableBindings.length === 0\"\n class=\"mint-component-binding-renderer__empty\"\n >\n {{ emptyText }}\n </p>\n </div>\n</template>\n\n<style scoped>\n.mint-component-binding-renderer {\n display: grid;\n gap: 1rem;\n min-width: 0;\n}\n\n.mint-component-binding-renderer--grid {\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 28rem), 1fr));\n}\n\n.mint-component-binding-renderer--stack {\n grid-template-columns: minmax(0, 1fr);\n}\n\n.mint-component-binding-renderer--dense {\n gap: 0.75rem;\n}\n\n.mint-component-binding-renderer__item {\n min-width: 0;\n overflow: hidden;\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 0.5rem;\n background: var(--bg-card, #ffffff);\n box-shadow: var(--shadow-sm, 0 1px 2px rgb(15 23 42 / 0.06));\n}\n\n.mint-component-binding-renderer__header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 1rem;\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--border-color, #e5e7eb);\n background: var(--bg-secondary, #f8fafc);\n}\n\n.mint-component-binding-renderer__title-group {\n min-width: 0;\n}\n\n.mint-component-binding-renderer__component {\n margin: 0;\n color: var(--text-primary, #0f172a);\n font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);\n font-size: 0.8125rem;\n font-weight: 600;\n}\n\n.mint-component-binding-renderer__source,\n.mint-component-binding-renderer__description,\n.mint-component-binding-renderer__empty {\n margin: 0;\n color: var(--text-secondary, #475569);\n font-size: 0.75rem;\n line-height: 1.4;\n}\n\n.mint-component-binding-renderer__description {\n max-width: 24rem;\n text-align: right;\n}\n\n.mint-component-binding-renderer__body {\n min-width: 0;\n padding: 1rem;\n}\n\n.mint-component-binding-renderer--dense .mint-component-binding-renderer__header,\n.mint-component-binding-renderer--dense .mint-component-binding-renderer__body {\n padding: 0.75rem;\n}\n\n.mint-component-binding-renderer__empty {\n padding: 1rem;\n border: 1px dashed var(--border-color, #e5e7eb);\n border-radius: 0.5rem;\n background: var(--bg-secondary, #f8fafc);\n text-align: center;\n}\n\n@media (max-width: 720px) {\n .mint-component-binding-renderer__header {\n display: block;\n }\n\n .mint-component-binding-renderer__description {\n max-width: none;\n margin-top: 0.25rem;\n text-align: left;\n }\n}\n</style>\n","<script setup lang=\"ts\">\n/** Render generated SDK component bindings such as WellPlate, DoseCalculator, DataFrame, and PlateMapEditor. */\nimport { computed, type Component } from 'vue'\nimport DataFrame from './DataFrame.vue'\nimport DoseCalculator from './DoseCalculator.vue'\nimport ExperimentTimeline from './ExperimentTimeline.vue'\nimport PlateMapEditor from './PlateMapEditor.vue'\nimport ReagentList from './ReagentList.vue'\nimport SampleSelector from './SampleSelector.vue'\nimport ScheduleCalendar from './ScheduleCalendar.vue'\nimport WellPlate from './WellPlate.vue'\n\nexport interface ComponentBindingRendererBinding {\n id?: string\n component: string\n props?: Record<string, unknown> | readonly string[]\n propsObject?: Record<string, unknown>\n description?: string\n template_id?: string\n}\n\ntype ComponentBindingRendererLayout = 'grid' | 'stack'\n\ninterface Props {\n /** Single generated SDK component binding to render. */\n binding?: ComponentBindingRendererBinding\n /** Generated SDK component bindings to render. */\n bindings?: ComponentBindingRendererBinding[]\n /** Optional allow-list of component names, for example ['WellPlate', 'DataFrame']. */\n include?: string[]\n /** Optional deny-list of component names. */\n exclude?: string[]\n /** Compact child component sizing. */\n dense?: boolean\n /** Prefer preview-safe props for editable components. */\n readonly?: boolean\n /** Show component/template labels above each rendered component. */\n showHeaders?: boolean\n /** Show binding descriptions in each header. */\n showDescriptions?: boolean\n /** Grid or vertical stack layout. */\n layout?: ComponentBindingRendererLayout\n /** Message shown when no binding can be rendered. */\n emptyText?: string\n}\n\ntype RenderableBinding = ComponentBindingRendererBinding & {\n id: string\n componentImpl: Component\n normalizedProps: Record<string, unknown>\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n binding: undefined,\n bindings: () => [],\n include: () => [],\n exclude: () => [],\n dense: false,\n readonly: true,\n showHeaders: true,\n showDescriptions: true,\n layout: 'grid',\n emptyText: 'No renderable SDK component bindings.',\n})\n\nconst componentRegistry: Record<string, Component> = {\n DataFrame,\n DoseCalculator,\n ExperimentTimeline,\n PlateMapEditor,\n ReagentList,\n SampleSelector,\n ScheduleCalendar,\n WellPlate,\n}\n\nconst inputBindings = computed<ComponentBindingRendererBinding[]>(() => [\n ...(props.binding ? [props.binding] : []),\n ...props.bindings,\n])\n\nconst renderableBindings = computed<RenderableBinding[]>(() =>\n inputBindings.value\n .filter(binding => isIncluded(binding.component))\n .map((binding, index) => {\n const componentImpl = componentRegistry[binding.component]\n if (!componentImpl) return null\n return {\n ...binding,\n id: binding.id ?? `${binding.component}-${index + 1}`,\n componentImpl,\n normalizedProps: normalizeProps(binding),\n }\n })\n .filter((binding): binding is RenderableBinding => binding !== null)\n)\n\nconst rendererClasses = computed(() => [\n 'mint-component-binding-renderer',\n `mint-component-binding-renderer--${props.layout}`,\n props.dense ? 'mint-component-binding-renderer--dense' : '',\n])\n\nfunction isIncluded(componentName: string): boolean {\n if (props.include.length > 0 && !props.include.includes(componentName)) return false\n return !props.exclude.includes(componentName)\n}\n\nfunction baseProps(binding: ComponentBindingRendererBinding): Record<string, unknown> {\n if (binding.propsObject) return { ...binding.propsObject }\n if (isRecord(binding.props)) return { ...binding.props }\n return {}\n}\n\nfunction normalizeProps(binding: ComponentBindingRendererBinding): Record<string, unknown> {\n const base = baseProps(binding)\n\n switch (binding.component) {\n case 'WellPlate':\n return {\n ...base,\n readonly: props.readonly || Boolean(base.readonly),\n size: props.dense ? 'md' : (base.size ?? 'fill'),\n }\n case 'PlateMapEditor':\n return {\n ...base,\n size: props.dense ? 'md' : (base.size ?? 'fill'),\n showToolbar: props.readonly ? false : (base.showToolbar ?? true),\n showSidebar: props.readonly ? false : (base.showSidebar ?? true),\n allowAddPlates: props.readonly ? false : (base.allowAddPlates ?? true),\n allowAddSamples: props.readonly ? false : (base.allowAddSamples ?? true),\n }\n case 'DataFrame':\n return {\n ...base,\n size: props.dense ? 'sm' : (base.size ?? 'md'),\n maxHeight: base.maxHeight ?? (props.dense ? '280px' : undefined),\n }\n case 'ExperimentTimeline':\n return {\n ...base,\n editable: props.readonly ? false : base.editable,\n size: props.dense ? 'sm' : (base.size ?? 'md'),\n }\n case 'ReagentList':\n return {\n ...base,\n readonly: props.readonly || Boolean(base.readonly),\n }\n case 'SampleSelector':\n return {\n ...base,\n enableGrouping: props.readonly ? false : (base.enableGrouping ?? true),\n enableSmartGroup: props.readonly ? false : (base.enableSmartGroup ?? true),\n }\n case 'ScheduleCalendar':\n return {\n ...base,\n readonly: props.readonly || Boolean(base.readonly),\n showNavigation: props.dense ? false : (base.showNavigation ?? true),\n showViewToggle: props.dense ? false : (base.showViewToggle ?? true),\n }\n default:\n return base\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n</script>\n\n<template>\n <div :class=\"rendererClasses\">\n <section\n v-for=\"renderableBinding in renderableBindings\"\n :key=\"renderableBinding.id\"\n class=\"mint-component-binding-renderer__item\"\n :data-component-binding-id=\"renderableBinding.id\"\n :data-template-id=\"renderableBinding.template_id\"\n :data-template-component=\"renderableBinding.component\"\n >\n <header\n v-if=\"showHeaders\"\n class=\"mint-component-binding-renderer__header\"\n >\n <div class=\"mint-component-binding-renderer__title-group\">\n <p class=\"mint-component-binding-renderer__component\">\n {{ renderableBinding.component }}\n </p>\n <p\n v-if=\"renderableBinding.template_id || renderableBinding.id\"\n class=\"mint-component-binding-renderer__source\"\n >\n {{ renderableBinding.template_id ?? renderableBinding.id }}\n </p>\n </div>\n <p\n v-if=\"showDescriptions && renderableBinding.description\"\n class=\"mint-component-binding-renderer__description\"\n >\n {{ renderableBinding.description }}\n </p>\n </header>\n\n <div class=\"mint-component-binding-renderer__body\">\n <component\n :is=\"renderableBinding.componentImpl\"\n v-bind=\"renderableBinding.normalizedProps\"\n />\n </div>\n </section>\n\n <p\n v-if=\"renderableBindings.length === 0\"\n class=\"mint-component-binding-renderer__empty\"\n >\n {{ emptyText }}\n </p>\n </div>\n</template>\n\n<style scoped>\n.mint-component-binding-renderer {\n display: grid;\n gap: 1rem;\n min-width: 0;\n}\n\n.mint-component-binding-renderer--grid {\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 28rem), 1fr));\n}\n\n.mint-component-binding-renderer--stack {\n grid-template-columns: minmax(0, 1fr);\n}\n\n.mint-component-binding-renderer--dense {\n gap: 0.75rem;\n}\n\n.mint-component-binding-renderer__item {\n min-width: 0;\n overflow: hidden;\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 0.5rem;\n background: var(--bg-card, #ffffff);\n box-shadow: var(--shadow-sm, 0 1px 2px rgb(15 23 42 / 0.06));\n}\n\n.mint-component-binding-renderer__header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 1rem;\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--border-color, #e5e7eb);\n background: var(--bg-secondary, #f8fafc);\n}\n\n.mint-component-binding-renderer__title-group {\n min-width: 0;\n}\n\n.mint-component-binding-renderer__component {\n margin: 0;\n color: var(--text-primary, #0f172a);\n font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);\n font-size: 0.8125rem;\n font-weight: 600;\n}\n\n.mint-component-binding-renderer__source,\n.mint-component-binding-renderer__description,\n.mint-component-binding-renderer__empty {\n margin: 0;\n color: var(--text-secondary, #475569);\n font-size: 0.75rem;\n line-height: 1.4;\n}\n\n.mint-component-binding-renderer__description {\n max-width: 24rem;\n text-align: right;\n}\n\n.mint-component-binding-renderer__body {\n min-width: 0;\n padding: 1rem;\n}\n\n.mint-component-binding-renderer--dense .mint-component-binding-renderer__header,\n.mint-component-binding-renderer--dense .mint-component-binding-renderer__body {\n padding: 0.75rem;\n}\n\n.mint-component-binding-renderer__empty {\n padding: 1rem;\n border: 1px dashed var(--border-color, #e5e7eb);\n border-radius: 0.5rem;\n background: var(--bg-secondary, #f8fafc);\n text-align: center;\n}\n\n@media (max-width: 720px) {\n .mint-component-binding-renderer__header {\n display: block;\n }\n\n .mint-component-binding-renderer__description {\n max-width: none;\n margin-top: 0.25rem;\n text-align: left;\n }\n}\n</style>\n","<script setup lang=\"ts\">\n/** Complete control workspace component that turns a simple controls data model into AppTopBar, AppSidebar, and FormBuilder forms. */\nimport { computed, effectScope, onScopeDispose, shallowRef, toRaw, watch, type EffectScope } from 'vue'\nimport type { TopBarVariant } from '../types'\nimport type { FormEnhancements } from '../types/form-builder'\nimport type {\n ControlComponentBindingsConfig,\n ControlComponentPropsByIdMap,\n ControlComponentPropsMap,\n ControlModel,\n ControlModelBinding,\n ControlSchema,\n ControlWorkspaceOptions,\n UseControlWorkspaceReturn,\n} from '../composables/useControlSchema'\nimport { mergeControlWorkspaceOptions, resolveControlModel, useControlWorkspace } from '../composables/useControlSchema'\nimport AppLayout from './AppLayout.vue'\nimport AppSidebar from './AppSidebar.vue'\nimport AppTopBar from './AppTopBar.vue'\nimport FormBuilder from './FormBuilder.vue'\n\ntype ControlWorkspaceSidebarVariant = 'default' | 'analysis'\ntype ResolvedControlWorkspace = UseControlWorkspaceReturn<ControlSchema>\n\ninterface ControlWorkspaceDefaultSlotProps {\n workspace: ResolvedControlWorkspace\n bindings: ResolvedControlWorkspace['bindings']\n values: ResolvedControlWorkspace['values']\n componentBindings: ReturnType<ResolvedControlWorkspace['getComponentBindings']>\n componentBindingsById: ReturnType<ResolvedControlWorkspace['getComponentBindingsById']>\n componentProps: ReturnType<ResolvedControlWorkspace['getComponentProps']>\n componentPropsById: ReturnType<ResolvedControlWorkspace['getComponentPropsById']>\n}\n\ninterface ControlWorkspaceTopbarSlotProps {\n workspace: ResolvedControlWorkspace\n bindings: ResolvedControlWorkspace['bindings']\n topBar: ResolvedControlWorkspace['bindings']['topBar']['value']\n}\n\ninterface ControlWorkspaceSidebarSlotProps {\n workspace: ResolvedControlWorkspace\n bindings: ResolvedControlWorkspace['bindings']\n sidebar: ResolvedControlWorkspace['sidebar']\n}\n\ninterface Props {\n /** Model returned by defineControlModel()/defineDoseDesignControlModel(), or a raw nested ControlModel. */\n model?: ControlModel | ControlModelBinding\n /** Workspace returned by useControlWorkspace(). Use for full manual control. */\n workspace?: UseControlWorkspaceReturn<ControlSchema>\n /** Compact controls schema. When provided without workspace, the view creates the workspace internally. */\n controls?: ControlSchema\n /** Options passed to the internally generated useControlWorkspace() call. */\n controlOptions?: ControlWorkspaceOptions\n /** Initial values for the internally generated workspace. Merged over controlOptions.initialValues. */\n initialValues?: Record<string, unknown>\n /** External values for the internally generated workspace. Supports default v-model. */\n modelValue?: Record<string, unknown>\n /** External values for the internally generated workspace. Supports v-model:values. */\n values?: Record<string, unknown>\n /** AppTopBar title. */\n title?: string\n /** AppTopBar subtitle. */\n subtitle?: string\n /** AppTopBar visual variant. */\n topBarVariant?: TopBarVariant\n /** AppSidebar/AppLayout sidebar width. */\n sidebarWidth?: string\n /** AppSidebar visual preset. `analysis` matches the LEAF-style MINT analysis sidebar design language. */\n sidebarVariant?: ControlWorkspaceSidebarVariant\n /** Convert the sidebar into an SDK-owned mobile overlay below the AppLayout breakpoint. */\n responsiveSidebar?: boolean\n /** Sidebar position in AppLayout. */\n sidebarPosition?: 'left' | 'right'\n /** Optional AppSidebar chrome title for LEAF-style plugin workbenches. */\n sidebarTitle?: string\n /** Optional AppSidebar chrome subtitle for active experiment/run context. */\n sidebarSubtitle?: string\n /** Optional compact badge/count rendered in the AppSidebar chrome header. */\n sidebarBadge?: string | number\n /** Floating AppLayout style. */\n floating?: boolean\n /** Compact AppSidebar density. */\n dense?: boolean\n /** Whether AppTopBar should show generated settings. */\n showSettings?: boolean\n /** Render FormBuilder actions in the default content. */\n showFormActions?: boolean\n /** Runtime FormBuilder enhancements passed to generated forms. */\n formEnhancements?: FormEnhancements<Record<string, unknown>>\n /** Optional SDK component bindings exposed to the default slot with resolved props. */\n componentBindings?: ControlComponentBindingsConfig\n /** Optional mapping from workspace values to component props exposed to the default slot. */\n componentProps?: ControlComponentPropsMap\n /** Optional named mappings from workspace values to component props exposed to the default slot. */\n componentPropsById?: ControlComponentPropsByIdMap\n /** Loading/saving state passed to generated forms. */\n formLoading?: boolean\n /** Disabled state passed to generated forms. */\n formDisabled?: boolean\n /** Readonly state passed to generated forms. */\n formReadonly?: boolean\n /** FormBuilder size in the default content. */\n formSize?: 'sm' | 'md' | 'lg'\n}\n\nconst emit = defineEmits<{\n /** Emitted when generated workspace values change through FormBuilder, AppSidebar, or AppTopBar settings. */\n 'update:modelValue': [values: Record<string, unknown>]\n /** Emitted when generated workspace values change through FormBuilder, AppSidebar, or AppTopBar settings. */\n 'update:values': [values: Record<string, unknown>]\n /** Forwarded from the default FormBuilder when showFormActions is enabled. */\n submit: [values: Record<string, unknown>]\n /** Forwarded from the default FormBuilder when showFormActions is enabled. */\n cancel: []\n}>()\n\ndefineSlots<{\n default?: (props: ControlWorkspaceDefaultSlotProps) => unknown\n topbar?: (props: ControlWorkspaceTopbarSlotProps) => unknown\n sidebar?: (props: ControlWorkspaceSidebarSlotProps) => unknown\n}>()\n\nconst props = withDefaults(defineProps<Props>(), {\n model: undefined,\n workspace: undefined,\n controls: undefined,\n controlOptions: () => ({}),\n initialValues: undefined,\n modelValue: undefined,\n values: undefined,\n title: 'Workspace',\n subtitle: undefined,\n topBarVariant: 'card',\n sidebarWidth: '320px',\n sidebarVariant: 'analysis',\n responsiveSidebar: true,\n sidebarPosition: 'left',\n sidebarTitle: undefined,\n sidebarSubtitle: undefined,\n sidebarBadge: undefined,\n floating: false,\n dense: true,\n showSettings: true,\n showFormActions: false,\n formEnhancements: undefined,\n componentBindings: undefined,\n componentProps: undefined,\n componentPropsById: undefined,\n formLoading: false,\n formDisabled: false,\n formReadonly: false,\n formSize: 'md',\n})\n\nconst externalValues = computed(() => props.modelValue ?? props.values)\nconst resolvedModel = computed<ControlModelBinding | undefined>(() => {\n return resolveControlModel(props.model)\n})\nconst resolvedControls = computed<ControlSchema>(() => props.controls ?? resolvedModel.value?.controls ?? {})\nconst baseControlOptions = computed<ControlWorkspaceOptions>(() =>\n mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)\n)\n\nconst resolvedControlOptions = computed<ControlWorkspaceOptions>(() => {\n if (props.initialValues === undefined && externalValues.value === undefined) return baseControlOptions.value\n\n return {\n ...baseControlOptions.value,\n initialValues: {\n ...(baseControlOptions.value.initialValues ?? {}),\n ...(props.initialValues ?? {}),\n ...(externalValues.value ?? {}),\n },\n }\n})\n\nlet generatedWorkspaceScope: EffectScope | undefined\n\nfunction createGeneratedWorkspace(seedValues: Record<string, unknown> = {}) {\n generatedWorkspaceScope?.stop()\n generatedWorkspaceScope = effectScope()\n return generatedWorkspaceScope.run(() =>\n useControlWorkspace(resolvedControls.value, {\n ...resolvedControlOptions.value,\n initialValues: {\n ...seedValues,\n ...(resolvedControlOptions.value.initialValues ?? {}),\n },\n })\n )!\n}\n\nconst generatedWorkspace = shallowRef<UseControlWorkspaceReturn<ControlSchema>>(createGeneratedWorkspace())\nconst resolvedWorkspace = computed<UseControlWorkspaceReturn<ControlSchema>>(() => props.workspace ?? generatedWorkspace.value)\nconst resolvedComponentBindings = computed(() =>\n resolvedWorkspace.value.getComponentBindings(props.componentBindings ?? resolvedModel.value?.componentBindings)\n)\nconst resolvedComponentBindingsById = computed(() =>\n resolvedWorkspace.value.getComponentBindingsById(props.componentBindings ?? resolvedModel.value?.componentBindings)\n)\nconst resolvedComponentProps = computed(() =>\n resolvedWorkspace.value.getComponentProps(props.componentProps ?? resolvedModel.value?.componentProps)\n)\nconst resolvedComponentPropsById = computed(() =>\n resolvedWorkspace.value.getComponentPropsById(props.componentPropsById ?? resolvedModel.value?.componentPropsById)\n)\nconst resolvedBindings = computed<ResolvedControlWorkspace['bindings']>(() => ({\n ...resolvedWorkspace.value.bindings,\n componentBindings: resolvedComponentBindings,\n componentBindingsById: resolvedComponentBindingsById,\n componentProps: resolvedComponentProps,\n componentPropsById: resolvedComponentPropsById,\n}))\nconst resolvedTopBarSlot = computed(() => resolvedWorkspace.value.bindings.topBar.value)\n\nwatch(\n [() => props.model, () => props.controls, () => props.controlOptions, () => props.initialValues],\n () => {\n const previousWorkspace = generatedWorkspace.value\n const previousActiveView = previousWorkspace.activeView.value\n generatedWorkspace.value = createGeneratedWorkspace({ ...previousWorkspace.values })\n generatedWorkspace.value.setActiveView(previousActiveView)\n },\n { deep: true },\n)\n\nonScopeDispose(() => generatedWorkspaceScope?.stop())\n\nwatch(\n externalValues,\n (values) => {\n if (values === undefined || props.workspace !== undefined) return\n if (recordsEqual(generatedWorkspace.value.values, values)) return\n generatedWorkspace.value.setValues(values)\n },\n { deep: true },\n)\n\nwatch(\n () => generatedWorkspace.value.values,\n (values) => {\n if (props.workspace !== undefined) return\n const nextValues = { ...values }\n if (externalValues.value !== undefined && recordsEqual(externalValues.value, nextValues)) return\n emit('update:modelValue', nextValues)\n emit('update:values', nextValues)\n },\n { deep: true },\n)\n\nfunction recordsEqual(left: Record<string, unknown>, right: Record<string, unknown>): boolean {\n const leftKeys = Object.keys(left)\n const rightKeys = Object.keys(right)\n if (leftKeys.length !== rightKeys.length) return false\n return leftKeys.every(key => valuesEqual(left[key], right[key]))\n}\n\nfunction valuesEqual(left: unknown, right: unknown): boolean {\n const leftRaw = toRaw(left)\n const rightRaw = toRaw(right)\n\n if (Object.is(leftRaw, rightRaw)) return true\n\n if (Array.isArray(leftRaw) || Array.isArray(rightRaw)) {\n if (!Array.isArray(leftRaw) || !Array.isArray(rightRaw)) return false\n if (leftRaw.length !== rightRaw.length) return false\n return leftRaw.every((item, index) => valuesEqual(item, rightRaw[index]))\n }\n\n if (isPlainRecord(leftRaw) || isPlainRecord(rightRaw)) {\n if (!isPlainRecord(leftRaw) || !isPlainRecord(rightRaw)) return false\n return recordsEqual(leftRaw, rightRaw)\n }\n\n return false\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n</script>\n\n<template>\n <AppLayout\n class=\"mint-control-workspace-view\"\n :sidebar-position=\"sidebarPosition\"\n :sidebar-width=\"sidebarWidth\"\n :responsive-sidebar=\"responsiveSidebar\"\n :floating=\"floating\"\n >\n <template #topbar>\n <slot\n name=\"topbar\"\n :workspace=\"resolvedWorkspace\"\n :bindings=\"resolvedBindings\"\n :top-bar=\"resolvedTopBarSlot\"\n >\n <AppTopBar\n :title=\"title\"\n :subtitle=\"subtitle\"\n :variant=\"topBarVariant\"\n :pill-nav=\"resolvedWorkspace.pillNav.items\"\n :current-pill-id=\"resolvedWorkspace.pillNav.currentItemId\"\n :show-settings=\"showSettings\"\n :settings-config=\"resolvedWorkspace.topBarSettings.settingsConfig\"\n @pill-select=\"resolvedWorkspace.pillNav.onSelect\"\n @settings-values-change=\"resolvedWorkspace.topBarSettings.onSettingsValuesChange\"\n />\n </slot>\n </template>\n\n <template #sidebar>\n <slot\n name=\"sidebar\"\n :workspace=\"resolvedWorkspace\"\n :bindings=\"resolvedBindings\"\n :sidebar=\"resolvedWorkspace.sidebar\"\n >\n <AppSidebar\n v-bind=\"resolvedWorkspace.sidebar\"\n :title=\"sidebarTitle\"\n :subtitle=\"sidebarSubtitle\"\n :badge=\"sidebarBadge\"\n :variant=\"sidebarVariant\"\n :floating=\"false\"\n :dense=\"dense\"\n :width=\"sidebarWidth\"\n :form-enhancements=\"formEnhancements\"\n :form-loading=\"formLoading\"\n :form-disabled=\"formDisabled\"\n :form-readonly=\"formReadonly\"\n />\n </slot>\n </template>\n\n <main class=\"mint-control-workspace-view__content\">\n <slot\n :workspace=\"resolvedWorkspace\"\n :bindings=\"resolvedBindings\"\n :values=\"resolvedWorkspace.values\"\n :component-bindings=\"resolvedComponentBindings\"\n :component-bindings-by-id=\"resolvedComponentBindingsById\"\n :component-props=\"resolvedComponentProps\"\n :component-props-by-id=\"resolvedComponentPropsById\"\n >\n <FormBuilder\n v-bind=\"resolvedWorkspace.form\"\n :enhancements=\"formEnhancements\"\n :loading=\"formLoading\"\n :disabled=\"formDisabled\"\n :readonly=\"formReadonly\"\n :show-actions=\"showFormActions\"\n :size=\"formSize\"\n @submit=\"emit('submit', $event)\"\n @cancel=\"emit('cancel')\"\n />\n </slot>\n </main>\n </AppLayout>\n</template>\n\n<style scoped>\n.mint-control-workspace-view {\n min-width: 0;\n}\n\n.mint-control-workspace-view__content {\n min-width: 0;\n padding: 1rem;\n}\n</style>\n","<script setup lang=\"ts\">\n/** Complete control workspace component that turns a simple controls data model into AppTopBar, AppSidebar, and FormBuilder forms. */\nimport { computed, effectScope, onScopeDispose, shallowRef, toRaw, watch, type EffectScope } from 'vue'\nimport type { TopBarVariant } from '../types'\nimport type { FormEnhancements } from '../types/form-builder'\nimport type {\n ControlComponentBindingsConfig,\n ControlComponentPropsByIdMap,\n ControlComponentPropsMap,\n ControlModel,\n ControlModelBinding,\n ControlSchema,\n ControlWorkspaceOptions,\n UseControlWorkspaceReturn,\n} from '../composables/useControlSchema'\nimport { mergeControlWorkspaceOptions, resolveControlModel, useControlWorkspace } from '../composables/useControlSchema'\nimport AppLayout from './AppLayout.vue'\nimport AppSidebar from './AppSidebar.vue'\nimport AppTopBar from './AppTopBar.vue'\nimport FormBuilder from './FormBuilder.vue'\n\ntype ControlWorkspaceSidebarVariant = 'default' | 'analysis'\ntype ResolvedControlWorkspace = UseControlWorkspaceReturn<ControlSchema>\n\ninterface ControlWorkspaceDefaultSlotProps {\n workspace: ResolvedControlWorkspace\n bindings: ResolvedControlWorkspace['bindings']\n values: ResolvedControlWorkspace['values']\n componentBindings: ReturnType<ResolvedControlWorkspace['getComponentBindings']>\n componentBindingsById: ReturnType<ResolvedControlWorkspace['getComponentBindingsById']>\n componentProps: ReturnType<ResolvedControlWorkspace['getComponentProps']>\n componentPropsById: ReturnType<ResolvedControlWorkspace['getComponentPropsById']>\n}\n\ninterface ControlWorkspaceTopbarSlotProps {\n workspace: ResolvedControlWorkspace\n bindings: ResolvedControlWorkspace['bindings']\n topBar: ResolvedControlWorkspace['bindings']['topBar']['value']\n}\n\ninterface ControlWorkspaceSidebarSlotProps {\n workspace: ResolvedControlWorkspace\n bindings: ResolvedControlWorkspace['bindings']\n sidebar: ResolvedControlWorkspace['sidebar']\n}\n\ninterface Props {\n /** Model returned by defineControlModel()/defineDoseDesignControlModel(), or a raw nested ControlModel. */\n model?: ControlModel | ControlModelBinding\n /** Workspace returned by useControlWorkspace(). Use for full manual control. */\n workspace?: UseControlWorkspaceReturn<ControlSchema>\n /** Compact controls schema. When provided without workspace, the view creates the workspace internally. */\n controls?: ControlSchema\n /** Options passed to the internally generated useControlWorkspace() call. */\n controlOptions?: ControlWorkspaceOptions\n /** Initial values for the internally generated workspace. Merged over controlOptions.initialValues. */\n initialValues?: Record<string, unknown>\n /** External values for the internally generated workspace. Supports default v-model. */\n modelValue?: Record<string, unknown>\n /** External values for the internally generated workspace. Supports v-model:values. */\n values?: Record<string, unknown>\n /** AppTopBar title. */\n title?: string\n /** AppTopBar subtitle. */\n subtitle?: string\n /** AppTopBar visual variant. */\n topBarVariant?: TopBarVariant\n /** AppSidebar/AppLayout sidebar width. */\n sidebarWidth?: string\n /** AppSidebar visual preset. `analysis` matches the LEAF-style MINT analysis sidebar design language. */\n sidebarVariant?: ControlWorkspaceSidebarVariant\n /** Convert the sidebar into an SDK-owned mobile overlay below the AppLayout breakpoint. */\n responsiveSidebar?: boolean\n /** Sidebar position in AppLayout. */\n sidebarPosition?: 'left' | 'right'\n /** Optional AppSidebar chrome title for LEAF-style plugin workbenches. */\n sidebarTitle?: string\n /** Optional AppSidebar chrome subtitle for active experiment/run context. */\n sidebarSubtitle?: string\n /** Optional compact badge/count rendered in the AppSidebar chrome header. */\n sidebarBadge?: string | number\n /** Floating AppLayout style. */\n floating?: boolean\n /** Compact AppSidebar density. */\n dense?: boolean\n /** Whether AppTopBar should show generated settings. */\n showSettings?: boolean\n /** Render FormBuilder actions in the default content. */\n showFormActions?: boolean\n /** Runtime FormBuilder enhancements passed to generated forms. */\n formEnhancements?: FormEnhancements<Record<string, unknown>>\n /** Optional SDK component bindings exposed to the default slot with resolved props. */\n componentBindings?: ControlComponentBindingsConfig\n /** Optional mapping from workspace values to component props exposed to the default slot. */\n componentProps?: ControlComponentPropsMap\n /** Optional named mappings from workspace values to component props exposed to the default slot. */\n componentPropsById?: ControlComponentPropsByIdMap\n /** Loading/saving state passed to generated forms. */\n formLoading?: boolean\n /** Disabled state passed to generated forms. */\n formDisabled?: boolean\n /** Readonly state passed to generated forms. */\n formReadonly?: boolean\n /** FormBuilder size in the default content. */\n formSize?: 'sm' | 'md' | 'lg'\n}\n\nconst emit = defineEmits<{\n /** Emitted when generated workspace values change through FormBuilder, AppSidebar, or AppTopBar settings. */\n 'update:modelValue': [values: Record<string, unknown>]\n /** Emitted when generated workspace values change through FormBuilder, AppSidebar, or AppTopBar settings. */\n 'update:values': [values: Record<string, unknown>]\n /** Forwarded from the default FormBuilder when showFormActions is enabled. */\n submit: [values: Record<string, unknown>]\n /** Forwarded from the default FormBuilder when showFormActions is enabled. */\n cancel: []\n}>()\n\ndefineSlots<{\n default?: (props: ControlWorkspaceDefaultSlotProps) => unknown\n topbar?: (props: ControlWorkspaceTopbarSlotProps) => unknown\n sidebar?: (props: ControlWorkspaceSidebarSlotProps) => unknown\n}>()\n\nconst props = withDefaults(defineProps<Props>(), {\n model: undefined,\n workspace: undefined,\n controls: undefined,\n controlOptions: () => ({}),\n initialValues: undefined,\n modelValue: undefined,\n values: undefined,\n title: 'Workspace',\n subtitle: undefined,\n topBarVariant: 'card',\n sidebarWidth: '320px',\n sidebarVariant: 'analysis',\n responsiveSidebar: true,\n sidebarPosition: 'left',\n sidebarTitle: undefined,\n sidebarSubtitle: undefined,\n sidebarBadge: undefined,\n floating: false,\n dense: true,\n showSettings: true,\n showFormActions: false,\n formEnhancements: undefined,\n componentBindings: undefined,\n componentProps: undefined,\n componentPropsById: undefined,\n formLoading: false,\n formDisabled: false,\n formReadonly: false,\n formSize: 'md',\n})\n\nconst externalValues = computed(() => props.modelValue ?? props.values)\nconst resolvedModel = computed<ControlModelBinding | undefined>(() => {\n return resolveControlModel(props.model)\n})\nconst resolvedControls = computed<ControlSchema>(() => props.controls ?? resolvedModel.value?.controls ?? {})\nconst baseControlOptions = computed<ControlWorkspaceOptions>(() =>\n mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)\n)\n\nconst resolvedControlOptions = computed<ControlWorkspaceOptions>(() => {\n if (props.initialValues === undefined && externalValues.value === undefined) return baseControlOptions.value\n\n return {\n ...baseControlOptions.value,\n initialValues: {\n ...(baseControlOptions.value.initialValues ?? {}),\n ...(props.initialValues ?? {}),\n ...(externalValues.value ?? {}),\n },\n }\n})\n\nlet generatedWorkspaceScope: EffectScope | undefined\n\nfunction createGeneratedWorkspace(seedValues: Record<string, unknown> = {}) {\n generatedWorkspaceScope?.stop()\n generatedWorkspaceScope = effectScope()\n return generatedWorkspaceScope.run(() =>\n useControlWorkspace(resolvedControls.value, {\n ...resolvedControlOptions.value,\n initialValues: {\n ...seedValues,\n ...(resolvedControlOptions.value.initialValues ?? {}),\n },\n })\n )!\n}\n\nconst generatedWorkspace = shallowRef<UseControlWorkspaceReturn<ControlSchema>>(createGeneratedWorkspace())\nconst resolvedWorkspace = computed<UseControlWorkspaceReturn<ControlSchema>>(() => props.workspace ?? generatedWorkspace.value)\nconst resolvedComponentBindings = computed(() =>\n resolvedWorkspace.value.getComponentBindings(props.componentBindings ?? resolvedModel.value?.componentBindings)\n)\nconst resolvedComponentBindingsById = computed(() =>\n resolvedWorkspace.value.getComponentBindingsById(props.componentBindings ?? resolvedModel.value?.componentBindings)\n)\nconst resolvedComponentProps = computed(() =>\n resolvedWorkspace.value.getComponentProps(props.componentProps ?? resolvedModel.value?.componentProps)\n)\nconst resolvedComponentPropsById = computed(() =>\n resolvedWorkspace.value.getComponentPropsById(props.componentPropsById ?? resolvedModel.value?.componentPropsById)\n)\nconst resolvedBindings = computed<ResolvedControlWorkspace['bindings']>(() => ({\n ...resolvedWorkspace.value.bindings,\n componentBindings: resolvedComponentBindings,\n componentBindingsById: resolvedComponentBindingsById,\n componentProps: resolvedComponentProps,\n componentPropsById: resolvedComponentPropsById,\n}))\nconst resolvedTopBarSlot = computed(() => resolvedWorkspace.value.bindings.topBar.value)\n\nwatch(\n [() => props.model, () => props.controls, () => props.controlOptions, () => props.initialValues],\n () => {\n const previousWorkspace = generatedWorkspace.value\n const previousActiveView = previousWorkspace.activeView.value\n generatedWorkspace.value = createGeneratedWorkspace({ ...previousWorkspace.values })\n generatedWorkspace.value.setActiveView(previousActiveView)\n },\n { deep: true },\n)\n\nonScopeDispose(() => generatedWorkspaceScope?.stop())\n\nwatch(\n externalValues,\n (values) => {\n if (values === undefined || props.workspace !== undefined) return\n if (recordsEqual(generatedWorkspace.value.values, values)) return\n generatedWorkspace.value.setValues(values)\n },\n { deep: true },\n)\n\nwatch(\n () => generatedWorkspace.value.values,\n (values) => {\n if (props.workspace !== undefined) return\n const nextValues = { ...values }\n if (externalValues.value !== undefined && recordsEqual(externalValues.value, nextValues)) return\n emit('update:modelValue', nextValues)\n emit('update:values', nextValues)\n },\n { deep: true },\n)\n\nfunction recordsEqual(left: Record<string, unknown>, right: Record<string, unknown>): boolean {\n const leftKeys = Object.keys(left)\n const rightKeys = Object.keys(right)\n if (leftKeys.length !== rightKeys.length) return false\n return leftKeys.every(key => valuesEqual(left[key], right[key]))\n}\n\nfunction valuesEqual(left: unknown, right: unknown): boolean {\n const leftRaw = toRaw(left)\n const rightRaw = toRaw(right)\n\n if (Object.is(leftRaw, rightRaw)) return true\n\n if (Array.isArray(leftRaw) || Array.isArray(rightRaw)) {\n if (!Array.isArray(leftRaw) || !Array.isArray(rightRaw)) return false\n if (leftRaw.length !== rightRaw.length) return false\n return leftRaw.every((item, index) => valuesEqual(item, rightRaw[index]))\n }\n\n if (isPlainRecord(leftRaw) || isPlainRecord(rightRaw)) {\n if (!isPlainRecord(leftRaw) || !isPlainRecord(rightRaw)) return false\n return recordsEqual(leftRaw, rightRaw)\n }\n\n return false\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n</script>\n\n<template>\n <AppLayout\n class=\"mint-control-workspace-view\"\n :sidebar-position=\"sidebarPosition\"\n :sidebar-width=\"sidebarWidth\"\n :responsive-sidebar=\"responsiveSidebar\"\n :floating=\"floating\"\n >\n <template #topbar>\n <slot\n name=\"topbar\"\n :workspace=\"resolvedWorkspace\"\n :bindings=\"resolvedBindings\"\n :top-bar=\"resolvedTopBarSlot\"\n >\n <AppTopBar\n :title=\"title\"\n :subtitle=\"subtitle\"\n :variant=\"topBarVariant\"\n :pill-nav=\"resolvedWorkspace.pillNav.items\"\n :current-pill-id=\"resolvedWorkspace.pillNav.currentItemId\"\n :show-settings=\"showSettings\"\n :settings-config=\"resolvedWorkspace.topBarSettings.settingsConfig\"\n @pill-select=\"resolvedWorkspace.pillNav.onSelect\"\n @settings-values-change=\"resolvedWorkspace.topBarSettings.onSettingsValuesChange\"\n />\n </slot>\n </template>\n\n <template #sidebar>\n <slot\n name=\"sidebar\"\n :workspace=\"resolvedWorkspace\"\n :bindings=\"resolvedBindings\"\n :sidebar=\"resolvedWorkspace.sidebar\"\n >\n <AppSidebar\n v-bind=\"resolvedWorkspace.sidebar\"\n :title=\"sidebarTitle\"\n :subtitle=\"sidebarSubtitle\"\n :badge=\"sidebarBadge\"\n :variant=\"sidebarVariant\"\n :floating=\"false\"\n :dense=\"dense\"\n :width=\"sidebarWidth\"\n :form-enhancements=\"formEnhancements\"\n :form-loading=\"formLoading\"\n :form-disabled=\"formDisabled\"\n :form-readonly=\"formReadonly\"\n />\n </slot>\n </template>\n\n <main class=\"mint-control-workspace-view__content\">\n <slot\n :workspace=\"resolvedWorkspace\"\n :bindings=\"resolvedBindings\"\n :values=\"resolvedWorkspace.values\"\n :component-bindings=\"resolvedComponentBindings\"\n :component-bindings-by-id=\"resolvedComponentBindingsById\"\n :component-props=\"resolvedComponentProps\"\n :component-props-by-id=\"resolvedComponentPropsById\"\n >\n <FormBuilder\n v-bind=\"resolvedWorkspace.form\"\n :enhancements=\"formEnhancements\"\n :loading=\"formLoading\"\n :disabled=\"formDisabled\"\n :readonly=\"formReadonly\"\n :show-actions=\"showFormActions\"\n :size=\"formSize\"\n @submit=\"emit('submit', $event)\"\n @cancel=\"emit('cancel')\"\n />\n </slot>\n </main>\n </AppLayout>\n</template>\n\n<style scoped>\n.mint-control-workspace-view {\n min-width: 0;\n}\n\n.mint-control-workspace-view__content {\n min-width: 0;\n padding: 1rem;\n}\n</style>\n","<script setup lang=\"ts\">\n/** Complete ControlWorkspaceView page shell for WellPlate + DoseCalculator dose design. */\nimport { computed } from 'vue'\nimport type { TopBarVariant } from '../types'\nimport type { FormEnhancements } from '../types/form-builder'\nimport type {\n ControlModel,\n ControlModelBinding,\n ControlSchema,\n ControlWorkspaceOptions,\n DoseDesignControlModelOptions,\n UseControlWorkspaceReturn,\n} from '../composables/useControlSchema'\nimport { defineDoseDesignControlModel } from '../composables/useControlSchema'\nimport ControlWorkspaceView from './ControlWorkspaceView.vue'\nimport DoseCalculator from './DoseCalculator.vue'\nimport WellPlate from './WellPlate.vue'\n\ntype DoseDesignSidebarVariant = 'default' | 'analysis'\ntype ComponentProps = Record<string, unknown>\ntype ComponentPropsById = Record<string, ComponentProps>\ntype ComponentBinding = { id: string, component: string, props: ComponentProps }\ntype ComponentBindingsById = Record<string, ComponentBinding>\ntype ResolvedControlWorkspace = UseControlWorkspaceReturn<ControlSchema>\n\ninterface DoseDesignWorkspaceSlotProps {\n workspace: ResolvedControlWorkspace\n bindings: ResolvedControlWorkspace['bindings']\n values: ResolvedControlWorkspace['values']\n componentBindings: ComponentBinding[]\n componentBindingsById: ComponentBindingsById\n componentPropsById: ComponentPropsById\n wellPlateProps: ComponentProps\n doseCalculatorProps: ComponentProps\n}\n\ninterface Props {\n /** Model returned by defineDoseDesignControlModel(), or a custom compatible ControlWorkspace model. */\n model?: ControlModel | ControlModelBinding\n /** Workspace returned by useControlWorkspace(). Use for full manual control. */\n workspace?: UseControlWorkspaceReturn<ControlSchema>\n /** Options used when this view creates the default dose-design model internally. */\n doseDesignOptions?: DoseDesignControlModelOptions\n /** Options passed to the internally generated ControlWorkspaceView workspace. */\n controlOptions?: ControlWorkspaceOptions\n /** Initial values for the internally generated workspace. */\n initialValues?: Record<string, unknown>\n /** External values for the internally generated workspace. Supports default v-model. */\n modelValue?: Record<string, unknown>\n /** External values for the internally generated workspace. Supports v-model:values. */\n values?: Record<string, unknown>\n /** Named component props id for the generated WellPlate binding. */\n plateId?: string\n /** Named component props id for the generated DoseCalculator binding. */\n doseId?: string\n /** Extra props merged into the generated WellPlate binding. */\n wellPlateProps?: ComponentProps\n /** Extra props merged into the generated DoseCalculator binding. */\n doseCalculatorProps?: ComponentProps\n /** AppTopBar title. */\n title?: string\n /** AppTopBar subtitle. */\n subtitle?: string\n /** AppTopBar visual variant. */\n topBarVariant?: TopBarVariant\n /** AppSidebar/AppLayout sidebar width. */\n sidebarWidth?: string\n /** AppSidebar visual preset. `analysis` matches the LEAF-style MINT analysis sidebar design language. */\n sidebarVariant?: DoseDesignSidebarVariant\n /** Convert the sidebar into an SDK-owned mobile overlay below the AppLayout breakpoint. */\n responsiveSidebar?: boolean\n /** Sidebar position in AppLayout. */\n sidebarPosition?: 'left' | 'right'\n /** Optional AppSidebar chrome title for LEAF-style plugin workbenches. */\n sidebarTitle?: string\n /** Optional AppSidebar chrome subtitle for active experiment/run context. */\n sidebarSubtitle?: string\n /** Optional compact badge/count rendered in the AppSidebar chrome header. */\n sidebarBadge?: string | number\n /** Floating AppLayout style. */\n floating?: boolean\n /** Compact AppSidebar density. */\n dense?: boolean\n /** Whether AppTopBar should show generated settings. */\n showSettings?: boolean\n /** Render FormBuilder actions in the default generated forms. */\n showFormActions?: boolean\n /** Runtime FormBuilder enhancements passed to generated forms. */\n formEnhancements?: FormEnhancements<Record<string, unknown>>\n /** Loading/saving state passed to generated forms. */\n formLoading?: boolean\n /** Disabled state passed to generated forms. */\n formDisabled?: boolean\n /** Readonly state passed to generated forms. */\n formReadonly?: boolean\n /** FormBuilder size in generated forms. */\n formSize?: 'sm' | 'md' | 'lg'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n model: undefined,\n workspace: undefined,\n doseDesignOptions: () => ({}),\n controlOptions: () => ({}),\n initialValues: undefined,\n modelValue: undefined,\n values: undefined,\n plateId: undefined,\n doseId: undefined,\n wellPlateProps: () => ({}),\n doseCalculatorProps: () => ({}),\n title: 'Dose Design Workspace',\n subtitle: undefined,\n topBarVariant: 'card',\n sidebarWidth: '320px',\n sidebarVariant: 'analysis',\n responsiveSidebar: true,\n sidebarPosition: 'left',\n sidebarTitle: undefined,\n sidebarSubtitle: undefined,\n sidebarBadge: undefined,\n floating: false,\n dense: true,\n showSettings: true,\n showFormActions: false,\n formEnhancements: undefined,\n formLoading: false,\n formDisabled: false,\n formReadonly: false,\n formSize: 'md',\n})\n\nconst emit = defineEmits<{\n /** Emitted when generated workspace values change through WellPlate, FormBuilder, AppSidebar, or AppTopBar settings. */\n 'update:modelValue': [values: Record<string, unknown>]\n /** Emitted when generated workspace values change through WellPlate, FormBuilder, AppSidebar, or AppTopBar settings. */\n 'update:values': [values: Record<string, unknown>]\n /** Forwarded from the internal ControlWorkspaceView when form actions are enabled. */\n submit: [values: Record<string, unknown>]\n /** Forwarded from the internal ControlWorkspaceView when form actions are enabled. */\n cancel: []\n}>()\n\ndefineSlots<{\n default?: (props: DoseDesignWorkspaceSlotProps) => unknown\n plate?: (props: DoseDesignWorkspaceSlotProps) => unknown\n dose?: (props: DoseDesignWorkspaceSlotProps) => unknown\n}>()\n\nconst resolvedModel = computed<ControlModel | ControlModelBinding>(() =>\n props.model ?? defineDoseDesignControlModel(props.doseDesignOptions)\n)\n\nfunction resolvedPlateId(): string {\n return props.plateId ?? props.doseDesignOptions.componentProps?.plateId ?? 'plate'\n}\n\nfunction resolvedDoseId(): string {\n return props.doseId ?? props.doseDesignOptions.componentProps?.doseId ?? 'dose'\n}\n\nfunction wellPlateBinding(componentPropsById: ComponentPropsById): ComponentProps {\n return {\n size: 'fill',\n selectionMode: 'multiple',\n ...(componentPropsById[resolvedPlateId()] ?? {}),\n ...props.wellPlateProps,\n }\n}\n\nfunction doseCalculatorBinding(componentPropsById: ComponentPropsById): ComponentProps {\n return {\n ...(componentPropsById[resolvedDoseId()] ?? {}),\n ...props.doseCalculatorProps,\n }\n}\n\nfunction slotProps(slotProps: {\n workspace: ResolvedControlWorkspace\n bindings: ResolvedControlWorkspace['bindings']\n values: ResolvedControlWorkspace['values']\n componentBindings: ComponentBinding[]\n componentBindingsById: ComponentBindingsById\n componentPropsById: ComponentPropsById\n}): DoseDesignWorkspaceSlotProps {\n return {\n ...slotProps,\n wellPlateProps: wellPlateBinding(slotProps.componentPropsById),\n doseCalculatorProps: doseCalculatorBinding(slotProps.componentPropsById),\n }\n}\n</script>\n\n<template>\n <ControlWorkspaceView\n :model=\"resolvedModel\"\n :workspace=\"workspace\"\n :control-options=\"controlOptions\"\n :initial-values=\"initialValues\"\n :model-value=\"modelValue\"\n :values=\"values\"\n :title=\"title\"\n :subtitle=\"subtitle\"\n :top-bar-variant=\"topBarVariant\"\n :sidebar-width=\"sidebarWidth\"\n :sidebar-variant=\"sidebarVariant\"\n :responsive-sidebar=\"responsiveSidebar\"\n :sidebar-position=\"sidebarPosition\"\n :sidebar-title=\"sidebarTitle\"\n :sidebar-subtitle=\"sidebarSubtitle\"\n :sidebar-badge=\"sidebarBadge\"\n :floating=\"floating\"\n :dense=\"dense\"\n :show-settings=\"showSettings\"\n :show-form-actions=\"showFormActions\"\n :form-enhancements=\"formEnhancements\"\n :form-loading=\"formLoading\"\n :form-disabled=\"formDisabled\"\n :form-readonly=\"formReadonly\"\n :form-size=\"formSize\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n @update:values=\"emit('update:values', $event)\"\n @submit=\"emit('submit', $event)\"\n @cancel=\"emit('cancel')\"\n >\n <template #default=\"workspaceSlotProps\">\n <slot v-bind=\"slotProps(workspaceSlotProps)\">\n <div class=\"mint-dose-design-workspace\">\n <slot name=\"plate\" v-bind=\"slotProps(workspaceSlotProps)\">\n <WellPlate v-bind=\"wellPlateBinding(workspaceSlotProps.componentPropsById)\" />\n </slot>\n <slot name=\"dose\" v-bind=\"slotProps(workspaceSlotProps)\">\n <DoseCalculator v-bind=\"doseCalculatorBinding(workspaceSlotProps.componentPropsById)\" />\n </slot>\n </div>\n </slot>\n </template>\n </ControlWorkspaceView>\n</template>\n\n<style scoped>\n.mint-dose-design-workspace {\n display: grid;\n grid-template-columns: minmax(360px, 1fr) minmax(300px, 420px);\n gap: 1rem;\n align-items: start;\n min-width: 0;\n}\n\n@media (max-width: 900px) {\n .mint-dose-design-workspace {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n</style>\n","<script setup lang=\"ts\">\n/** Complete ControlWorkspaceView page shell for WellPlate + DoseCalculator dose design. */\nimport { computed } from 'vue'\nimport type { TopBarVariant } from '../types'\nimport type { FormEnhancements } from '../types/form-builder'\nimport type {\n ControlModel,\n ControlModelBinding,\n ControlSchema,\n ControlWorkspaceOptions,\n DoseDesignControlModelOptions,\n UseControlWorkspaceReturn,\n} from '../composables/useControlSchema'\nimport { defineDoseDesignControlModel } from '../composables/useControlSchema'\nimport ControlWorkspaceView from './ControlWorkspaceView.vue'\nimport DoseCalculator from './DoseCalculator.vue'\nimport WellPlate from './WellPlate.vue'\n\ntype DoseDesignSidebarVariant = 'default' | 'analysis'\ntype ComponentProps = Record<string, unknown>\ntype ComponentPropsById = Record<string, ComponentProps>\ntype ComponentBinding = { id: string, component: string, props: ComponentProps }\ntype ComponentBindingsById = Record<string, ComponentBinding>\ntype ResolvedControlWorkspace = UseControlWorkspaceReturn<ControlSchema>\n\ninterface DoseDesignWorkspaceSlotProps {\n workspace: ResolvedControlWorkspace\n bindings: ResolvedControlWorkspace['bindings']\n values: ResolvedControlWorkspace['values']\n componentBindings: ComponentBinding[]\n componentBindingsById: ComponentBindingsById\n componentPropsById: ComponentPropsById\n wellPlateProps: ComponentProps\n doseCalculatorProps: ComponentProps\n}\n\ninterface Props {\n /** Model returned by defineDoseDesignControlModel(), or a custom compatible ControlWorkspace model. */\n model?: ControlModel | ControlModelBinding\n /** Workspace returned by useControlWorkspace(). Use for full manual control. */\n workspace?: UseControlWorkspaceReturn<ControlSchema>\n /** Options used when this view creates the default dose-design model internally. */\n doseDesignOptions?: DoseDesignControlModelOptions\n /** Options passed to the internally generated ControlWorkspaceView workspace. */\n controlOptions?: ControlWorkspaceOptions\n /** Initial values for the internally generated workspace. */\n initialValues?: Record<string, unknown>\n /** External values for the internally generated workspace. Supports default v-model. */\n modelValue?: Record<string, unknown>\n /** External values for the internally generated workspace. Supports v-model:values. */\n values?: Record<string, unknown>\n /** Named component props id for the generated WellPlate binding. */\n plateId?: string\n /** Named component props id for the generated DoseCalculator binding. */\n doseId?: string\n /** Extra props merged into the generated WellPlate binding. */\n wellPlateProps?: ComponentProps\n /** Extra props merged into the generated DoseCalculator binding. */\n doseCalculatorProps?: ComponentProps\n /** AppTopBar title. */\n title?: string\n /** AppTopBar subtitle. */\n subtitle?: string\n /** AppTopBar visual variant. */\n topBarVariant?: TopBarVariant\n /** AppSidebar/AppLayout sidebar width. */\n sidebarWidth?: string\n /** AppSidebar visual preset. `analysis` matches the LEAF-style MINT analysis sidebar design language. */\n sidebarVariant?: DoseDesignSidebarVariant\n /** Convert the sidebar into an SDK-owned mobile overlay below the AppLayout breakpoint. */\n responsiveSidebar?: boolean\n /** Sidebar position in AppLayout. */\n sidebarPosition?: 'left' | 'right'\n /** Optional AppSidebar chrome title for LEAF-style plugin workbenches. */\n sidebarTitle?: string\n /** Optional AppSidebar chrome subtitle for active experiment/run context. */\n sidebarSubtitle?: string\n /** Optional compact badge/count rendered in the AppSidebar chrome header. */\n sidebarBadge?: string | number\n /** Floating AppLayout style. */\n floating?: boolean\n /** Compact AppSidebar density. */\n dense?: boolean\n /** Whether AppTopBar should show generated settings. */\n showSettings?: boolean\n /** Render FormBuilder actions in the default generated forms. */\n showFormActions?: boolean\n /** Runtime FormBuilder enhancements passed to generated forms. */\n formEnhancements?: FormEnhancements<Record<string, unknown>>\n /** Loading/saving state passed to generated forms. */\n formLoading?: boolean\n /** Disabled state passed to generated forms. */\n formDisabled?: boolean\n /** Readonly state passed to generated forms. */\n formReadonly?: boolean\n /** FormBuilder size in generated forms. */\n formSize?: 'sm' | 'md' | 'lg'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n model: undefined,\n workspace: undefined,\n doseDesignOptions: () => ({}),\n controlOptions: () => ({}),\n initialValues: undefined,\n modelValue: undefined,\n values: undefined,\n plateId: undefined,\n doseId: undefined,\n wellPlateProps: () => ({}),\n doseCalculatorProps: () => ({}),\n title: 'Dose Design Workspace',\n subtitle: undefined,\n topBarVariant: 'card',\n sidebarWidth: '320px',\n sidebarVariant: 'analysis',\n responsiveSidebar: true,\n sidebarPosition: 'left',\n sidebarTitle: undefined,\n sidebarSubtitle: undefined,\n sidebarBadge: undefined,\n floating: false,\n dense: true,\n showSettings: true,\n showFormActions: false,\n formEnhancements: undefined,\n formLoading: false,\n formDisabled: false,\n formReadonly: false,\n formSize: 'md',\n})\n\nconst emit = defineEmits<{\n /** Emitted when generated workspace values change through WellPlate, FormBuilder, AppSidebar, or AppTopBar settings. */\n 'update:modelValue': [values: Record<string, unknown>]\n /** Emitted when generated workspace values change through WellPlate, FormBuilder, AppSidebar, or AppTopBar settings. */\n 'update:values': [values: Record<string, unknown>]\n /** Forwarded from the internal ControlWorkspaceView when form actions are enabled. */\n submit: [values: Record<string, unknown>]\n /** Forwarded from the internal ControlWorkspaceView when form actions are enabled. */\n cancel: []\n}>()\n\ndefineSlots<{\n default?: (props: DoseDesignWorkspaceSlotProps) => unknown\n plate?: (props: DoseDesignWorkspaceSlotProps) => unknown\n dose?: (props: DoseDesignWorkspaceSlotProps) => unknown\n}>()\n\nconst resolvedModel = computed<ControlModel | ControlModelBinding>(() =>\n props.model ?? defineDoseDesignControlModel(props.doseDesignOptions)\n)\n\nfunction resolvedPlateId(): string {\n return props.plateId ?? props.doseDesignOptions.componentProps?.plateId ?? 'plate'\n}\n\nfunction resolvedDoseId(): string {\n return props.doseId ?? props.doseDesignOptions.componentProps?.doseId ?? 'dose'\n}\n\nfunction wellPlateBinding(componentPropsById: ComponentPropsById): ComponentProps {\n return {\n size: 'fill',\n selectionMode: 'multiple',\n ...(componentPropsById[resolvedPlateId()] ?? {}),\n ...props.wellPlateProps,\n }\n}\n\nfunction doseCalculatorBinding(componentPropsById: ComponentPropsById): ComponentProps {\n return {\n ...(componentPropsById[resolvedDoseId()] ?? {}),\n ...props.doseCalculatorProps,\n }\n}\n\nfunction slotProps(slotProps: {\n workspace: ResolvedControlWorkspace\n bindings: ResolvedControlWorkspace['bindings']\n values: ResolvedControlWorkspace['values']\n componentBindings: ComponentBinding[]\n componentBindingsById: ComponentBindingsById\n componentPropsById: ComponentPropsById\n}): DoseDesignWorkspaceSlotProps {\n return {\n ...slotProps,\n wellPlateProps: wellPlateBinding(slotProps.componentPropsById),\n doseCalculatorProps: doseCalculatorBinding(slotProps.componentPropsById),\n }\n}\n</script>\n\n<template>\n <ControlWorkspaceView\n :model=\"resolvedModel\"\n :workspace=\"workspace\"\n :control-options=\"controlOptions\"\n :initial-values=\"initialValues\"\n :model-value=\"modelValue\"\n :values=\"values\"\n :title=\"title\"\n :subtitle=\"subtitle\"\n :top-bar-variant=\"topBarVariant\"\n :sidebar-width=\"sidebarWidth\"\n :sidebar-variant=\"sidebarVariant\"\n :responsive-sidebar=\"responsiveSidebar\"\n :sidebar-position=\"sidebarPosition\"\n :sidebar-title=\"sidebarTitle\"\n :sidebar-subtitle=\"sidebarSubtitle\"\n :sidebar-badge=\"sidebarBadge\"\n :floating=\"floating\"\n :dense=\"dense\"\n :show-settings=\"showSettings\"\n :show-form-actions=\"showFormActions\"\n :form-enhancements=\"formEnhancements\"\n :form-loading=\"formLoading\"\n :form-disabled=\"formDisabled\"\n :form-readonly=\"formReadonly\"\n :form-size=\"formSize\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n @update:values=\"emit('update:values', $event)\"\n @submit=\"emit('submit', $event)\"\n @cancel=\"emit('cancel')\"\n >\n <template #default=\"workspaceSlotProps\">\n <slot v-bind=\"slotProps(workspaceSlotProps)\">\n <div class=\"mint-dose-design-workspace\">\n <slot name=\"plate\" v-bind=\"slotProps(workspaceSlotProps)\">\n <WellPlate v-bind=\"wellPlateBinding(workspaceSlotProps.componentPropsById)\" />\n </slot>\n <slot name=\"dose\" v-bind=\"slotProps(workspaceSlotProps)\">\n <DoseCalculator v-bind=\"doseCalculatorBinding(workspaceSlotProps.componentPropsById)\" />\n </slot>\n </div>\n </slot>\n </template>\n </ControlWorkspaceView>\n</template>\n\n<style scoped>\n.mint-dose-design-workspace {\n display: grid;\n grid-template-columns: minmax(360px, 1fr) minmax(300px, 420px);\n gap: 1rem;\n align-items: start;\n min-width: 0;\n}\n\n@media (max-width: 900px) {\n .mint-dose-design-workspace {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n</style>\n","<script setup lang=\"ts\">\n/** Content card with optional scroll lock, or a transparent flex layout shell with configurable direction and gap. */\nimport { computed } from 'vue'\n\ninterface Props {\n /** Makes the container scrollable (overflow-y: auto). Ignored when direction is set. */\n scrollable?: boolean\n /** Renders as a transparent flex container instead of a card. */\n direction?: 'row' | 'column'\n /** Flex gap between children. Only applies when direction is set. */\n gap?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n scrollable: false,\n gap: '1rem',\n})\n\nconst isLayout = computed(() => !!props.direction)\n</script>\n\n<template>\n <div\n class=\"mint-container\"\n :class=\"\n isLayout\n ? ['mint-container--layout', direction === 'row' ? 'mint-container--row' : 'mint-container--column']\n : ['mint-container--card', scrollable ? 'mint-container--scrollable' : '']\n \"\n :style=\"isLayout ? { gap } : undefined\"\n >\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Content card with optional scroll lock, or a transparent flex layout shell with configurable direction and gap. */\nimport { computed } from 'vue'\n\ninterface Props {\n /** Makes the container scrollable (overflow-y: auto). Ignored when direction is set. */\n scrollable?: boolean\n /** Renders as a transparent flex container instead of a card. */\n direction?: 'row' | 'column'\n /** Flex gap between children. Only applies when direction is set. */\n gap?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n scrollable: false,\n gap: '1rem',\n})\n\nconst isLayout = computed(() => !!props.direction)\n</script>\n\n<template>\n <div\n class=\"mint-container\"\n :class=\"\n isLayout\n ? ['mint-container--layout', direction === 'row' ? 'mint-container--row' : 'mint-container--column']\n : ['mint-container--card', scrollable ? 'mint-container--scrollable' : '']\n \"\n :style=\"isLayout ? { gap } : undefined\"\n >\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Horizontal or vertical rule with optional inline text label at center or left. */\ninterface Props {\n vertical?: boolean\n label?: string\n align?: 'center' | 'left'\n spacing?: 'sm' | 'md' | 'lg'\n}\n\nwithDefaults(defineProps<Props>(), {\n vertical: false,\n label: undefined,\n align: 'center',\n spacing: 'md',\n})\n</script>\n\n<template>\n <div\n v-if=\"vertical\"\n :class=\"[\n 'mint-divider',\n 'mint-divider--vertical',\n `mint-divider--${spacing}`,\n ]\"\n role=\"separator\"\n aria-orientation=\"vertical\"\n />\n <div\n v-else\n :class=\"[\n 'mint-divider',\n `mint-divider--${spacing}`,\n label ? `mint-divider--labeled mint-divider--${align}` : '',\n ]\"\n role=\"separator\"\n aria-orientation=\"horizontal\"\n >\n <template v-if=\"label\">\n <span v-if=\"align === 'center'\" class=\"mint-divider__line\" />\n <span class=\"mint-divider__label\">{{ label }}</span>\n <span class=\"mint-divider__line\" />\n </template>\n </div>\n</template>\n\n<style>\n@import '../styles/components/divider.css';\n</style>\n","<script setup lang=\"ts\">\n/** Horizontal or vertical rule with optional inline text label at center or left. */\ninterface Props {\n vertical?: boolean\n label?: string\n align?: 'center' | 'left'\n spacing?: 'sm' | 'md' | 'lg'\n}\n\nwithDefaults(defineProps<Props>(), {\n vertical: false,\n label: undefined,\n align: 'center',\n spacing: 'md',\n})\n</script>\n\n<template>\n <div\n v-if=\"vertical\"\n :class=\"[\n 'mint-divider',\n 'mint-divider--vertical',\n `mint-divider--${spacing}`,\n ]\"\n role=\"separator\"\n aria-orientation=\"vertical\"\n />\n <div\n v-else\n :class=\"[\n 'mint-divider',\n `mint-divider--${spacing}`,\n label ? `mint-divider--labeled mint-divider--${align}` : '',\n ]\"\n role=\"separator\"\n aria-orientation=\"horizontal\"\n >\n <template v-if=\"label\">\n <span v-if=\"align === 'center'\" class=\"mint-divider__line\" />\n <span class=\"mint-divider__label\">{{ label }}</span>\n <span class=\"mint-divider__line\" />\n </template>\n </div>\n</template>\n\n<style>\n@import '../styles/components/divider.css';\n</style>\n","<script setup lang=\"ts\">\n/** Colored dot with optional label and pulse animation for live-status indication. */\nimport { computed } from 'vue'\n\ninterface Props {\n status?: 'success' | 'warning' | 'error' | 'info' | 'muted'\n label?: string\n pulse?: boolean\n color?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n status: 'muted',\n label: undefined,\n pulse: false,\n color: undefined,\n})\n\nconst dotStyle = computed(() =>\n props.color ? { backgroundColor: props.color, color: props.color } : {}\n)\n</script>\n\n<template>\n <span class=\"mint-status\">\n <span\n :class=\"[\n 'mint-status__dot',\n { [`mint-status__dot--${status}`]: !color },\n { 'mint-status__dot--pulse': pulse },\n ]\"\n :style=\"dotStyle\"\n />\n <span v-if=\"label\" class=\"mint-status__label\">{{ label }}</span>\n </span>\n</template>\n\n<style>\n@import '../styles/components/status-indicator.css';\n</style>\n","<script setup lang=\"ts\">\n/** Colored dot with optional label and pulse animation for live-status indication. */\nimport { computed } from 'vue'\n\ninterface Props {\n status?: 'success' | 'warning' | 'error' | 'info' | 'muted'\n label?: string\n pulse?: boolean\n color?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n status: 'muted',\n label: undefined,\n pulse: false,\n color: undefined,\n})\n\nconst dotStyle = computed(() =>\n props.color ? { backgroundColor: props.color, color: props.color } : {}\n)\n</script>\n\n<template>\n <span class=\"mint-status\">\n <span\n :class=\"[\n 'mint-status__dot',\n { [`mint-status__dot--${status}`]: !color },\n { 'mint-status__dot--pulse': pulse },\n ]\"\n :style=\"dotStyle\"\n />\n <span v-if=\"label\" class=\"mint-status__label\">{{ label }}</span>\n </span>\n</template>\n\n<style>\n@import '../styles/components/status-indicator.css';\n</style>\n","<script setup lang=\"ts\">\n/** Displays progress as a filled rail or discrete labeled segments, with indeterminate mode. */\nimport { computed } from 'vue'\n\ninterface Props {\n value?: number\n variant?: 'rail' | 'segmented'\n color?: 'primary' | 'success' | 'warning' | 'error' | 'info'\n size?: 'sm' | 'md' | 'lg'\n label?: string\n showValue?: boolean\n indeterminate?: boolean\n steps?: string[]\n currentStep?: number\n ariaLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n value: 0,\n variant: 'rail',\n color: 'primary',\n size: 'md',\n label: undefined,\n showValue: false,\n indeterminate: false,\n steps: () => [],\n currentStep: 0,\n})\n\nconst clampedValue = computed(() => Math.min(100, Math.max(0, props.value)))\n\nconst barStyle = computed(() => {\n if (props.indeterminate) return undefined\n return { width: `${clampedValue.value}%` }\n})\n\nconst segmentCount = computed(() => props.steps.length || 0)\n</script>\n\n<template>\n <div class=\"mint-progress\" :class=\"`mint-progress--${variant}`\">\n <div\n v-if=\"label || showValue\"\n class=\"mint-progress__header\"\n >\n <span v-if=\"label\" class=\"mint-progress__label\">{{ label }}</span>\n <span v-if=\"showValue && !indeterminate && variant === 'rail'\" class=\"mint-progress__value\">{{ clampedValue }}%</span>\n <span v-if=\"showValue && variant === 'segmented' && segmentCount\" class=\"mint-progress__value\">{{ currentStep }} / {{ segmentCount }}</span>\n </div>\n\n <!-- Rail variant: single filled track -->\n <div\n v-if=\"variant === 'rail'\"\n :class=\"[\n 'mint-progress__track',\n `mint-progress__track--${size}`,\n ]\"\n role=\"progressbar\"\n :aria-valuenow=\"indeterminate ? undefined : clampedValue\"\n :aria-valuemin=\"indeterminate ? undefined : 0\"\n :aria-valuemax=\"indeterminate ? undefined : 100\"\n :aria-label=\"ariaLabel ?? label\"\n >\n <div\n :class=\"[\n 'mint-progress__bar',\n `mint-progress__bar--${color}`,\n indeterminate ? 'mint-progress__bar--indeterminate' : '',\n ]\"\n :style=\"barStyle\"\n />\n </div>\n\n <!-- Segmented variant: discrete step segments -->\n <div\n v-else\n :class=\"['mint-progress__segments', `mint-progress__segments--${size}`]\"\n role=\"progressbar\"\n :aria-valuenow=\"currentStep\"\n :aria-valuemin=\"0\"\n :aria-valuemax=\"segmentCount\"\n :aria-label=\"ariaLabel ?? label\"\n >\n <span\n v-for=\"(_step, i) in steps\"\n :key=\"i\"\n :class=\"[\n 'mint-progress__segment',\n i < currentStep ? `mint-progress__segment--done mint-progress__segment--${color}` : '',\n i === currentStep ? `mint-progress__segment--active mint-progress__segment--${color}` : '',\n ]\"\n />\n </div>\n\n <!-- Segmented step labels -->\n <div\n v-if=\"variant === 'segmented' && steps.length\"\n class=\"mint-progress__step-labels\"\n >\n <span\n v-for=\"(step, i) in steps\"\n :key=\"i\"\n :class=\"[\n 'mint-progress__step-label',\n i === currentStep ? 'mint-progress__step-label--active' : '',\n i < currentStep ? 'mint-progress__step-label--done' : '',\n ]\"\n >\n {{ step }}\n </span>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/progress-bar.css';\n</style>\n","<script setup lang=\"ts\">\n/** Displays progress as a filled rail or discrete labeled segments, with indeterminate mode. */\nimport { computed } from 'vue'\n\ninterface Props {\n value?: number\n variant?: 'rail' | 'segmented'\n color?: 'primary' | 'success' | 'warning' | 'error' | 'info'\n size?: 'sm' | 'md' | 'lg'\n label?: string\n showValue?: boolean\n indeterminate?: boolean\n steps?: string[]\n currentStep?: number\n ariaLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n value: 0,\n variant: 'rail',\n color: 'primary',\n size: 'md',\n label: undefined,\n showValue: false,\n indeterminate: false,\n steps: () => [],\n currentStep: 0,\n})\n\nconst clampedValue = computed(() => Math.min(100, Math.max(0, props.value)))\n\nconst barStyle = computed(() => {\n if (props.indeterminate) return undefined\n return { width: `${clampedValue.value}%` }\n})\n\nconst segmentCount = computed(() => props.steps.length || 0)\n</script>\n\n<template>\n <div class=\"mint-progress\" :class=\"`mint-progress--${variant}`\">\n <div\n v-if=\"label || showValue\"\n class=\"mint-progress__header\"\n >\n <span v-if=\"label\" class=\"mint-progress__label\">{{ label }}</span>\n <span v-if=\"showValue && !indeterminate && variant === 'rail'\" class=\"mint-progress__value\">{{ clampedValue }}%</span>\n <span v-if=\"showValue && variant === 'segmented' && segmentCount\" class=\"mint-progress__value\">{{ currentStep }} / {{ segmentCount }}</span>\n </div>\n\n <!-- Rail variant: single filled track -->\n <div\n v-if=\"variant === 'rail'\"\n :class=\"[\n 'mint-progress__track',\n `mint-progress__track--${size}`,\n ]\"\n role=\"progressbar\"\n :aria-valuenow=\"indeterminate ? undefined : clampedValue\"\n :aria-valuemin=\"indeterminate ? undefined : 0\"\n :aria-valuemax=\"indeterminate ? undefined : 100\"\n :aria-label=\"ariaLabel ?? label\"\n >\n <div\n :class=\"[\n 'mint-progress__bar',\n `mint-progress__bar--${color}`,\n indeterminate ? 'mint-progress__bar--indeterminate' : '',\n ]\"\n :style=\"barStyle\"\n />\n </div>\n\n <!-- Segmented variant: discrete step segments -->\n <div\n v-else\n :class=\"['mint-progress__segments', `mint-progress__segments--${size}`]\"\n role=\"progressbar\"\n :aria-valuenow=\"currentStep\"\n :aria-valuemin=\"0\"\n :aria-valuemax=\"segmentCount\"\n :aria-label=\"ariaLabel ?? label\"\n >\n <span\n v-for=\"(_step, i) in steps\"\n :key=\"i\"\n :class=\"[\n 'mint-progress__segment',\n i < currentStep ? `mint-progress__segment--done mint-progress__segment--${color}` : '',\n i === currentStep ? `mint-progress__segment--active mint-progress__segment--${color}` : '',\n ]\"\n />\n </div>\n\n <!-- Segmented step labels -->\n <div\n v-if=\"variant === 'segmented' && steps.length\"\n class=\"mint-progress__step-labels\"\n >\n <span\n v-for=\"(step, i) in steps\"\n :key=\"i\"\n :class=\"[\n 'mint-progress__step-label',\n i === currentStep ? 'mint-progress__step-label--active' : '',\n i < currentStep ? 'mint-progress__step-label--done' : '',\n ]\"\n >\n {{ step }}\n </span>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/progress-bar.css';\n</style>\n","<script setup lang=\"ts\">\n/** Displays a user avatar as an image, a single derived initial, or a fallback icon, with a deterministic background color and optional status dot. */\nimport { computed } from 'vue'\n\ninterface Props {\n name?: string\n initials?: string\n src?: string\n alt?: string\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n color?: string\n status?: 'online' | 'away' | 'busy' | 'offline'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n alt: '',\n size: 'md',\n})\n\nconst PALETTE = ['#3B82F6', '#8B5CF6', '#EC4899', '#F97316', '#10B981', '#F59E0B', '#06B6D4', '#6366F1']\n\n// Default: first letter of `name`, uppercased — matches the platform's\n// canonical `getAvatarInitial(user)` helper. Consumers wanting two letters\n// (e.g. role badges like \"PI\", \"QC\") pass `:initials=\"'XX'\"` explicitly.\nconst computedInitials = computed(() =>\n props.initials || props.name?.charAt(0).toUpperCase() || ''\n)\n\nconst backgroundColor = computed(() => {\n if (props.color) return props.color\n if (!props.name) return undefined\n\n let hash = 0\n for (let i = 0; i < props.name.length; i++) {\n hash = props.name.charCodeAt(i) + ((hash << 5) - hash)\n }\n return PALETTE[Math.abs(hash) % PALETTE.length]\n})\n</script>\n\n<template>\n <span\n :class=\"['mint-avatar', `mint-avatar--${size}`]\"\n role=\"img\"\n :aria-label=\"alt || name || 'User avatar'\"\n >\n <span\n class=\"mint-avatar__face\"\n :style=\"backgroundColor ? { backgroundColor } : undefined\"\n >\n <img\n v-if=\"src\"\n :src=\"src\"\n :alt=\"alt || name || ''\"\n class=\"mint-avatar__image\"\n >\n <span v-else-if=\"computedInitials\" class=\"mint-avatar__initials\">\n {{ computedInitials }}\n </span>\n <svg\n v-else\n class=\"mint-avatar__fallback\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path d=\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z\" />\n </svg>\n </span>\n <span\n v-if=\"status\"\n :class=\"['mint-avatar__status', `mint-avatar__status--${status}`]\"\n aria-hidden=\"true\"\n />\n </span>\n</template>\n\n<style>\n@import '../styles/components/avatar.css';\n</style>\n","<script setup lang=\"ts\">\n/** Displays a user avatar as an image, a single derived initial, or a fallback icon, with a deterministic background color and optional status dot. */\nimport { computed } from 'vue'\n\ninterface Props {\n name?: string\n initials?: string\n src?: string\n alt?: string\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n color?: string\n status?: 'online' | 'away' | 'busy' | 'offline'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n alt: '',\n size: 'md',\n})\n\nconst PALETTE = ['#3B82F6', '#8B5CF6', '#EC4899', '#F97316', '#10B981', '#F59E0B', '#06B6D4', '#6366F1']\n\n// Default: first letter of `name`, uppercased — matches the platform's\n// canonical `getAvatarInitial(user)` helper. Consumers wanting two letters\n// (e.g. role badges like \"PI\", \"QC\") pass `:initials=\"'XX'\"` explicitly.\nconst computedInitials = computed(() =>\n props.initials || props.name?.charAt(0).toUpperCase() || ''\n)\n\nconst backgroundColor = computed(() => {\n if (props.color) return props.color\n if (!props.name) return undefined\n\n let hash = 0\n for (let i = 0; i < props.name.length; i++) {\n hash = props.name.charCodeAt(i) + ((hash << 5) - hash)\n }\n return PALETTE[Math.abs(hash) % PALETTE.length]\n})\n</script>\n\n<template>\n <span\n :class=\"['mint-avatar', `mint-avatar--${size}`]\"\n role=\"img\"\n :aria-label=\"alt || name || 'User avatar'\"\n >\n <span\n class=\"mint-avatar__face\"\n :style=\"backgroundColor ? { backgroundColor } : undefined\"\n >\n <img\n v-if=\"src\"\n :src=\"src\"\n :alt=\"alt || name || ''\"\n class=\"mint-avatar__image\"\n >\n <span v-else-if=\"computedInitials\" class=\"mint-avatar__initials\">\n {{ computedInitials }}\n </span>\n <svg\n v-else\n class=\"mint-avatar__fallback\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path d=\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z\" />\n </svg>\n </span>\n <span\n v-if=\"status\"\n :class=\"['mint-avatar__status', `mint-avatar__status--${status}`]\"\n aria-hidden=\"true\"\n />\n </span>\n</template>\n\n<style>\n@import '../styles/components/avatar.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a breadcrumb trail where ancestors are links or emit-based buttons and the last item is the current page. */\nimport { computed } from 'vue'\nimport type { BreadcrumbItem, BreadcrumbItemInput } from '../types'\nimport { normalizeLabelItemInput } from '../utils/items'\n\ninterface Props {\n items: BreadcrumbItemInput[]\n separator?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n separator: '/',\n})\n\nconst emit = defineEmits<{\n navigate: [item: BreadcrumbItem]\n}>()\n\nconst normalizedItems = computed<BreadcrumbItem[]>(() =>\n props.items.map(normalizeLabelItemInput)\n)\n\nfunction handleClick(item: BreadcrumbItem) {\n if (!item.href) {\n emit('navigate', item)\n }\n}\n</script>\n\n<template>\n <nav class=\"mint-breadcrumb\" aria-label=\"Breadcrumb\">\n <ol class=\"mint-breadcrumb__list\">\n <li\n v-for=\"(item, index) in normalizedItems\"\n :key=\"index\"\n class=\"mint-breadcrumb__item\"\n >\n <slot name=\"item\" :item=\"item\" :index=\"index\" :is-last=\"index === normalizedItems.length - 1\">\n <a\n v-if=\"item.href && index !== normalizedItems.length - 1\"\n :href=\"item.href\"\n class=\"mint-breadcrumb__link\"\n >\n {{ item.label }}\n </a>\n <button\n v-else-if=\"index !== normalizedItems.length - 1\"\n class=\"mint-breadcrumb__link\"\n @click=\"handleClick(item)\"\n >\n {{ item.label }}\n </button>\n <span\n v-else\n class=\"mint-breadcrumb__current\"\n aria-current=\"page\"\n >\n {{ item.label }}\n </span>\n </slot>\n <span\n v-if=\"index !== normalizedItems.length - 1\"\n class=\"mint-breadcrumb__separator\"\n aria-hidden=\"true\"\n >\n <slot name=\"separator\">\n <svg v-if=\"separator === '/'\" class=\"mint-breadcrumb__chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n <template v-else>{{ separator }}</template>\n </slot>\n </span>\n </li>\n </ol>\n </nav>\n</template>\n\n<style>\n@import '../styles/components/breadcrumb.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a breadcrumb trail where ancestors are links or emit-based buttons and the last item is the current page. */\nimport { computed } from 'vue'\nimport type { BreadcrumbItem, BreadcrumbItemInput } from '../types'\nimport { normalizeLabelItemInput } from '../utils/items'\n\ninterface Props {\n items: BreadcrumbItemInput[]\n separator?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n separator: '/',\n})\n\nconst emit = defineEmits<{\n navigate: [item: BreadcrumbItem]\n}>()\n\nconst normalizedItems = computed<BreadcrumbItem[]>(() =>\n props.items.map(normalizeLabelItemInput)\n)\n\nfunction handleClick(item: BreadcrumbItem) {\n if (!item.href) {\n emit('navigate', item)\n }\n}\n</script>\n\n<template>\n <nav class=\"mint-breadcrumb\" aria-label=\"Breadcrumb\">\n <ol class=\"mint-breadcrumb__list\">\n <li\n v-for=\"(item, index) in normalizedItems\"\n :key=\"index\"\n class=\"mint-breadcrumb__item\"\n >\n <slot name=\"item\" :item=\"item\" :index=\"index\" :is-last=\"index === normalizedItems.length - 1\">\n <a\n v-if=\"item.href && index !== normalizedItems.length - 1\"\n :href=\"item.href\"\n class=\"mint-breadcrumb__link\"\n >\n {{ item.label }}\n </a>\n <button\n v-else-if=\"index !== normalizedItems.length - 1\"\n class=\"mint-breadcrumb__link\"\n @click=\"handleClick(item)\"\n >\n {{ item.label }}\n </button>\n <span\n v-else\n class=\"mint-breadcrumb__current\"\n aria-current=\"page\"\n >\n {{ item.label }}\n </span>\n </slot>\n <span\n v-if=\"index !== normalizedItems.length - 1\"\n class=\"mint-breadcrumb__separator\"\n aria-hidden=\"true\"\n >\n <slot name=\"separator\">\n <svg v-if=\"separator === '/'\" class=\"mint-breadcrumb__chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n <template v-else>{{ separator }}</template>\n </slot>\n </span>\n </li>\n </ol>\n </nav>\n</template>\n\n<style>\n@import '../styles/components/breadcrumb.css';\n</style>\n","<script setup lang=\"ts\">\n/** Wraps a chart with header, toolbar slot, loading overlay, empty state, and legend slot. */\nimport LoadingSpinner from './LoadingSpinner.vue'\nimport EmptyState from './EmptyState.vue'\n\ninterface Props {\n title?: string\n description?: string\n loading?: boolean\n empty?: boolean\n emptyMessage?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n loading: false,\n empty: false,\n emptyMessage: 'No data available',\n})\n</script>\n\n<template>\n <div class=\"mint-chart\">\n <div v-if=\"title || description || $slots.toolbar\" class=\"mint-chart__header\">\n <div v-if=\"title || description\" class=\"mint-chart__header-text\">\n <h3 v-if=\"title\" class=\"mint-chart__title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"mint-chart__description\">{{ description }}</p>\n </div>\n <div v-if=\"$slots.toolbar\" class=\"mint-chart__toolbar\">\n <slot name=\"toolbar\" />\n </div>\n </div>\n\n <div class=\"mint-chart__body\">\n <!-- Loading overlay -->\n <div v-if=\"loading\" class=\"mint-chart__loading-overlay\">\n <LoadingSpinner size=\"lg\" />\n </div>\n\n <!-- Empty state -->\n <template v-else-if=\"empty\">\n <slot name=\"empty\">\n <EmptyState\n :title=\"emptyMessage\"\n size=\"sm\"\n icon-path=\"M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z\"\n />\n </slot>\n </template>\n\n <!-- Chart content -->\n <template v-else>\n <slot />\n </template>\n </div>\n\n <div v-if=\"$slots.legend\" class=\"mint-chart__legend\">\n <slot name=\"legend\" />\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/chart-container.css';\n</style>\n","<script setup lang=\"ts\">\n/** Wraps a chart with header, toolbar slot, loading overlay, empty state, and legend slot. */\nimport LoadingSpinner from './LoadingSpinner.vue'\nimport EmptyState from './EmptyState.vue'\n\ninterface Props {\n title?: string\n description?: string\n loading?: boolean\n empty?: boolean\n emptyMessage?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n loading: false,\n empty: false,\n emptyMessage: 'No data available',\n})\n</script>\n\n<template>\n <div class=\"mint-chart\">\n <div v-if=\"title || description || $slots.toolbar\" class=\"mint-chart__header\">\n <div v-if=\"title || description\" class=\"mint-chart__header-text\">\n <h3 v-if=\"title\" class=\"mint-chart__title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"mint-chart__description\">{{ description }}</p>\n </div>\n <div v-if=\"$slots.toolbar\" class=\"mint-chart__toolbar\">\n <slot name=\"toolbar\" />\n </div>\n </div>\n\n <div class=\"mint-chart__body\">\n <!-- Loading overlay -->\n <div v-if=\"loading\" class=\"mint-chart__loading-overlay\">\n <LoadingSpinner size=\"lg\" />\n </div>\n\n <!-- Empty state -->\n <template v-else-if=\"empty\">\n <slot name=\"empty\">\n <EmptyState\n :title=\"emptyMessage\"\n size=\"sm\"\n icon-path=\"M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z\"\n />\n </slot>\n </template>\n\n <!-- Chart content -->\n <template v-else>\n <slot />\n </template>\n </div>\n\n <div v-if=\"$slots.legend\" class=\"mint-chart__legend\">\n <slot name=\"legend\" />\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/chart-container.css';\n</style>\n","<script setup lang=\"ts\">\n/** Render biology template envelopes with matching SDK components such as WellPlate, DoseCalculator, and DataFrame. */\nimport { computed } from 'vue'\nimport {\n toBioTemplateComponentBindings,\n} from '../templates/componentBindings'\nimport type {\n BioTemplateEnvelope,\n TemplateCollectionEnvelope,\n} from '../templates/types'\nimport ComponentBindingRenderer from './ComponentBindingRenderer.vue'\n\ninterface Props {\n /** Template envelope or template collection to render. */\n target: BioTemplateEnvelope<unknown> | TemplateCollectionEnvelope\n /** Optional allow-list of component names, for example ['WellPlate', 'DataFrame']. */\n include?: string[]\n /** Optional deny-list of component names. */\n exclude?: string[]\n /** Compact child component sizing. */\n dense?: boolean\n /** Prefer preview-safe props for editable components. */\n readonly?: boolean\n /** Show component/template labels above each rendered component. */\n showHeaders?: boolean\n /** Show binding descriptions in each header. */\n showDescriptions?: boolean\n /** Message shown when the target has no renderable bindings. */\n emptyText?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n include: () => [],\n exclude: () => [],\n dense: false,\n readonly: true,\n showHeaders: true,\n showDescriptions: true,\n emptyText: 'No renderable biology template components.',\n})\n\nconst componentBindings = computed(() => toBioTemplateComponentBindings(props.target))\n</script>\n\n<template>\n <ComponentBindingRenderer\n class=\"mint-bio-template-renderer\"\n :bindings=\"componentBindings\"\n :include=\"include\"\n :exclude=\"exclude\"\n :dense=\"dense\"\n :readonly=\"readonly\"\n :show-headers=\"showHeaders\"\n :show-descriptions=\"showDescriptions\"\n :empty-text=\"emptyText\"\n />\n</template>\n","<script setup lang=\"ts\">\n/** Render biology template envelopes with matching SDK components such as WellPlate, DoseCalculator, and DataFrame. */\nimport { computed } from 'vue'\nimport {\n toBioTemplateComponentBindings,\n} from '../templates/componentBindings'\nimport type {\n BioTemplateEnvelope,\n TemplateCollectionEnvelope,\n} from '../templates/types'\nimport ComponentBindingRenderer from './ComponentBindingRenderer.vue'\n\ninterface Props {\n /** Template envelope or template collection to render. */\n target: BioTemplateEnvelope<unknown> | TemplateCollectionEnvelope\n /** Optional allow-list of component names, for example ['WellPlate', 'DataFrame']. */\n include?: string[]\n /** Optional deny-list of component names. */\n exclude?: string[]\n /** Compact child component sizing. */\n dense?: boolean\n /** Prefer preview-safe props for editable components. */\n readonly?: boolean\n /** Show component/template labels above each rendered component. */\n showHeaders?: boolean\n /** Show binding descriptions in each header. */\n showDescriptions?: boolean\n /** Message shown when the target has no renderable bindings. */\n emptyText?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n include: () => [],\n exclude: () => [],\n dense: false,\n readonly: true,\n showHeaders: true,\n showDescriptions: true,\n emptyText: 'No renderable biology template components.',\n})\n\nconst componentBindings = computed(() => toBioTemplateComponentBindings(props.target))\n</script>\n\n<template>\n <ComponentBindingRenderer\n class=\"mint-bio-template-renderer\"\n :bindings=\"componentBindings\"\n :include=\"include\"\n :exclude=\"exclude\"\n :dense=\"dense\"\n :readonly=\"readonly\"\n :show-headers=\"showHeaders\"\n :show-descriptions=\"showDescriptions\"\n :empty-text=\"emptyText\"\n />\n</template>\n","<script setup lang=\"ts\">\n/** Current-experiment template workspace with load/save/reset controls and WellPlate/DataFrame/DoseCalculator previews. */\nimport { computed } from 'vue'\nimport {\n extractTemplateCollection,\n getBioTemplateComponentProps,\n toBioTemplateComponentBindingsById,\n toBioTemplateComponentProps,\n toBioTemplateComponentPropsByComponent,\n toBioTemplateComponentPropsById,\n type BioTemplateEnvelope,\n type BioTemplateComponentPropsLookupOptions,\n type TemplateCollectionEnvelope,\n} from '../templates'\nimport AlertBox from './AlertBox.vue'\nimport BaseButton from './BaseButton.vue'\nimport BioTemplateRenderer from './BioTemplateRenderer.vue'\n\ntype BioTemplateWorkspaceTarget = BioTemplateEnvelope<unknown> | TemplateCollectionEnvelope\ntype WorkspaceKind = 'template' | 'collection'\n\ninterface TemplateWorkspaceStatus {\n loading?: boolean\n saving?: boolean\n error?: string | null\n hasExperiment?: boolean\n currentExperimentId?: number\n lastLoadedAt?: Date | null\n lastSavedAt?: Date | null\n}\n\ninterface TemplateWorkspaceActions {\n load?: () => unknown\n reset?: () => unknown\n save?: () => unknown\n}\n\ninterface Props {\n /** Template envelope or template collection shown in BioTemplateRenderer. */\n target: BioTemplateWorkspaceTarget\n /** Human-readable workspace label. */\n label: string\n /** Grouped request and current-experiment state for generated plugin pages. */\n status?: TemplateWorkspaceStatus\n /** Grouped load/reset/save handlers for generated plugin pages. */\n actions?: TemplateWorkspaceActions\n /** Copy shown in the status banner. */\n message?: string\n /** Whether the load/save request is active. */\n loading?: boolean\n /** Current request error. */\n error?: string | null\n /** Whether a current experiment id is available. */\n hasExperiment?: boolean\n /** Current experiment id displayed in the footer. */\n currentExperimentId?: number\n /** Last successful load timestamp. */\n lastLoadedAt?: Date | null\n /** Last successful save timestamp. */\n lastSavedAt?: Date | null\n /** Template vs collection copy defaults. */\n kind?: WorkspaceKind\n /** Compact preview layout. */\n dense?: boolean\n /** Render preview components in read-only mode. */\n readonly?: boolean\n /** Show load/save timestamps. */\n showStatus?: boolean\n /** Show template id/version cards for collection targets. */\n showTemplateSummary?: boolean\n loadLabel?: string\n resetLabel?: string\n saveLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n status: undefined,\n actions: undefined,\n message: undefined,\n loading: false,\n error: null,\n hasExperiment: false,\n currentExperimentId: undefined,\n lastLoadedAt: null,\n lastSavedAt: null,\n kind: 'template',\n dense: true,\n readonly: true,\n showStatus: true,\n showTemplateSummary: false,\n loadLabel: 'Load',\n resetLabel: 'Reset defaults',\n saveLabel: 'Save',\n})\n\nconst emit = defineEmits<{\n load: []\n reset: []\n save: []\n}>()\n\nconst statusMessage = computed(() => {\n if (props.message) return props.message\n if (props.kind === 'collection') {\n return `${props.label} saves the full template collection to the current experiment.`\n }\n return `${props.label} template data saves to experiment design_data.`\n})\n\nconst resolvedLoading = computed(() => {\n if (props.status?.loading !== undefined || props.status?.saving !== undefined) {\n return Boolean(props.status.loading) || Boolean(props.status.saving)\n }\n return props.loading\n})\nconst resolvedError = computed(() =>\n props.status?.error !== undefined ? props.status.error : props.error,\n)\nconst resolvedHasExperiment = computed(() =>\n props.status?.hasExperiment ?? props.hasExperiment,\n)\nconst resolvedCurrentExperimentId = computed(() =>\n props.status?.currentExperimentId ?? props.currentExperimentId,\n)\nconst resolvedLastLoadedAt = computed(() =>\n props.status?.lastLoadedAt !== undefined ? props.status.lastLoadedAt : props.lastLoadedAt,\n)\nconst resolvedLastSavedAt = computed(() =>\n props.status?.lastSavedAt !== undefined ? props.status.lastSavedAt : props.lastSavedAt,\n)\n\nconst templateSummaries = computed(() =>\n Object.values(extractTemplateCollection(props.target)).map(template => ({\n id: String(template.template_id),\n version: String(template.template_version ?? 'unknown'),\n }))\n)\nconst componentProps = computed(() => toBioTemplateComponentProps(props.target))\nconst componentBindingsById = computed(() => toBioTemplateComponentBindingsById(props.target))\nconst componentPropsById = computed(() => toBioTemplateComponentPropsById(props.target))\nconst componentPropsByComponent = computed(() => toBioTemplateComponentPropsByComponent(props.target))\nconst renderer = computed(() => ({ target: props.target }))\n\nfunction getComponentProps(component: string, options?: BioTemplateComponentPropsLookupOptions) {\n return getBioTemplateComponentProps(props.target, component, options)\n}\n\ninterface BioTemplateExperimentWorkspaceBindings {\n renderer: { target: BioTemplateWorkspaceTarget }\n componentProps: ReturnType<typeof toBioTemplateComponentProps>\n componentBindingsById: ReturnType<typeof toBioTemplateComponentBindingsById>\n componentPropsById: ReturnType<typeof toBioTemplateComponentPropsById>\n componentPropsByComponent: ReturnType<typeof toBioTemplateComponentPropsByComponent>\n getComponentProps: typeof getComponentProps\n}\n\nconst bindings = computed<BioTemplateExperimentWorkspaceBindings>(() => ({\n renderer: renderer.value,\n componentProps: componentProps.value,\n componentBindingsById: componentBindingsById.value,\n componentPropsById: componentPropsById.value,\n componentPropsByComponent: componentPropsByComponent.value,\n getComponentProps,\n}))\n\ninterface BioTemplateExperimentWorkspaceSlotProps {\n target: BioTemplateWorkspaceTarget\n bindings: BioTemplateExperimentWorkspaceBindings\n componentProps: ReturnType<typeof toBioTemplateComponentProps>\n componentBindingsById: ReturnType<typeof toBioTemplateComponentBindingsById>\n componentPropsById: ReturnType<typeof toBioTemplateComponentPropsById>\n componentPropsByComponent: ReturnType<typeof toBioTemplateComponentPropsByComponent>\n getComponentProps: typeof getComponentProps\n}\n\ndefineSlots<{\n default?: (props: BioTemplateExperimentWorkspaceSlotProps) => unknown\n}>()\n\nfunction handleLoad() {\n if (props.actions?.load) {\n return props.actions.load()\n }\n emit('load')\n}\n\nfunction handleReset() {\n if (props.actions?.reset) {\n return props.actions.reset()\n }\n emit('reset')\n}\n\nfunction handleSave() {\n if (props.actions?.save) {\n return props.actions.save()\n }\n emit('save')\n}\n</script>\n\n<template>\n <div class=\"mint-bio-template-experiment-workspace\">\n <div class=\"mint-bio-template-experiment-workspace__toolbar\">\n <AlertBox type=\"info\" class=\"mint-bio-template-experiment-workspace__status\">\n {{ statusMessage }}\n </AlertBox>\n\n <div class=\"mint-bio-template-experiment-workspace__actions\">\n <BaseButton\n variant=\"secondary\"\n :loading=\"resolvedLoading\"\n :disabled=\"!resolvedHasExperiment\"\n @click=\"handleLoad\"\n >\n {{ loadLabel }}\n </BaseButton>\n <BaseButton variant=\"secondary\" :loading=\"resolvedLoading\" @click=\"handleReset\">\n {{ resetLabel }}\n </BaseButton>\n <BaseButton\n variant=\"primary\"\n :loading=\"resolvedLoading\"\n :disabled=\"!resolvedHasExperiment\"\n @click=\"handleSave\"\n >\n {{ saveLabel }}\n </BaseButton>\n </div>\n </div>\n\n <AlertBox v-if=\"resolvedError\" type=\"error\">\n {{ resolvedError }}\n </AlertBox>\n\n <p v-if=\"showStatus\" class=\"mint-bio-template-experiment-workspace__meta\">\n loaded: {{ resolvedLastLoadedAt?.toLocaleString() ?? 'not loaded' }} -\n saved: {{ resolvedLastSavedAt?.toLocaleString() ?? 'not saved' }}\n </p>\n\n <div\n v-if=\"showTemplateSummary\"\n class=\"mint-bio-template-experiment-workspace__templates\"\n >\n <div\n v-for=\"template in templateSummaries\"\n :key=\"template.id\"\n class=\"mint-bio-template-experiment-workspace__template\"\n >\n <p class=\"mint-bio-template-experiment-workspace__template-id\">\n {{ template.id }}\n </p>\n <p class=\"mint-bio-template-experiment-workspace__template-version\">\n v{{ template.version }}\n </p>\n </div>\n </div>\n\n <slot\n :target=\"target\"\n :bindings=\"bindings\"\n :component-props=\"componentProps\"\n :component-bindings-by-id=\"componentBindingsById\"\n :component-props-by-id=\"componentPropsById\"\n :component-props-by-component=\"componentPropsByComponent\"\n :get-component-props=\"getComponentProps\"\n >\n <BioTemplateRenderer\n :target=\"target\"\n :dense=\"dense\"\n :readonly=\"readonly\"\n />\n </slot>\n\n <p class=\"mint-bio-template-experiment-workspace__meta\">\n experiment: {{ resolvedCurrentExperimentId ?? 'not selected' }}\n </p>\n </div>\n</template>\n\n<style scoped>\n.mint-bio-template-experiment-workspace {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n min-width: 0;\n}\n\n.mint-bio-template-experiment-workspace__toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n}\n\n.mint-bio-template-experiment-workspace__status {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.mint-bio-template-experiment-workspace__actions {\n display: flex;\n flex: 0 0 auto;\n gap: 0.5rem;\n}\n\n.mint-bio-template-experiment-workspace__meta,\n.mint-bio-template-experiment-workspace__template-version {\n margin: 0;\n color: var(--text-secondary, #64748b);\n font-family: var(--font-mono, 'Fira Code', monospace);\n font-size: 0.75rem;\n}\n\n.mint-bio-template-experiment-workspace__templates {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));\n gap: 0.75rem;\n}\n\n.mint-bio-template-experiment-workspace__template {\n min-width: 0;\n padding: 1rem;\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 0.5rem;\n background: var(--bg-primary, #ffffff);\n}\n\n.mint-bio-template-experiment-workspace__template-id {\n margin: 0;\n color: var(--text-primary, #0f172a);\n font-family: var(--font-mono, 'Fira Code', monospace);\n font-size: 0.875rem;\n}\n\n@media (max-width: 48rem) {\n .mint-bio-template-experiment-workspace__toolbar,\n .mint-bio-template-experiment-workspace__actions {\n align-items: stretch;\n flex-direction: column;\n }\n}\n</style>\n","<script setup lang=\"ts\">\n/** Current-experiment template workspace with load/save/reset controls and WellPlate/DataFrame/DoseCalculator previews. */\nimport { computed } from 'vue'\nimport {\n extractTemplateCollection,\n getBioTemplateComponentProps,\n toBioTemplateComponentBindingsById,\n toBioTemplateComponentProps,\n toBioTemplateComponentPropsByComponent,\n toBioTemplateComponentPropsById,\n type BioTemplateEnvelope,\n type BioTemplateComponentPropsLookupOptions,\n type TemplateCollectionEnvelope,\n} from '../templates'\nimport AlertBox from './AlertBox.vue'\nimport BaseButton from './BaseButton.vue'\nimport BioTemplateRenderer from './BioTemplateRenderer.vue'\n\ntype BioTemplateWorkspaceTarget = BioTemplateEnvelope<unknown> | TemplateCollectionEnvelope\ntype WorkspaceKind = 'template' | 'collection'\n\ninterface TemplateWorkspaceStatus {\n loading?: boolean\n saving?: boolean\n error?: string | null\n hasExperiment?: boolean\n currentExperimentId?: number\n lastLoadedAt?: Date | null\n lastSavedAt?: Date | null\n}\n\ninterface TemplateWorkspaceActions {\n load?: () => unknown\n reset?: () => unknown\n save?: () => unknown\n}\n\ninterface Props {\n /** Template envelope or template collection shown in BioTemplateRenderer. */\n target: BioTemplateWorkspaceTarget\n /** Human-readable workspace label. */\n label: string\n /** Grouped request and current-experiment state for generated plugin pages. */\n status?: TemplateWorkspaceStatus\n /** Grouped load/reset/save handlers for generated plugin pages. */\n actions?: TemplateWorkspaceActions\n /** Copy shown in the status banner. */\n message?: string\n /** Whether the load/save request is active. */\n loading?: boolean\n /** Current request error. */\n error?: string | null\n /** Whether a current experiment id is available. */\n hasExperiment?: boolean\n /** Current experiment id displayed in the footer. */\n currentExperimentId?: number\n /** Last successful load timestamp. */\n lastLoadedAt?: Date | null\n /** Last successful save timestamp. */\n lastSavedAt?: Date | null\n /** Template vs collection copy defaults. */\n kind?: WorkspaceKind\n /** Compact preview layout. */\n dense?: boolean\n /** Render preview components in read-only mode. */\n readonly?: boolean\n /** Show load/save timestamps. */\n showStatus?: boolean\n /** Show template id/version cards for collection targets. */\n showTemplateSummary?: boolean\n loadLabel?: string\n resetLabel?: string\n saveLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n status: undefined,\n actions: undefined,\n message: undefined,\n loading: false,\n error: null,\n hasExperiment: false,\n currentExperimentId: undefined,\n lastLoadedAt: null,\n lastSavedAt: null,\n kind: 'template',\n dense: true,\n readonly: true,\n showStatus: true,\n showTemplateSummary: false,\n loadLabel: 'Load',\n resetLabel: 'Reset defaults',\n saveLabel: 'Save',\n})\n\nconst emit = defineEmits<{\n load: []\n reset: []\n save: []\n}>()\n\nconst statusMessage = computed(() => {\n if (props.message) return props.message\n if (props.kind === 'collection') {\n return `${props.label} saves the full template collection to the current experiment.`\n }\n return `${props.label} template data saves to experiment design_data.`\n})\n\nconst resolvedLoading = computed(() => {\n if (props.status?.loading !== undefined || props.status?.saving !== undefined) {\n return Boolean(props.status.loading) || Boolean(props.status.saving)\n }\n return props.loading\n})\nconst resolvedError = computed(() =>\n props.status?.error !== undefined ? props.status.error : props.error,\n)\nconst resolvedHasExperiment = computed(() =>\n props.status?.hasExperiment ?? props.hasExperiment,\n)\nconst resolvedCurrentExperimentId = computed(() =>\n props.status?.currentExperimentId ?? props.currentExperimentId,\n)\nconst resolvedLastLoadedAt = computed(() =>\n props.status?.lastLoadedAt !== undefined ? props.status.lastLoadedAt : props.lastLoadedAt,\n)\nconst resolvedLastSavedAt = computed(() =>\n props.status?.lastSavedAt !== undefined ? props.status.lastSavedAt : props.lastSavedAt,\n)\n\nconst templateSummaries = computed(() =>\n Object.values(extractTemplateCollection(props.target)).map(template => ({\n id: String(template.template_id),\n version: String(template.template_version ?? 'unknown'),\n }))\n)\nconst componentProps = computed(() => toBioTemplateComponentProps(props.target))\nconst componentBindingsById = computed(() => toBioTemplateComponentBindingsById(props.target))\nconst componentPropsById = computed(() => toBioTemplateComponentPropsById(props.target))\nconst componentPropsByComponent = computed(() => toBioTemplateComponentPropsByComponent(props.target))\nconst renderer = computed(() => ({ target: props.target }))\n\nfunction getComponentProps(component: string, options?: BioTemplateComponentPropsLookupOptions) {\n return getBioTemplateComponentProps(props.target, component, options)\n}\n\ninterface BioTemplateExperimentWorkspaceBindings {\n renderer: { target: BioTemplateWorkspaceTarget }\n componentProps: ReturnType<typeof toBioTemplateComponentProps>\n componentBindingsById: ReturnType<typeof toBioTemplateComponentBindingsById>\n componentPropsById: ReturnType<typeof toBioTemplateComponentPropsById>\n componentPropsByComponent: ReturnType<typeof toBioTemplateComponentPropsByComponent>\n getComponentProps: typeof getComponentProps\n}\n\nconst bindings = computed<BioTemplateExperimentWorkspaceBindings>(() => ({\n renderer: renderer.value,\n componentProps: componentProps.value,\n componentBindingsById: componentBindingsById.value,\n componentPropsById: componentPropsById.value,\n componentPropsByComponent: componentPropsByComponent.value,\n getComponentProps,\n}))\n\ninterface BioTemplateExperimentWorkspaceSlotProps {\n target: BioTemplateWorkspaceTarget\n bindings: BioTemplateExperimentWorkspaceBindings\n componentProps: ReturnType<typeof toBioTemplateComponentProps>\n componentBindingsById: ReturnType<typeof toBioTemplateComponentBindingsById>\n componentPropsById: ReturnType<typeof toBioTemplateComponentPropsById>\n componentPropsByComponent: ReturnType<typeof toBioTemplateComponentPropsByComponent>\n getComponentProps: typeof getComponentProps\n}\n\ndefineSlots<{\n default?: (props: BioTemplateExperimentWorkspaceSlotProps) => unknown\n}>()\n\nfunction handleLoad() {\n if (props.actions?.load) {\n return props.actions.load()\n }\n emit('load')\n}\n\nfunction handleReset() {\n if (props.actions?.reset) {\n return props.actions.reset()\n }\n emit('reset')\n}\n\nfunction handleSave() {\n if (props.actions?.save) {\n return props.actions.save()\n }\n emit('save')\n}\n</script>\n\n<template>\n <div class=\"mint-bio-template-experiment-workspace\">\n <div class=\"mint-bio-template-experiment-workspace__toolbar\">\n <AlertBox type=\"info\" class=\"mint-bio-template-experiment-workspace__status\">\n {{ statusMessage }}\n </AlertBox>\n\n <div class=\"mint-bio-template-experiment-workspace__actions\">\n <BaseButton\n variant=\"secondary\"\n :loading=\"resolvedLoading\"\n :disabled=\"!resolvedHasExperiment\"\n @click=\"handleLoad\"\n >\n {{ loadLabel }}\n </BaseButton>\n <BaseButton variant=\"secondary\" :loading=\"resolvedLoading\" @click=\"handleReset\">\n {{ resetLabel }}\n </BaseButton>\n <BaseButton\n variant=\"primary\"\n :loading=\"resolvedLoading\"\n :disabled=\"!resolvedHasExperiment\"\n @click=\"handleSave\"\n >\n {{ saveLabel }}\n </BaseButton>\n </div>\n </div>\n\n <AlertBox v-if=\"resolvedError\" type=\"error\">\n {{ resolvedError }}\n </AlertBox>\n\n <p v-if=\"showStatus\" class=\"mint-bio-template-experiment-workspace__meta\">\n loaded: {{ resolvedLastLoadedAt?.toLocaleString() ?? 'not loaded' }} -\n saved: {{ resolvedLastSavedAt?.toLocaleString() ?? 'not saved' }}\n </p>\n\n <div\n v-if=\"showTemplateSummary\"\n class=\"mint-bio-template-experiment-workspace__templates\"\n >\n <div\n v-for=\"template in templateSummaries\"\n :key=\"template.id\"\n class=\"mint-bio-template-experiment-workspace__template\"\n >\n <p class=\"mint-bio-template-experiment-workspace__template-id\">\n {{ template.id }}\n </p>\n <p class=\"mint-bio-template-experiment-workspace__template-version\">\n v{{ template.version }}\n </p>\n </div>\n </div>\n\n <slot\n :target=\"target\"\n :bindings=\"bindings\"\n :component-props=\"componentProps\"\n :component-bindings-by-id=\"componentBindingsById\"\n :component-props-by-id=\"componentPropsById\"\n :component-props-by-component=\"componentPropsByComponent\"\n :get-component-props=\"getComponentProps\"\n >\n <BioTemplateRenderer\n :target=\"target\"\n :dense=\"dense\"\n :readonly=\"readonly\"\n />\n </slot>\n\n <p class=\"mint-bio-template-experiment-workspace__meta\">\n experiment: {{ resolvedCurrentExperimentId ?? 'not selected' }}\n </p>\n </div>\n</template>\n\n<style scoped>\n.mint-bio-template-experiment-workspace {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n min-width: 0;\n}\n\n.mint-bio-template-experiment-workspace__toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n}\n\n.mint-bio-template-experiment-workspace__status {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.mint-bio-template-experiment-workspace__actions {\n display: flex;\n flex: 0 0 auto;\n gap: 0.5rem;\n}\n\n.mint-bio-template-experiment-workspace__meta,\n.mint-bio-template-experiment-workspace__template-version {\n margin: 0;\n color: var(--text-secondary, #64748b);\n font-family: var(--font-mono, 'Fira Code', monospace);\n font-size: 0.75rem;\n}\n\n.mint-bio-template-experiment-workspace__templates {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));\n gap: 0.75rem;\n}\n\n.mint-bio-template-experiment-workspace__template {\n min-width: 0;\n padding: 1rem;\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 0.5rem;\n background: var(--bg-primary, #ffffff);\n}\n\n.mint-bio-template-experiment-workspace__template-id {\n margin: 0;\n color: var(--text-primary, #0f172a);\n font-family: var(--font-mono, 'Fira Code', monospace);\n font-size: 0.875rem;\n}\n\n@media (max-width: 48rem) {\n .mint-bio-template-experiment-workspace__toolbar,\n .mint-bio-template-experiment-workspace__actions {\n align-items: stretch;\n flex-direction: column;\n }\n}\n</style>\n","<script setup lang=\"ts\">\n/** Complete current-experiment workspace for a curated biology template pack with save/load controls and component-prop slots. */\nimport { computed, effectScope, onScopeDispose, shallowRef, unref, useSlots, watch, type EffectScope } from 'vue'\nimport type { BioTemplateEnvelope, TemplateCollectionEnvelope, TemplatePackId } from '../templates'\nimport type {\n UseBioTemplatePackWorkspaceOptions,\n UseBioTemplatePackWorkspaceReturn,\n} from '../composables/useBioTemplatePackWorkspace'\nimport { useBioTemplatePackWorkspace } from '../composables/useBioTemplatePackWorkspace'\nimport BioTemplateExperimentWorkspaceView from './BioTemplateExperimentWorkspaceView.vue'\n\ninterface BioTemplatePackWorkspaceSlotProps {\n workspace: UseBioTemplatePackWorkspaceReturn\n bindings: UseBioTemplatePackWorkspaceReturn['bindings']['value']\n pack: UseBioTemplatePackWorkspaceReturn['pack']\n target: BioTemplateEnvelope<unknown> | TemplateCollectionEnvelope\n componentBindingsById: UseBioTemplatePackWorkspaceReturn['componentBindingsById']['value']\n componentProps: UseBioTemplatePackWorkspaceReturn['componentProps']['value']\n componentPropsById: UseBioTemplatePackWorkspaceReturn['componentPropsById']['value']\n componentPropsByComponent: UseBioTemplatePackWorkspaceReturn['componentPropsByComponent']['value']\n getComponentProps: UseBioTemplatePackWorkspaceReturn['getComponentProps']\n form: UseBioTemplatePackWorkspaceReturn['form']['value']\n sidebar: UseBioTemplatePackWorkspaceReturn['sidebar']['value']\n topBar: UseBioTemplatePackWorkspaceReturn['topBar']['value']\n pillNav: UseBioTemplatePackWorkspaceReturn['pillNav']['value']\n topBarSettings: UseBioTemplatePackWorkspaceReturn['topBarSettings']['value']\n}\n\ninterface PackWorkspaceStatus {\n loading?: boolean\n saving?: boolean\n error?: string | null\n hasExperiment?: boolean\n currentExperimentId?: number\n lastLoadedAt?: Date | null\n lastSavedAt?: Date | null\n}\n\ninterface PackWorkspaceActions {\n load?: () => unknown\n reset?: () => unknown\n save?: () => unknown\n}\n\ninterface Props {\n /** Workspace returned by useBioTemplatePackWorkspace(). Use for full manual control. */\n workspace?: UseBioTemplatePackWorkspaceReturn\n /** Built-in template pack id or alias used when workspace is not provided. */\n pack?: TemplatePackId | string\n /** Options passed to the internally generated useBioTemplatePackWorkspace() call. */\n workspaceOptions?: UseBioTemplatePackWorkspaceOptions\n /** External request state when plugin endpoints handle pack persistence. */\n status?: PackWorkspaceStatus\n /** External load/reset/save handlers when plugin endpoints handle pack persistence. */\n actions?: PackWorkspaceActions\n /** Human-readable workspace label. Defaults to the pack catalog label. */\n label?: string\n /** Copy shown in the status banner. */\n message?: string\n /** Compact preview layout. */\n dense?: boolean\n /** Render preview components in read-only mode. */\n readonly?: boolean\n /** Show load/save timestamps. */\n showStatus?: boolean\n /** Show template id/version cards for the generated collection. */\n showTemplateSummary?: boolean\n loadLabel?: string\n resetLabel?: string\n saveLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n workspace: undefined,\n pack: 'cell-culture-screen',\n workspaceOptions: () => ({}),\n status: undefined,\n actions: undefined,\n label: undefined,\n message: undefined,\n dense: true,\n readonly: true,\n showStatus: true,\n showTemplateSummary: true,\n loadLabel: 'Load',\n resetLabel: 'Reset defaults',\n saveLabel: 'Save',\n})\n\ndefineSlots<{\n default?: (props: BioTemplatePackWorkspaceSlotProps) => unknown\n}>()\n\nconst slots = useSlots()\nconst hasDefaultSlot = computed(() => Boolean(slots.default))\n\nlet generatedWorkspaceScope: EffectScope | undefined\n\nfunction createGeneratedWorkspace(): UseBioTemplatePackWorkspaceReturn {\n generatedWorkspaceScope?.stop()\n generatedWorkspaceScope = effectScope()\n return generatedWorkspaceScope.run(() => useBioTemplatePackWorkspace(props.pack, props.workspaceOptions))!\n}\n\nconst generatedWorkspace = shallowRef<UseBioTemplatePackWorkspaceReturn>(createGeneratedWorkspace())\nconst resolvedWorkspace = computed(() => props.workspace ?? generatedWorkspace.value)\nconst target = computed(() => unref(resolvedWorkspace.value.collection))\nconst label = computed(() => props.label ?? resolvedWorkspace.value.pack.label)\nconst form = computed(() => unref(resolvedWorkspace.value.form))\nconst sidebar = computed(() => unref(resolvedWorkspace.value.sidebar))\nconst topBar = computed(() => unref(resolvedWorkspace.value.topBar))\nconst pillNav = computed(() => unref(resolvedWorkspace.value.pillNav))\nconst topBarSettings = computed(() => unref(resolvedWorkspace.value.topBarSettings))\nconst bindings = computed(() => unref(resolvedWorkspace.value.bindings))\nconst resolvedStatus = computed<PackWorkspaceStatus>(() => ({\n loading: props.status?.loading ?? unref(resolvedWorkspace.value.loading),\n saving: props.status?.saving ?? unref(resolvedWorkspace.value.saving),\n error: props.status?.error !== undefined\n ? props.status.error\n : unref(resolvedWorkspace.value.error),\n hasExperiment: props.status?.hasExperiment ?? unref(resolvedWorkspace.value.hasCurrentExperiment),\n currentExperimentId: props.status?.currentExperimentId ?? unref(resolvedWorkspace.value.currentExperimentId),\n lastLoadedAt: props.status?.lastLoadedAt !== undefined\n ? props.status.lastLoadedAt\n : unref(resolvedWorkspace.value.lastLoadedAt),\n lastSavedAt: props.status?.lastSavedAt !== undefined\n ? props.status.lastSavedAt\n : unref(resolvedWorkspace.value.lastSavedAt),\n}))\nconst resolvedActions = computed<PackWorkspaceActions>(() => ({\n load: props.actions?.load ?? (() => resolvedWorkspace.value.loadCurrent()),\n reset: props.actions?.reset ?? (() => resolvedWorkspace.value.resetToDefaultCollection()),\n save: props.actions?.save ?? (() => resolvedWorkspace.value.saveCurrent()),\n}))\n\nwatch(\n [() => props.pack, () => props.workspaceOptions],\n () => {\n generatedWorkspace.value = createGeneratedWorkspace()\n },\n { deep: true },\n)\n\nonScopeDispose(() => generatedWorkspaceScope?.stop())\n</script>\n\n<template>\n <BioTemplateExperimentWorkspaceView\n :target=\"target\"\n kind=\"collection\"\n :label=\"label\"\n :message=\"message\"\n :status=\"resolvedStatus\"\n :actions=\"resolvedActions\"\n :dense=\"dense\"\n :readonly=\"readonly\"\n :show-status=\"showStatus\"\n :show-template-summary=\"showTemplateSummary\"\n :load-label=\"loadLabel\"\n :reset-label=\"resetLabel\"\n :save-label=\"saveLabel\"\n >\n <template v-if=\"hasDefaultSlot\" #default=\"slotProps\">\n <slot\n v-bind=\"slotProps\"\n :workspace=\"resolvedWorkspace\"\n :bindings=\"bindings\"\n :pack=\"resolvedWorkspace.pack\"\n :form=\"form\"\n :sidebar=\"sidebar\"\n :top-bar=\"topBar\"\n :pill-nav=\"pillNav\"\n :top-bar-settings=\"topBarSettings\"\n />\n </template>\n </BioTemplateExperimentWorkspaceView>\n</template>\n","<script setup lang=\"ts\">\n/** Complete current-experiment workspace for a curated biology template pack with save/load controls and component-prop slots. */\nimport { computed, effectScope, onScopeDispose, shallowRef, unref, useSlots, watch, type EffectScope } from 'vue'\nimport type { BioTemplateEnvelope, TemplateCollectionEnvelope, TemplatePackId } from '../templates'\nimport type {\n UseBioTemplatePackWorkspaceOptions,\n UseBioTemplatePackWorkspaceReturn,\n} from '../composables/useBioTemplatePackWorkspace'\nimport { useBioTemplatePackWorkspace } from '../composables/useBioTemplatePackWorkspace'\nimport BioTemplateExperimentWorkspaceView from './BioTemplateExperimentWorkspaceView.vue'\n\ninterface BioTemplatePackWorkspaceSlotProps {\n workspace: UseBioTemplatePackWorkspaceReturn\n bindings: UseBioTemplatePackWorkspaceReturn['bindings']['value']\n pack: UseBioTemplatePackWorkspaceReturn['pack']\n target: BioTemplateEnvelope<unknown> | TemplateCollectionEnvelope\n componentBindingsById: UseBioTemplatePackWorkspaceReturn['componentBindingsById']['value']\n componentProps: UseBioTemplatePackWorkspaceReturn['componentProps']['value']\n componentPropsById: UseBioTemplatePackWorkspaceReturn['componentPropsById']['value']\n componentPropsByComponent: UseBioTemplatePackWorkspaceReturn['componentPropsByComponent']['value']\n getComponentProps: UseBioTemplatePackWorkspaceReturn['getComponentProps']\n form: UseBioTemplatePackWorkspaceReturn['form']['value']\n sidebar: UseBioTemplatePackWorkspaceReturn['sidebar']['value']\n topBar: UseBioTemplatePackWorkspaceReturn['topBar']['value']\n pillNav: UseBioTemplatePackWorkspaceReturn['pillNav']['value']\n topBarSettings: UseBioTemplatePackWorkspaceReturn['topBarSettings']['value']\n}\n\ninterface PackWorkspaceStatus {\n loading?: boolean\n saving?: boolean\n error?: string | null\n hasExperiment?: boolean\n currentExperimentId?: number\n lastLoadedAt?: Date | null\n lastSavedAt?: Date | null\n}\n\ninterface PackWorkspaceActions {\n load?: () => unknown\n reset?: () => unknown\n save?: () => unknown\n}\n\ninterface Props {\n /** Workspace returned by useBioTemplatePackWorkspace(). Use for full manual control. */\n workspace?: UseBioTemplatePackWorkspaceReturn\n /** Built-in template pack id or alias used when workspace is not provided. */\n pack?: TemplatePackId | string\n /** Options passed to the internally generated useBioTemplatePackWorkspace() call. */\n workspaceOptions?: UseBioTemplatePackWorkspaceOptions\n /** External request state when plugin endpoints handle pack persistence. */\n status?: PackWorkspaceStatus\n /** External load/reset/save handlers when plugin endpoints handle pack persistence. */\n actions?: PackWorkspaceActions\n /** Human-readable workspace label. Defaults to the pack catalog label. */\n label?: string\n /** Copy shown in the status banner. */\n message?: string\n /** Compact preview layout. */\n dense?: boolean\n /** Render preview components in read-only mode. */\n readonly?: boolean\n /** Show load/save timestamps. */\n showStatus?: boolean\n /** Show template id/version cards for the generated collection. */\n showTemplateSummary?: boolean\n loadLabel?: string\n resetLabel?: string\n saveLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n workspace: undefined,\n pack: 'cell-culture-screen',\n workspaceOptions: () => ({}),\n status: undefined,\n actions: undefined,\n label: undefined,\n message: undefined,\n dense: true,\n readonly: true,\n showStatus: true,\n showTemplateSummary: true,\n loadLabel: 'Load',\n resetLabel: 'Reset defaults',\n saveLabel: 'Save',\n})\n\ndefineSlots<{\n default?: (props: BioTemplatePackWorkspaceSlotProps) => unknown\n}>()\n\nconst slots = useSlots()\nconst hasDefaultSlot = computed(() => Boolean(slots.default))\n\nlet generatedWorkspaceScope: EffectScope | undefined\n\nfunction createGeneratedWorkspace(): UseBioTemplatePackWorkspaceReturn {\n generatedWorkspaceScope?.stop()\n generatedWorkspaceScope = effectScope()\n return generatedWorkspaceScope.run(() => useBioTemplatePackWorkspace(props.pack, props.workspaceOptions))!\n}\n\nconst generatedWorkspace = shallowRef<UseBioTemplatePackWorkspaceReturn>(createGeneratedWorkspace())\nconst resolvedWorkspace = computed(() => props.workspace ?? generatedWorkspace.value)\nconst target = computed(() => unref(resolvedWorkspace.value.collection))\nconst label = computed(() => props.label ?? resolvedWorkspace.value.pack.label)\nconst form = computed(() => unref(resolvedWorkspace.value.form))\nconst sidebar = computed(() => unref(resolvedWorkspace.value.sidebar))\nconst topBar = computed(() => unref(resolvedWorkspace.value.topBar))\nconst pillNav = computed(() => unref(resolvedWorkspace.value.pillNav))\nconst topBarSettings = computed(() => unref(resolvedWorkspace.value.topBarSettings))\nconst bindings = computed(() => unref(resolvedWorkspace.value.bindings))\nconst resolvedStatus = computed<PackWorkspaceStatus>(() => ({\n loading: props.status?.loading ?? unref(resolvedWorkspace.value.loading),\n saving: props.status?.saving ?? unref(resolvedWorkspace.value.saving),\n error: props.status?.error !== undefined\n ? props.status.error\n : unref(resolvedWorkspace.value.error),\n hasExperiment: props.status?.hasExperiment ?? unref(resolvedWorkspace.value.hasCurrentExperiment),\n currentExperimentId: props.status?.currentExperimentId ?? unref(resolvedWorkspace.value.currentExperimentId),\n lastLoadedAt: props.status?.lastLoadedAt !== undefined\n ? props.status.lastLoadedAt\n : unref(resolvedWorkspace.value.lastLoadedAt),\n lastSavedAt: props.status?.lastSavedAt !== undefined\n ? props.status.lastSavedAt\n : unref(resolvedWorkspace.value.lastSavedAt),\n}))\nconst resolvedActions = computed<PackWorkspaceActions>(() => ({\n load: props.actions?.load ?? (() => resolvedWorkspace.value.loadCurrent()),\n reset: props.actions?.reset ?? (() => resolvedWorkspace.value.resetToDefaultCollection()),\n save: props.actions?.save ?? (() => resolvedWorkspace.value.saveCurrent()),\n}))\n\nwatch(\n [() => props.pack, () => props.workspaceOptions],\n () => {\n generatedWorkspace.value = createGeneratedWorkspace()\n },\n { deep: true },\n)\n\nonScopeDispose(() => generatedWorkspaceScope?.stop())\n</script>\n\n<template>\n <BioTemplateExperimentWorkspaceView\n :target=\"target\"\n kind=\"collection\"\n :label=\"label\"\n :message=\"message\"\n :status=\"resolvedStatus\"\n :actions=\"resolvedActions\"\n :dense=\"dense\"\n :readonly=\"readonly\"\n :show-status=\"showStatus\"\n :show-template-summary=\"showTemplateSummary\"\n :load-label=\"loadLabel\"\n :reset-label=\"resetLabel\"\n :save-label=\"saveLabel\"\n >\n <template v-if=\"hasDefaultSlot\" #default=\"slotProps\">\n <slot\n v-bind=\"slotProps\"\n :workspace=\"resolvedWorkspace\"\n :bindings=\"bindings\"\n :pack=\"resolvedWorkspace.pack\"\n :form=\"form\"\n :sidebar=\"sidebar\"\n :top-bar=\"topBar\"\n :pill-nav=\"pillNav\"\n :top-bar-settings=\"topBarSettings\"\n />\n </template>\n </BioTemplateExperimentWorkspaceView>\n</template>\n","<script setup lang=\"ts\">\n/** Complete editable biology template preset workspace for WellPlate/DoseCalculator, LC-MS, qPCR, generated controls, preview components, and current-experiment save wiring. */\nimport { computed, effectScope, onScopeDispose, shallowRef, toRaw, unref, watch, type EffectScope } from 'vue'\nimport type { BioTemplateControlValues, TemplatePresetId } from '../templates'\nimport type {\n UseBioTemplatePresetWorkspaceOptions,\n UseBioTemplatePresetWorkspaceReturn,\n} from '../composables/useBioTemplatePresetWorkspace'\nimport { useBioTemplatePresetWorkspace } from '../composables/useBioTemplatePresetWorkspace'\nimport AlertBox from './AlertBox.vue'\nimport AppSidebar from './AppSidebar.vue'\nimport BaseButton from './BaseButton.vue'\nimport BioTemplateRenderer from './BioTemplateRenderer.vue'\nimport AppTopBarPillNavInternal from './internal/AppTopBarPillNavInternal.vue'\n\ntype BioTemplatePresetSidebarVariant = 'default' | 'analysis'\n\ninterface BioTemplatePresetWorkspaceSlotProps {\n workspace: UseBioTemplatePresetWorkspaceReturn\n bindings: UseBioTemplatePresetWorkspaceReturn['bindings']\n collection: UseBioTemplatePresetWorkspaceReturn['collection']['value']\n renderer: UseBioTemplatePresetWorkspaceReturn['renderer']['value']\n componentBindingsById: UseBioTemplatePresetWorkspaceReturn['componentBindingsById']['value']\n componentProps: UseBioTemplatePresetWorkspaceReturn['componentProps']['value']\n componentPropsById: UseBioTemplatePresetWorkspaceReturn['componentPropsById']['value']\n componentPropsByComponent: UseBioTemplatePresetWorkspaceReturn['componentPropsByComponent']['value']\n getComponentProps: UseBioTemplatePresetWorkspaceReturn['getComponentProps']\n}\n\ninterface Props {\n /** Workspace returned by useBioTemplatePresetWorkspace(). Use for full manual control. */\n workspace?: UseBioTemplatePresetWorkspaceReturn\n /** Built-in preset id used to create the workspace when workspace is not provided. */\n preset?: TemplatePresetId\n /** Options passed to the internally generated useBioTemplatePresetWorkspace() call. */\n workspaceOptions?: UseBioTemplatePresetWorkspaceOptions\n /** Initial control values for the internally generated preset workspace. */\n initialValues?: BioTemplateControlValues\n /** External control values for the internally generated preset workspace. Supports default v-model. */\n modelValue?: BioTemplateControlValues\n /** External control values for the internally generated preset workspace. Supports v-model:values. */\n values?: BioTemplateControlValues\n /** Label shown in the status banner. Defaults to the humanized preset id. */\n label?: string\n /** Sidebar CSS width. */\n sidebarWidth?: string\n /** AppSidebar visual preset. `analysis` matches the LEAF-style MINT analysis sidebar design language. */\n sidebarVariant?: BioTemplatePresetSidebarVariant\n /** Compact sidebar and preview layout. */\n dense?: boolean\n /** Render preview components in read-only mode. */\n readonly?: boolean\n /** Show save timestamp. */\n showStatus?: boolean\n /** Show cards for templates included in the preset collection. */\n showTemplateSummary?: boolean\n /** Show component binding count. */\n showComponentSummary?: boolean\n saveLabel?: string\n resetLabel?: string\n}\n\nconst emit = defineEmits<{\n /** Emitted when generated preset controls change through the embedded sidebar or reset action. */\n 'update:modelValue': [values: BioTemplateControlValues]\n /** Emitted when generated preset controls change through the embedded sidebar or reset action. */\n 'update:values': [values: BioTemplateControlValues]\n}>()\n\ndefineSlots<{\n default?: (props: BioTemplatePresetWorkspaceSlotProps) => unknown\n}>()\n\nconst props = withDefaults(defineProps<Props>(), {\n workspace: undefined,\n preset: 'wellplate-screen',\n workspaceOptions: () => ({}),\n initialValues: undefined,\n modelValue: undefined,\n values: undefined,\n sidebarWidth: '320px',\n sidebarVariant: 'analysis',\n dense: true,\n readonly: true,\n showStatus: true,\n showTemplateSummary: true,\n showComponentSummary: false,\n saveLabel: 'Save',\n resetLabel: 'Reset defaults',\n})\n\nconst externalValues = computed(() => props.modelValue ?? props.values)\nconst resolvedWorkspaceOptions = computed<UseBioTemplatePresetWorkspaceOptions>(() => {\n if (props.initialValues === undefined && externalValues.value === undefined) return props.workspaceOptions\n\n return {\n ...props.workspaceOptions,\n initialValues: {\n ...(props.workspaceOptions.initialValues ?? {}),\n ...(props.initialValues ?? {}),\n ...(externalValues.value ?? {}),\n },\n }\n})\n\nlet generatedWorkspaceScope: EffectScope | undefined\n\nfunction createGeneratedWorkspace(): UseBioTemplatePresetWorkspaceReturn {\n generatedWorkspaceScope?.stop()\n generatedWorkspaceScope = effectScope()\n return generatedWorkspaceScope.run(() => useBioTemplatePresetWorkspace(props.preset, resolvedWorkspaceOptions.value))!\n}\n\nconst generatedWorkspace = shallowRef<UseBioTemplatePresetWorkspaceReturn>(createGeneratedWorkspace())\nconst resolvedWorkspace = computed(() => props.workspace ?? generatedWorkspace.value)\n\nwatch(\n [() => props.preset, () => props.workspaceOptions, () => props.initialValues],\n () => {\n generatedWorkspace.value = createGeneratedWorkspace()\n },\n { deep: true },\n)\n\nonScopeDispose(() => generatedWorkspaceScope?.stop())\n\nwatch(\n externalValues,\n (values) => {\n if (values === undefined || props.workspace !== undefined) return\n if (recordsEqual(toPlainRecord(generatedWorkspace.value.controlValues.value), values)) return\n generatedWorkspace.value.setControlValues(values)\n },\n { deep: true },\n)\n\nwatch(\n () => generatedWorkspace.value.controlValues.value,\n (values) => {\n if (props.workspace !== undefined) return\n const nextValues = toPlainRecord(values)\n if (externalValues.value !== undefined && recordsEqual(nextValues, externalValues.value)) return\n emit('update:modelValue', nextValues)\n emit('update:values', nextValues)\n },\n { deep: true },\n)\n\nconst presetId = computed(() => resolvedWorkspace.value.presetId)\nconst label = computed(() => props.label ?? unref(resolvedWorkspace.value.presetLabel))\nconst collection = computed(() => unref(resolvedWorkspace.value.collection))\nconst templateIds = computed(() => unref(resolvedWorkspace.value.templateIds))\nconst currentExperimentId = computed(() => unref(resolvedWorkspace.value.currentExperimentId))\nconst hasCurrentExperiment = computed(() => unref(resolvedWorkspace.value.hasCurrentExperiment))\nconst isSaving = computed(() => unref(resolvedWorkspace.value.saving))\nconst error = computed(() => unref(resolvedWorkspace.value.error))\nconst lastSavedAt = computed(() => unref(resolvedWorkspace.value.lastSavedAt))\nconst renderer = computed(() => unref(resolvedWorkspace.value.renderer))\nconst componentBindingsById = computed(() => unref(resolvedWorkspace.value.componentBindingsById))\nconst componentProps = computed(() => unref(resolvedWorkspace.value.componentProps))\nconst componentPropsById = computed(() => unref(resolvedWorkspace.value.componentPropsById))\nconst componentPropsByComponent = computed(() => unref(resolvedWorkspace.value.componentPropsByComponent))\nconst componentPropsCount = computed(() => unref(resolvedWorkspace.value.componentProps).length)\nconst bindings = computed(() => resolvedWorkspace.value.bindings)\n\nfunction toPlainRecord(value: Record<string, unknown>): Record<string, unknown> {\n return toPlain(value) as Record<string, unknown>\n}\n\nfunction toPlain(value: unknown): unknown {\n const raw = toRaw(value)\n if (Array.isArray(raw)) return raw.map(item => toPlain(item))\n if (raw && typeof raw === 'object') {\n return Object.fromEntries(\n Object.entries(raw as Record<string, unknown>).map(([key, item]) => [key, toPlain(item)])\n )\n }\n return raw\n}\n\nfunction recordsEqual(left: Record<string, unknown>, right: Record<string, unknown>): boolean {\n const leftKeys = Object.keys(left)\n const rightKeys = Object.keys(right)\n if (leftKeys.length !== rightKeys.length) return false\n return leftKeys.every(key => valuesEqual(left[key], right[key]))\n}\n\nfunction valuesEqual(left: unknown, right: unknown): boolean {\n const leftRaw = toRaw(left)\n const rightRaw = toRaw(right)\n\n if (Object.is(leftRaw, rightRaw)) return true\n\n if (Array.isArray(leftRaw) || Array.isArray(rightRaw)) {\n if (!Array.isArray(leftRaw) || !Array.isArray(rightRaw)) return false\n if (leftRaw.length !== rightRaw.length) return false\n return leftRaw.every((item, index) => valuesEqual(item, rightRaw[index]))\n }\n\n if (isPlainRecord(leftRaw) || isPlainRecord(rightRaw)) {\n if (!isPlainRecord(leftRaw) || !isPlainRecord(rightRaw)) return false\n return recordsEqual(leftRaw, rightRaw)\n }\n\n return false\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n</script>\n\n<template>\n <div class=\"mint-bio-template-preset-workspace\">\n <div class=\"mint-bio-template-preset-workspace__toolbar\">\n <AlertBox type=\"info\" class=\"mint-bio-template-preset-workspace__status\">\n {{ label }} preset saves to the current experiment design_data.\n </AlertBox>\n\n <div class=\"mint-bio-template-preset-workspace__actions\">\n <BaseButton\n variant=\"secondary\"\n :disabled=\"isSaving\"\n @click=\"resolvedWorkspace.resetToDefaultCollection()\"\n >\n {{ resetLabel }}\n </BaseButton>\n <BaseButton\n variant=\"primary\"\n :loading=\"isSaving\"\n :disabled=\"!hasCurrentExperiment\"\n @click=\"resolvedWorkspace.saveCurrent()\"\n >\n {{ saveLabel }}\n </BaseButton>\n </div>\n </div>\n\n <AlertBox v-if=\"error\" type=\"error\">\n {{ error }}\n </AlertBox>\n\n <p v-if=\"showStatus\" class=\"mint-bio-template-preset-workspace__meta\">\n saved: {{ lastSavedAt?.toLocaleString() ?? 'not saved' }}\n </p>\n\n <div class=\"mint-bio-template-preset-workspace__main\">\n <AppSidebar\n v-bind=\"resolvedWorkspace.sidebar\"\n :variant=\"sidebarVariant\"\n :floating=\"false\"\n :width=\"sidebarWidth\"\n :dense=\"dense\"\n >\n <template #header>\n <p class=\"mint-bio-template-preset-workspace__sidebar-title\">\n Controls\n </p>\n <AppTopBarPillNavInternal\n v-if=\"resolvedWorkspace.pillNav.items.length > 1\"\n class=\"mint-bio-template-preset-workspace__view-nav\"\n :items=\"resolvedWorkspace.pillNav.items\"\n :current-item-id=\"resolvedWorkspace.pillNav.currentItemId\"\n @select=\"resolvedWorkspace.pillNav.onSelect\"\n />\n <p class=\"mint-bio-template-preset-workspace__sidebar-subtitle\">\n {{ presetId }}\n </p>\n </template>\n </AppSidebar>\n\n <slot\n :workspace=\"resolvedWorkspace\"\n :bindings=\"bindings\"\n :collection=\"collection\"\n :renderer=\"renderer\"\n :component-bindings-by-id=\"componentBindingsById\"\n :component-props=\"componentProps\"\n :component-props-by-id=\"componentPropsById\"\n :component-props-by-component=\"componentPropsByComponent\"\n :get-component-props=\"resolvedWorkspace.getComponentProps\"\n >\n <BioTemplateRenderer\n v-bind=\"renderer\"\n :dense=\"dense\"\n :readonly=\"readonly\"\n />\n </slot>\n </div>\n\n <div\n v-if=\"showTemplateSummary\"\n class=\"mint-bio-template-preset-workspace__templates\"\n >\n <div\n v-for=\"templateId in templateIds\"\n :key=\"templateId\"\n class=\"mint-bio-template-preset-workspace__template\"\n >\n <p class=\"mint-bio-template-preset-workspace__template-id\">\n {{ templateId }}\n </p>\n <p class=\"mint-bio-template-preset-workspace__template-meta\">\n {{ presetId }} - Experiment {{ currentExperimentId ?? 'not selected' }}\n </p>\n </div>\n </div>\n\n <p v-if=\"showComponentSummary\" class=\"mint-bio-template-preset-workspace__meta\">\n component prop bindings: {{ componentPropsCount }}\n </p>\n </div>\n</template>\n\n<style scoped>\n.mint-bio-template-preset-workspace {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n min-width: 0;\n}\n\n.mint-bio-template-preset-workspace__toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n}\n\n.mint-bio-template-preset-workspace__status {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.mint-bio-template-preset-workspace__actions {\n display: flex;\n flex: 0 0 auto;\n gap: 0.5rem;\n}\n\n.mint-bio-template-preset-workspace__meta,\n.mint-bio-template-preset-workspace__sidebar-subtitle,\n.mint-bio-template-preset-workspace__template-meta {\n margin: 0;\n color: var(--text-secondary, #64748b);\n font-family: var(--font-mono, 'Fira Code', monospace);\n font-size: 0.75rem;\n}\n\n.mint-bio-template-preset-workspace__main {\n display: grid;\n grid-template-columns: minmax(16rem, max-content) minmax(0, 1fr);\n gap: 1rem;\n align-items: start;\n min-width: 0;\n}\n\n.mint-bio-template-preset-workspace__sidebar-title {\n margin: 0;\n color: var(--text-primary, #0f172a);\n font-size: 0.875rem;\n font-weight: 600;\n}\n\n.mint-bio-template-preset-workspace__view-nav {\n margin-top: 0.5rem;\n}\n\n.mint-bio-template-preset-workspace__templates {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));\n gap: 0.75rem;\n}\n\n.mint-bio-template-preset-workspace__template {\n min-width: 0;\n padding: 1rem;\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 0.5rem;\n background: var(--bg-primary, #ffffff);\n}\n\n.mint-bio-template-preset-workspace__template-id {\n margin: 0;\n color: var(--text-primary, #0f172a);\n font-family: var(--font-mono, 'Fira Code', monospace);\n font-size: 0.875rem;\n}\n\n@media (max-width: 48rem) {\n .mint-bio-template-preset-workspace__toolbar,\n .mint-bio-template-preset-workspace__actions {\n align-items: stretch;\n flex-direction: column;\n }\n\n .mint-bio-template-preset-workspace__main {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n</style>\n","<script setup lang=\"ts\">\n/** Complete editable biology template preset workspace for WellPlate/DoseCalculator, LC-MS, qPCR, generated controls, preview components, and current-experiment save wiring. */\nimport { computed, effectScope, onScopeDispose, shallowRef, toRaw, unref, watch, type EffectScope } from 'vue'\nimport type { BioTemplateControlValues, TemplatePresetId } from '../templates'\nimport type {\n UseBioTemplatePresetWorkspaceOptions,\n UseBioTemplatePresetWorkspaceReturn,\n} from '../composables/useBioTemplatePresetWorkspace'\nimport { useBioTemplatePresetWorkspace } from '../composables/useBioTemplatePresetWorkspace'\nimport AlertBox from './AlertBox.vue'\nimport AppSidebar from './AppSidebar.vue'\nimport BaseButton from './BaseButton.vue'\nimport BioTemplateRenderer from './BioTemplateRenderer.vue'\nimport AppTopBarPillNavInternal from './internal/AppTopBarPillNavInternal.vue'\n\ntype BioTemplatePresetSidebarVariant = 'default' | 'analysis'\n\ninterface BioTemplatePresetWorkspaceSlotProps {\n workspace: UseBioTemplatePresetWorkspaceReturn\n bindings: UseBioTemplatePresetWorkspaceReturn['bindings']\n collection: UseBioTemplatePresetWorkspaceReturn['collection']['value']\n renderer: UseBioTemplatePresetWorkspaceReturn['renderer']['value']\n componentBindingsById: UseBioTemplatePresetWorkspaceReturn['componentBindingsById']['value']\n componentProps: UseBioTemplatePresetWorkspaceReturn['componentProps']['value']\n componentPropsById: UseBioTemplatePresetWorkspaceReturn['componentPropsById']['value']\n componentPropsByComponent: UseBioTemplatePresetWorkspaceReturn['componentPropsByComponent']['value']\n getComponentProps: UseBioTemplatePresetWorkspaceReturn['getComponentProps']\n}\n\ninterface Props {\n /** Workspace returned by useBioTemplatePresetWorkspace(). Use for full manual control. */\n workspace?: UseBioTemplatePresetWorkspaceReturn\n /** Built-in preset id used to create the workspace when workspace is not provided. */\n preset?: TemplatePresetId\n /** Options passed to the internally generated useBioTemplatePresetWorkspace() call. */\n workspaceOptions?: UseBioTemplatePresetWorkspaceOptions\n /** Initial control values for the internally generated preset workspace. */\n initialValues?: BioTemplateControlValues\n /** External control values for the internally generated preset workspace. Supports default v-model. */\n modelValue?: BioTemplateControlValues\n /** External control values for the internally generated preset workspace. Supports v-model:values. */\n values?: BioTemplateControlValues\n /** Label shown in the status banner. Defaults to the humanized preset id. */\n label?: string\n /** Sidebar CSS width. */\n sidebarWidth?: string\n /** AppSidebar visual preset. `analysis` matches the LEAF-style MINT analysis sidebar design language. */\n sidebarVariant?: BioTemplatePresetSidebarVariant\n /** Compact sidebar and preview layout. */\n dense?: boolean\n /** Render preview components in read-only mode. */\n readonly?: boolean\n /** Show save timestamp. */\n showStatus?: boolean\n /** Show cards for templates included in the preset collection. */\n showTemplateSummary?: boolean\n /** Show component binding count. */\n showComponentSummary?: boolean\n saveLabel?: string\n resetLabel?: string\n}\n\nconst emit = defineEmits<{\n /** Emitted when generated preset controls change through the embedded sidebar or reset action. */\n 'update:modelValue': [values: BioTemplateControlValues]\n /** Emitted when generated preset controls change through the embedded sidebar or reset action. */\n 'update:values': [values: BioTemplateControlValues]\n}>()\n\ndefineSlots<{\n default?: (props: BioTemplatePresetWorkspaceSlotProps) => unknown\n}>()\n\nconst props = withDefaults(defineProps<Props>(), {\n workspace: undefined,\n preset: 'wellplate-screen',\n workspaceOptions: () => ({}),\n initialValues: undefined,\n modelValue: undefined,\n values: undefined,\n sidebarWidth: '320px',\n sidebarVariant: 'analysis',\n dense: true,\n readonly: true,\n showStatus: true,\n showTemplateSummary: true,\n showComponentSummary: false,\n saveLabel: 'Save',\n resetLabel: 'Reset defaults',\n})\n\nconst externalValues = computed(() => props.modelValue ?? props.values)\nconst resolvedWorkspaceOptions = computed<UseBioTemplatePresetWorkspaceOptions>(() => {\n if (props.initialValues === undefined && externalValues.value === undefined) return props.workspaceOptions\n\n return {\n ...props.workspaceOptions,\n initialValues: {\n ...(props.workspaceOptions.initialValues ?? {}),\n ...(props.initialValues ?? {}),\n ...(externalValues.value ?? {}),\n },\n }\n})\n\nlet generatedWorkspaceScope: EffectScope | undefined\n\nfunction createGeneratedWorkspace(): UseBioTemplatePresetWorkspaceReturn {\n generatedWorkspaceScope?.stop()\n generatedWorkspaceScope = effectScope()\n return generatedWorkspaceScope.run(() => useBioTemplatePresetWorkspace(props.preset, resolvedWorkspaceOptions.value))!\n}\n\nconst generatedWorkspace = shallowRef<UseBioTemplatePresetWorkspaceReturn>(createGeneratedWorkspace())\nconst resolvedWorkspace = computed(() => props.workspace ?? generatedWorkspace.value)\n\nwatch(\n [() => props.preset, () => props.workspaceOptions, () => props.initialValues],\n () => {\n generatedWorkspace.value = createGeneratedWorkspace()\n },\n { deep: true },\n)\n\nonScopeDispose(() => generatedWorkspaceScope?.stop())\n\nwatch(\n externalValues,\n (values) => {\n if (values === undefined || props.workspace !== undefined) return\n if (recordsEqual(toPlainRecord(generatedWorkspace.value.controlValues.value), values)) return\n generatedWorkspace.value.setControlValues(values)\n },\n { deep: true },\n)\n\nwatch(\n () => generatedWorkspace.value.controlValues.value,\n (values) => {\n if (props.workspace !== undefined) return\n const nextValues = toPlainRecord(values)\n if (externalValues.value !== undefined && recordsEqual(nextValues, externalValues.value)) return\n emit('update:modelValue', nextValues)\n emit('update:values', nextValues)\n },\n { deep: true },\n)\n\nconst presetId = computed(() => resolvedWorkspace.value.presetId)\nconst label = computed(() => props.label ?? unref(resolvedWorkspace.value.presetLabel))\nconst collection = computed(() => unref(resolvedWorkspace.value.collection))\nconst templateIds = computed(() => unref(resolvedWorkspace.value.templateIds))\nconst currentExperimentId = computed(() => unref(resolvedWorkspace.value.currentExperimentId))\nconst hasCurrentExperiment = computed(() => unref(resolvedWorkspace.value.hasCurrentExperiment))\nconst isSaving = computed(() => unref(resolvedWorkspace.value.saving))\nconst error = computed(() => unref(resolvedWorkspace.value.error))\nconst lastSavedAt = computed(() => unref(resolvedWorkspace.value.lastSavedAt))\nconst renderer = computed(() => unref(resolvedWorkspace.value.renderer))\nconst componentBindingsById = computed(() => unref(resolvedWorkspace.value.componentBindingsById))\nconst componentProps = computed(() => unref(resolvedWorkspace.value.componentProps))\nconst componentPropsById = computed(() => unref(resolvedWorkspace.value.componentPropsById))\nconst componentPropsByComponent = computed(() => unref(resolvedWorkspace.value.componentPropsByComponent))\nconst componentPropsCount = computed(() => unref(resolvedWorkspace.value.componentProps).length)\nconst bindings = computed(() => resolvedWorkspace.value.bindings)\n\nfunction toPlainRecord(value: Record<string, unknown>): Record<string, unknown> {\n return toPlain(value) as Record<string, unknown>\n}\n\nfunction toPlain(value: unknown): unknown {\n const raw = toRaw(value)\n if (Array.isArray(raw)) return raw.map(item => toPlain(item))\n if (raw && typeof raw === 'object') {\n return Object.fromEntries(\n Object.entries(raw as Record<string, unknown>).map(([key, item]) => [key, toPlain(item)])\n )\n }\n return raw\n}\n\nfunction recordsEqual(left: Record<string, unknown>, right: Record<string, unknown>): boolean {\n const leftKeys = Object.keys(left)\n const rightKeys = Object.keys(right)\n if (leftKeys.length !== rightKeys.length) return false\n return leftKeys.every(key => valuesEqual(left[key], right[key]))\n}\n\nfunction valuesEqual(left: unknown, right: unknown): boolean {\n const leftRaw = toRaw(left)\n const rightRaw = toRaw(right)\n\n if (Object.is(leftRaw, rightRaw)) return true\n\n if (Array.isArray(leftRaw) || Array.isArray(rightRaw)) {\n if (!Array.isArray(leftRaw) || !Array.isArray(rightRaw)) return false\n if (leftRaw.length !== rightRaw.length) return false\n return leftRaw.every((item, index) => valuesEqual(item, rightRaw[index]))\n }\n\n if (isPlainRecord(leftRaw) || isPlainRecord(rightRaw)) {\n if (!isPlainRecord(leftRaw) || !isPlainRecord(rightRaw)) return false\n return recordsEqual(leftRaw, rightRaw)\n }\n\n return false\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n</script>\n\n<template>\n <div class=\"mint-bio-template-preset-workspace\">\n <div class=\"mint-bio-template-preset-workspace__toolbar\">\n <AlertBox type=\"info\" class=\"mint-bio-template-preset-workspace__status\">\n {{ label }} preset saves to the current experiment design_data.\n </AlertBox>\n\n <div class=\"mint-bio-template-preset-workspace__actions\">\n <BaseButton\n variant=\"secondary\"\n :disabled=\"isSaving\"\n @click=\"resolvedWorkspace.resetToDefaultCollection()\"\n >\n {{ resetLabel }}\n </BaseButton>\n <BaseButton\n variant=\"primary\"\n :loading=\"isSaving\"\n :disabled=\"!hasCurrentExperiment\"\n @click=\"resolvedWorkspace.saveCurrent()\"\n >\n {{ saveLabel }}\n </BaseButton>\n </div>\n </div>\n\n <AlertBox v-if=\"error\" type=\"error\">\n {{ error }}\n </AlertBox>\n\n <p v-if=\"showStatus\" class=\"mint-bio-template-preset-workspace__meta\">\n saved: {{ lastSavedAt?.toLocaleString() ?? 'not saved' }}\n </p>\n\n <div class=\"mint-bio-template-preset-workspace__main\">\n <AppSidebar\n v-bind=\"resolvedWorkspace.sidebar\"\n :variant=\"sidebarVariant\"\n :floating=\"false\"\n :width=\"sidebarWidth\"\n :dense=\"dense\"\n >\n <template #header>\n <p class=\"mint-bio-template-preset-workspace__sidebar-title\">\n Controls\n </p>\n <AppTopBarPillNavInternal\n v-if=\"resolvedWorkspace.pillNav.items.length > 1\"\n class=\"mint-bio-template-preset-workspace__view-nav\"\n :items=\"resolvedWorkspace.pillNav.items\"\n :current-item-id=\"resolvedWorkspace.pillNav.currentItemId\"\n @select=\"resolvedWorkspace.pillNav.onSelect\"\n />\n <p class=\"mint-bio-template-preset-workspace__sidebar-subtitle\">\n {{ presetId }}\n </p>\n </template>\n </AppSidebar>\n\n <slot\n :workspace=\"resolvedWorkspace\"\n :bindings=\"bindings\"\n :collection=\"collection\"\n :renderer=\"renderer\"\n :component-bindings-by-id=\"componentBindingsById\"\n :component-props=\"componentProps\"\n :component-props-by-id=\"componentPropsById\"\n :component-props-by-component=\"componentPropsByComponent\"\n :get-component-props=\"resolvedWorkspace.getComponentProps\"\n >\n <BioTemplateRenderer\n v-bind=\"renderer\"\n :dense=\"dense\"\n :readonly=\"readonly\"\n />\n </slot>\n </div>\n\n <div\n v-if=\"showTemplateSummary\"\n class=\"mint-bio-template-preset-workspace__templates\"\n >\n <div\n v-for=\"templateId in templateIds\"\n :key=\"templateId\"\n class=\"mint-bio-template-preset-workspace__template\"\n >\n <p class=\"mint-bio-template-preset-workspace__template-id\">\n {{ templateId }}\n </p>\n <p class=\"mint-bio-template-preset-workspace__template-meta\">\n {{ presetId }} - Experiment {{ currentExperimentId ?? 'not selected' }}\n </p>\n </div>\n </div>\n\n <p v-if=\"showComponentSummary\" class=\"mint-bio-template-preset-workspace__meta\">\n component prop bindings: {{ componentPropsCount }}\n </p>\n </div>\n</template>\n\n<style scoped>\n.mint-bio-template-preset-workspace {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n min-width: 0;\n}\n\n.mint-bio-template-preset-workspace__toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n}\n\n.mint-bio-template-preset-workspace__status {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.mint-bio-template-preset-workspace__actions {\n display: flex;\n flex: 0 0 auto;\n gap: 0.5rem;\n}\n\n.mint-bio-template-preset-workspace__meta,\n.mint-bio-template-preset-workspace__sidebar-subtitle,\n.mint-bio-template-preset-workspace__template-meta {\n margin: 0;\n color: var(--text-secondary, #64748b);\n font-family: var(--font-mono, 'Fira Code', monospace);\n font-size: 0.75rem;\n}\n\n.mint-bio-template-preset-workspace__main {\n display: grid;\n grid-template-columns: minmax(16rem, max-content) minmax(0, 1fr);\n gap: 1rem;\n align-items: start;\n min-width: 0;\n}\n\n.mint-bio-template-preset-workspace__sidebar-title {\n margin: 0;\n color: var(--text-primary, #0f172a);\n font-size: 0.875rem;\n font-weight: 600;\n}\n\n.mint-bio-template-preset-workspace__view-nav {\n margin-top: 0.5rem;\n}\n\n.mint-bio-template-preset-workspace__templates {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));\n gap: 0.75rem;\n}\n\n.mint-bio-template-preset-workspace__template {\n min-width: 0;\n padding: 1rem;\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 0.5rem;\n background: var(--bg-primary, #ffffff);\n}\n\n.mint-bio-template-preset-workspace__template-id {\n margin: 0;\n color: var(--text-primary, #0f172a);\n font-family: var(--font-mono, 'Fira Code', monospace);\n font-size: 0.875rem;\n}\n\n@media (max-width: 48rem) {\n .mint-bio-template-preset-workspace__toolbar,\n .mint-bio-template-preset-workspace__actions {\n align-items: stretch;\n flex-direction: column;\n }\n\n .mint-bio-template-preset-workspace__main {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n</style>\n","<script setup lang=\"ts\">\n/** Multi-rack editor for managing collections of well-plate racks with add/remove/reorder controls and per-well editing. */\nimport { ref, computed, watch } from 'vue'\nimport type { Rack, SlotPosition, WellPlateFormat, WellPlateSize, WellEditData, Well, WellEditField, WellSampleDropData, RackSampleDropMapper } from '../types'\nimport { useRackEditor } from '../composables/useRackEditor'\nimport WellPlate from './WellPlate.vue'\n\ninterface Props {\n modelValue?: Rack[]\n activeRackId?: string\n maxRacks?: number\n minRacks?: number\n allowReorder?: boolean\n editable?: boolean\n readonly?: boolean\n wellPlateSize?: WellPlateSize\n showLegend?: boolean\n showBadges?: boolean\n allowSampleDrop?: boolean\n sampleDropMapper?: RackSampleDropMapper\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: undefined,\n activeRackId: undefined,\n maxRacks: 10,\n minRacks: 1,\n allowReorder: true,\n editable: true,\n readonly: false,\n wellPlateSize: 'md',\n showLegend: true,\n showBadges: true,\n allowSampleDrop: false,\n sampleDropMapper: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [racks: Rack[]]\n 'update:activeRackId': [rackId: string]\n 'rack-add': [rack: Rack]\n 'rack-remove': [rackId: string]\n 'rack-reorder': [rackIds: string[]]\n 'well-edit': [rackId: string, wellId: string, data: WellEditData]\n 'sample-drop': [rackId: string, wellId: string, data: WellSampleDropData, event: DragEvent]\n}>()\n\ninterface RackWellEditorSlotProps {\n rack?: Rack\n rackId: string\n wellId: string\n wellData?: Partial<Well>\n editFields: WellEditField[]\n defaultInjectionVolume: number\n position: { x: number; y: number }\n save: (data: WellEditData) => void\n clear: () => void\n close: () => void\n}\n\ndefineSlots<{\n 'well-editor'?: (props: RackWellEditorSlotProps) => unknown\n}>()\n\nconst editor = useRackEditor(props.modelValue, {\n maxRacks: props.maxRacks,\n minRacks: props.minRacks,\n})\n\n// Sync external modelValue -> internal racks\nwatch(\n () => props.modelValue,\n (newRacks) => {\n if (newRacks && JSON.stringify(newRacks) !== JSON.stringify(editor.racks.value)) {\n editor.racks.value = newRacks.map(r => ({ ...r }))\n }\n },\n)\n\n// Sync external activeRackId -> internal\nwatch(\n () => props.activeRackId,\n (id) => {\n if (id && id !== editor.activeRackId.value) {\n editor.setActiveRack(id)\n }\n },\n)\n\n// Sync internal racks -> external modelValue\nwatch(\n editor.racks,\n (newRacks) => {\n emit('update:modelValue', newRacks)\n },\n { deep: true },\n)\n\n// Sync internal activeRackId -> external\nwatch(\n editor.activeRackId,\n (id) => {\n emit('update:activeRackId', id)\n },\n)\n\nconst SLOT_COLORS: Record<SlotPosition, string> = {\n R: '#ef4444',\n G: '#22c55e',\n B: '#3b82f6',\n Y: '#eab308',\n}\n\nconst SLOT_OPTIONS: SlotPosition[] = ['R', 'G', 'B', 'Y']\n\nconst FORMAT_OPTIONS: { value: WellPlateFormat; label: string }[] = [\n { value: 54, label: '54' },\n { value: 96, label: '96' },\n]\n\n// Drag state for rack reordering\nconst draggingRackId = ref<string | null>(null)\nconst dragOverRackId = ref<string | null>(null)\n\nfunction handleAddRack() {\n const rack = editor.addRack()\n emit('rack-add', rack)\n}\n\nfunction handleRemoveRack(rackId: string) {\n editor.removeRack(rackId)\n emit('rack-remove', rackId)\n}\n\nfunction handleTabClick(rackId: string) {\n editor.setActiveRack(rackId)\n}\n\n// Rack drag handlers\nfunction handleRackDragStart(event: DragEvent, rackId: string) {\n if (!props.allowReorder) return\n draggingRackId.value = rackId\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', rackId)\n }\n}\n\nfunction handleRackDragOver(event: DragEvent, rackId: string) {\n event.preventDefault()\n if (draggingRackId.value && draggingRackId.value !== rackId) {\n dragOverRackId.value = rackId\n }\n}\n\nfunction handleRackDragLeave() {\n dragOverRackId.value = null\n}\n\nfunction handleRackDrop(event: DragEvent, targetRackId: string) {\n event.preventDefault()\n if (!draggingRackId.value || draggingRackId.value === targetRackId) {\n clearDragState()\n return\n }\n\n const fromIndex = editor.racks.value.findIndex(r => r.id === draggingRackId.value)\n const toIndex = editor.racks.value.findIndex(r => r.id === targetRackId)\n\n if (fromIndex !== -1 && toIndex !== -1) {\n editor.reorderRacks(fromIndex, toIndex)\n emit('rack-reorder', editor.racks.value.map(r => r.id))\n }\n\n clearDragState()\n}\n\nfunction handleRackDragEnd() {\n clearDragState()\n}\n\nfunction clearDragState() {\n draggingRackId.value = null\n dragOverRackId.value = null\n}\n\n// Toolbar actions\nfunction handleFormatChange(format: WellPlateFormat) {\n if (!editor.activeRack.value) return\n editor.updateRack(editor.activeRack.value.id, { format })\n}\n\nfunction handleSlotChange(slot: SlotPosition) {\n if (!editor.activeRack.value) return\n editor.updateRack(editor.activeRack.value.id, { slot })\n}\n\nfunction handleClearAll() {\n if (!editor.activeRack.value) return\n editor.clearAllWells(editor.activeRack.value.id)\n}\n\nfunction handleFillSeries() {\n if (!editor.activeRack.value) return\n editor.fillSeries(editor.activeRack.value.id)\n}\n\n// Well edit from WellPlate\nfunction handleWellEdit(wellId: string, data: WellEditData) {\n if (!editor.activeRack.value) return\n const rackId = editor.activeRack.value.id\n\n const wellData: Partial<Well> = {\n id: wellId,\n state: data.label ? 'filled' : 'empty',\n sampleType: data.sampleType || undefined,\n metadata: {\n label: data.label,\n injectionVolume: data.injectionVolume,\n injectionCount: data.injectionCount,\n customMethod: data.customMethod || null,\n },\n }\n\n if (data.label) {\n editor.setWellData(rackId, wellId, wellData)\n } else {\n editor.clearWell(rackId, wellId)\n }\n\n emit('well-edit', rackId, wellId, data)\n}\n\nfunction handleWellClear(wellId: string) {\n if (!editor.activeRack.value) return\n editor.clearWell(editor.activeRack.value.id, wellId)\n}\n\nfunction handleSampleDrop(wellId: string, data: WellSampleDropData, event: DragEvent) {\n const rack = editor.activeRack.value\n if (!rack) return\n\n emit('sample-drop', rack.id, wellId, data, event)\n\n const editData = props.sampleDropMapper?.(data, {\n rack,\n rackId: rack.id,\n wellId,\n event,\n }) ?? defaultSampleDropEditData(wellId, data, rack)\n\n if (!editData) return\n handleWellEdit(wellId, { ...editData, wellId })\n}\n\nfunction defaultSampleDropEditData(wellId: string, data: WellSampleDropData, rack: Rack): WellEditData | null {\n const label = data.label ?? data.sampleName ?? data.id ?? ''\n if (!label.trim()) return null\n\n return {\n wellId,\n label: label.trim(),\n sampleType: data.sampleType ?? 'sample',\n injectionVolume: data.injectionVolume ?? rack.injectionVolume,\n injectionCount: data.injectionCount ?? 1,\n customMethod: data.customMethod ?? '',\n }\n}\n\n// Computed well count per rack\nfunction getWellCount(rack: Rack): number {\n return Object.keys(rack.wells).length\n}\n\nconst activeRackWells = computed(() => editor.activeRack.value?.wells ?? {})\n</script>\n\n<template>\n <div :class=\"['mint-rack-editor', { 'mint-rack-editor--readonly': readonly }]\">\n <!-- Rack tabs -->\n <div class=\"mint-rack-editor__tabs\">\n <div class=\"mint-rack-editor__tabs-inner\">\n <button\n v-for=\"rack in editor.racks.value\"\n :key=\"rack.id\"\n :draggable=\"allowReorder && !readonly\"\n :class=\"[\n 'mint-rack-editor__tab',\n {\n 'mint-rack-editor__tab--active': editor.activeRackId.value === rack.id,\n 'mint-rack-editor__tab--dragging': draggingRackId === rack.id,\n 'mint-rack-editor__tab--drag-over': dragOverRackId === rack.id,\n },\n ]\"\n @click=\"handleTabClick(rack.id)\"\n @dragstart=\"handleRackDragStart($event, rack.id)\"\n @dragover=\"handleRackDragOver($event, rack.id)\"\n @dragleave=\"handleRackDragLeave\"\n @drop=\"handleRackDrop($event, rack.id)\"\n @dragend=\"handleRackDragEnd\"\n >\n <span\n class=\"mint-rack-editor__slot-dot\"\n :style=\"{ backgroundColor: SLOT_COLORS[rack.slot] }\"\n />\n {{ rack.name }}\n <span\n v-if=\"getWellCount(rack) > 0\"\n :class=\"[\n 'mint-rack-editor__count',\n editor.activeRackId.value === rack.id ? 'mint-rack-editor__count--active' : 'mint-rack-editor__count--inactive',\n ]\"\n >\n {{ getWellCount(rack) }}\n </span>\n <!-- Remove button -->\n <button\n v-if=\"editor.racks.value.length > minRacks && !readonly\"\n class=\"mint-rack-editor__tab-remove\"\n @click.stop=\"handleRemoveRack(rack.id)\"\n >\n ×\n </button>\n </button>\n </div>\n\n <!-- Add rack button -->\n <button\n v-if=\"editor.racks.value.length < maxRacks && !readonly\"\n class=\"mint-rack-editor__add-btn\"\n @click=\"handleAddRack\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M5 12h14\" /><path d=\"M12 5v14\" />\n </svg>\n Add Rack\n </button>\n </div>\n\n <!-- Toolbar for active rack -->\n <div v-if=\"editor.activeRack.value && !readonly\" class=\"mint-rack-editor__toolbar\">\n <div class=\"mint-rack-editor__toolbar-group\">\n <!-- Format selector -->\n <div class=\"mint-rack-editor__toolbar-section\">\n <span class=\"mint-rack-editor__toolbar-label\">Plate</span>\n <div class=\"mint-rack-editor__format-btns\">\n <button\n v-for=\"opt in FORMAT_OPTIONS\"\n :key=\"opt.value\"\n :class=\"[\n 'mint-rack-editor__format-btn',\n editor.activeRack.value.format === opt.value ? 'mint-rack-editor__format-btn--active' : 'mint-rack-editor__format-btn--inactive',\n ]\"\n @click=\"handleFormatChange(opt.value)\"\n >\n {{ opt.label }}\n </button>\n </div>\n </div>\n\n <div class=\"mint-rack-editor__toolbar-divider\" />\n\n <!-- Slot selector -->\n <div class=\"mint-rack-editor__toolbar-section\">\n <span class=\"mint-rack-editor__toolbar-label\">Slot</span>\n <div class=\"mint-rack-editor__slot-btns\">\n <button\n v-for=\"s in SLOT_OPTIONS\"\n :key=\"s\"\n class=\"mint-rack-editor__slot-btn\"\n :style=\"{\n backgroundColor: editor.activeRack.value.slot === s ? SLOT_COLORS[s] : 'var(--bg-secondary)',\n color: editor.activeRack.value.slot === s ? 'white' : SLOT_COLORS[s],\n borderColor: editor.activeRack.value.slot === s ? 'transparent' : `${SLOT_COLORS[s]}40`,\n boxShadow: editor.activeRack.value.slot === s ? '0 2px 4px rgba(0,0,0,0.15)' : 'none',\n }\"\n @click=\"handleSlotChange(s)\"\n >\n {{ s }}\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"mint-rack-editor__toolbar-spacer\" />\n\n <!-- Actions -->\n <button class=\"mint-rack-editor__action-btn\" @click=\"handleClearAll\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10 11v6\" /><path d=\"M14 11v6\" /><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6\" /><path d=\"M3 6h18\" /><path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" />\n </svg>\n Clear\n </button>\n <button class=\"mint-rack-editor__action-btn\" @click=\"handleFillSeries\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" /><path d=\"M3 9h18\" /><path d=\"M3 15h18\" /><path d=\"M9 3v18\" /><path d=\"M15 3v18\" />\n </svg>\n Fill Series\n </button>\n </div>\n\n <!-- Plate view -->\n <div v-if=\"editor.activeRack.value\" class=\"mint-rack-editor__plate\">\n <WellPlate\n :format=\"editor.activeRack.value.format\"\n :wells=\"activeRackWells\"\n :editable=\"editable && !readonly\"\n :show-well-labels=\"true\"\n :show-badges=\"showBadges\"\n :show-legend=\"showLegend\"\n :default-injection-volume=\"editor.activeRack.value.injectionVolume\"\n :readonly=\"readonly\"\n :size=\"wellPlateSize\"\n :show-sample-type-indicator=\"true\"\n :allow-sample-drop=\"allowSampleDrop && !readonly\"\n @well-edit=\"handleWellEdit\"\n @well-clear=\"handleWellClear\"\n @sample-drop=\"handleSampleDrop\"\n >\n <template v-if=\"$slots['well-editor']\" #well-editor=\"slotProps\">\n <slot\n name=\"well-editor\"\n :rack=\"editor.activeRack.value\"\n :rack-id=\"editor.activeRack.value?.id ?? ''\"\n :well-id=\"slotProps.wellId\"\n :well-data=\"slotProps.wellData\"\n :edit-fields=\"slotProps.editFields\"\n :default-injection-volume=\"slotProps.defaultInjectionVolume\"\n :position=\"slotProps.position\"\n :save=\"slotProps.save\"\n :clear=\"slotProps.clear\"\n :close=\"slotProps.close\"\n />\n </template>\n </WellPlate>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/rack-editor.css';\n</style>\n","<script setup lang=\"ts\">\n/** Multi-rack editor for managing collections of well-plate racks with add/remove/reorder controls and per-well editing. */\nimport { ref, computed, watch } from 'vue'\nimport type { Rack, SlotPosition, WellPlateFormat, WellPlateSize, WellEditData, Well, WellEditField, WellSampleDropData, RackSampleDropMapper } from '../types'\nimport { useRackEditor } from '../composables/useRackEditor'\nimport WellPlate from './WellPlate.vue'\n\ninterface Props {\n modelValue?: Rack[]\n activeRackId?: string\n maxRacks?: number\n minRacks?: number\n allowReorder?: boolean\n editable?: boolean\n readonly?: boolean\n wellPlateSize?: WellPlateSize\n showLegend?: boolean\n showBadges?: boolean\n allowSampleDrop?: boolean\n sampleDropMapper?: RackSampleDropMapper\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: undefined,\n activeRackId: undefined,\n maxRacks: 10,\n minRacks: 1,\n allowReorder: true,\n editable: true,\n readonly: false,\n wellPlateSize: 'md',\n showLegend: true,\n showBadges: true,\n allowSampleDrop: false,\n sampleDropMapper: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [racks: Rack[]]\n 'update:activeRackId': [rackId: string]\n 'rack-add': [rack: Rack]\n 'rack-remove': [rackId: string]\n 'rack-reorder': [rackIds: string[]]\n 'well-edit': [rackId: string, wellId: string, data: WellEditData]\n 'sample-drop': [rackId: string, wellId: string, data: WellSampleDropData, event: DragEvent]\n}>()\n\ninterface RackWellEditorSlotProps {\n rack?: Rack\n rackId: string\n wellId: string\n wellData?: Partial<Well>\n editFields: WellEditField[]\n defaultInjectionVolume: number\n position: { x: number; y: number }\n save: (data: WellEditData) => void\n clear: () => void\n close: () => void\n}\n\ndefineSlots<{\n 'well-editor'?: (props: RackWellEditorSlotProps) => unknown\n}>()\n\nconst editor = useRackEditor(props.modelValue, {\n maxRacks: props.maxRacks,\n minRacks: props.minRacks,\n})\n\n// Sync external modelValue -> internal racks\nwatch(\n () => props.modelValue,\n (newRacks) => {\n if (newRacks && JSON.stringify(newRacks) !== JSON.stringify(editor.racks.value)) {\n editor.racks.value = newRacks.map(r => ({ ...r }))\n }\n },\n)\n\n// Sync external activeRackId -> internal\nwatch(\n () => props.activeRackId,\n (id) => {\n if (id && id !== editor.activeRackId.value) {\n editor.setActiveRack(id)\n }\n },\n)\n\n// Sync internal racks -> external modelValue\nwatch(\n editor.racks,\n (newRacks) => {\n emit('update:modelValue', newRacks)\n },\n { deep: true },\n)\n\n// Sync internal activeRackId -> external\nwatch(\n editor.activeRackId,\n (id) => {\n emit('update:activeRackId', id)\n },\n)\n\nconst SLOT_COLORS: Record<SlotPosition, string> = {\n R: '#ef4444',\n G: '#22c55e',\n B: '#3b82f6',\n Y: '#eab308',\n}\n\nconst SLOT_OPTIONS: SlotPosition[] = ['R', 'G', 'B', 'Y']\n\nconst FORMAT_OPTIONS: { value: WellPlateFormat; label: string }[] = [\n { value: 54, label: '54' },\n { value: 96, label: '96' },\n]\n\n// Drag state for rack reordering\nconst draggingRackId = ref<string | null>(null)\nconst dragOverRackId = ref<string | null>(null)\n\nfunction handleAddRack() {\n const rack = editor.addRack()\n emit('rack-add', rack)\n}\n\nfunction handleRemoveRack(rackId: string) {\n editor.removeRack(rackId)\n emit('rack-remove', rackId)\n}\n\nfunction handleTabClick(rackId: string) {\n editor.setActiveRack(rackId)\n}\n\n// Rack drag handlers\nfunction handleRackDragStart(event: DragEvent, rackId: string) {\n if (!props.allowReorder) return\n draggingRackId.value = rackId\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', rackId)\n }\n}\n\nfunction handleRackDragOver(event: DragEvent, rackId: string) {\n event.preventDefault()\n if (draggingRackId.value && draggingRackId.value !== rackId) {\n dragOverRackId.value = rackId\n }\n}\n\nfunction handleRackDragLeave() {\n dragOverRackId.value = null\n}\n\nfunction handleRackDrop(event: DragEvent, targetRackId: string) {\n event.preventDefault()\n if (!draggingRackId.value || draggingRackId.value === targetRackId) {\n clearDragState()\n return\n }\n\n const fromIndex = editor.racks.value.findIndex(r => r.id === draggingRackId.value)\n const toIndex = editor.racks.value.findIndex(r => r.id === targetRackId)\n\n if (fromIndex !== -1 && toIndex !== -1) {\n editor.reorderRacks(fromIndex, toIndex)\n emit('rack-reorder', editor.racks.value.map(r => r.id))\n }\n\n clearDragState()\n}\n\nfunction handleRackDragEnd() {\n clearDragState()\n}\n\nfunction clearDragState() {\n draggingRackId.value = null\n dragOverRackId.value = null\n}\n\n// Toolbar actions\nfunction handleFormatChange(format: WellPlateFormat) {\n if (!editor.activeRack.value) return\n editor.updateRack(editor.activeRack.value.id, { format })\n}\n\nfunction handleSlotChange(slot: SlotPosition) {\n if (!editor.activeRack.value) return\n editor.updateRack(editor.activeRack.value.id, { slot })\n}\n\nfunction handleClearAll() {\n if (!editor.activeRack.value) return\n editor.clearAllWells(editor.activeRack.value.id)\n}\n\nfunction handleFillSeries() {\n if (!editor.activeRack.value) return\n editor.fillSeries(editor.activeRack.value.id)\n}\n\n// Well edit from WellPlate\nfunction handleWellEdit(wellId: string, data: WellEditData) {\n if (!editor.activeRack.value) return\n const rackId = editor.activeRack.value.id\n\n const wellData: Partial<Well> = {\n id: wellId,\n state: data.label ? 'filled' : 'empty',\n sampleType: data.sampleType || undefined,\n metadata: {\n label: data.label,\n injectionVolume: data.injectionVolume,\n injectionCount: data.injectionCount,\n customMethod: data.customMethod || null,\n },\n }\n\n if (data.label) {\n editor.setWellData(rackId, wellId, wellData)\n } else {\n editor.clearWell(rackId, wellId)\n }\n\n emit('well-edit', rackId, wellId, data)\n}\n\nfunction handleWellClear(wellId: string) {\n if (!editor.activeRack.value) return\n editor.clearWell(editor.activeRack.value.id, wellId)\n}\n\nfunction handleSampleDrop(wellId: string, data: WellSampleDropData, event: DragEvent) {\n const rack = editor.activeRack.value\n if (!rack) return\n\n emit('sample-drop', rack.id, wellId, data, event)\n\n const editData = props.sampleDropMapper?.(data, {\n rack,\n rackId: rack.id,\n wellId,\n event,\n }) ?? defaultSampleDropEditData(wellId, data, rack)\n\n if (!editData) return\n handleWellEdit(wellId, { ...editData, wellId })\n}\n\nfunction defaultSampleDropEditData(wellId: string, data: WellSampleDropData, rack: Rack): WellEditData | null {\n const label = data.label ?? data.sampleName ?? data.id ?? ''\n if (!label.trim()) return null\n\n return {\n wellId,\n label: label.trim(),\n sampleType: data.sampleType ?? 'sample',\n injectionVolume: data.injectionVolume ?? rack.injectionVolume,\n injectionCount: data.injectionCount ?? 1,\n customMethod: data.customMethod ?? '',\n }\n}\n\n// Computed well count per rack\nfunction getWellCount(rack: Rack): number {\n return Object.keys(rack.wells).length\n}\n\nconst activeRackWells = computed(() => editor.activeRack.value?.wells ?? {})\n</script>\n\n<template>\n <div :class=\"['mint-rack-editor', { 'mint-rack-editor--readonly': readonly }]\">\n <!-- Rack tabs -->\n <div class=\"mint-rack-editor__tabs\">\n <div class=\"mint-rack-editor__tabs-inner\">\n <button\n v-for=\"rack in editor.racks.value\"\n :key=\"rack.id\"\n :draggable=\"allowReorder && !readonly\"\n :class=\"[\n 'mint-rack-editor__tab',\n {\n 'mint-rack-editor__tab--active': editor.activeRackId.value === rack.id,\n 'mint-rack-editor__tab--dragging': draggingRackId === rack.id,\n 'mint-rack-editor__tab--drag-over': dragOverRackId === rack.id,\n },\n ]\"\n @click=\"handleTabClick(rack.id)\"\n @dragstart=\"handleRackDragStart($event, rack.id)\"\n @dragover=\"handleRackDragOver($event, rack.id)\"\n @dragleave=\"handleRackDragLeave\"\n @drop=\"handleRackDrop($event, rack.id)\"\n @dragend=\"handleRackDragEnd\"\n >\n <span\n class=\"mint-rack-editor__slot-dot\"\n :style=\"{ backgroundColor: SLOT_COLORS[rack.slot] }\"\n />\n {{ rack.name }}\n <span\n v-if=\"getWellCount(rack) > 0\"\n :class=\"[\n 'mint-rack-editor__count',\n editor.activeRackId.value === rack.id ? 'mint-rack-editor__count--active' : 'mint-rack-editor__count--inactive',\n ]\"\n >\n {{ getWellCount(rack) }}\n </span>\n <!-- Remove button -->\n <button\n v-if=\"editor.racks.value.length > minRacks && !readonly\"\n class=\"mint-rack-editor__tab-remove\"\n @click.stop=\"handleRemoveRack(rack.id)\"\n >\n ×\n </button>\n </button>\n </div>\n\n <!-- Add rack button -->\n <button\n v-if=\"editor.racks.value.length < maxRacks && !readonly\"\n class=\"mint-rack-editor__add-btn\"\n @click=\"handleAddRack\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M5 12h14\" /><path d=\"M12 5v14\" />\n </svg>\n Add Rack\n </button>\n </div>\n\n <!-- Toolbar for active rack -->\n <div v-if=\"editor.activeRack.value && !readonly\" class=\"mint-rack-editor__toolbar\">\n <div class=\"mint-rack-editor__toolbar-group\">\n <!-- Format selector -->\n <div class=\"mint-rack-editor__toolbar-section\">\n <span class=\"mint-rack-editor__toolbar-label\">Plate</span>\n <div class=\"mint-rack-editor__format-btns\">\n <button\n v-for=\"opt in FORMAT_OPTIONS\"\n :key=\"opt.value\"\n :class=\"[\n 'mint-rack-editor__format-btn',\n editor.activeRack.value.format === opt.value ? 'mint-rack-editor__format-btn--active' : 'mint-rack-editor__format-btn--inactive',\n ]\"\n @click=\"handleFormatChange(opt.value)\"\n >\n {{ opt.label }}\n </button>\n </div>\n </div>\n\n <div class=\"mint-rack-editor__toolbar-divider\" />\n\n <!-- Slot selector -->\n <div class=\"mint-rack-editor__toolbar-section\">\n <span class=\"mint-rack-editor__toolbar-label\">Slot</span>\n <div class=\"mint-rack-editor__slot-btns\">\n <button\n v-for=\"s in SLOT_OPTIONS\"\n :key=\"s\"\n class=\"mint-rack-editor__slot-btn\"\n :style=\"{\n backgroundColor: editor.activeRack.value.slot === s ? SLOT_COLORS[s] : 'var(--bg-secondary)',\n color: editor.activeRack.value.slot === s ? 'white' : SLOT_COLORS[s],\n borderColor: editor.activeRack.value.slot === s ? 'transparent' : `${SLOT_COLORS[s]}40`,\n boxShadow: editor.activeRack.value.slot === s ? '0 2px 4px rgba(0,0,0,0.15)' : 'none',\n }\"\n @click=\"handleSlotChange(s)\"\n >\n {{ s }}\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"mint-rack-editor__toolbar-spacer\" />\n\n <!-- Actions -->\n <button class=\"mint-rack-editor__action-btn\" @click=\"handleClearAll\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10 11v6\" /><path d=\"M14 11v6\" /><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6\" /><path d=\"M3 6h18\" /><path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" />\n </svg>\n Clear\n </button>\n <button class=\"mint-rack-editor__action-btn\" @click=\"handleFillSeries\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" /><path d=\"M3 9h18\" /><path d=\"M3 15h18\" /><path d=\"M9 3v18\" /><path d=\"M15 3v18\" />\n </svg>\n Fill Series\n </button>\n </div>\n\n <!-- Plate view -->\n <div v-if=\"editor.activeRack.value\" class=\"mint-rack-editor__plate\">\n <WellPlate\n :format=\"editor.activeRack.value.format\"\n :wells=\"activeRackWells\"\n :editable=\"editable && !readonly\"\n :show-well-labels=\"true\"\n :show-badges=\"showBadges\"\n :show-legend=\"showLegend\"\n :default-injection-volume=\"editor.activeRack.value.injectionVolume\"\n :readonly=\"readonly\"\n :size=\"wellPlateSize\"\n :show-sample-type-indicator=\"true\"\n :allow-sample-drop=\"allowSampleDrop && !readonly\"\n @well-edit=\"handleWellEdit\"\n @well-clear=\"handleWellClear\"\n @sample-drop=\"handleSampleDrop\"\n >\n <template v-if=\"$slots['well-editor']\" #well-editor=\"slotProps\">\n <slot\n name=\"well-editor\"\n :rack=\"editor.activeRack.value\"\n :rack-id=\"editor.activeRack.value?.id ?? ''\"\n :well-id=\"slotProps.wellId\"\n :well-data=\"slotProps.wellData\"\n :edit-fields=\"slotProps.editFields\"\n :default-injection-volume=\"slotProps.defaultInjectionVolume\"\n :position=\"slotProps.position\"\n :save=\"slotProps.save\"\n :clear=\"slotProps.clear\"\n :close=\"slotProps.close\"\n />\n </template>\n </WellPlate>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/rack-editor.css';\n</style>\n","<script setup lang=\"ts\">\n/** Drag-and-drop two-zone assigner for splitting sample groups into control and treatment arms. */\nimport { ref } from 'vue'\nimport type { GroupItem } from '../types'\nimport { useGroupAssignment, type GroupAssignmentState, type GroupAssignmentZone } from '../composables/useGroupAssignment'\n\ninterface Props {\n groups: GroupItem[]\n group1: string[]\n group2: string[]\n label1?: string\n label2?: string\n color1?: string\n color2?: string\n minPerGroup?: number\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label1: 'Control',\n label2: 'Treatment',\n color1: '#3B82F6',\n color2: '#F43F5E',\n minPerGroup: 1,\n})\n\nconst emit = defineEmits<{\n 'update:group1': [names: string[]]\n 'update:group2': [names: string[]]\n}>()\n\nconst dragOverZone = ref<'zone1' | 'zone2' | null>(null)\nconst draggingGroup = ref<string | null>(null)\n\nconst assignment = useGroupAssignment({\n groups: () => props.groups,\n group1: () => props.group1,\n group2: () => props.group2,\n label1: () => props.label1,\n label2: () => props.label2,\n minPerGroup: () => props.minPerGroup,\n})\nconst { unassignedGroups, zone1Groups, zone2Groups, zone1Count, zone2Count, validationMessage } = assignment\n\nfunction handleDragStart(event: DragEvent, groupName: string) {\n const transfer = event.dataTransfer\n if (!transfer) return\n\n draggingGroup.value = groupName\n transfer.setData('groupName', groupName)\n transfer.effectAllowed = 'move'\n}\n\nfunction handleDragEnd() {\n draggingGroup.value = null\n dragOverZone.value = null\n}\n\nfunction handleDragOver(event: DragEvent, zone: GroupAssignmentZone) {\n event.preventDefault()\n if (event.dataTransfer) {\n event.dataTransfer.dropEffect = 'move'\n }\n dragOverZone.value = zone\n}\n\nfunction handleDragLeave() {\n dragOverZone.value = null\n}\n\nfunction handleDrop(event: DragEvent, zone: GroupAssignmentZone) {\n event.preventDefault()\n dragOverZone.value = null\n\n const groupName = event.dataTransfer?.getData('groupName')\n if (!groupName) return\n\n assignToZone(groupName, zone)\n}\n\nfunction assignToZone(groupName: string, zone: GroupAssignmentZone) {\n emitAssignment(assignment.assignToZone(groupName, zone))\n}\n\nfunction removeFromZone(groupName: string, zone: GroupAssignmentZone) {\n const state = assignment.removeFromZone(groupName, zone)\n if (zone === 'zone1') {\n emit('update:group1', state.group1)\n } else {\n emit('update:group2', state.group2)\n }\n}\n\nfunction clearAll() {\n emitAssignment(assignment.clearAll())\n}\n\nfunction emitAssignment(state: GroupAssignmentState) {\n emit('update:group1', state.group1)\n emit('update:group2', state.group2)\n}\n</script>\n\n<template>\n <div class=\"mint-group-assigner\">\n <!-- Drop zones -->\n <div class=\"mint-group-assigner__zones\">\n <!-- Zone 1 (Control) -->\n <div\n :class=\"[\n 'mint-group-assigner__zone',\n 'mint-group-assigner__zone--1',\n dragOverZone === 'zone1' ? 'mint-group-assigner__zone--dragover' : '',\n ]\"\n :style=\"{ '--zone-color': color1 }\"\n @dragover=\"handleDragOver($event, 'zone1')\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop($event, 'zone1')\"\n >\n <div class=\"mint-group-assigner__zone-header\">\n <span class=\"mint-group-assigner__zone-label\">{{ label1 }}</span>\n <span class=\"mint-group-assigner__zone-count\">{{ zone1Count }} samples</span>\n </div>\n\n <div class=\"mint-group-assigner__zone-content\">\n <div\n v-for=\"group in zone1Groups\"\n :key=\"group.name\"\n class=\"mint-group-assigner__pill\"\n draggable=\"true\"\n @dragstart=\"handleDragStart($event, group.name)\"\n @dragend=\"handleDragEnd\"\n >\n <span\n class=\"mint-group-assigner__pill-color\"\n :style=\"{ backgroundColor: group.color }\"\n />\n <span class=\"mint-group-assigner__pill-name\">{{ group.name }}</span>\n <span class=\"mint-group-assigner__pill-count\">{{ group.count }}</span>\n <button\n type=\"button\"\n class=\"mint-group-assigner__pill-remove\"\n @click=\"removeFromZone(group.name, 'zone1')\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div v-if=\"zone1Groups.length === 0\" class=\"mint-group-assigner__zone-empty\">\n Drag groups here\n </div>\n </div>\n </div>\n\n <!-- Zone 2 (Treatment) -->\n <div\n :class=\"[\n 'mint-group-assigner__zone',\n 'mint-group-assigner__zone--2',\n dragOverZone === 'zone2' ? 'mint-group-assigner__zone--dragover' : '',\n ]\"\n :style=\"{ '--zone-color': color2 }\"\n @dragover=\"handleDragOver($event, 'zone2')\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop($event, 'zone2')\"\n >\n <div class=\"mint-group-assigner__zone-header\">\n <span class=\"mint-group-assigner__zone-label\">{{ label2 }}</span>\n <span class=\"mint-group-assigner__zone-count\">{{ zone2Count }} samples</span>\n </div>\n\n <div class=\"mint-group-assigner__zone-content\">\n <div\n v-for=\"group in zone2Groups\"\n :key=\"group.name\"\n class=\"mint-group-assigner__pill\"\n draggable=\"true\"\n @dragstart=\"handleDragStart($event, group.name)\"\n @dragend=\"handleDragEnd\"\n >\n <span\n class=\"mint-group-assigner__pill-color\"\n :style=\"{ backgroundColor: group.color }\"\n />\n <span class=\"mint-group-assigner__pill-name\">{{ group.name }}</span>\n <span class=\"mint-group-assigner__pill-count\">{{ group.count }}</span>\n <button\n type=\"button\"\n class=\"mint-group-assigner__pill-remove\"\n @click=\"removeFromZone(group.name, 'zone2')\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div v-if=\"zone2Groups.length === 0\" class=\"mint-group-assigner__zone-empty\">\n Drag groups here\n </div>\n </div>\n </div>\n </div>\n\n <!-- Unassigned groups -->\n <div v-if=\"unassignedGroups.length > 0\" class=\"mint-group-assigner__unassigned\">\n <div class=\"mint-group-assigner__unassigned-header\">\n <span class=\"mint-group-assigner__unassigned-title\">Available Groups</span>\n <button\n v-if=\"group1.length > 0 || group2.length > 0\"\n type=\"button\"\n class=\"mint-group-assigner__clear-btn\"\n @click=\"clearAll\"\n >\n Clear all\n </button>\n </div>\n\n <div class=\"mint-group-assigner__unassigned-list\">\n <div\n v-for=\"group in unassignedGroups\"\n :key=\"group.name\"\n :class=\"[\n 'mint-group-assigner__pill',\n 'mint-group-assigner__pill--unassigned',\n draggingGroup === group.name ? 'mint-group-assigner__pill--dragging' : '',\n ]\"\n draggable=\"true\"\n @dragstart=\"handleDragStart($event, group.name)\"\n @dragend=\"handleDragEnd\"\n >\n <span\n class=\"mint-group-assigner__pill-color\"\n :style=\"{ backgroundColor: group.color }\"\n />\n <span class=\"mint-group-assigner__pill-name\">{{ group.name }}</span>\n <span class=\"mint-group-assigner__pill-count\">{{ group.count }}</span>\n </div>\n </div>\n </div>\n\n <!-- Validation message -->\n <div v-if=\"validationMessage\" class=\"mint-group-assigner__validation\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\" />\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\" />\n </svg>\n {{ validationMessage }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/group-assigner.css';\n</style>\n","<script setup lang=\"ts\">\n/** Drag-and-drop two-zone assigner for splitting sample groups into control and treatment arms. */\nimport { ref } from 'vue'\nimport type { GroupItem } from '../types'\nimport { useGroupAssignment, type GroupAssignmentState, type GroupAssignmentZone } from '../composables/useGroupAssignment'\n\ninterface Props {\n groups: GroupItem[]\n group1: string[]\n group2: string[]\n label1?: string\n label2?: string\n color1?: string\n color2?: string\n minPerGroup?: number\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label1: 'Control',\n label2: 'Treatment',\n color1: '#3B82F6',\n color2: '#F43F5E',\n minPerGroup: 1,\n})\n\nconst emit = defineEmits<{\n 'update:group1': [names: string[]]\n 'update:group2': [names: string[]]\n}>()\n\nconst dragOverZone = ref<'zone1' | 'zone2' | null>(null)\nconst draggingGroup = ref<string | null>(null)\n\nconst assignment = useGroupAssignment({\n groups: () => props.groups,\n group1: () => props.group1,\n group2: () => props.group2,\n label1: () => props.label1,\n label2: () => props.label2,\n minPerGroup: () => props.minPerGroup,\n})\nconst { unassignedGroups, zone1Groups, zone2Groups, zone1Count, zone2Count, validationMessage } = assignment\n\nfunction handleDragStart(event: DragEvent, groupName: string) {\n const transfer = event.dataTransfer\n if (!transfer) return\n\n draggingGroup.value = groupName\n transfer.setData('groupName', groupName)\n transfer.effectAllowed = 'move'\n}\n\nfunction handleDragEnd() {\n draggingGroup.value = null\n dragOverZone.value = null\n}\n\nfunction handleDragOver(event: DragEvent, zone: GroupAssignmentZone) {\n event.preventDefault()\n if (event.dataTransfer) {\n event.dataTransfer.dropEffect = 'move'\n }\n dragOverZone.value = zone\n}\n\nfunction handleDragLeave() {\n dragOverZone.value = null\n}\n\nfunction handleDrop(event: DragEvent, zone: GroupAssignmentZone) {\n event.preventDefault()\n dragOverZone.value = null\n\n const groupName = event.dataTransfer?.getData('groupName')\n if (!groupName) return\n\n assignToZone(groupName, zone)\n}\n\nfunction assignToZone(groupName: string, zone: GroupAssignmentZone) {\n emitAssignment(assignment.assignToZone(groupName, zone))\n}\n\nfunction removeFromZone(groupName: string, zone: GroupAssignmentZone) {\n const state = assignment.removeFromZone(groupName, zone)\n if (zone === 'zone1') {\n emit('update:group1', state.group1)\n } else {\n emit('update:group2', state.group2)\n }\n}\n\nfunction clearAll() {\n emitAssignment(assignment.clearAll())\n}\n\nfunction emitAssignment(state: GroupAssignmentState) {\n emit('update:group1', state.group1)\n emit('update:group2', state.group2)\n}\n</script>\n\n<template>\n <div class=\"mint-group-assigner\">\n <!-- Drop zones -->\n <div class=\"mint-group-assigner__zones\">\n <!-- Zone 1 (Control) -->\n <div\n :class=\"[\n 'mint-group-assigner__zone',\n 'mint-group-assigner__zone--1',\n dragOverZone === 'zone1' ? 'mint-group-assigner__zone--dragover' : '',\n ]\"\n :style=\"{ '--zone-color': color1 }\"\n @dragover=\"handleDragOver($event, 'zone1')\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop($event, 'zone1')\"\n >\n <div class=\"mint-group-assigner__zone-header\">\n <span class=\"mint-group-assigner__zone-label\">{{ label1 }}</span>\n <span class=\"mint-group-assigner__zone-count\">{{ zone1Count }} samples</span>\n </div>\n\n <div class=\"mint-group-assigner__zone-content\">\n <div\n v-for=\"group in zone1Groups\"\n :key=\"group.name\"\n class=\"mint-group-assigner__pill\"\n draggable=\"true\"\n @dragstart=\"handleDragStart($event, group.name)\"\n @dragend=\"handleDragEnd\"\n >\n <span\n class=\"mint-group-assigner__pill-color\"\n :style=\"{ backgroundColor: group.color }\"\n />\n <span class=\"mint-group-assigner__pill-name\">{{ group.name }}</span>\n <span class=\"mint-group-assigner__pill-count\">{{ group.count }}</span>\n <button\n type=\"button\"\n class=\"mint-group-assigner__pill-remove\"\n @click=\"removeFromZone(group.name, 'zone1')\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div v-if=\"zone1Groups.length === 0\" class=\"mint-group-assigner__zone-empty\">\n Drag groups here\n </div>\n </div>\n </div>\n\n <!-- Zone 2 (Treatment) -->\n <div\n :class=\"[\n 'mint-group-assigner__zone',\n 'mint-group-assigner__zone--2',\n dragOverZone === 'zone2' ? 'mint-group-assigner__zone--dragover' : '',\n ]\"\n :style=\"{ '--zone-color': color2 }\"\n @dragover=\"handleDragOver($event, 'zone2')\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop($event, 'zone2')\"\n >\n <div class=\"mint-group-assigner__zone-header\">\n <span class=\"mint-group-assigner__zone-label\">{{ label2 }}</span>\n <span class=\"mint-group-assigner__zone-count\">{{ zone2Count }} samples</span>\n </div>\n\n <div class=\"mint-group-assigner__zone-content\">\n <div\n v-for=\"group in zone2Groups\"\n :key=\"group.name\"\n class=\"mint-group-assigner__pill\"\n draggable=\"true\"\n @dragstart=\"handleDragStart($event, group.name)\"\n @dragend=\"handleDragEnd\"\n >\n <span\n class=\"mint-group-assigner__pill-color\"\n :style=\"{ backgroundColor: group.color }\"\n />\n <span class=\"mint-group-assigner__pill-name\">{{ group.name }}</span>\n <span class=\"mint-group-assigner__pill-count\">{{ group.count }}</span>\n <button\n type=\"button\"\n class=\"mint-group-assigner__pill-remove\"\n @click=\"removeFromZone(group.name, 'zone2')\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div v-if=\"zone2Groups.length === 0\" class=\"mint-group-assigner__zone-empty\">\n Drag groups here\n </div>\n </div>\n </div>\n </div>\n\n <!-- Unassigned groups -->\n <div v-if=\"unassignedGroups.length > 0\" class=\"mint-group-assigner__unassigned\">\n <div class=\"mint-group-assigner__unassigned-header\">\n <span class=\"mint-group-assigner__unassigned-title\">Available Groups</span>\n <button\n v-if=\"group1.length > 0 || group2.length > 0\"\n type=\"button\"\n class=\"mint-group-assigner__clear-btn\"\n @click=\"clearAll\"\n >\n Clear all\n </button>\n </div>\n\n <div class=\"mint-group-assigner__unassigned-list\">\n <div\n v-for=\"group in unassignedGroups\"\n :key=\"group.name\"\n :class=\"[\n 'mint-group-assigner__pill',\n 'mint-group-assigner__pill--unassigned',\n draggingGroup === group.name ? 'mint-group-assigner__pill--dragging' : '',\n ]\"\n draggable=\"true\"\n @dragstart=\"handleDragStart($event, group.name)\"\n @dragend=\"handleDragEnd\"\n >\n <span\n class=\"mint-group-assigner__pill-color\"\n :style=\"{ backgroundColor: group.color }\"\n />\n <span class=\"mint-group-assigner__pill-name\">{{ group.name }}</span>\n <span class=\"mint-group-assigner__pill-count\">{{ group.count }}</span>\n </div>\n </div>\n </div>\n\n <!-- Validation message -->\n <div v-if=\"validationMessage\" class=\"mint-group-assigner__validation\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\" />\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\" />\n </svg>\n {{ validationMessage }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/group-assigner.css';\n</style>\n","<script setup lang=\"ts\">\n/** Editor for a single reagent definition with axis orientation, dilution-series generator, and position-count validation for 96/384-well plates. */\nimport { computed, ref } from 'vue'\nimport NumberInput from './NumberInput.vue'\nimport BaseButton from './BaseButton.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport {\n useReagentSeries,\n DEFAULT_PRESETS,\n DEFAULT_UNITS,\n type LevelEntry,\n type DilutionPreset,\n} from '../composables/useReagentSeries'\n\nexport interface ReagentDefinition {\n id: string\n name: string\n color: string\n axis: 'row' | 'column'\n startPosition: string\n levels: LevelEntry[]\n unit: string\n}\n\ninterface Props {\n modelValue: ReagentDefinition\n plateFormat?: 96 | 384\n units?: string[]\n presets?: DilutionPreset[]\n maxLevels?: number\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n plateFormat: 96,\n units: () => DEFAULT_UNITS,\n presets: () => DEFAULT_PRESETS,\n maxLevels: 24,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: ReagentDefinition]\n remove: []\n}>()\n\nconst { generateSeries, sortLevels, totalPositions } = useReagentSeries()\n\n// --- Local generator state ---\nconst generatorStart = ref(100)\nconst generatorCount = ref(8)\nconst manualValue = ref<number | undefined>(undefined)\n\n// --- Computed plate dimensions ---\nconst plateRows = computed(() => (props.plateFormat === 384 ? 16 : 8))\nconst plateCols = computed(() => (props.plateFormat === 384 ? 24 : 12))\n\nconst axisOptions = [\n { value: 'row', label: 'Rows' },\n { value: 'column', label: 'Columns' },\n]\n\n// --- Start position validation ---\nconst startPositionError = computed<string | null>(() => {\n const { axis, startPosition } = props.modelValue\n if (!startPosition) return null\n\n if (axis === 'row') {\n const letter = startPosition.toUpperCase()\n if (!/^[A-Z]$/.test(letter)) return 'Must be a letter'\n const idx = letter.charCodeAt(0) - 65\n if (idx >= plateRows.value) return `Max row: ${String.fromCharCode(64 + plateRows.value)}`\n } else {\n const num = parseInt(startPosition, 10)\n if (isNaN(num) || num < 1) return 'Must be a number ≥ 1'\n if (num > plateCols.value) return `Max col: ${plateCols.value}`\n }\n return null\n})\n\n// --- Position summary ---\nconst positionCount = computed(() => totalPositions(props.modelValue.levels))\n\nconst positionOverflows = computed(() => {\n const { axis, startPosition, levels } = props.modelValue\n if (!startPosition || levels.length === 0 || startPositionError.value) return false\n\n const span = positionCount.value\n if (axis === 'row') {\n const startIdx = startPosition.toUpperCase().charCodeAt(0) - 65\n return startIdx + span > plateRows.value\n } else {\n const startIdx = parseInt(startPosition, 10)\n return startIdx + span - 1 > plateCols.value\n }\n})\n\nconst positionRange = computed(() => {\n const { axis, startPosition, levels } = props.modelValue\n if (!startPosition || levels.length === 0 || startPositionError.value) return null\n\n const span = positionCount.value\n if (span === 0) return null\n\n if (axis === 'row') {\n const startIdx = startPosition.toUpperCase().charCodeAt(0) - 65\n const endIdx = Math.min(startIdx + span - 1, plateRows.value - 1)\n const startLetter = String.fromCharCode(65 + startIdx)\n const endLetter = String.fromCharCode(65 + endIdx)\n return `${startLetter}–${endLetter}`\n } else {\n const startIdx = parseInt(startPosition, 10)\n if (isNaN(startIdx)) return null\n const endIdx = Math.min(startIdx + span - 1, plateCols.value)\n return `${startIdx}–${endIdx}`\n }\n})\n\n// --- Update helpers ---\nfunction update(partial: Partial<ReagentDefinition>) {\n emit('update:modelValue', { ...props.modelValue, ...partial })\n}\n\nfunction updateName(event: Event) {\n update({ name: (event.target as HTMLInputElement).value })\n}\n\nfunction updateColor(event: Event) {\n update({ color: (event.target as HTMLInputElement).value })\n}\n\nfunction updateAxis(value: string | number) {\n update({ axis: value as 'row' | 'column', startPosition: '' })\n}\n\nfunction updateStartPosition(event: Event) {\n const raw = (event.target as HTMLInputElement).value\n update({ startPosition: raw })\n}\n\nfunction updateUnit(event: Event) {\n update({ unit: (event.target as HTMLSelectElement).value })\n}\n\n// --- Level manipulation ---\nfunction setLevels(levels: LevelEntry[]) {\n update({ levels: sortLevels(levels) })\n}\n\nfunction applyPreset(preset: DilutionPreset) {\n if (generatorStart.value <= 0 || generatorCount.value < 1) return\n const count = Math.min(generatorCount.value, props.maxLevels)\n const levels = generateSeries(generatorStart.value, count, preset.factor)\n setLevels(levels)\n}\n\nfunction addManualLevel() {\n if (manualValue.value === undefined || manualValue.value < 0) return\n if (props.modelValue.levels.length >= props.maxLevels) return\n\n const exists = props.modelValue.levels.some((l) => l.value === manualValue.value)\n if (exists) return\n\n setLevels([...props.modelValue.levels, { value: manualValue.value, replicates: 1 }])\n manualValue.value = undefined\n}\n\nfunction handleManualKeydown(event: KeyboardEvent) {\n if (event.key === 'Enter') {\n event.preventDefault()\n addManualLevel()\n }\n}\n\nfunction removeLevel(index: number) {\n const next = [...props.modelValue.levels]\n next.splice(index, 1)\n setLevels(next)\n}\n\nfunction updateLevelReplicates(index: number, replicates: number | undefined) {\n if (replicates === undefined || replicates < 1) return\n const next = [...props.modelValue.levels]\n next[index] = { ...next[index], replicates }\n update({ levels: next })\n}\n\nfunction formatLevel(value: number): string {\n if (!Number.isFinite(value)) return '–'\n if (value === 0) return 'Ctrl (0)'\n if (value >= 1000) return value.toLocaleString('en-US', { maximumSignificantDigits: 4 })\n if (value >= 1) return value.toString()\n if (value >= 0.001) return value.toFixed(3)\n return value.toExponential(1)\n}\n</script>\n\n<template>\n <div\n class=\"mint-reagent-editor\"\n :style=\"{ '--reagent-color': modelValue.color }\"\n >\n <!-- Identity -->\n <div class=\"mint-reagent-editor__section\">\n <div class=\"mint-reagent-editor__identity\">\n <label class=\"mint-reagent-editor__color-wrapper\">\n <input\n type=\"color\"\n :value=\"modelValue.color\"\n class=\"mint-reagent-editor__color-input\"\n aria-label=\"Reagent color\"\n @input=\"updateColor\"\n />\n <span\n class=\"mint-reagent-editor__color-swatch\"\n :style=\"{ backgroundColor: modelValue.color }\"\n />\n </label>\n <input\n type=\"text\"\n :value=\"modelValue.name\"\n placeholder=\"Reagent name\"\n class=\"mint-reagent-editor__name-input\"\n maxlength=\"64\"\n aria-label=\"Reagent name\"\n @input=\"updateName\"\n />\n </div>\n </div>\n\n <!-- Position Mapping -->\n <div class=\"mint-reagent-editor__section\">\n <div class=\"mint-reagent-editor__section-header\">\n <svg class=\"mint-reagent-editor__section-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" /><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" /><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" /><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" />\n </svg>\n Position\n </div>\n <div class=\"mint-reagent-editor__position-row\">\n <div class=\"mint-reagent-editor__field mint-reagent-editor__field--flex\">\n <label class=\"mint-reagent-editor__label\">Axis</label>\n <BaseSelect\n :model-value=\"modelValue.axis\"\n :options=\"axisOptions\"\n size=\"sm\"\n @update:model-value=\"updateAxis\"\n />\n </div>\n <div class=\"mint-reagent-editor__field mint-reagent-editor__field--flex\">\n <label class=\"mint-reagent-editor__label\">Start</label>\n <input\n type=\"text\"\n :value=\"modelValue.startPosition\"\n :placeholder=\"modelValue.axis === 'row' ? 'e.g. B' : 'e.g. 2'\"\n :class=\"[\n 'mint-reagent-editor__inline-input',\n startPositionError ? 'mint-reagent-editor__inline-input--error' : '',\n ]\"\n :maxlength=\"modelValue.axis === 'row' ? 1 : 2\"\n :aria-invalid=\"!!startPositionError\"\n aria-label=\"Start position\"\n @input=\"updateStartPosition\"\n />\n <span v-if=\"startPositionError\" class=\"mint-reagent-editor__field-error\">\n {{ startPositionError }}\n </span>\n </div>\n </div>\n </div>\n\n <!-- Series -->\n <div class=\"mint-reagent-editor__section\">\n <div class=\"mint-reagent-editor__section-header\">\n <svg class=\"mint-reagent-editor__section-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M3 3v18h18\" /><path d=\"m19 9-5 5-4-4-3 3\" />\n </svg>\n Series\n <select\n :value=\"modelValue.unit\"\n class=\"mint-reagent-editor__unit-select\"\n @change=\"updateUnit\"\n >\n <option v-for=\"u in units\" :key=\"u\" :value=\"u\">{{ u }}</option>\n </select>\n </div>\n\n <!-- Dilution generator -->\n <div class=\"mint-reagent-editor__generator\">\n <div class=\"mint-reagent-editor__generator-inputs\">\n <div class=\"mint-reagent-editor__field mint-reagent-editor__field--flex\">\n <label class=\"mint-reagent-editor__label\">Start</label>\n <NumberInput v-model=\"generatorStart\" :min=\"0\" :step=\"10\" size=\"sm\" />\n </div>\n <div class=\"mint-reagent-editor__field mint-reagent-editor__field--flex\">\n <label class=\"mint-reagent-editor__label\">Doses</label>\n <NumberInput v-model=\"generatorCount\" :min=\"2\" :max=\"maxLevels\" :step=\"1\" size=\"sm\" />\n </div>\n </div>\n <div class=\"mint-reagent-editor__presets\">\n <button\n v-for=\"preset in presets\"\n :key=\"preset.label\"\n type=\"button\"\n class=\"mint-reagent-editor__preset-btn\"\n @click=\"applyPreset(preset)\"\n >\n {{ preset.label }}\n </button>\n </div>\n </div>\n\n <!-- Manual entry -->\n <div class=\"mint-reagent-editor__manual\" @keydown=\"handleManualKeydown\">\n <NumberInput\n v-model=\"manualValue\"\n :min=\"0\"\n :step=\"1\"\n size=\"sm\"\n placeholder=\"Add value\"\n />\n <button\n type=\"button\"\n class=\"mint-reagent-editor__add-btn\"\n :disabled=\"manualValue === undefined || manualValue < 0 || modelValue.levels.length >= maxLevels\"\n aria-label=\"Add level\"\n @click=\"addManualLevel\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M5 12h14\" /><path d=\"M12 5v14\" />\n </svg>\n </button>\n </div>\n\n <!-- Level table -->\n <div v-if=\"modelValue.levels.length > 0\" class=\"mint-reagent-editor__table\">\n <div class=\"mint-reagent-editor__table-header\">\n <span class=\"mint-reagent-editor__table-col mint-reagent-editor__table-col--level\">Level</span>\n <span class=\"mint-reagent-editor__table-col mint-reagent-editor__table-col--reps\">Reps</span>\n <span class=\"mint-reagent-editor__table-col mint-reagent-editor__table-col--action\" />\n </div>\n <div class=\"mint-reagent-editor__table-body\">\n <div\n v-for=\"(level, i) in modelValue.levels\"\n :key=\"level.value\"\n class=\"mint-reagent-editor__table-row\"\n >\n <span class=\"mint-reagent-editor__table-col mint-reagent-editor__table-col--level\">\n <span class=\"mint-reagent-editor__level-value\">{{ formatLevel(level.value) }}</span>\n <span v-if=\"level.value !== 0\" class=\"mint-reagent-editor__level-unit\">{{ modelValue.unit }}</span>\n </span>\n <span class=\"mint-reagent-editor__table-col mint-reagent-editor__table-col--reps\">\n <NumberInput\n :model-value=\"level.replicates\"\n :min=\"1\"\n :max=\"12\"\n :step=\"1\"\n size=\"sm\"\n @update:model-value=\"updateLevelReplicates(i, $event)\"\n />\n </span>\n <span class=\"mint-reagent-editor__table-col mint-reagent-editor__table-col--action\">\n <button\n type=\"button\"\n class=\"mint-reagent-editor__remove-level-btn\"\n aria-label=\"Remove level\"\n @click=\"removeLevel(i)\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </span>\n </div>\n </div>\n </div>\n\n <div v-else class=\"mint-reagent-editor__empty\">\n Use a preset above or add levels manually\n </div>\n </div>\n\n <!-- Position Summary -->\n <div\n v-if=\"modelValue.levels.length > 0 && modelValue.startPosition && !startPositionError\"\n :class=\"[\n 'mint-reagent-editor__summary',\n positionOverflows ? 'mint-reagent-editor__summary--overflow' : '',\n ]\"\n >\n <div class=\"mint-reagent-editor__summary-item\">\n <span class=\"mint-reagent-editor__summary-value\">{{ modelValue.levels.length }}</span>\n <span class=\"mint-reagent-editor__summary-label\">levels</span>\n </div>\n <div class=\"mint-reagent-editor__summary-dot\" />\n <div class=\"mint-reagent-editor__summary-item\">\n <span class=\"mint-reagent-editor__summary-value\">{{ positionCount }}</span>\n <span class=\"mint-reagent-editor__summary-label\">positions</span>\n </div>\n <div v-if=\"positionRange\" class=\"mint-reagent-editor__summary-dot\" />\n <div v-if=\"positionRange\" class=\"mint-reagent-editor__summary-item\">\n <span class=\"mint-reagent-editor__summary-value\">{{ positionRange }}</span>\n <span class=\"mint-reagent-editor__summary-label\">{{ modelValue.axis === 'row' ? 'rows' : 'cols' }}</span>\n </div>\n <span v-if=\"positionOverflows\" class=\"mint-reagent-editor__summary-warning\">\n Exceeds plate\n </span>\n </div>\n\n <!-- Remove -->\n <div class=\"mint-reagent-editor__footer\">\n <BaseButton variant=\"danger\" size=\"sm\" @click=\"emit('remove')\">\n Remove Reagent\n </BaseButton>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/reagent-editor.css';\n</style>\n","<script setup lang=\"ts\">\n/** Editor for a single reagent definition with axis orientation, dilution-series generator, and position-count validation for 96/384-well plates. */\nimport { computed, ref } from 'vue'\nimport NumberInput from './NumberInput.vue'\nimport BaseButton from './BaseButton.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport {\n useReagentSeries,\n DEFAULT_PRESETS,\n DEFAULT_UNITS,\n type LevelEntry,\n type DilutionPreset,\n} from '../composables/useReagentSeries'\n\nexport interface ReagentDefinition {\n id: string\n name: string\n color: string\n axis: 'row' | 'column'\n startPosition: string\n levels: LevelEntry[]\n unit: string\n}\n\ninterface Props {\n modelValue: ReagentDefinition\n plateFormat?: 96 | 384\n units?: string[]\n presets?: DilutionPreset[]\n maxLevels?: number\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n plateFormat: 96,\n units: () => DEFAULT_UNITS,\n presets: () => DEFAULT_PRESETS,\n maxLevels: 24,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: ReagentDefinition]\n remove: []\n}>()\n\nconst { generateSeries, sortLevels, totalPositions } = useReagentSeries()\n\n// --- Local generator state ---\nconst generatorStart = ref(100)\nconst generatorCount = ref(8)\nconst manualValue = ref<number | undefined>(undefined)\n\n// --- Computed plate dimensions ---\nconst plateRows = computed(() => (props.plateFormat === 384 ? 16 : 8))\nconst plateCols = computed(() => (props.plateFormat === 384 ? 24 : 12))\n\nconst axisOptions = [\n { value: 'row', label: 'Rows' },\n { value: 'column', label: 'Columns' },\n]\n\n// --- Start position validation ---\nconst startPositionError = computed<string | null>(() => {\n const { axis, startPosition } = props.modelValue\n if (!startPosition) return null\n\n if (axis === 'row') {\n const letter = startPosition.toUpperCase()\n if (!/^[A-Z]$/.test(letter)) return 'Must be a letter'\n const idx = letter.charCodeAt(0) - 65\n if (idx >= plateRows.value) return `Max row: ${String.fromCharCode(64 + plateRows.value)}`\n } else {\n const num = parseInt(startPosition, 10)\n if (isNaN(num) || num < 1) return 'Must be a number ≥ 1'\n if (num > plateCols.value) return `Max col: ${plateCols.value}`\n }\n return null\n})\n\n// --- Position summary ---\nconst positionCount = computed(() => totalPositions(props.modelValue.levels))\n\nconst positionOverflows = computed(() => {\n const { axis, startPosition, levels } = props.modelValue\n if (!startPosition || levels.length === 0 || startPositionError.value) return false\n\n const span = positionCount.value\n if (axis === 'row') {\n const startIdx = startPosition.toUpperCase().charCodeAt(0) - 65\n return startIdx + span > plateRows.value\n } else {\n const startIdx = parseInt(startPosition, 10)\n return startIdx + span - 1 > plateCols.value\n }\n})\n\nconst positionRange = computed(() => {\n const { axis, startPosition, levels } = props.modelValue\n if (!startPosition || levels.length === 0 || startPositionError.value) return null\n\n const span = positionCount.value\n if (span === 0) return null\n\n if (axis === 'row') {\n const startIdx = startPosition.toUpperCase().charCodeAt(0) - 65\n const endIdx = Math.min(startIdx + span - 1, plateRows.value - 1)\n const startLetter = String.fromCharCode(65 + startIdx)\n const endLetter = String.fromCharCode(65 + endIdx)\n return `${startLetter}–${endLetter}`\n } else {\n const startIdx = parseInt(startPosition, 10)\n if (isNaN(startIdx)) return null\n const endIdx = Math.min(startIdx + span - 1, plateCols.value)\n return `${startIdx}–${endIdx}`\n }\n})\n\n// --- Update helpers ---\nfunction update(partial: Partial<ReagentDefinition>) {\n emit('update:modelValue', { ...props.modelValue, ...partial })\n}\n\nfunction updateName(event: Event) {\n update({ name: (event.target as HTMLInputElement).value })\n}\n\nfunction updateColor(event: Event) {\n update({ color: (event.target as HTMLInputElement).value })\n}\n\nfunction updateAxis(value: string | number) {\n update({ axis: value as 'row' | 'column', startPosition: '' })\n}\n\nfunction updateStartPosition(event: Event) {\n const raw = (event.target as HTMLInputElement).value\n update({ startPosition: raw })\n}\n\nfunction updateUnit(event: Event) {\n update({ unit: (event.target as HTMLSelectElement).value })\n}\n\n// --- Level manipulation ---\nfunction setLevels(levels: LevelEntry[]) {\n update({ levels: sortLevels(levels) })\n}\n\nfunction applyPreset(preset: DilutionPreset) {\n if (generatorStart.value <= 0 || generatorCount.value < 1) return\n const count = Math.min(generatorCount.value, props.maxLevels)\n const levels = generateSeries(generatorStart.value, count, preset.factor)\n setLevels(levels)\n}\n\nfunction addManualLevel() {\n if (manualValue.value === undefined || manualValue.value < 0) return\n if (props.modelValue.levels.length >= props.maxLevels) return\n\n const exists = props.modelValue.levels.some((l) => l.value === manualValue.value)\n if (exists) return\n\n setLevels([...props.modelValue.levels, { value: manualValue.value, replicates: 1 }])\n manualValue.value = undefined\n}\n\nfunction handleManualKeydown(event: KeyboardEvent) {\n if (event.key === 'Enter') {\n event.preventDefault()\n addManualLevel()\n }\n}\n\nfunction removeLevel(index: number) {\n const next = [...props.modelValue.levels]\n next.splice(index, 1)\n setLevels(next)\n}\n\nfunction updateLevelReplicates(index: number, replicates: number | undefined) {\n if (replicates === undefined || replicates < 1) return\n const next = [...props.modelValue.levels]\n next[index] = { ...next[index], replicates }\n update({ levels: next })\n}\n\nfunction formatLevel(value: number): string {\n if (!Number.isFinite(value)) return '–'\n if (value === 0) return 'Ctrl (0)'\n if (value >= 1000) return value.toLocaleString('en-US', { maximumSignificantDigits: 4 })\n if (value >= 1) return value.toString()\n if (value >= 0.001) return value.toFixed(3)\n return value.toExponential(1)\n}\n</script>\n\n<template>\n <div\n class=\"mint-reagent-editor\"\n :style=\"{ '--reagent-color': modelValue.color }\"\n >\n <!-- Identity -->\n <div class=\"mint-reagent-editor__section\">\n <div class=\"mint-reagent-editor__identity\">\n <label class=\"mint-reagent-editor__color-wrapper\">\n <input\n type=\"color\"\n :value=\"modelValue.color\"\n class=\"mint-reagent-editor__color-input\"\n aria-label=\"Reagent color\"\n @input=\"updateColor\"\n />\n <span\n class=\"mint-reagent-editor__color-swatch\"\n :style=\"{ backgroundColor: modelValue.color }\"\n />\n </label>\n <input\n type=\"text\"\n :value=\"modelValue.name\"\n placeholder=\"Reagent name\"\n class=\"mint-reagent-editor__name-input\"\n maxlength=\"64\"\n aria-label=\"Reagent name\"\n @input=\"updateName\"\n />\n </div>\n </div>\n\n <!-- Position Mapping -->\n <div class=\"mint-reagent-editor__section\">\n <div class=\"mint-reagent-editor__section-header\">\n <svg class=\"mint-reagent-editor__section-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" /><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" /><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" /><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" />\n </svg>\n Position\n </div>\n <div class=\"mint-reagent-editor__position-row\">\n <div class=\"mint-reagent-editor__field mint-reagent-editor__field--flex\">\n <label class=\"mint-reagent-editor__label\">Axis</label>\n <BaseSelect\n :model-value=\"modelValue.axis\"\n :options=\"axisOptions\"\n size=\"sm\"\n @update:model-value=\"updateAxis\"\n />\n </div>\n <div class=\"mint-reagent-editor__field mint-reagent-editor__field--flex\">\n <label class=\"mint-reagent-editor__label\">Start</label>\n <input\n type=\"text\"\n :value=\"modelValue.startPosition\"\n :placeholder=\"modelValue.axis === 'row' ? 'e.g. B' : 'e.g. 2'\"\n :class=\"[\n 'mint-reagent-editor__inline-input',\n startPositionError ? 'mint-reagent-editor__inline-input--error' : '',\n ]\"\n :maxlength=\"modelValue.axis === 'row' ? 1 : 2\"\n :aria-invalid=\"!!startPositionError\"\n aria-label=\"Start position\"\n @input=\"updateStartPosition\"\n />\n <span v-if=\"startPositionError\" class=\"mint-reagent-editor__field-error\">\n {{ startPositionError }}\n </span>\n </div>\n </div>\n </div>\n\n <!-- Series -->\n <div class=\"mint-reagent-editor__section\">\n <div class=\"mint-reagent-editor__section-header\">\n <svg class=\"mint-reagent-editor__section-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M3 3v18h18\" /><path d=\"m19 9-5 5-4-4-3 3\" />\n </svg>\n Series\n <select\n :value=\"modelValue.unit\"\n class=\"mint-reagent-editor__unit-select\"\n @change=\"updateUnit\"\n >\n <option v-for=\"u in units\" :key=\"u\" :value=\"u\">{{ u }}</option>\n </select>\n </div>\n\n <!-- Dilution generator -->\n <div class=\"mint-reagent-editor__generator\">\n <div class=\"mint-reagent-editor__generator-inputs\">\n <div class=\"mint-reagent-editor__field mint-reagent-editor__field--flex\">\n <label class=\"mint-reagent-editor__label\">Start</label>\n <NumberInput v-model=\"generatorStart\" :min=\"0\" :step=\"10\" size=\"sm\" />\n </div>\n <div class=\"mint-reagent-editor__field mint-reagent-editor__field--flex\">\n <label class=\"mint-reagent-editor__label\">Doses</label>\n <NumberInput v-model=\"generatorCount\" :min=\"2\" :max=\"maxLevels\" :step=\"1\" size=\"sm\" />\n </div>\n </div>\n <div class=\"mint-reagent-editor__presets\">\n <button\n v-for=\"preset in presets\"\n :key=\"preset.label\"\n type=\"button\"\n class=\"mint-reagent-editor__preset-btn\"\n @click=\"applyPreset(preset)\"\n >\n {{ preset.label }}\n </button>\n </div>\n </div>\n\n <!-- Manual entry -->\n <div class=\"mint-reagent-editor__manual\" @keydown=\"handleManualKeydown\">\n <NumberInput\n v-model=\"manualValue\"\n :min=\"0\"\n :step=\"1\"\n size=\"sm\"\n placeholder=\"Add value\"\n />\n <button\n type=\"button\"\n class=\"mint-reagent-editor__add-btn\"\n :disabled=\"manualValue === undefined || manualValue < 0 || modelValue.levels.length >= maxLevels\"\n aria-label=\"Add level\"\n @click=\"addManualLevel\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M5 12h14\" /><path d=\"M12 5v14\" />\n </svg>\n </button>\n </div>\n\n <!-- Level table -->\n <div v-if=\"modelValue.levels.length > 0\" class=\"mint-reagent-editor__table\">\n <div class=\"mint-reagent-editor__table-header\">\n <span class=\"mint-reagent-editor__table-col mint-reagent-editor__table-col--level\">Level</span>\n <span class=\"mint-reagent-editor__table-col mint-reagent-editor__table-col--reps\">Reps</span>\n <span class=\"mint-reagent-editor__table-col mint-reagent-editor__table-col--action\" />\n </div>\n <div class=\"mint-reagent-editor__table-body\">\n <div\n v-for=\"(level, i) in modelValue.levels\"\n :key=\"level.value\"\n class=\"mint-reagent-editor__table-row\"\n >\n <span class=\"mint-reagent-editor__table-col mint-reagent-editor__table-col--level\">\n <span class=\"mint-reagent-editor__level-value\">{{ formatLevel(level.value) }}</span>\n <span v-if=\"level.value !== 0\" class=\"mint-reagent-editor__level-unit\">{{ modelValue.unit }}</span>\n </span>\n <span class=\"mint-reagent-editor__table-col mint-reagent-editor__table-col--reps\">\n <NumberInput\n :model-value=\"level.replicates\"\n :min=\"1\"\n :max=\"12\"\n :step=\"1\"\n size=\"sm\"\n @update:model-value=\"updateLevelReplicates(i, $event)\"\n />\n </span>\n <span class=\"mint-reagent-editor__table-col mint-reagent-editor__table-col--action\">\n <button\n type=\"button\"\n class=\"mint-reagent-editor__remove-level-btn\"\n aria-label=\"Remove level\"\n @click=\"removeLevel(i)\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </span>\n </div>\n </div>\n </div>\n\n <div v-else class=\"mint-reagent-editor__empty\">\n Use a preset above or add levels manually\n </div>\n </div>\n\n <!-- Position Summary -->\n <div\n v-if=\"modelValue.levels.length > 0 && modelValue.startPosition && !startPositionError\"\n :class=\"[\n 'mint-reagent-editor__summary',\n positionOverflows ? 'mint-reagent-editor__summary--overflow' : '',\n ]\"\n >\n <div class=\"mint-reagent-editor__summary-item\">\n <span class=\"mint-reagent-editor__summary-value\">{{ modelValue.levels.length }}</span>\n <span class=\"mint-reagent-editor__summary-label\">levels</span>\n </div>\n <div class=\"mint-reagent-editor__summary-dot\" />\n <div class=\"mint-reagent-editor__summary-item\">\n <span class=\"mint-reagent-editor__summary-value\">{{ positionCount }}</span>\n <span class=\"mint-reagent-editor__summary-label\">positions</span>\n </div>\n <div v-if=\"positionRange\" class=\"mint-reagent-editor__summary-dot\" />\n <div v-if=\"positionRange\" class=\"mint-reagent-editor__summary-item\">\n <span class=\"mint-reagent-editor__summary-value\">{{ positionRange }}</span>\n <span class=\"mint-reagent-editor__summary-label\">{{ modelValue.axis === 'row' ? 'rows' : 'cols' }}</span>\n </div>\n <span v-if=\"positionOverflows\" class=\"mint-reagent-editor__summary-warning\">\n Exceeds plate\n </span>\n </div>\n\n <!-- Remove -->\n <div class=\"mint-reagent-editor__footer\">\n <BaseButton variant=\"danger\" size=\"sm\" @click=\"emit('remove')\">\n Remove Reagent\n </BaseButton>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/reagent-editor.css';\n</style>\n","<script setup lang=\"ts\">\n/** Collapsible tree visualizing the biological sample hierarchy (study → experiment → plate → sample → cell line → passage → clone → treatment). */\nimport { h, Transition, type VNode } from 'vue'\nimport type { TreeNodeType, BadgeVariant, TreeNode } from '../types'\nimport { useExpansionSet } from '../composables/useExpansionSet'\n\ninterface Props {\n nodes: TreeNode[]\n defaultExpandedIds?: string[]\n expandAll?: boolean\n showIcons?: boolean\n showCounts?: boolean\n maxDepth?: number\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n defaultExpandedIds: () => [],\n expandAll: false,\n showIcons: true,\n showCounts: true,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'node-click': [node: TreeNode]\n 'expand': [nodeId: string]\n 'collapse': [nodeId: string]\n}>()\n\n// Default icons by type (Lucide SVG paths - arrays to support multi-path icons)\ninterface IconElement {\n tag: 'path' | 'circle' | 'rect'\n attrs: Record<string, string | number>\n}\n\nconst typeIcons: Record<TreeNodeType, IconElement[]> = {\n study: [\n { tag: 'path', attrs: { d: 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' } },\n ],\n experiment: [\n { tag: 'path', attrs: { d: 'M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2' } },\n { tag: 'path', attrs: { d: 'M6.453 15h11.094' } },\n { tag: 'path', attrs: { d: 'M8.5 2h7' } },\n ],\n plate: [\n { tag: 'rect', attrs: { width: '7', height: '7', x: '3', y: '3', rx: '1' } },\n { tag: 'rect', attrs: { width: '7', height: '7', x: '14', y: '3', rx: '1' } },\n { tag: 'rect', attrs: { width: '7', height: '7', x: '14', y: '14', rx: '1' } },\n { tag: 'rect', attrs: { width: '7', height: '7', x: '3', y: '14', rx: '1' } },\n ],\n sample: [\n { tag: 'circle', attrs: { cx: '12', cy: '12', r: '10' } },\n { tag: 'path', attrs: { d: 'M12 6v6l4 2' } },\n ],\n cell_line: [\n { tag: 'path', attrs: { d: 'M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5' } },\n { tag: 'path', attrs: { d: 'M9 18h6' } },\n { tag: 'path', attrs: { d: 'M10 22h4' } },\n ],\n passage: [\n { tag: 'path', attrs: { d: 'M3 5h.01' } },\n { tag: 'path', attrs: { d: 'M3 12h.01' } },\n { tag: 'path', attrs: { d: 'M3 19h.01' } },\n { tag: 'path', attrs: { d: 'M8 5h13' } },\n { tag: 'path', attrs: { d: 'M8 12h13' } },\n { tag: 'path', attrs: { d: 'M8 19h13' } },\n ],\n clone: [\n { tag: 'rect', attrs: { width: '14', height: '14', x: '8', y: '8', rx: '2', ry: '2' } },\n { tag: 'path', attrs: { d: 'M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2' } },\n ],\n treatment: [\n { tag: 'path', attrs: { d: 'M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2' } },\n { tag: 'path', attrs: { d: 'M6.453 15h11.094' } },\n { tag: 'path', attrs: { d: 'M8.5 2h7' } },\n ],\n folder: [\n { tag: 'path', attrs: { d: 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' } },\n ],\n custom: [\n { tag: 'path', attrs: { d: 'M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915' } },\n { tag: 'circle', attrs: { cx: '12', cy: '12', r: '3' } },\n ],\n}\n\n// Collect all node IDs for expand all\nfunction collectAllIds(nodes: TreeNode[]): string[] {\n const ids: string[] = []\n function traverse(node: TreeNode) {\n ids.push(node.id)\n if (node.children) {\n node.children.forEach(traverse)\n }\n }\n nodes.forEach(traverse)\n return ids\n}\n\nconst expansion = useExpansionSet({\n defaultIds: () => props.defaultExpandedIds,\n allIds: () => collectAllIds(props.nodes),\n expandAll: () => props.expandAll,\n})\n\nfunction isExpanded(nodeId: string): boolean {\n return expansion.isExpanded(nodeId)\n}\n\nfunction toggleExpand(node: TreeNode) {\n const expanded = expansion.toggle(node.id)\n if (expanded) {\n emit('expand', node.id)\n } else {\n emit('collapse', node.id)\n }\n}\n\nfunction handleNodeClick(node: TreeNode) {\n emit('node-click', node)\n}\n\nfunction getIconElements(node: TreeNode): IconElement[] {\n if (node.icon) return [{ tag: 'path', attrs: { d: node.icon } }]\n return typeIcons[node.type || 'custom']\n}\n\nfunction getBadgeContent(node: TreeNode): string | number | undefined {\n if (node.badge !== undefined) return node.badge\n if (props.showCounts && node.children && node.children.length > 0) {\n return node.children.length\n }\n return undefined\n}\n\nfunction getBadgeVariant(node: TreeNode): BadgeVariant {\n return node.badgeVariant || 'default'\n}\n\nfunction hasChildren(node: TreeNode): boolean {\n return !!node.children && node.children.length > 0\n}\n\nfunction canShowChildren(node: TreeNode, depth: number): boolean {\n if (!hasChildren(node)) return false\n if (props.maxDepth !== undefined && depth >= props.maxDepth) return false\n return isExpanded(node.id)\n}\n\n// Render tree node recursively\nfunction renderNode(node: TreeNode, depth: number): VNode {\n const children = node.children\n const expanded = isExpanded(node.id)\n const canExpand = hasChildren(node)\n const showChildNodes = canShowChildren(node, depth)\n const badge = getBadgeContent(node)\n const badgeVar = getBadgeVariant(node)\n const iconElements = getIconElements(node)\n\n const header = h(\n 'div',\n {\n class: 'mint-sample-tree__node-header',\n onClick: () => handleNodeClick(node),\n },\n [\n canExpand\n ? h(\n 'button',\n {\n type: 'button',\n class: 'mint-sample-tree__toggle',\n 'aria-label': expanded ? 'Collapse' : 'Expand',\n onClick: (e: Event) => {\n e.stopPropagation()\n toggleExpand(node)\n },\n },\n [\n h('svg', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' }, [\n h('path', { d: 'm9 18 6-6-6-6' }),\n ]),\n ]\n )\n : h('span', { class: 'mint-sample-tree__toggle-placeholder' }),\n props.showIcons\n ? h('span', { class: ['mint-sample-tree__icon', `mint-sample-tree__icon--${node.type || 'custom'}`] }, [\n h('svg', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' },\n iconElements.map(el => h(el.tag, el.attrs))\n ),\n ])\n : null,\n h('span', { class: 'mint-sample-tree__label' }, node.label),\n badge !== undefined\n ? h('span', { class: ['mint-sample-tree__badge', `mint-sample-tree__badge--${badgeVar}`] }, String(badge))\n : null,\n ]\n )\n\n const childNodes =\n showChildNodes && children\n ? h(\n Transition,\n { enterActiveClass: 'mint-sample-tree__children--entering', leaveActiveClass: 'mint-sample-tree__children--leaving' },\n () => h('div', { class: 'mint-sample-tree__children' }, children.map((child) => renderNode(child, depth + 1)))\n )\n : null\n\n return h(\n 'div',\n {\n key: node.id,\n class: ['mint-sample-tree__node', expanded ? 'mint-sample-tree__node--expanded' : '', !canExpand ? 'mint-sample-tree__node--leaf' : ''],\n role: 'treeitem',\n 'aria-expanded': canExpand ? expanded : undefined,\n },\n [header, childNodes]\n )\n}\n\n// Render the entire tree\nfunction renderTree(): VNode[] {\n return props.nodes.map((node) => renderNode(node, 0))\n}\n</script>\n\n<template>\n <div :class=\"['mint-sample-tree', `mint-sample-tree--${size}`]\" role=\"tree\">\n <!-- Empty state -->\n <div v-if=\"nodes.length === 0\" class=\"mint-sample-tree__empty\">\n <svg class=\"mint-sample-tree__empty-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\" />\n </svg>\n <p class=\"mint-sample-tree__empty-text\">No items</p>\n </div>\n\n <!-- Tree nodes rendered via render function -->\n <template v-else>\n <component :is=\"() => renderTree()\" />\n </template>\n </div>\n</template>\n\n<style>\n@import '../styles/components/sample-hierarchy-tree.css';\n</style>\n","<script setup lang=\"ts\">\n/** Collapsible tree visualizing the biological sample hierarchy (study → experiment → plate → sample → cell line → passage → clone → treatment). */\nimport { h, Transition, type VNode } from 'vue'\nimport type { TreeNodeType, BadgeVariant, TreeNode } from '../types'\nimport { useExpansionSet } from '../composables/useExpansionSet'\n\ninterface Props {\n nodes: TreeNode[]\n defaultExpandedIds?: string[]\n expandAll?: boolean\n showIcons?: boolean\n showCounts?: boolean\n maxDepth?: number\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n defaultExpandedIds: () => [],\n expandAll: false,\n showIcons: true,\n showCounts: true,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'node-click': [node: TreeNode]\n 'expand': [nodeId: string]\n 'collapse': [nodeId: string]\n}>()\n\n// Default icons by type (Lucide SVG paths - arrays to support multi-path icons)\ninterface IconElement {\n tag: 'path' | 'circle' | 'rect'\n attrs: Record<string, string | number>\n}\n\nconst typeIcons: Record<TreeNodeType, IconElement[]> = {\n study: [\n { tag: 'path', attrs: { d: 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' } },\n ],\n experiment: [\n { tag: 'path', attrs: { d: 'M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2' } },\n { tag: 'path', attrs: { d: 'M6.453 15h11.094' } },\n { tag: 'path', attrs: { d: 'M8.5 2h7' } },\n ],\n plate: [\n { tag: 'rect', attrs: { width: '7', height: '7', x: '3', y: '3', rx: '1' } },\n { tag: 'rect', attrs: { width: '7', height: '7', x: '14', y: '3', rx: '1' } },\n { tag: 'rect', attrs: { width: '7', height: '7', x: '14', y: '14', rx: '1' } },\n { tag: 'rect', attrs: { width: '7', height: '7', x: '3', y: '14', rx: '1' } },\n ],\n sample: [\n { tag: 'circle', attrs: { cx: '12', cy: '12', r: '10' } },\n { tag: 'path', attrs: { d: 'M12 6v6l4 2' } },\n ],\n cell_line: [\n { tag: 'path', attrs: { d: 'M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5' } },\n { tag: 'path', attrs: { d: 'M9 18h6' } },\n { tag: 'path', attrs: { d: 'M10 22h4' } },\n ],\n passage: [\n { tag: 'path', attrs: { d: 'M3 5h.01' } },\n { tag: 'path', attrs: { d: 'M3 12h.01' } },\n { tag: 'path', attrs: { d: 'M3 19h.01' } },\n { tag: 'path', attrs: { d: 'M8 5h13' } },\n { tag: 'path', attrs: { d: 'M8 12h13' } },\n { tag: 'path', attrs: { d: 'M8 19h13' } },\n ],\n clone: [\n { tag: 'rect', attrs: { width: '14', height: '14', x: '8', y: '8', rx: '2', ry: '2' } },\n { tag: 'path', attrs: { d: 'M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2' } },\n ],\n treatment: [\n { tag: 'path', attrs: { d: 'M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2' } },\n { tag: 'path', attrs: { d: 'M6.453 15h11.094' } },\n { tag: 'path', attrs: { d: 'M8.5 2h7' } },\n ],\n folder: [\n { tag: 'path', attrs: { d: 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' } },\n ],\n custom: [\n { tag: 'path', attrs: { d: 'M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915' } },\n { tag: 'circle', attrs: { cx: '12', cy: '12', r: '3' } },\n ],\n}\n\n// Collect all node IDs for expand all\nfunction collectAllIds(nodes: TreeNode[]): string[] {\n const ids: string[] = []\n function traverse(node: TreeNode) {\n ids.push(node.id)\n if (node.children) {\n node.children.forEach(traverse)\n }\n }\n nodes.forEach(traverse)\n return ids\n}\n\nconst expansion = useExpansionSet({\n defaultIds: () => props.defaultExpandedIds,\n allIds: () => collectAllIds(props.nodes),\n expandAll: () => props.expandAll,\n})\n\nfunction isExpanded(nodeId: string): boolean {\n return expansion.isExpanded(nodeId)\n}\n\nfunction toggleExpand(node: TreeNode) {\n const expanded = expansion.toggle(node.id)\n if (expanded) {\n emit('expand', node.id)\n } else {\n emit('collapse', node.id)\n }\n}\n\nfunction handleNodeClick(node: TreeNode) {\n emit('node-click', node)\n}\n\nfunction getIconElements(node: TreeNode): IconElement[] {\n if (node.icon) return [{ tag: 'path', attrs: { d: node.icon } }]\n return typeIcons[node.type || 'custom']\n}\n\nfunction getBadgeContent(node: TreeNode): string | number | undefined {\n if (node.badge !== undefined) return node.badge\n if (props.showCounts && node.children && node.children.length > 0) {\n return node.children.length\n }\n return undefined\n}\n\nfunction getBadgeVariant(node: TreeNode): BadgeVariant {\n return node.badgeVariant || 'default'\n}\n\nfunction hasChildren(node: TreeNode): boolean {\n return !!node.children && node.children.length > 0\n}\n\nfunction canShowChildren(node: TreeNode, depth: number): boolean {\n if (!hasChildren(node)) return false\n if (props.maxDepth !== undefined && depth >= props.maxDepth) return false\n return isExpanded(node.id)\n}\n\n// Render tree node recursively\nfunction renderNode(node: TreeNode, depth: number): VNode {\n const children = node.children\n const expanded = isExpanded(node.id)\n const canExpand = hasChildren(node)\n const showChildNodes = canShowChildren(node, depth)\n const badge = getBadgeContent(node)\n const badgeVar = getBadgeVariant(node)\n const iconElements = getIconElements(node)\n\n const header = h(\n 'div',\n {\n class: 'mint-sample-tree__node-header',\n onClick: () => handleNodeClick(node),\n },\n [\n canExpand\n ? h(\n 'button',\n {\n type: 'button',\n class: 'mint-sample-tree__toggle',\n 'aria-label': expanded ? 'Collapse' : 'Expand',\n onClick: (e: Event) => {\n e.stopPropagation()\n toggleExpand(node)\n },\n },\n [\n h('svg', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' }, [\n h('path', { d: 'm9 18 6-6-6-6' }),\n ]),\n ]\n )\n : h('span', { class: 'mint-sample-tree__toggle-placeholder' }),\n props.showIcons\n ? h('span', { class: ['mint-sample-tree__icon', `mint-sample-tree__icon--${node.type || 'custom'}`] }, [\n h('svg', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' },\n iconElements.map(el => h(el.tag, el.attrs))\n ),\n ])\n : null,\n h('span', { class: 'mint-sample-tree__label' }, node.label),\n badge !== undefined\n ? h('span', { class: ['mint-sample-tree__badge', `mint-sample-tree__badge--${badgeVar}`] }, String(badge))\n : null,\n ]\n )\n\n const childNodes =\n showChildNodes && children\n ? h(\n Transition,\n { enterActiveClass: 'mint-sample-tree__children--entering', leaveActiveClass: 'mint-sample-tree__children--leaving' },\n () => h('div', { class: 'mint-sample-tree__children' }, children.map((child) => renderNode(child, depth + 1)))\n )\n : null\n\n return h(\n 'div',\n {\n key: node.id,\n class: ['mint-sample-tree__node', expanded ? 'mint-sample-tree__node--expanded' : '', !canExpand ? 'mint-sample-tree__node--leaf' : ''],\n role: 'treeitem',\n 'aria-expanded': canExpand ? expanded : undefined,\n },\n [header, childNodes]\n )\n}\n\n// Render the entire tree\nfunction renderTree(): VNode[] {\n return props.nodes.map((node) => renderNode(node, 0))\n}\n</script>\n\n<template>\n <div :class=\"['mint-sample-tree', `mint-sample-tree--${size}`]\" role=\"tree\">\n <!-- Empty state -->\n <div v-if=\"nodes.length === 0\" class=\"mint-sample-tree__empty\">\n <svg class=\"mint-sample-tree__empty-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\" />\n </svg>\n <p class=\"mint-sample-tree__empty-text\">No items</p>\n </div>\n\n <!-- Tree nodes rendered via render function -->\n <template v-else>\n <component :is=\"() => renderTree()\" />\n </template>\n </div>\n</template>\n\n<style>\n@import '../styles/components/sample-hierarchy-tree.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders one typed parameter control for ProtocolStepEditor. */\nimport { computed } from 'vue'\nimport type { ParameterDefinition } from '../composables/useProtocolTemplates'\nimport type { ConcentrationValue } from '../composables/useConcentrationUnits'\nimport ConcentrationInput from './ConcentrationInput.vue'\n\ninterface Props {\n parameter: ParameterDefinition\n modelValue?: unknown\n error?: string\n}\n\nconst props = defineProps<Props>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: unknown]\n}>()\n\nconst scalarValue = computed(() => (\n props.modelValue as string | number | undefined\n))\n\nconst concentrationValue = computed(() => (\n props.modelValue as ConcentrationValue | undefined\n))\n\nconst hasError = computed(() => !!props.error)\n\nfunction handleNumberInput(event: Event) {\n emit('update:modelValue', (event.target as HTMLInputElement).valueAsNumber)\n}\n\nfunction handleTextInput(event: Event) {\n emit('update:modelValue', (event.target as HTMLInputElement).value)\n}\n\nfunction handleSelectChange(event: Event) {\n emit('update:modelValue', (event.target as HTMLSelectElement).value)\n}\n\nfunction handleConcentrationChange(value: ConcentrationValue | undefined) {\n emit('update:modelValue', value)\n}\n</script>\n\n<template>\n <div class=\"mint-protocol-editor__field\">\n <label\n :class=\"[\n 'mint-protocol-editor__field-label',\n parameter.required ? 'mint-protocol-editor__field-label--required' : '',\n ]\"\n >\n {{ parameter.label }}\n </label>\n\n <div\n v-if=\"parameter.type === 'number' || parameter.type === 'temperature' || parameter.type === 'duration'\"\n class=\"mint-protocol-editor__input-unit\"\n >\n <input\n type=\"number\"\n :value=\"scalarValue\"\n :min=\"parameter.min\"\n :max=\"parameter.max\"\n :placeholder=\"parameter.placeholder || `Enter ${parameter.label.toLowerCase()}`\"\n :class=\"[\n 'mint-protocol-editor__input',\n hasError ? 'mint-protocol-editor__input--error' : '',\n ]\"\n @input=\"handleNumberInput\"\n />\n <span v-if=\"parameter.unit\" class=\"mint-protocol-editor__unit-suffix\">\n {{ parameter.unit }}\n </span>\n </div>\n\n <input\n v-else-if=\"parameter.type === 'text'\"\n type=\"text\"\n :value=\"scalarValue\"\n :placeholder=\"parameter.placeholder || `Enter ${parameter.label.toLowerCase()}`\"\n :class=\"[\n 'mint-protocol-editor__input',\n hasError ? 'mint-protocol-editor__input--error' : '',\n ]\"\n @input=\"handleTextInput\"\n />\n\n <select\n v-else-if=\"parameter.type === 'select'\"\n :value=\"scalarValue\"\n :class=\"[\n 'mint-protocol-editor__select',\n hasError ? 'mint-protocol-editor__input--error' : '',\n ]\"\n @change=\"handleSelectChange\"\n >\n <option value=\"\" disabled>Select {{ parameter.label.toLowerCase() }}</option>\n <option\n v-for=\"option in parameter.options\"\n :key=\"option.value\"\n :value=\"option.value\"\n >\n {{ option.label }}\n </option>\n </select>\n\n <ConcentrationInput\n v-else-if=\"parameter.type === 'concentration'\"\n :model-value=\"concentrationValue\"\n :error=\"hasError\"\n size=\"md\"\n @update:model-value=\"handleConcentrationChange\"\n />\n\n <input\n v-else-if=\"parameter.type === 'reagent'\"\n type=\"text\"\n :value=\"scalarValue\"\n :placeholder=\"parameter.placeholder || 'Enter reagent name'\"\n :class=\"[\n 'mint-protocol-editor__input',\n hasError ? 'mint-protocol-editor__input--error' : '',\n ]\"\n @input=\"handleTextInput\"\n />\n\n <span v-if=\"error\" class=\"mint-protocol-editor__field-error\">\n {{ error }}\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Renders one typed parameter control for ProtocolStepEditor. */\nimport { computed } from 'vue'\nimport type { ParameterDefinition } from '../composables/useProtocolTemplates'\nimport type { ConcentrationValue } from '../composables/useConcentrationUnits'\nimport ConcentrationInput from './ConcentrationInput.vue'\n\ninterface Props {\n parameter: ParameterDefinition\n modelValue?: unknown\n error?: string\n}\n\nconst props = defineProps<Props>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: unknown]\n}>()\n\nconst scalarValue = computed(() => (\n props.modelValue as string | number | undefined\n))\n\nconst concentrationValue = computed(() => (\n props.modelValue as ConcentrationValue | undefined\n))\n\nconst hasError = computed(() => !!props.error)\n\nfunction handleNumberInput(event: Event) {\n emit('update:modelValue', (event.target as HTMLInputElement).valueAsNumber)\n}\n\nfunction handleTextInput(event: Event) {\n emit('update:modelValue', (event.target as HTMLInputElement).value)\n}\n\nfunction handleSelectChange(event: Event) {\n emit('update:modelValue', (event.target as HTMLSelectElement).value)\n}\n\nfunction handleConcentrationChange(value: ConcentrationValue | undefined) {\n emit('update:modelValue', value)\n}\n</script>\n\n<template>\n <div class=\"mint-protocol-editor__field\">\n <label\n :class=\"[\n 'mint-protocol-editor__field-label',\n parameter.required ? 'mint-protocol-editor__field-label--required' : '',\n ]\"\n >\n {{ parameter.label }}\n </label>\n\n <div\n v-if=\"parameter.type === 'number' || parameter.type === 'temperature' || parameter.type === 'duration'\"\n class=\"mint-protocol-editor__input-unit\"\n >\n <input\n type=\"number\"\n :value=\"scalarValue\"\n :min=\"parameter.min\"\n :max=\"parameter.max\"\n :placeholder=\"parameter.placeholder || `Enter ${parameter.label.toLowerCase()}`\"\n :class=\"[\n 'mint-protocol-editor__input',\n hasError ? 'mint-protocol-editor__input--error' : '',\n ]\"\n @input=\"handleNumberInput\"\n />\n <span v-if=\"parameter.unit\" class=\"mint-protocol-editor__unit-suffix\">\n {{ parameter.unit }}\n </span>\n </div>\n\n <input\n v-else-if=\"parameter.type === 'text'\"\n type=\"text\"\n :value=\"scalarValue\"\n :placeholder=\"parameter.placeholder || `Enter ${parameter.label.toLowerCase()}`\"\n :class=\"[\n 'mint-protocol-editor__input',\n hasError ? 'mint-protocol-editor__input--error' : '',\n ]\"\n @input=\"handleTextInput\"\n />\n\n <select\n v-else-if=\"parameter.type === 'select'\"\n :value=\"scalarValue\"\n :class=\"[\n 'mint-protocol-editor__select',\n hasError ? 'mint-protocol-editor__input--error' : '',\n ]\"\n @change=\"handleSelectChange\"\n >\n <option value=\"\" disabled>Select {{ parameter.label.toLowerCase() }}</option>\n <option\n v-for=\"option in parameter.options\"\n :key=\"option.value\"\n :value=\"option.value\"\n >\n {{ option.label }}\n </option>\n </select>\n\n <ConcentrationInput\n v-else-if=\"parameter.type === 'concentration'\"\n :model-value=\"concentrationValue\"\n :error=\"hasError\"\n size=\"md\"\n @update:model-value=\"handleConcentrationChange\"\n />\n\n <input\n v-else-if=\"parameter.type === 'reagent'\"\n type=\"text\"\n :value=\"scalarValue\"\n :placeholder=\"parameter.placeholder || 'Enter reagent name'\"\n :class=\"[\n 'mint-protocol-editor__input',\n hasError ? 'mint-protocol-editor__input--error' : '',\n ]\"\n @input=\"handleTextInput\"\n />\n\n <span v-if=\"error\" class=\"mint-protocol-editor__field-error\">\n {{ error }}\n </span>\n </div>\n</template>\n","import type { ProtocolStep, ProtocolStepStatus } from '../types'\nimport type { ParameterDefinition, StepTemplate } from '../composables/useProtocolTemplates'\n\nexport interface ProtocolStepEditorFields {\n name: string\n description: string\n duration?: number\n parameters: Record<string, unknown>\n}\n\nexport interface InitializedProtocolStepEditorState {\n selectedTemplateId: string | null\n fields: ProtocolStepEditorFields\n}\n\nexport type ProtocolParameterFormatter = (\n value: unknown,\n parameter: ParameterDefinition\n) => string\n\nexport function buildProtocolTemplateDefaults(\n template: StepTemplate\n): ProtocolStepEditorFields {\n const parameters: Record<string, unknown> = {}\n for (const parameter of template.parameters) {\n if (parameter.default !== undefined) {\n parameters[parameter.key] = parameter.default\n }\n }\n\n return {\n name: template.name,\n description: template.description || '',\n duration: template.defaultDuration,\n parameters,\n }\n}\n\nexport function buildProtocolStepEditorState(\n step: ProtocolStep,\n getTemplateByType: (type: ProtocolStep['type']) => StepTemplate | undefined\n): InitializedProtocolStepEditorState {\n const template = getTemplateByType(step.type)\n\n return {\n selectedTemplateId: template?.id || null,\n fields: {\n name: step.name,\n description: step.description || '',\n duration: step.duration,\n parameters: { ...(step.parameters || {}) },\n },\n }\n}\n\nexport function buildProtocolStepPreview(\n template: StepTemplate,\n fields: ProtocolStepEditorFields,\n sourceStep?: ProtocolStep\n): ProtocolStep {\n return {\n id: sourceStep?.id || 'preview',\n type: template.type,\n name: fields.name || template.name,\n description: fields.description,\n duration: fields.duration ?? template.defaultDuration,\n status: (sourceStep?.status || 'pending') as ProtocolStepStatus,\n parameters: { ...fields.parameters },\n order: sourceStep?.order || 0,\n }\n}\n\nexport function buildProtocolPreviewParams(\n template: StepTemplate,\n parameters: Record<string, unknown>,\n formatParameterValue: ProtocolParameterFormatter\n): string {\n return template.parameters\n .filter((parameter) => (\n parameters[parameter.key] !== undefined\n && parameters[parameter.key] !== ''\n ))\n .map((parameter) => formatParameterValue(parameters[parameter.key], parameter))\n .join(', ')\n}\n\nexport function buildCustomProtocolTemplate(\n template: StepTemplate,\n fields: ProtocolStepEditorFields,\n templateId = `custom-${Date.now()}`\n): StepTemplate {\n return {\n id: templateId,\n type: template.type,\n name: fields.name || template.name,\n description: fields.description,\n defaultDuration: fields.duration,\n parameters: template.parameters.map((parameter) => ({\n ...parameter,\n default: fields.parameters[parameter.key],\n })),\n isBuiltIn: false,\n }\n}\n","<script setup lang=\"ts\">\n/** Form for creating or editing a single protocol step (incubation, wash, addition, centrifuge, etc.) with template picker, typed parameters, and duration. */\nimport { ref, computed, watch, onMounted } from 'vue'\nimport type { ProtocolStep } from '../types'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport {\n useProtocolTemplates,\n type StepTemplate,\n} from '../composables/useProtocolTemplates'\nimport {\n PROTOCOL_STEP_TYPE_ICONS as stepTypeIcons,\n formatProtocolDuration as formatDuration,\n} from './ProtocolStep.presentation'\nimport ProtocolStepParameterField from './ProtocolStepParameterField.vue'\nimport {\n buildCustomProtocolTemplate,\n buildProtocolPreviewParams,\n buildProtocolStepEditorState,\n buildProtocolStepPreview,\n buildProtocolTemplateDefaults,\n type ProtocolStepEditorFields,\n} from './ProtocolStepEditor.state'\n\ninterface Props {\n modelValue?: ProtocolStep\n templates?: StepTemplate[]\n customTemplates?: StepTemplate[]\n mode?: 'create' | 'edit'\n showPreview?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n mode: 'create',\n showPreview: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [step: ProtocolStep]\n 'save': [step: ProtocolStep]\n 'save-template': [template: StepTemplate]\n 'cancel': []\n}>()\n\nconst {\n allTemplates,\n getTemplateByType,\n validateStep,\n formatParameterValue,\n} = useProtocolTemplates()\n\n// State\nconst selectedTemplateId = ref<string | null>(null)\nconst {\n isOpen: templateDropdownOpen,\n rootRef: templateDropdownRef,\n close: closeTemplateDropdown,\n toggle: toggleTemplateDropdown,\n} = useDropdownState()\nconst stepName = ref('')\nconst stepDescription = ref('')\nconst stepDuration = ref<number | undefined>()\nconst parameters = ref<Record<string, unknown>>({})\nconst validationErrors = ref<Record<string, string>>({})\n\n// Combined templates from props and composable\nconst availableTemplates = computed(() => {\n if (props.templates && props.templates.length > 0) {\n return props.templates\n }\n const combined = [...allTemplates.value]\n if (props.customTemplates) {\n combined.push(...props.customTemplates)\n }\n return combined\n})\n\n// Selected template\nconst selectedTemplate = computed(() => {\n if (!selectedTemplateId.value) return null\n return availableTemplates.value.find((t) => t.id === selectedTemplateId.value) || null\n})\n\n// Preview computed\nconst previewStep = computed<ProtocolStep | null>(() => {\n if (!selectedTemplate.value) return null\n return buildProtocolStepPreview(selectedTemplate.value, editorFields(), props.modelValue)\n})\n\nconst previewParams = computed(() => {\n if (!selectedTemplate.value || !previewStep.value) return ''\n return buildProtocolPreviewParams(\n selectedTemplate.value,\n parameters.value,\n formatParameterValue\n )\n})\n\nfunction editorFields(): ProtocolStepEditorFields {\n return {\n name: stepName.value,\n description: stepDescription.value,\n duration: stepDuration.value,\n parameters: parameters.value,\n }\n}\n\nfunction applyEditorFields(fields: ProtocolStepEditorFields) {\n stepName.value = fields.name\n stepDescription.value = fields.description\n stepDuration.value = fields.duration\n parameters.value = { ...fields.parameters }\n}\n\nfunction initFromStep(step: ProtocolStep) {\n const state = buildProtocolStepEditorState(step, getTemplateByType)\n selectedTemplateId.value = state.selectedTemplateId\n applyEditorFields(state.fields)\n}\n\n// Handle template selection\nfunction selectTemplate(template: StepTemplate) {\n selectedTemplateId.value = template.id\n closeTemplateDropdown()\n\n // Reset to template defaults if creating new\n if (props.mode === 'create') {\n applyEditorFields(buildProtocolTemplateDefaults(template))\n }\n}\n\n// Handle parameter change\nfunction handleParamChange(key: string, value: unknown) {\n parameters.value[key] = value\n // Clear validation error when user changes value\n if (validationErrors.value[key]) {\n delete validationErrors.value[key]\n }\n}\n\n// Validate and save\nfunction handleSave() {\n if (!selectedTemplate.value || !previewStep.value) return\n\n const result = validateStep(previewStep.value, selectedTemplate.value)\n validationErrors.value = result.errors\n\n if (result.valid) {\n emit('save', previewStep.value)\n emit('update:modelValue', previewStep.value)\n }\n}\n\n// Save as template\nfunction handleSaveTemplate() {\n if (!selectedTemplate.value) return\n emit('save-template', buildCustomProtocolTemplate(selectedTemplate.value, editorFields()))\n}\n\n// Handle cancel\nfunction handleCancel() {\n emit('cancel')\n}\n\nonMounted(() => {\n if (props.modelValue) {\n initFromStep(props.modelValue)\n } else if (availableTemplates.value.length > 0) {\n selectTemplate(availableTemplates.value[0])\n }\n})\n\n// Watch for external changes\nwatch(\n () => props.modelValue,\n (step) => {\n if (step) {\n initFromStep(step)\n }\n }\n)\n</script>\n\n<template>\n <div class=\"mint-protocol-editor\">\n <!-- Header -->\n <div class=\"mint-protocol-editor__header\">\n <span class=\"mint-protocol-editor__title\">\n {{ mode === 'create' ? 'Create Protocol Step' : 'Edit Protocol Step' }}\n </span>\n <span\n :class=\"[\n 'mint-protocol-editor__mode-badge',\n `mint-protocol-editor__mode-badge--${mode}`,\n ]\"\n >\n {{ mode }}\n </span>\n </div>\n\n <!-- Form -->\n <div class=\"mint-protocol-editor__form\">\n <!-- Template selector -->\n <div class=\"mint-protocol-editor__template-select\">\n <label class=\"mint-protocol-editor__template-label\">Template</label>\n <div ref=\"templateDropdownRef\" class=\"mint-protocol-editor__template-dropdown\">\n <button\n type=\"button\"\n class=\"mint-protocol-editor__template-btn\"\n @click=\"toggleTemplateDropdown\"\n >\n <svg\n v-if=\"selectedTemplate\"\n class=\"mint-protocol-editor__template-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n :d=\"stepTypeIcons[selectedTemplate.type]\"\n />\n </svg>\n <span class=\"mint-protocol-editor__template-text\">\n {{ selectedTemplate?.name || 'Select template...' }}\n </span>\n <svg\n :class=\"[\n 'mint-protocol-editor__template-arrow',\n templateDropdownOpen ? 'mint-protocol-editor__template-arrow--open' : '',\n ]\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n\n <div v-if=\"templateDropdownOpen\" class=\"mint-protocol-editor__template-menu\">\n <button\n v-for=\"template in availableTemplates\"\n :key=\"template.id\"\n type=\"button\"\n :class=\"[\n 'mint-protocol-editor__template-option',\n selectedTemplateId === template.id ? 'mint-protocol-editor__template-option--active' : '',\n ]\"\n @click=\"selectTemplate(template)\"\n >\n <svg\n class=\"mint-protocol-editor__template-option-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n :d=\"stepTypeIcons[template.type]\"\n />\n </svg>\n <div class=\"mint-protocol-editor__template-option-text\">\n <div>{{ template.name }}</div>\n <div v-if=\"template.description\" class=\"mint-protocol-editor__template-option-desc\">\n {{ template.description }}\n </div>\n </div>\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"mint-protocol-editor__divider\" />\n\n <!-- Step name -->\n <div class=\"mint-protocol-editor__field\">\n <label class=\"mint-protocol-editor__field-label mint-protocol-editor__field-label--required\">\n Step Name\n </label>\n <input\n v-model=\"stepName\"\n type=\"text\"\n :class=\"[\n 'mint-protocol-editor__input',\n validationErrors.name ? 'mint-protocol-editor__input--error' : '',\n ]\"\n placeholder=\"Enter step name\"\n />\n <span v-if=\"validationErrors.name\" class=\"mint-protocol-editor__field-error\">\n {{ validationErrors.name }}\n </span>\n </div>\n\n <!-- Dynamic parameters -->\n <template v-if=\"selectedTemplate\">\n <ProtocolStepParameterField\n v-for=\"param in selectedTemplate.parameters\"\n :key=\"param.key\"\n :parameter=\"param\"\n :model-value=\"parameters[param.key]\"\n :error=\"validationErrors[param.key]\"\n @update:model-value=\"handleParamChange(param.key, $event)\"\n />\n </template>\n\n <!-- Description -->\n <div class=\"mint-protocol-editor__field\">\n <label class=\"mint-protocol-editor__field-label\">Description</label>\n <textarea\n v-model=\"stepDescription\"\n class=\"mint-protocol-editor__textarea\"\n placeholder=\"Optional step description\"\n rows=\"2\"\n />\n </div>\n </div>\n\n <!-- Preview -->\n <div v-if=\"showPreview && previewStep\" class=\"mint-protocol-editor__preview\">\n <div class=\"mint-protocol-editor__preview-title\">Preview</div>\n <div class=\"mint-protocol-editor__preview-card\">\n <div class=\"mint-protocol-editor__preview-icon\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n :d=\"stepTypeIcons[previewStep.type]\"\n />\n </svg>\n </div>\n <div class=\"mint-protocol-editor__preview-content\">\n <div class=\"mint-protocol-editor__preview-name\">{{ previewStep.name }}</div>\n <div v-if=\"previewParams\" class=\"mint-protocol-editor__preview-params\">\n {{ previewParams }}\n </div>\n </div>\n <div v-if=\"previewStep.duration\" class=\"mint-protocol-editor__preview-duration\">\n {{ formatDuration(previewStep.duration) }}\n </div>\n </div>\n </div>\n\n <!-- Actions -->\n <div class=\"mint-protocol-editor__actions\">\n <div class=\"mint-protocol-editor__actions-left\">\n <button\n type=\"button\"\n class=\"mint-protocol-editor__btn mint-protocol-editor__btn--ghost\"\n @click=\"handleSaveTemplate\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4\" />\n </svg>\n Save as Template\n </button>\n </div>\n <button\n type=\"button\"\n class=\"mint-protocol-editor__btn mint-protocol-editor__btn--secondary\"\n @click=\"handleCancel\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n class=\"mint-protocol-editor__btn mint-protocol-editor__btn--primary\"\n :disabled=\"!selectedTemplate\"\n @click=\"handleSave\"\n >\n {{ mode === 'create' ? 'Add Step' : 'Save Changes' }}\n </button>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/protocol-step-editor.css';\n</style>\n","<script setup lang=\"ts\">\n/** Form for creating or editing a single protocol step (incubation, wash, addition, centrifuge, etc.) with template picker, typed parameters, and duration. */\nimport { ref, computed, watch, onMounted } from 'vue'\nimport type { ProtocolStep } from '../types'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport {\n useProtocolTemplates,\n type StepTemplate,\n} from '../composables/useProtocolTemplates'\nimport {\n PROTOCOL_STEP_TYPE_ICONS as stepTypeIcons,\n formatProtocolDuration as formatDuration,\n} from './ProtocolStep.presentation'\nimport ProtocolStepParameterField from './ProtocolStepParameterField.vue'\nimport {\n buildCustomProtocolTemplate,\n buildProtocolPreviewParams,\n buildProtocolStepEditorState,\n buildProtocolStepPreview,\n buildProtocolTemplateDefaults,\n type ProtocolStepEditorFields,\n} from './ProtocolStepEditor.state'\n\ninterface Props {\n modelValue?: ProtocolStep\n templates?: StepTemplate[]\n customTemplates?: StepTemplate[]\n mode?: 'create' | 'edit'\n showPreview?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n mode: 'create',\n showPreview: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [step: ProtocolStep]\n 'save': [step: ProtocolStep]\n 'save-template': [template: StepTemplate]\n 'cancel': []\n}>()\n\nconst {\n allTemplates,\n getTemplateByType,\n validateStep,\n formatParameterValue,\n} = useProtocolTemplates()\n\n// State\nconst selectedTemplateId = ref<string | null>(null)\nconst {\n isOpen: templateDropdownOpen,\n rootRef: templateDropdownRef,\n close: closeTemplateDropdown,\n toggle: toggleTemplateDropdown,\n} = useDropdownState()\nconst stepName = ref('')\nconst stepDescription = ref('')\nconst stepDuration = ref<number | undefined>()\nconst parameters = ref<Record<string, unknown>>({})\nconst validationErrors = ref<Record<string, string>>({})\n\n// Combined templates from props and composable\nconst availableTemplates = computed(() => {\n if (props.templates && props.templates.length > 0) {\n return props.templates\n }\n const combined = [...allTemplates.value]\n if (props.customTemplates) {\n combined.push(...props.customTemplates)\n }\n return combined\n})\n\n// Selected template\nconst selectedTemplate = computed(() => {\n if (!selectedTemplateId.value) return null\n return availableTemplates.value.find((t) => t.id === selectedTemplateId.value) || null\n})\n\n// Preview computed\nconst previewStep = computed<ProtocolStep | null>(() => {\n if (!selectedTemplate.value) return null\n return buildProtocolStepPreview(selectedTemplate.value, editorFields(), props.modelValue)\n})\n\nconst previewParams = computed(() => {\n if (!selectedTemplate.value || !previewStep.value) return ''\n return buildProtocolPreviewParams(\n selectedTemplate.value,\n parameters.value,\n formatParameterValue\n )\n})\n\nfunction editorFields(): ProtocolStepEditorFields {\n return {\n name: stepName.value,\n description: stepDescription.value,\n duration: stepDuration.value,\n parameters: parameters.value,\n }\n}\n\nfunction applyEditorFields(fields: ProtocolStepEditorFields) {\n stepName.value = fields.name\n stepDescription.value = fields.description\n stepDuration.value = fields.duration\n parameters.value = { ...fields.parameters }\n}\n\nfunction initFromStep(step: ProtocolStep) {\n const state = buildProtocolStepEditorState(step, getTemplateByType)\n selectedTemplateId.value = state.selectedTemplateId\n applyEditorFields(state.fields)\n}\n\n// Handle template selection\nfunction selectTemplate(template: StepTemplate) {\n selectedTemplateId.value = template.id\n closeTemplateDropdown()\n\n // Reset to template defaults if creating new\n if (props.mode === 'create') {\n applyEditorFields(buildProtocolTemplateDefaults(template))\n }\n}\n\n// Handle parameter change\nfunction handleParamChange(key: string, value: unknown) {\n parameters.value[key] = value\n // Clear validation error when user changes value\n if (validationErrors.value[key]) {\n delete validationErrors.value[key]\n }\n}\n\n// Validate and save\nfunction handleSave() {\n if (!selectedTemplate.value || !previewStep.value) return\n\n const result = validateStep(previewStep.value, selectedTemplate.value)\n validationErrors.value = result.errors\n\n if (result.valid) {\n emit('save', previewStep.value)\n emit('update:modelValue', previewStep.value)\n }\n}\n\n// Save as template\nfunction handleSaveTemplate() {\n if (!selectedTemplate.value) return\n emit('save-template', buildCustomProtocolTemplate(selectedTemplate.value, editorFields()))\n}\n\n// Handle cancel\nfunction handleCancel() {\n emit('cancel')\n}\n\nonMounted(() => {\n if (props.modelValue) {\n initFromStep(props.modelValue)\n } else if (availableTemplates.value.length > 0) {\n selectTemplate(availableTemplates.value[0])\n }\n})\n\n// Watch for external changes\nwatch(\n () => props.modelValue,\n (step) => {\n if (step) {\n initFromStep(step)\n }\n }\n)\n</script>\n\n<template>\n <div class=\"mint-protocol-editor\">\n <!-- Header -->\n <div class=\"mint-protocol-editor__header\">\n <span class=\"mint-protocol-editor__title\">\n {{ mode === 'create' ? 'Create Protocol Step' : 'Edit Protocol Step' }}\n </span>\n <span\n :class=\"[\n 'mint-protocol-editor__mode-badge',\n `mint-protocol-editor__mode-badge--${mode}`,\n ]\"\n >\n {{ mode }}\n </span>\n </div>\n\n <!-- Form -->\n <div class=\"mint-protocol-editor__form\">\n <!-- Template selector -->\n <div class=\"mint-protocol-editor__template-select\">\n <label class=\"mint-protocol-editor__template-label\">Template</label>\n <div ref=\"templateDropdownRef\" class=\"mint-protocol-editor__template-dropdown\">\n <button\n type=\"button\"\n class=\"mint-protocol-editor__template-btn\"\n @click=\"toggleTemplateDropdown\"\n >\n <svg\n v-if=\"selectedTemplate\"\n class=\"mint-protocol-editor__template-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n :d=\"stepTypeIcons[selectedTemplate.type]\"\n />\n </svg>\n <span class=\"mint-protocol-editor__template-text\">\n {{ selectedTemplate?.name || 'Select template...' }}\n </span>\n <svg\n :class=\"[\n 'mint-protocol-editor__template-arrow',\n templateDropdownOpen ? 'mint-protocol-editor__template-arrow--open' : '',\n ]\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n\n <div v-if=\"templateDropdownOpen\" class=\"mint-protocol-editor__template-menu\">\n <button\n v-for=\"template in availableTemplates\"\n :key=\"template.id\"\n type=\"button\"\n :class=\"[\n 'mint-protocol-editor__template-option',\n selectedTemplateId === template.id ? 'mint-protocol-editor__template-option--active' : '',\n ]\"\n @click=\"selectTemplate(template)\"\n >\n <svg\n class=\"mint-protocol-editor__template-option-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n :d=\"stepTypeIcons[template.type]\"\n />\n </svg>\n <div class=\"mint-protocol-editor__template-option-text\">\n <div>{{ template.name }}</div>\n <div v-if=\"template.description\" class=\"mint-protocol-editor__template-option-desc\">\n {{ template.description }}\n </div>\n </div>\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"mint-protocol-editor__divider\" />\n\n <!-- Step name -->\n <div class=\"mint-protocol-editor__field\">\n <label class=\"mint-protocol-editor__field-label mint-protocol-editor__field-label--required\">\n Step Name\n </label>\n <input\n v-model=\"stepName\"\n type=\"text\"\n :class=\"[\n 'mint-protocol-editor__input',\n validationErrors.name ? 'mint-protocol-editor__input--error' : '',\n ]\"\n placeholder=\"Enter step name\"\n />\n <span v-if=\"validationErrors.name\" class=\"mint-protocol-editor__field-error\">\n {{ validationErrors.name }}\n </span>\n </div>\n\n <!-- Dynamic parameters -->\n <template v-if=\"selectedTemplate\">\n <ProtocolStepParameterField\n v-for=\"param in selectedTemplate.parameters\"\n :key=\"param.key\"\n :parameter=\"param\"\n :model-value=\"parameters[param.key]\"\n :error=\"validationErrors[param.key]\"\n @update:model-value=\"handleParamChange(param.key, $event)\"\n />\n </template>\n\n <!-- Description -->\n <div class=\"mint-protocol-editor__field\">\n <label class=\"mint-protocol-editor__field-label\">Description</label>\n <textarea\n v-model=\"stepDescription\"\n class=\"mint-protocol-editor__textarea\"\n placeholder=\"Optional step description\"\n rows=\"2\"\n />\n </div>\n </div>\n\n <!-- Preview -->\n <div v-if=\"showPreview && previewStep\" class=\"mint-protocol-editor__preview\">\n <div class=\"mint-protocol-editor__preview-title\">Preview</div>\n <div class=\"mint-protocol-editor__preview-card\">\n <div class=\"mint-protocol-editor__preview-icon\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n :d=\"stepTypeIcons[previewStep.type]\"\n />\n </svg>\n </div>\n <div class=\"mint-protocol-editor__preview-content\">\n <div class=\"mint-protocol-editor__preview-name\">{{ previewStep.name }}</div>\n <div v-if=\"previewParams\" class=\"mint-protocol-editor__preview-params\">\n {{ previewParams }}\n </div>\n </div>\n <div v-if=\"previewStep.duration\" class=\"mint-protocol-editor__preview-duration\">\n {{ formatDuration(previewStep.duration) }}\n </div>\n </div>\n </div>\n\n <!-- Actions -->\n <div class=\"mint-protocol-editor__actions\">\n <div class=\"mint-protocol-editor__actions-left\">\n <button\n type=\"button\"\n class=\"mint-protocol-editor__btn mint-protocol-editor__btn--ghost\"\n @click=\"handleSaveTemplate\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4\" />\n </svg>\n Save as Template\n </button>\n </div>\n <button\n type=\"button\"\n class=\"mint-protocol-editor__btn mint-protocol-editor__btn--secondary\"\n @click=\"handleCancel\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n class=\"mint-protocol-editor__btn mint-protocol-editor__btn--primary\"\n :disabled=\"!selectedTemplate\"\n @click=\"handleSave\"\n >\n {{ mode === 'create' ? 'Add Step' : 'Save Changes' }}\n </button>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/protocol-step-editor.css';\n</style>\n","<script setup lang=\"ts\">\n/** Read-only display of a numeric measurement in plain, scientific (×10ⁿ), or SI-prefix compact notation with optional unit and copy-to-clipboard. */\nimport { computed, ref } from 'vue'\nimport type { NumberNotation } from '../types'\n\ninterface Props {\n value: number\n precision?: number\n notation?: NumberNotation\n unit?: string\n copyable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n precision: 3,\n notation: 'auto',\n copyable: false,\n})\n\nconst copied = ref(false)\n\nconst SUPERSCRIPT_DIGITS: Record<string, string> = {\n '0': '\\u2070',\n '1': '\\u00B9',\n '2': '\\u00B2',\n '3': '\\u00B3',\n '4': '\\u2074',\n '5': '\\u2075',\n '6': '\\u2076',\n '7': '\\u2077',\n '8': '\\u2078',\n '9': '\\u2079',\n '-': '\\u207B',\n}\n\nconst SI_SUFFIXES: [number, string][] = [\n [1e12, 'T'],\n [1e9, 'G'],\n [1e6, 'M'],\n [1e3, 'k'],\n [1e-3, 'm'],\n [1e-6, '\\u00B5'],\n [1e-9, 'n'],\n [1e-12, 'p'],\n]\n\nfunction toSuperscript(exp: number): string {\n return String(exp)\n .split('')\n .map(ch => SUPERSCRIPT_DIGITS[ch] ?? ch)\n .join('')\n}\n\nfunction roundToPrecision(n: number, digits: number): string {\n return n.toFixed(digits).replace(/\\.?0+$/, '') || '0'\n}\n\ninterface FormattedNumber {\n type: 'plain' | 'scientific' | 'compact'\n mantissa?: string\n exponent?: number\n exponentStr?: string\n plain?: string\n suffix?: string\n}\n\nconst formatted = computed<FormattedNumber>(() => {\n const v = props.value\n\n if (!Number.isFinite(v)) return { type: 'plain', plain: '' }\n if (v === 0) return { type: 'plain', plain: '0' }\n\n const absV = Math.abs(v)\n\n if (props.notation === 'compact') {\n for (const [threshold, suffix] of SI_SUFFIXES) {\n if (absV >= threshold * 0.999) {\n return {\n type: 'compact',\n plain: roundToPrecision(v / threshold, props.precision),\n suffix,\n }\n }\n }\n return { type: 'plain', plain: roundToPrecision(v, props.precision) }\n }\n\n const useScientific =\n props.notation === 'scientific' ||\n props.notation === 'engineering' ||\n (props.notation === 'auto' && (absV < 0.01 || absV >= 10000))\n\n if (!useScientific) {\n return { type: 'plain', plain: roundToPrecision(v, props.precision) }\n }\n\n let exp = Math.floor(Math.log10(absV))\n\n if (props.notation === 'engineering') {\n exp = Math.floor(exp / 3) * 3\n }\n\n const mantissa = v / Math.pow(10, exp)\n const mantissaStr = roundToPrecision(mantissa, props.precision)\n\n return {\n type: 'scientific',\n mantissa: mantissaStr,\n exponent: exp,\n exponentStr: toSuperscript(exp),\n }\n})\n\nconst specialDisplay = computed(() => {\n if (Number.isNaN(props.value)) return 'NaN'\n if (props.value === Infinity) return '\\u221E'\n if (props.value === -Infinity) return '-\\u221E'\n return null\n})\n\nconst plainTextValue = computed(() => {\n if (specialDisplay.value) return specialDisplay.value\n const f = formatted.value\n let text: string\n if (f.type === 'scientific') {\n text = `${f.mantissa}e${f.exponent}`\n } else if (f.type === 'compact') {\n text = `${f.plain}${f.suffix ?? ''}`\n } else {\n text = f.plain ?? '0'\n }\n if (props.unit) text += ` ${props.unit}`\n return text\n})\n\nasync function copyToClipboard() {\n try {\n await navigator.clipboard.writeText(plainTextValue.value)\n copied.value = true\n setTimeout(() => { copied.value = false }, 1500)\n } catch {\n // clipboard not available\n }\n}\n</script>\n\n<template>\n <span class=\"mint-sci-number\">\n <!-- Special values: NaN, Infinity -->\n <template v-if=\"specialDisplay\">\n <span class=\"mint-sci-number__plain\">{{ specialDisplay }}</span>\n </template>\n\n <!-- Scientific / Engineering notation -->\n <template v-else-if=\"formatted.type === 'scientific'\">\n <span class=\"mint-sci-number__mantissa\">{{ formatted.mantissa }}</span>\n <span class=\"mint-sci-number__times\">×</span>\n <span class=\"mint-sci-number__base\">10</span>\n <span class=\"mint-sci-number__exponent\">{{ formatted.exponentStr }}</span>\n </template>\n\n <!-- Compact notation with SI suffix -->\n <template v-else-if=\"formatted.type === 'compact'\">\n <span class=\"mint-sci-number__plain\">{{ formatted.plain }}</span>\n <span v-if=\"formatted.suffix\" class=\"mint-sci-number__suffix\">{{ formatted.suffix }}</span>\n </template>\n\n <!-- Plain number -->\n <template v-else>\n <span class=\"mint-sci-number__plain\">{{ formatted.plain }}</span>\n </template>\n\n <!-- Unit -->\n <span v-if=\"unit\" class=\"mint-sci-number__unit\">{{ unit }}</span>\n\n <!-- Copy button -->\n <button\n v-if=\"copyable\"\n :class=\"['mint-sci-number__copy-btn', { 'mint-sci-number__copy-btn--copied': copied }]\"\n type=\"button\"\n :title=\"copied ? 'Copied!' : 'Copy value'\"\n @click.stop=\"copyToClipboard\"\n >\n <svg v-if=\"!copied\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\" />\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\" />\n </svg>\n <svg v-else width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n </button>\n </span>\n</template>\n\n<style>\n@import '../styles/components/scientific-number.css';\n</style>\n","<script setup lang=\"ts\">\n/** Read-only display of a numeric measurement in plain, scientific (×10ⁿ), or SI-prefix compact notation with optional unit and copy-to-clipboard. */\nimport { computed, ref } from 'vue'\nimport type { NumberNotation } from '../types'\n\ninterface Props {\n value: number\n precision?: number\n notation?: NumberNotation\n unit?: string\n copyable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n precision: 3,\n notation: 'auto',\n copyable: false,\n})\n\nconst copied = ref(false)\n\nconst SUPERSCRIPT_DIGITS: Record<string, string> = {\n '0': '\\u2070',\n '1': '\\u00B9',\n '2': '\\u00B2',\n '3': '\\u00B3',\n '4': '\\u2074',\n '5': '\\u2075',\n '6': '\\u2076',\n '7': '\\u2077',\n '8': '\\u2078',\n '9': '\\u2079',\n '-': '\\u207B',\n}\n\nconst SI_SUFFIXES: [number, string][] = [\n [1e12, 'T'],\n [1e9, 'G'],\n [1e6, 'M'],\n [1e3, 'k'],\n [1e-3, 'm'],\n [1e-6, '\\u00B5'],\n [1e-9, 'n'],\n [1e-12, 'p'],\n]\n\nfunction toSuperscript(exp: number): string {\n return String(exp)\n .split('')\n .map(ch => SUPERSCRIPT_DIGITS[ch] ?? ch)\n .join('')\n}\n\nfunction roundToPrecision(n: number, digits: number): string {\n return n.toFixed(digits).replace(/\\.?0+$/, '') || '0'\n}\n\ninterface FormattedNumber {\n type: 'plain' | 'scientific' | 'compact'\n mantissa?: string\n exponent?: number\n exponentStr?: string\n plain?: string\n suffix?: string\n}\n\nconst formatted = computed<FormattedNumber>(() => {\n const v = props.value\n\n if (!Number.isFinite(v)) return { type: 'plain', plain: '' }\n if (v === 0) return { type: 'plain', plain: '0' }\n\n const absV = Math.abs(v)\n\n if (props.notation === 'compact') {\n for (const [threshold, suffix] of SI_SUFFIXES) {\n if (absV >= threshold * 0.999) {\n return {\n type: 'compact',\n plain: roundToPrecision(v / threshold, props.precision),\n suffix,\n }\n }\n }\n return { type: 'plain', plain: roundToPrecision(v, props.precision) }\n }\n\n const useScientific =\n props.notation === 'scientific' ||\n props.notation === 'engineering' ||\n (props.notation === 'auto' && (absV < 0.01 || absV >= 10000))\n\n if (!useScientific) {\n return { type: 'plain', plain: roundToPrecision(v, props.precision) }\n }\n\n let exp = Math.floor(Math.log10(absV))\n\n if (props.notation === 'engineering') {\n exp = Math.floor(exp / 3) * 3\n }\n\n const mantissa = v / Math.pow(10, exp)\n const mantissaStr = roundToPrecision(mantissa, props.precision)\n\n return {\n type: 'scientific',\n mantissa: mantissaStr,\n exponent: exp,\n exponentStr: toSuperscript(exp),\n }\n})\n\nconst specialDisplay = computed(() => {\n if (Number.isNaN(props.value)) return 'NaN'\n if (props.value === Infinity) return '\\u221E'\n if (props.value === -Infinity) return '-\\u221E'\n return null\n})\n\nconst plainTextValue = computed(() => {\n if (specialDisplay.value) return specialDisplay.value\n const f = formatted.value\n let text: string\n if (f.type === 'scientific') {\n text = `${f.mantissa}e${f.exponent}`\n } else if (f.type === 'compact') {\n text = `${f.plain}${f.suffix ?? ''}`\n } else {\n text = f.plain ?? '0'\n }\n if (props.unit) text += ` ${props.unit}`\n return text\n})\n\nasync function copyToClipboard() {\n try {\n await navigator.clipboard.writeText(plainTextValue.value)\n copied.value = true\n setTimeout(() => { copied.value = false }, 1500)\n } catch {\n // clipboard not available\n }\n}\n</script>\n\n<template>\n <span class=\"mint-sci-number\">\n <!-- Special values: NaN, Infinity -->\n <template v-if=\"specialDisplay\">\n <span class=\"mint-sci-number__plain\">{{ specialDisplay }}</span>\n </template>\n\n <!-- Scientific / Engineering notation -->\n <template v-else-if=\"formatted.type === 'scientific'\">\n <span class=\"mint-sci-number__mantissa\">{{ formatted.mantissa }}</span>\n <span class=\"mint-sci-number__times\">×</span>\n <span class=\"mint-sci-number__base\">10</span>\n <span class=\"mint-sci-number__exponent\">{{ formatted.exponentStr }}</span>\n </template>\n\n <!-- Compact notation with SI suffix -->\n <template v-else-if=\"formatted.type === 'compact'\">\n <span class=\"mint-sci-number__plain\">{{ formatted.plain }}</span>\n <span v-if=\"formatted.suffix\" class=\"mint-sci-number__suffix\">{{ formatted.suffix }}</span>\n </template>\n\n <!-- Plain number -->\n <template v-else>\n <span class=\"mint-sci-number__plain\">{{ formatted.plain }}</span>\n </template>\n\n <!-- Unit -->\n <span v-if=\"unit\" class=\"mint-sci-number__unit\">{{ unit }}</span>\n\n <!-- Copy button -->\n <button\n v-if=\"copyable\"\n :class=\"['mint-sci-number__copy-btn', { 'mint-sci-number__copy-btn--copied': copied }]\"\n type=\"button\"\n :title=\"copied ? 'Copied!' : 'Copy value'\"\n @click.stop=\"copyToClipboard\"\n >\n <svg v-if=\"!copied\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\" />\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\" />\n </svg>\n <svg v-else width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n </button>\n </span>\n</template>\n\n<style>\n@import '../styles/components/scientific-number.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a chemical formula string (H₂O, C₆H₁₂O₆, Ca(OH)₂) with typographically correct subscripts and superscripts for charges. */\nimport { useChemicalFormula, type FormulaPart } from '../composables/useChemicalFormula'\n\ninterface Props {\n formula: string\n inline?: boolean\n}\n\nwithDefaults(defineProps<Props>(), {\n inline: true,\n})\n\nconst { renderFormulaParts } = useChemicalFormula()\n\nfunction getParts(f: string): FormulaPart[] {\n return renderFormulaParts(f)\n}\n</script>\n\n<template>\n <component\n :is=\"inline ? 'span' : 'div'\"\n :class=\"['mint-chem-formula', !inline ? 'mint-chem-formula--block' : '']\"\n >\n <template v-for=\"(part, i) in getParts(formula)\" :key=\"i\">\n <span v-if=\"part.type === 'element'\" class=\"mint-chem-formula__element\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'subscript'\" class=\"mint-chem-formula__subscript\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'superscript'\" class=\"mint-chem-formula__superscript\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'paren'\" class=\"mint-chem-formula__paren\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'dot'\" class=\"mint-chem-formula__dot\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'charge'\" class=\"mint-chem-formula__charge\">{{ part.text }}</span>\n </template>\n </component>\n</template>\n\n<style>\n@import '../styles/components/chemical-formula.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a chemical formula string (H₂O, C₆H₁₂O₆, Ca(OH)₂) with typographically correct subscripts and superscripts for charges. */\nimport { useChemicalFormula, type FormulaPart } from '../composables/useChemicalFormula'\n\ninterface Props {\n formula: string\n inline?: boolean\n}\n\nwithDefaults(defineProps<Props>(), {\n inline: true,\n})\n\nconst { renderFormulaParts } = useChemicalFormula()\n\nfunction getParts(f: string): FormulaPart[] {\n return renderFormulaParts(f)\n}\n</script>\n\n<template>\n <component\n :is=\"inline ? 'span' : 'div'\"\n :class=\"['mint-chem-formula', !inline ? 'mint-chem-formula--block' : '']\"\n >\n <template v-for=\"(part, i) in getParts(formula)\" :key=\"i\">\n <span v-if=\"part.type === 'element'\" class=\"mint-chem-formula__element\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'subscript'\" class=\"mint-chem-formula__subscript\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'superscript'\" class=\"mint-chem-formula__superscript\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'paren'\" class=\"mint-chem-formula__paren\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'dot'\" class=\"mint-chem-formula__dot\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'charge'\" class=\"mint-chem-formula__charge\">{{ part.text }}</span>\n </template>\n </component>\n</template>\n\n<style>\n@import '../styles/components/chemical-formula.css';\n</style>\n","import type { SequenceProgress } from './types'\n\nexport function sequenceProgressPercent(progress: SequenceProgress | null | undefined): number {\n if (!progress || progress.total_samples <= 0) return 0\n return Math.round(clamp((progress.current_sample / progress.total_samples) * 100, 0, 100))\n}\n\nexport function sequenceSamplesRemaining(progress: SequenceProgress | null | undefined): number {\n if (!progress) return 0\n return Math.max(0, progress.total_samples - progress.current_sample)\n}\n\nexport function estimateSequenceRemainingSeconds(\n progress: SequenceProgress | null | undefined,\n): number | null {\n if (!progress) return null\n const samplesLeft = sequenceSamplesRemaining(progress)\n if (samplesLeft <= 0) return 0\n\n const explicit = progress.estimated_remaining_seconds\n if (typeof explicit === 'number' && explicit > 0) return explicit\n\n const avg = progress.avg_sample_seconds ?? average(progress.sample_durations)\n if (typeof avg === 'number' && avg > 0) return samplesLeft * avg\n\n const elapsed = progress.elapsed_seconds ?? 0\n if (elapsed > 0 && progress.current_sample > 0) {\n return (elapsed / progress.current_sample) * samplesLeft\n }\n\n return null\n}\n\nexport function estimateSequenceFinishDate(\n progress: SequenceProgress | null | undefined,\n now = new Date(),\n): Date | null {\n if (!progress) return null\n\n const explicit = coerceDate(progress.estimated_finish_time)\n if (explicit) return explicit\n\n const remaining = estimateSequenceRemainingSeconds(progress)\n if (remaining == null || remaining <= 0) return null\n return new Date(now.getTime() + remaining * 1000)\n}\n\nexport function formatSequenceRemaining(progress: SequenceProgress | null | undefined): string | null {\n const remaining = estimateSequenceRemainingSeconds(progress)\n if (remaining == null || remaining <= 0) return null\n\n const minutes = remaining / 60\n if (minutes < 1) return `${Math.round(remaining)}s left`\n if (minutes < 60) return `~${Math.round(minutes)}m left`\n\n const hours = Math.floor(minutes / 60)\n const mins = Math.round(minutes % 60)\n return mins > 0 ? `~${hours}h ${mins}m left` : `~${hours}h left`\n}\n\nexport function formatSequenceEta(\n progress: SequenceProgress | null | undefined,\n locale?: Intl.LocalesArgument,\n options: Intl.DateTimeFormatOptions = {},\n): string | null {\n const finish = estimateSequenceFinishDate(progress)\n if (!finish) return null\n return finish.toLocaleTimeString(locale, {\n hour: '2-digit',\n minute: '2-digit',\n ...options,\n })\n}\n\nfunction average(values: readonly number[] | undefined): number | null {\n if (!values?.length) return null\n const usable = values.filter(value => Number.isFinite(value) && value >= 0)\n if (usable.length === 0) return null\n return usable.reduce((sum, value) => sum + value, 0) / usable.length\n}\n\nfunction coerceDate(value: string | Date | null | undefined): Date | null {\n if (!value) return null\n const date = value instanceof Date ? value : new Date(value)\n return Number.isNaN(date.getTime()) ? null : date\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n","<script setup lang=\"ts\">\n/** Displays LC/MS or GC/MS acquisition sequence progress with ETA and remaining-time labels. */\nimport { computed } from 'vue'\nimport {\n formatSequenceEta,\n formatSequenceRemaining,\n sequenceProgressPercent,\n} from '../instrument'\nimport type { SequenceProgress } from '../types'\nimport ProgressBar from './ProgressBar.vue'\n\ninterface Props {\n progress: SequenceProgress\n compact?: boolean\n label?: string\n showEta?: boolean\n showRemaining?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n compact: false,\n label: 'samples',\n showEta: true,\n showRemaining: true,\n})\n\nconst percent = computed(() => sequenceProgressPercent(props.progress))\nconst remainingLabel = computed(() => formatSequenceRemaining(props.progress))\nconst etaLabel = computed(() => formatSequenceEta(props.progress))\nconst hasFooter = computed(() => {\n if (props.compact) return false\n return (props.showRemaining && remainingLabel.value) || (props.showEta && etaLabel.value)\n})\n</script>\n\n<template>\n <div class=\"mint-sequence-progress\">\n <div class=\"mint-sequence-progress__header\">\n <span class=\"mint-sequence-progress__label\">\n {{ progress.current_sample }} / {{ progress.total_samples }} {{ label }}\n </span>\n <span class=\"mint-sequence-progress__percent\">{{ percent }}%</span>\n </div>\n\n <ProgressBar\n :value=\"percent\"\n color=\"info\"\n size=\"sm\"\n aria-label=\"Sequence progress\"\n />\n\n <div v-if=\"hasFooter\" class=\"mint-sequence-progress__footer\">\n <span\n v-if=\"showRemaining && remainingLabel\"\n class=\"mint-sequence-progress__remaining\"\n >\n {{ remainingLabel }}\n </span>\n <span\n v-if=\"showEta && etaLabel\"\n class=\"mint-sequence-progress__eta\"\n >\n ETA {{ etaLabel }}\n </span>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/sequence-progress-bar.css';\n</style>\n","<script setup lang=\"ts\">\n/** Displays LC/MS or GC/MS acquisition sequence progress with ETA and remaining-time labels. */\nimport { computed } from 'vue'\nimport {\n formatSequenceEta,\n formatSequenceRemaining,\n sequenceProgressPercent,\n} from '../instrument'\nimport type { SequenceProgress } from '../types'\nimport ProgressBar from './ProgressBar.vue'\n\ninterface Props {\n progress: SequenceProgress\n compact?: boolean\n label?: string\n showEta?: boolean\n showRemaining?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n compact: false,\n label: 'samples',\n showEta: true,\n showRemaining: true,\n})\n\nconst percent = computed(() => sequenceProgressPercent(props.progress))\nconst remainingLabel = computed(() => formatSequenceRemaining(props.progress))\nconst etaLabel = computed(() => formatSequenceEta(props.progress))\nconst hasFooter = computed(() => {\n if (props.compact) return false\n return (props.showRemaining && remainingLabel.value) || (props.showEta && etaLabel.value)\n})\n</script>\n\n<template>\n <div class=\"mint-sequence-progress\">\n <div class=\"mint-sequence-progress__header\">\n <span class=\"mint-sequence-progress__label\">\n {{ progress.current_sample }} / {{ progress.total_samples }} {{ label }}\n </span>\n <span class=\"mint-sequence-progress__percent\">{{ percent }}%</span>\n </div>\n\n <ProgressBar\n :value=\"percent\"\n color=\"info\"\n size=\"sm\"\n aria-label=\"Sequence progress\"\n />\n\n <div v-if=\"hasFooter\" class=\"mint-sequence-progress__footer\">\n <span\n v-if=\"showRemaining && remainingLabel\"\n class=\"mint-sequence-progress__remaining\"\n >\n {{ remainingLabel }}\n </span>\n <span\n v-if=\"showEta && etaLabel\"\n class=\"mint-sequence-progress__eta\"\n >\n ETA {{ etaLabel }}\n </span>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/sequence-progress-bar.css';\n</style>\n","<script setup lang=\"ts\">\n/** Filterable instrument alert/event list with optional acknowledge actions. */\nimport { computed, ref } from 'vue'\nimport type { InstrumentAlert, InstrumentAlertLevel } from '../types'\n\ninterface Props {\n alerts?: InstrumentAlert[]\n title?: string\n acknowledgeable?: boolean\n emptyMessage?: string\n filteredEmptyMessage?: string\n locale?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n alerts: () => [],\n title: 'Event Log',\n acknowledgeable: true,\n emptyMessage: 'No alerts received yet',\n filteredEmptyMessage: 'No alerts match current filters',\n locale: undefined,\n})\n\nconst emit = defineEmits<{\n acknowledge: [alert: InstrumentAlert]\n}>()\n\nconst levelConfig: Record<InstrumentAlertLevel, { label: string; cssClass: string; color: string }> = {\n critical: { label: 'Critical', cssClass: 'mint-instrument-alert--critical', color: '#ef4444' },\n warning: { label: 'Warning', cssClass: 'mint-instrument-alert--warning', color: '#eab308' },\n info: { label: 'Info', cssClass: 'mint-instrument-alert--info', color: '#3b82f6' },\n}\n\nconst levels: InstrumentAlertLevel[] = ['critical', 'warning', 'info']\nconst activeLevels = ref<Set<InstrumentAlertLevel>>(new Set(levels))\nconst activeInstrument = ref<string | null>(null)\n\nconst instruments = computed(() => {\n const counts = new Map<string, number>()\n for (const alert of props.alerts) {\n const name = instrumentName(alert)\n counts.set(name, (counts.get(name) ?? 0) + 1)\n }\n return [...counts.entries()].map(([name, count]) => ({ name, count }))\n})\n\nconst alertCounts = computed(() => {\n const counts: Record<InstrumentAlertLevel, number> = { critical: 0, warning: 0, info: 0 }\n for (const alert of props.alerts) {\n if (alert.level in counts) counts[alert.level]++\n }\n return counts\n})\n\nconst filteredAlerts = computed(() =>\n [...props.alerts]\n .filter(alert => activeLevels.value.has(alert.level))\n .filter(alert => !activeInstrument.value || instrumentName(alert) === activeInstrument.value)\n .sort((a, b) => alertTime(b) - alertTime(a))\n)\n\nfunction toggleLevel(level: InstrumentAlertLevel) {\n const next = new Set(activeLevels.value)\n if (next.has(level)) {\n if (next.size > 1) next.delete(level)\n } else {\n next.add(level)\n }\n activeLevels.value = next\n}\n\nfunction toggleInstrument(name: string) {\n activeInstrument.value = activeInstrument.value === name ? null : name\n}\n\nfunction instrumentName(alert: InstrumentAlert): string {\n return alert.instrument_name || alert.instrument_id\n}\n\nfunction alertTime(alert: InstrumentAlert): number {\n if (!alert.timestamp) return 0\n const date = alert.timestamp instanceof Date ? alert.timestamp : new Date(alert.timestamp)\n return Number.isNaN(date.getTime()) ? 0 : date.getTime()\n}\n\nfunction timeAgo(value: string | Date | undefined): string {\n if (!value) return ''\n const date = value instanceof Date ? value : new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n const seconds = Math.floor((Date.now() - date.getTime()) / 1000)\n if (seconds < 60) return 'just now'\n const minutes = Math.floor(seconds / 60)\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${hours}h ago`\n const days = Math.floor(hours / 24)\n if (days < 7) return `${days}d ago`\n return date.toLocaleDateString(props.locale, { month: 'short', day: 'numeric' })\n}\n\nfunction alertDetail(alert: InstrumentAlert): string {\n return alert.body?.detail ?? alert.message ?? alert.rule ?? ''\n}\n</script>\n\n<template>\n <div class=\"mint-instrument-alert-log\">\n <div class=\"mint-instrument-alert-log__header\">\n <span class=\"mint-instrument-alert-log__title\">{{ title }}</span>\n </div>\n\n <div class=\"mint-instrument-alert-log__filters\">\n <div class=\"mint-instrument-alert-log__filter-row\">\n <button\n v-for=\"level in levels\"\n :key=\"level\"\n type=\"button\"\n class=\"mint-instrument-alert-log__filter\"\n :class=\"{ 'mint-instrument-alert-log__filter--active': activeLevels.has(level) }\"\n :style=\"activeLevels.has(level) ? { '--filter-color': levelConfig[level].color } : undefined\"\n @click=\"toggleLevel(level)\"\n >\n <span\n class=\"mint-instrument-alert-log__filter-dot\"\n :style=\"{ backgroundColor: levelConfig[level].color }\"\n />\n {{ levelConfig[level].label }}\n <span class=\"mint-instrument-alert-log__filter-count\">{{ alertCounts[level] }}</span>\n </button>\n </div>\n <div v-if=\"instruments.length > 1\" class=\"mint-instrument-alert-log__filter-row\">\n <button\n v-for=\"instrument in instruments\"\n :key=\"instrument.name\"\n type=\"button\"\n class=\"mint-instrument-alert-log__filter\"\n :class=\"{ 'mint-instrument-alert-log__filter--active': activeInstrument === instrument.name }\"\n @click=\"toggleInstrument(instrument.name)\"\n >\n {{ instrument.name }}\n <span class=\"mint-instrument-alert-log__filter-count\">{{ instrument.count }}</span>\n </button>\n </div>\n </div>\n\n <TransitionGroup name=\"mint-instrument-alert\" tag=\"div\" class=\"mint-instrument-alert-log__list\">\n <div\n v-for=\"alert in filteredAlerts\"\n :key=\"alert.id ?? `${alert.instrument_id}-${alert.rule ?? alert.message}-${alert.timestamp ?? ''}`\"\n class=\"mint-instrument-alert\"\n :class=\"[\n levelConfig[alert.level]?.cssClass,\n { 'mint-instrument-alert--acknowledged': alert.acknowledged },\n ]\"\n >\n <div class=\"mint-instrument-alert__header\">\n <span class=\"mint-instrument-alert__instrument\">{{ instrumentName(alert) }}</span>\n <span v-if=\"alert.body?.source\" class=\"mint-instrument-alert__source\">\n {{ alert.body.source }}\n </span>\n <span class=\"mint-instrument-alert__time\">{{ timeAgo(alert.timestamp) }}</span>\n </div>\n <div class=\"mint-instrument-alert__detail\">{{ alertDetail(alert) }}</div>\n <div\n v-if=\"alert.body?.code != null || (acknowledgeable && !alert.acknowledged && alert.level !== 'info')\"\n class=\"mint-instrument-alert__footer\"\n >\n <span v-if=\"alert.body?.code != null\" class=\"mint-instrument-alert__code\">\n Code {{ alert.body.code }}\n </span>\n <button\n v-if=\"acknowledgeable && !alert.acknowledged && alert.level !== 'info'\"\n type=\"button\"\n class=\"mint-instrument-alert__ack\"\n @click=\"emit('acknowledge', alert)\"\n >\n Acknowledge\n </button>\n </div>\n </div>\n\n <div v-if=\"filteredAlerts.length === 0\" key=\"empty\" class=\"mint-instrument-alert-log__empty\">\n {{ alerts.length === 0 ? emptyMessage : filteredEmptyMessage }}\n </div>\n </TransitionGroup>\n </div>\n</template>\n\n<style>\n@import '../styles/components/instrument-monitor.css';\n</style>\n","<script setup lang=\"ts\">\n/** Filterable instrument alert/event list with optional acknowledge actions. */\nimport { computed, ref } from 'vue'\nimport type { InstrumentAlert, InstrumentAlertLevel } from '../types'\n\ninterface Props {\n alerts?: InstrumentAlert[]\n title?: string\n acknowledgeable?: boolean\n emptyMessage?: string\n filteredEmptyMessage?: string\n locale?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n alerts: () => [],\n title: 'Event Log',\n acknowledgeable: true,\n emptyMessage: 'No alerts received yet',\n filteredEmptyMessage: 'No alerts match current filters',\n locale: undefined,\n})\n\nconst emit = defineEmits<{\n acknowledge: [alert: InstrumentAlert]\n}>()\n\nconst levelConfig: Record<InstrumentAlertLevel, { label: string; cssClass: string; color: string }> = {\n critical: { label: 'Critical', cssClass: 'mint-instrument-alert--critical', color: '#ef4444' },\n warning: { label: 'Warning', cssClass: 'mint-instrument-alert--warning', color: '#eab308' },\n info: { label: 'Info', cssClass: 'mint-instrument-alert--info', color: '#3b82f6' },\n}\n\nconst levels: InstrumentAlertLevel[] = ['critical', 'warning', 'info']\nconst activeLevels = ref<Set<InstrumentAlertLevel>>(new Set(levels))\nconst activeInstrument = ref<string | null>(null)\n\nconst instruments = computed(() => {\n const counts = new Map<string, number>()\n for (const alert of props.alerts) {\n const name = instrumentName(alert)\n counts.set(name, (counts.get(name) ?? 0) + 1)\n }\n return [...counts.entries()].map(([name, count]) => ({ name, count }))\n})\n\nconst alertCounts = computed(() => {\n const counts: Record<InstrumentAlertLevel, number> = { critical: 0, warning: 0, info: 0 }\n for (const alert of props.alerts) {\n if (alert.level in counts) counts[alert.level]++\n }\n return counts\n})\n\nconst filteredAlerts = computed(() =>\n [...props.alerts]\n .filter(alert => activeLevels.value.has(alert.level))\n .filter(alert => !activeInstrument.value || instrumentName(alert) === activeInstrument.value)\n .sort((a, b) => alertTime(b) - alertTime(a))\n)\n\nfunction toggleLevel(level: InstrumentAlertLevel) {\n const next = new Set(activeLevels.value)\n if (next.has(level)) {\n if (next.size > 1) next.delete(level)\n } else {\n next.add(level)\n }\n activeLevels.value = next\n}\n\nfunction toggleInstrument(name: string) {\n activeInstrument.value = activeInstrument.value === name ? null : name\n}\n\nfunction instrumentName(alert: InstrumentAlert): string {\n return alert.instrument_name || alert.instrument_id\n}\n\nfunction alertTime(alert: InstrumentAlert): number {\n if (!alert.timestamp) return 0\n const date = alert.timestamp instanceof Date ? alert.timestamp : new Date(alert.timestamp)\n return Number.isNaN(date.getTime()) ? 0 : date.getTime()\n}\n\nfunction timeAgo(value: string | Date | undefined): string {\n if (!value) return ''\n const date = value instanceof Date ? value : new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n const seconds = Math.floor((Date.now() - date.getTime()) / 1000)\n if (seconds < 60) return 'just now'\n const minutes = Math.floor(seconds / 60)\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${hours}h ago`\n const days = Math.floor(hours / 24)\n if (days < 7) return `${days}d ago`\n return date.toLocaleDateString(props.locale, { month: 'short', day: 'numeric' })\n}\n\nfunction alertDetail(alert: InstrumentAlert): string {\n return alert.body?.detail ?? alert.message ?? alert.rule ?? ''\n}\n</script>\n\n<template>\n <div class=\"mint-instrument-alert-log\">\n <div class=\"mint-instrument-alert-log__header\">\n <span class=\"mint-instrument-alert-log__title\">{{ title }}</span>\n </div>\n\n <div class=\"mint-instrument-alert-log__filters\">\n <div class=\"mint-instrument-alert-log__filter-row\">\n <button\n v-for=\"level in levels\"\n :key=\"level\"\n type=\"button\"\n class=\"mint-instrument-alert-log__filter\"\n :class=\"{ 'mint-instrument-alert-log__filter--active': activeLevels.has(level) }\"\n :style=\"activeLevels.has(level) ? { '--filter-color': levelConfig[level].color } : undefined\"\n @click=\"toggleLevel(level)\"\n >\n <span\n class=\"mint-instrument-alert-log__filter-dot\"\n :style=\"{ backgroundColor: levelConfig[level].color }\"\n />\n {{ levelConfig[level].label }}\n <span class=\"mint-instrument-alert-log__filter-count\">{{ alertCounts[level] }}</span>\n </button>\n </div>\n <div v-if=\"instruments.length > 1\" class=\"mint-instrument-alert-log__filter-row\">\n <button\n v-for=\"instrument in instruments\"\n :key=\"instrument.name\"\n type=\"button\"\n class=\"mint-instrument-alert-log__filter\"\n :class=\"{ 'mint-instrument-alert-log__filter--active': activeInstrument === instrument.name }\"\n @click=\"toggleInstrument(instrument.name)\"\n >\n {{ instrument.name }}\n <span class=\"mint-instrument-alert-log__filter-count\">{{ instrument.count }}</span>\n </button>\n </div>\n </div>\n\n <TransitionGroup name=\"mint-instrument-alert\" tag=\"div\" class=\"mint-instrument-alert-log__list\">\n <div\n v-for=\"alert in filteredAlerts\"\n :key=\"alert.id ?? `${alert.instrument_id}-${alert.rule ?? alert.message}-${alert.timestamp ?? ''}`\"\n class=\"mint-instrument-alert\"\n :class=\"[\n levelConfig[alert.level]?.cssClass,\n { 'mint-instrument-alert--acknowledged': alert.acknowledged },\n ]\"\n >\n <div class=\"mint-instrument-alert__header\">\n <span class=\"mint-instrument-alert__instrument\">{{ instrumentName(alert) }}</span>\n <span v-if=\"alert.body?.source\" class=\"mint-instrument-alert__source\">\n {{ alert.body.source }}\n </span>\n <span class=\"mint-instrument-alert__time\">{{ timeAgo(alert.timestamp) }}</span>\n </div>\n <div class=\"mint-instrument-alert__detail\">{{ alertDetail(alert) }}</div>\n <div\n v-if=\"alert.body?.code != null || (acknowledgeable && !alert.acknowledged && alert.level !== 'info')\"\n class=\"mint-instrument-alert__footer\"\n >\n <span v-if=\"alert.body?.code != null\" class=\"mint-instrument-alert__code\">\n Code {{ alert.body.code }}\n </span>\n <button\n v-if=\"acknowledgeable && !alert.acknowledged && alert.level !== 'info'\"\n type=\"button\"\n class=\"mint-instrument-alert__ack\"\n @click=\"emit('acknowledge', alert)\"\n >\n Acknowledge\n </button>\n </div>\n </div>\n\n <div v-if=\"filteredAlerts.length === 0\" key=\"empty\" class=\"mint-instrument-alert-log__empty\">\n {{ alerts.length === 0 ? emptyMessage : filteredEmptyMessage }}\n </div>\n </TransitionGroup>\n </div>\n</template>\n\n<style>\n@import '../styles/components/instrument-monitor.css';\n</style>\n","<script setup lang=\"ts\">\n/** Status badge for shared instrument monitor states. */\nimport { computed } from 'vue'\nimport type { InstrumentState } from '../types'\n\ninterface Props {\n state: InstrumentState | string\n label?: string\n pulseWhenRunning?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n pulseWhenRunning: true,\n})\n\nconst stateConfig: Record<InstrumentState, { label: string; tone: string }> = {\n running: { label: 'RUNNING', tone: 'success' },\n standby: { label: 'STANDBY', tone: 'warning' },\n idle: { label: 'IDLE', tone: 'muted' },\n error: { label: 'ERROR', tone: 'error' },\n connected: { label: 'CONNECTED', tone: 'info' },\n disconnected: { label: 'OFFLINE', tone: 'muted' },\n}\n\nconst normalizedState = computed(() => props.state.toLowerCase())\nconst config = computed(() => {\n const match = stateConfig[normalizedState.value as InstrumentState]\n return match ?? { label: props.state.toUpperCase(), tone: 'muted' }\n})\nconst shouldPulse = computed(() => props.pulseWhenRunning && normalizedState.value === 'running')\n</script>\n\n<template>\n <span\n class=\"mint-instrument-state\"\n :class=\"[\n `mint-instrument-state--${config.tone}`,\n { 'mint-instrument-state--pulse': shouldPulse },\n { 'mint-instrument-state--offline': normalizedState === 'disconnected' },\n ]\"\n >\n <span v-if=\"shouldPulse\" class=\"mint-instrument-state__dot\" />\n {{ label ?? config.label }}\n </span>\n</template>\n\n<style>\n@import '../styles/components/instrument-monitor.css';\n</style>\n","<script setup lang=\"ts\">\n/** Status badge for shared instrument monitor states. */\nimport { computed } from 'vue'\nimport type { InstrumentState } from '../types'\n\ninterface Props {\n state: InstrumentState | string\n label?: string\n pulseWhenRunning?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n pulseWhenRunning: true,\n})\n\nconst stateConfig: Record<InstrumentState, { label: string; tone: string }> = {\n running: { label: 'RUNNING', tone: 'success' },\n standby: { label: 'STANDBY', tone: 'warning' },\n idle: { label: 'IDLE', tone: 'muted' },\n error: { label: 'ERROR', tone: 'error' },\n connected: { label: 'CONNECTED', tone: 'info' },\n disconnected: { label: 'OFFLINE', tone: 'muted' },\n}\n\nconst normalizedState = computed(() => props.state.toLowerCase())\nconst config = computed(() => {\n const match = stateConfig[normalizedState.value as InstrumentState]\n return match ?? { label: props.state.toUpperCase(), tone: 'muted' }\n})\nconst shouldPulse = computed(() => props.pulseWhenRunning && normalizedState.value === 'running')\n</script>\n\n<template>\n <span\n class=\"mint-instrument-state\"\n :class=\"[\n `mint-instrument-state--${config.tone}`,\n { 'mint-instrument-state--pulse': shouldPulse },\n { 'mint-instrument-state--offline': normalizedState === 'disconnected' },\n ]\"\n >\n <span v-if=\"shouldPulse\" class=\"mint-instrument-state__dot\" />\n {{ label ?? config.label }}\n </span>\n</template>\n\n<style>\n@import '../styles/components/instrument-monitor.css';\n</style>\n","<script setup lang=\"ts\">\n/** Compact card for live instrument monitor status, sample, method, sequence progress, and ETA. */\nimport { computed } from 'vue'\nimport {\n estimateSequenceFinishDate,\n formatSequenceRemaining,\n estimateSequenceRemainingSeconds,\n} from '../instrument'\nimport type { InstrumentState, InstrumentStatus, SequenceProgress } from '../types'\nimport InstrumentStateBadge from './InstrumentStateBadge.vue'\nimport SequenceProgressBar from './SequenceProgressBar.vue'\n\ninterface Props {\n status: InstrumentStatus\n name?: string\n showPlaceholders?: boolean\n locale?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n name: undefined,\n showPlaceholders: true,\n locale: undefined,\n})\n\nconst displayName = computed(() =>\n props.name\n ?? props.status.instrument_name\n ?? props.status.instrument_id\n)\nconst isRunning = computed(() => props.status.state === 'running')\nconst sampleLabel = computed(() => {\n const sample = props.status.current_sample\n return sample ? (sample.sample_name ?? sample.sample_id ?? sample.file_name) : null\n})\n\nconst etaDisplay = computed(() => {\n const progress = props.status.sequence_progress\n if (!progress) return null\n const finish = estimateSequenceFinishDate(progress)\n if (!finish) return null\n\n const now = new Date()\n const sameDay =\n finish.getFullYear() === now.getFullYear()\n && finish.getMonth() === now.getMonth()\n && finish.getDate() === now.getDate()\n\n const time = finish.toLocaleTimeString(props.locale, { hour: 'numeric', minute: '2-digit' })\n if (sameDay) return time\n\n const sameYear = finish.getFullYear() === now.getFullYear()\n const date = finish.toLocaleDateString(props.locale, {\n month: 'short',\n day: 'numeric',\n ...(sameYear ? {} : { year: 'numeric' }),\n })\n return `${date}, ${time}`\n})\n\nconst remainingLabel = computed(() => {\n const progress = props.status.sequence_progress\n if (!progress) return null\n return formatSequenceRemaining(progress)\n})\n\nconst lastSeenAgo = computed(() => {\n const value = props.status.last_seen ?? props.status.timestamp\n if (!value) return null\n return formatRelativeAge(value)\n})\n\nconst stateClass = computed(() => `mint-instrument-card--${props.status.state}`)\n\nfunction stateTone(state: InstrumentState | string): string {\n if (state === 'running') return 'success'\n if (state === 'standby') return 'warning'\n if (state === 'error') return 'error'\n if (state === 'connected') return 'info'\n return 'muted'\n}\n\nfunction formatRelativeAge(value: string | Date): string | null {\n const date = value instanceof Date ? value : new Date(value)\n if (Number.isNaN(date.getTime())) return null\n const minutes = Math.floor((Date.now() - date.getTime()) / 60_000)\n if (minutes < 1) return 'just now'\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${hours}h ago`\n const days = Math.floor(hours / 24)\n return days === 1 ? '1 day ago' : `${days} days ago`\n}\n\nfunction hasRemaining(progress: SequenceProgress | null | undefined): boolean {\n const remaining = estimateSequenceRemainingSeconds(progress)\n return remaining !== null && remaining > 0\n}\n</script>\n\n<template>\n <div class=\"mint-instrument-card\" :class=\"stateClass\">\n <div class=\"mint-instrument-card__header\">\n <span\n class=\"mint-instrument-card__dot\"\n :class=\"`mint-instrument-card__dot--${stateTone(status.state)}`\"\n />\n <strong class=\"mint-instrument-card__name\">{{ displayName }}</strong>\n <InstrumentStateBadge :state=\"status.state\" />\n </div>\n\n <template v-if=\"isRunning\">\n <div v-if=\"status.current_sample\" class=\"mint-instrument-card__sample\">\n <span class=\"mint-instrument-card__sample-label\">Currently Running</span>\n <div class=\"mint-instrument-card__sample-row\">\n <span class=\"mint-instrument-card__sample-dot\" />\n <span class=\"mint-instrument-card__sample-name\">{{ sampleLabel }}</span>\n <span\n v-if=\"status.current_sample.vial_position\"\n class=\"mint-instrument-card__sample-vial\"\n >\n {{ status.current_sample.vial_position }}\n </span>\n </div>\n </div>\n\n <span\n v-if=\"status.active_method\"\n class=\"mint-instrument-card__method\"\n :title=\"status.active_method\"\n >\n {{ status.active_method }}\n </span>\n\n <SequenceProgressBar\n v-if=\"status.sequence_progress\"\n :progress=\"status.sequence_progress\"\n compact\n />\n\n <div\n v-if=\"etaDisplay || hasRemaining(status.sequence_progress)\"\n class=\"mint-instrument-card__eta\"\n >\n <span class=\"mint-instrument-card__eta-label\">ETA</span>\n <span v-if=\"etaDisplay\" class=\"mint-instrument-card__eta-time\">{{ etaDisplay }}</span>\n <span v-if=\"remainingLabel\" class=\"mint-instrument-card__eta-remaining\">\n {{ remainingLabel }}\n </span>\n </div>\n\n <span\n v-if=\"status.sequence_progress?.sequence_name\"\n class=\"mint-instrument-card__sequence\"\n :title=\"status.sequence_progress.sequence_name\"\n >\n {{ status.sequence_progress.sequence_name }}\n </span>\n </template>\n\n <template v-else-if=\"status.state === 'error'\">\n <div class=\"mint-instrument-card__error\">\n <div class=\"mint-instrument-card__error-message\">\n {{ status.active_method ?? 'Instrument error' }}\n </div>\n <div v-if=\"lastSeenAgo\" class=\"mint-instrument-card__error-detail\">\n Last seen {{ lastSeenAgo }}\n </div>\n </div>\n </template>\n\n <template v-else-if=\"showPlaceholders\">\n <div class=\"mint-instrument-card__placeholder\">\n <span class=\"mint-instrument-card__muted\">No sample running</span>\n <span class=\"mint-instrument-card__muted\">No method</span>\n <SequenceProgressBar\n :progress=\"{ current_sample: 0, total_samples: 0 }\"\n compact\n />\n <span class=\"mint-instrument-card__muted\">No sequence</span>\n </div>\n </template>\n </div>\n</template>\n\n<style>\n@import '../styles/components/instrument-monitor.css';\n</style>\n","<script setup lang=\"ts\">\n/** Compact card for live instrument monitor status, sample, method, sequence progress, and ETA. */\nimport { computed } from 'vue'\nimport {\n estimateSequenceFinishDate,\n formatSequenceRemaining,\n estimateSequenceRemainingSeconds,\n} from '../instrument'\nimport type { InstrumentState, InstrumentStatus, SequenceProgress } from '../types'\nimport InstrumentStateBadge from './InstrumentStateBadge.vue'\nimport SequenceProgressBar from './SequenceProgressBar.vue'\n\ninterface Props {\n status: InstrumentStatus\n name?: string\n showPlaceholders?: boolean\n locale?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n name: undefined,\n showPlaceholders: true,\n locale: undefined,\n})\n\nconst displayName = computed(() =>\n props.name\n ?? props.status.instrument_name\n ?? props.status.instrument_id\n)\nconst isRunning = computed(() => props.status.state === 'running')\nconst sampleLabel = computed(() => {\n const sample = props.status.current_sample\n return sample ? (sample.sample_name ?? sample.sample_id ?? sample.file_name) : null\n})\n\nconst etaDisplay = computed(() => {\n const progress = props.status.sequence_progress\n if (!progress) return null\n const finish = estimateSequenceFinishDate(progress)\n if (!finish) return null\n\n const now = new Date()\n const sameDay =\n finish.getFullYear() === now.getFullYear()\n && finish.getMonth() === now.getMonth()\n && finish.getDate() === now.getDate()\n\n const time = finish.toLocaleTimeString(props.locale, { hour: 'numeric', minute: '2-digit' })\n if (sameDay) return time\n\n const sameYear = finish.getFullYear() === now.getFullYear()\n const date = finish.toLocaleDateString(props.locale, {\n month: 'short',\n day: 'numeric',\n ...(sameYear ? {} : { year: 'numeric' }),\n })\n return `${date}, ${time}`\n})\n\nconst remainingLabel = computed(() => {\n const progress = props.status.sequence_progress\n if (!progress) return null\n return formatSequenceRemaining(progress)\n})\n\nconst lastSeenAgo = computed(() => {\n const value = props.status.last_seen ?? props.status.timestamp\n if (!value) return null\n return formatRelativeAge(value)\n})\n\nconst stateClass = computed(() => `mint-instrument-card--${props.status.state}`)\n\nfunction stateTone(state: InstrumentState | string): string {\n if (state === 'running') return 'success'\n if (state === 'standby') return 'warning'\n if (state === 'error') return 'error'\n if (state === 'connected') return 'info'\n return 'muted'\n}\n\nfunction formatRelativeAge(value: string | Date): string | null {\n const date = value instanceof Date ? value : new Date(value)\n if (Number.isNaN(date.getTime())) return null\n const minutes = Math.floor((Date.now() - date.getTime()) / 60_000)\n if (minutes < 1) return 'just now'\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${hours}h ago`\n const days = Math.floor(hours / 24)\n return days === 1 ? '1 day ago' : `${days} days ago`\n}\n\nfunction hasRemaining(progress: SequenceProgress | null | undefined): boolean {\n const remaining = estimateSequenceRemainingSeconds(progress)\n return remaining !== null && remaining > 0\n}\n</script>\n\n<template>\n <div class=\"mint-instrument-card\" :class=\"stateClass\">\n <div class=\"mint-instrument-card__header\">\n <span\n class=\"mint-instrument-card__dot\"\n :class=\"`mint-instrument-card__dot--${stateTone(status.state)}`\"\n />\n <strong class=\"mint-instrument-card__name\">{{ displayName }}</strong>\n <InstrumentStateBadge :state=\"status.state\" />\n </div>\n\n <template v-if=\"isRunning\">\n <div v-if=\"status.current_sample\" class=\"mint-instrument-card__sample\">\n <span class=\"mint-instrument-card__sample-label\">Currently Running</span>\n <div class=\"mint-instrument-card__sample-row\">\n <span class=\"mint-instrument-card__sample-dot\" />\n <span class=\"mint-instrument-card__sample-name\">{{ sampleLabel }}</span>\n <span\n v-if=\"status.current_sample.vial_position\"\n class=\"mint-instrument-card__sample-vial\"\n >\n {{ status.current_sample.vial_position }}\n </span>\n </div>\n </div>\n\n <span\n v-if=\"status.active_method\"\n class=\"mint-instrument-card__method\"\n :title=\"status.active_method\"\n >\n {{ status.active_method }}\n </span>\n\n <SequenceProgressBar\n v-if=\"status.sequence_progress\"\n :progress=\"status.sequence_progress\"\n compact\n />\n\n <div\n v-if=\"etaDisplay || hasRemaining(status.sequence_progress)\"\n class=\"mint-instrument-card__eta\"\n >\n <span class=\"mint-instrument-card__eta-label\">ETA</span>\n <span v-if=\"etaDisplay\" class=\"mint-instrument-card__eta-time\">{{ etaDisplay }}</span>\n <span v-if=\"remainingLabel\" class=\"mint-instrument-card__eta-remaining\">\n {{ remainingLabel }}\n </span>\n </div>\n\n <span\n v-if=\"status.sequence_progress?.sequence_name\"\n class=\"mint-instrument-card__sequence\"\n :title=\"status.sequence_progress.sequence_name\"\n >\n {{ status.sequence_progress.sequence_name }}\n </span>\n </template>\n\n <template v-else-if=\"status.state === 'error'\">\n <div class=\"mint-instrument-card__error\">\n <div class=\"mint-instrument-card__error-message\">\n {{ status.active_method ?? 'Instrument error' }}\n </div>\n <div v-if=\"lastSeenAgo\" class=\"mint-instrument-card__error-detail\">\n Last seen {{ lastSeenAgo }}\n </div>\n </div>\n </template>\n\n <template v-else-if=\"showPlaceholders\">\n <div class=\"mint-instrument-card__placeholder\">\n <span class=\"mint-instrument-card__muted\">No sample running</span>\n <span class=\"mint-instrument-card__muted\">No method</span>\n <SequenceProgressBar\n :progress=\"{ current_sample: 0, total_samples: 0 }\"\n compact\n />\n <span class=\"mint-instrument-card__muted\">No sequence</span>\n </div>\n </template>\n </div>\n</template>\n\n<style>\n@import '../styles/components/instrument-monitor.css';\n</style>\n","import type { LcmsPlateCell, LcmsPlateType } from './utils/rack'\n\nexport type LcmsPolarity = 'POS' | 'NEG' | 'BOTH'\nexport type LcmsContainerType = 'VIAL' | 'PLATE'\n\nexport interface LcmsSequenceItem {\n sample_type: string\n file_name: string\n sample_id: string\n path: string\n instrument_method: string\n position: string\n injection_volume: number\n}\n\nexport interface LcmsSequenceTableColumn {\n key: keyof LcmsSequenceItem | 'index' | 'actions'\n label: string\n align?: 'left' | 'right'\n}\n\nexport const DEFAULT_LCMS_SEQUENCE_COLUMNS: LcmsSequenceTableColumn[] = [\n { key: 'index', label: '#' },\n { key: 'sample_type', label: 'Type' },\n { key: 'file_name', label: 'File Name' },\n { key: 'position', label: 'Position' },\n { key: 'instrument_method', label: 'Method' },\n { key: 'injection_volume', label: 'Inj Vol', align: 'right' },\n]\n\nexport function extractLcmsCommonPrefix(fileNames: readonly string[]): string {\n if (fileNames.length === 0) return ''\n const stripped = fileNames.map(fileName => fileName.replace(/_\\d{3}$/, ''))\n const prefixes = stripped.map((fileName) => {\n const match = fileName.match(/^(.+?)_(POS|NEG)_/i)\n return match ? match[1] : fileName.replace(/_[^_]+$/, '')\n })\n\n let common = prefixes[0] ?? ''\n for (const prefix of prefixes.slice(1)) {\n while (common && !prefix.startsWith(common)) {\n const index = common.lastIndexOf('_')\n common = index > 0 ? common.substring(0, index) : common.substring(0, common.length - 1)\n }\n }\n return common\n}\n\nexport function extractLcmsSampleName(fileName: string, prefix = ''): string {\n let name = fileName.replace(/_\\d{3}$/, '')\n if (prefix && name.startsWith(prefix)) {\n name = name.substring(prefix.length).replace(/^_+/, '')\n }\n name = name.replace(/^(POS|NEG)_/i, '')\n return name || fileName\n}\n\nexport function lcmsWellIdFromPosition(position: string): string {\n return position.includes(':') ? position.split(':').pop() ?? position : position\n}\n\nexport function inferLcmsPlateTypeFromWellIds(wellIds: readonly string[]): LcmsPlateType | null {\n let maxCol = 0\n let maxRow = 0\n for (const wellId of wellIds) {\n const match = wellId.match(/^([A-Z])(\\d+)$/i)\n if (!match) continue\n maxRow = Math.max(maxRow, match[1].toUpperCase().charCodeAt(0) - 64)\n maxCol = Math.max(maxCol, Number(match[2]))\n }\n if (maxCol === 0 && maxRow === 0) return null\n return maxCol <= 9 && maxRow <= 6 ? '54vial' : '96well'\n}\n\nexport function reconstructLcmsPlateCellsFromSequenceItems(\n items: readonly LcmsSequenceItem[],\n): { cells: LcmsPlateCell[]; plateType: LcmsPlateType } | null {\n const unknownItems = items.filter(item => item.sample_type === 'Unknown')\n if (unknownItems.length === 0) return null\n\n const prefix = extractLcmsCommonPrefix(unknownItems.map(item => item.file_name))\n const seen = new Set<string>()\n const cells: LcmsPlateCell[] = []\n\n for (const item of unknownItems) {\n const wellId = lcmsWellIdFromPosition(item.position)\n if (seen.has(wellId)) continue\n seen.add(wellId)\n\n const match = wellId.match(/^([A-Z])(\\d+)$/i)\n if (!match) continue\n cells.push({\n row: match[1].toUpperCase(),\n column: Number(match[2]),\n sample_name: extractLcmsSampleName(item.file_name, prefix),\n })\n }\n\n if (cells.length === 0) return null\n const plateType = inferLcmsPlateTypeFromWellIds(cells.map(cell => `${cell.row}${cell.column}`))\n if (!plateType) return null\n return { cells, plateType }\n}\n\nexport function basenameFromWindowsPath(path: string | null | undefined): string {\n if (!path) return ''\n return path.split(/[\\\\/]/).pop() ?? path\n}\n","<script setup lang=\"ts\">\n/** Table for Xcalibur-compatible LCMS sequence rows with optional reorder/remove/duplicate controls. */\nimport { computed, ref } from 'vue'\nimport {\n basenameFromWindowsPath,\n DEFAULT_LCMS_SEQUENCE_COLUMNS,\n type LcmsSequenceItem,\n type LcmsSequenceTableColumn,\n} from '../lcms'\n\ninterface Props {\n items?: LcmsSequenceItem[]\n columns?: LcmsSequenceTableColumn[]\n editable?: boolean\n maxRows?: number\n showMoreLabel?: boolean\n emptyMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n items: () => [],\n columns: () => DEFAULT_LCMS_SEQUENCE_COLUMNS,\n editable: false,\n maxRows: undefined,\n showMoreLabel: true,\n emptyMessage: 'No sequence items to display',\n})\n\nconst emit = defineEmits<{\n reorder: [fromIndex: number, toIndex: number]\n remove: [index: number]\n duplicate: [index: number]\n}>()\n\nconst dragIndex = ref<number | null>(null)\nconst dropTargetIndex = ref<number | null>(null)\n\nconst displayItems = computed(() => {\n if (!props.maxRows) return props.items\n return props.items.slice(0, props.maxRows)\n})\n\nconst hasMore = computed(() =>\n props.maxRows != null && props.items.length > props.maxRows\n)\n\nfunction sampleTypeClass(type: string): string {\n const normalized = type.toLowerCase()\n if (normalized === 'blank') return 'mint-lcms-sequence-table__type--blank'\n if (normalized === 'qc' || normalized === 'iqc' || normalized === 'eqc') {\n return 'mint-lcms-sequence-table__type--qc'\n }\n return 'mint-lcms-sequence-table__type--sample'\n}\n\nfunction cellValue(item: LcmsSequenceItem, column: LcmsSequenceTableColumn, index: number): string | number {\n if (column.key === 'index') return index + 1\n if (column.key === 'actions') return ''\n if (column.key === 'instrument_method') return basenameFromWindowsPath(item.instrument_method)\n return item[column.key] ?? ''\n}\n\nfunction onDragStart(event: DragEvent, index: number) {\n if (!props.editable) return\n dragIndex.value = index\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', String(index))\n }\n}\n\nfunction onDragOver(event: DragEvent, index: number) {\n if (!props.editable) return\n event.preventDefault()\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'\n if (dragIndex.value !== null && dragIndex.value !== index) {\n dropTargetIndex.value = index\n }\n}\n\nfunction clearDragState() {\n dragIndex.value = null\n dropTargetIndex.value = null\n}\n\nfunction onDrop(event: DragEvent, toIndex: number) {\n if (!props.editable) return\n event.preventDefault()\n if (dragIndex.value !== null && dragIndex.value !== toIndex) {\n emit('reorder', dragIndex.value, toIndex)\n }\n clearDragState()\n}\n</script>\n\n<template>\n <div class=\"mint-lcms-sequence-table\">\n <div class=\"mint-lcms-sequence-table__scroller\">\n <table class=\"mint-lcms-sequence-table__table\">\n <thead>\n <tr>\n <th v-if=\"editable\" class=\"mint-lcms-sequence-table__drag-header\" />\n <th\n v-for=\"column in columns\"\n :key=\"column.key\"\n :class=\"[\n 'mint-lcms-sequence-table__th',\n { 'mint-lcms-sequence-table__th--right': column.align === 'right' },\n ]\"\n >\n {{ column.label }}\n </th>\n <th v-if=\"editable\" class=\"mint-lcms-sequence-table__actions-header\" />\n </tr>\n </thead>\n <tbody>\n <tr\n v-for=\"(item, index) in displayItems\"\n :key=\"`${item.file_name}-${item.position}-${index}`\"\n :draggable=\"editable\"\n class=\"mint-lcms-sequence-table__row\"\n :class=\"{\n 'mint-lcms-sequence-table__row--dragging': dragIndex === index,\n 'mint-lcms-sequence-table__row--drop-above': dropTargetIndex === index && dragIndex !== null && dragIndex > index,\n 'mint-lcms-sequence-table__row--drop-below': dropTargetIndex === index && dragIndex !== null && dragIndex < index,\n }\"\n @dragstart=\"onDragStart($event, index)\"\n @dragenter.prevent\n @dragover=\"onDragOver($event, index)\"\n @dragleave=\"dropTargetIndex = null\"\n @drop=\"onDrop($event, index)\"\n @dragend=\"clearDragState\"\n >\n <td v-if=\"editable\" class=\"mint-lcms-sequence-table__drag-cell\">\n <span class=\"mint-lcms-sequence-table__drag-handle\" aria-hidden=\"true\">::</span>\n </td>\n <td\n v-for=\"column in columns\"\n :key=\"column.key\"\n :class=\"[\n 'mint-lcms-sequence-table__td',\n `mint-lcms-sequence-table__td--${column.key}`,\n { 'mint-lcms-sequence-table__td--right': column.align === 'right' },\n ]\"\n :title=\"column.key === 'instrument_method' ? item.instrument_method : undefined\"\n >\n <span\n v-if=\"column.key === 'sample_type'\"\n class=\"mint-lcms-sequence-table__type\"\n :class=\"sampleTypeClass(item.sample_type)\"\n >\n {{ item.sample_type }}\n </span>\n <template v-else>{{ cellValue(item, column, index) }}</template>\n </td>\n <td v-if=\"editable\" class=\"mint-lcms-sequence-table__actions\">\n <button\n type=\"button\"\n class=\"mint-lcms-sequence-table__action\"\n title=\"Duplicate\"\n @click=\"emit('duplicate', index)\"\n >\n +\n </button>\n <button\n type=\"button\"\n class=\"mint-lcms-sequence-table__action mint-lcms-sequence-table__action--danger\"\n title=\"Delete\"\n @click=\"emit('remove', index)\"\n >\n x\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <div v-if=\"items.length === 0\" class=\"mint-lcms-sequence-table__empty\">\n {{ emptyMessage }}\n </div>\n\n <div v-if=\"hasMore && showMoreLabel\" class=\"mint-lcms-sequence-table__more\">\n Showing first {{ maxRows }} of {{ items.length }} items\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/lcms-sequence-table.css';\n</style>\n","<script setup lang=\"ts\">\n/** Table for Xcalibur-compatible LCMS sequence rows with optional reorder/remove/duplicate controls. */\nimport { computed, ref } from 'vue'\nimport {\n basenameFromWindowsPath,\n DEFAULT_LCMS_SEQUENCE_COLUMNS,\n type LcmsSequenceItem,\n type LcmsSequenceTableColumn,\n} from '../lcms'\n\ninterface Props {\n items?: LcmsSequenceItem[]\n columns?: LcmsSequenceTableColumn[]\n editable?: boolean\n maxRows?: number\n showMoreLabel?: boolean\n emptyMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n items: () => [],\n columns: () => DEFAULT_LCMS_SEQUENCE_COLUMNS,\n editable: false,\n maxRows: undefined,\n showMoreLabel: true,\n emptyMessage: 'No sequence items to display',\n})\n\nconst emit = defineEmits<{\n reorder: [fromIndex: number, toIndex: number]\n remove: [index: number]\n duplicate: [index: number]\n}>()\n\nconst dragIndex = ref<number | null>(null)\nconst dropTargetIndex = ref<number | null>(null)\n\nconst displayItems = computed(() => {\n if (!props.maxRows) return props.items\n return props.items.slice(0, props.maxRows)\n})\n\nconst hasMore = computed(() =>\n props.maxRows != null && props.items.length > props.maxRows\n)\n\nfunction sampleTypeClass(type: string): string {\n const normalized = type.toLowerCase()\n if (normalized === 'blank') return 'mint-lcms-sequence-table__type--blank'\n if (normalized === 'qc' || normalized === 'iqc' || normalized === 'eqc') {\n return 'mint-lcms-sequence-table__type--qc'\n }\n return 'mint-lcms-sequence-table__type--sample'\n}\n\nfunction cellValue(item: LcmsSequenceItem, column: LcmsSequenceTableColumn, index: number): string | number {\n if (column.key === 'index') return index + 1\n if (column.key === 'actions') return ''\n if (column.key === 'instrument_method') return basenameFromWindowsPath(item.instrument_method)\n return item[column.key] ?? ''\n}\n\nfunction onDragStart(event: DragEvent, index: number) {\n if (!props.editable) return\n dragIndex.value = index\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', String(index))\n }\n}\n\nfunction onDragOver(event: DragEvent, index: number) {\n if (!props.editable) return\n event.preventDefault()\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'\n if (dragIndex.value !== null && dragIndex.value !== index) {\n dropTargetIndex.value = index\n }\n}\n\nfunction clearDragState() {\n dragIndex.value = null\n dropTargetIndex.value = null\n}\n\nfunction onDrop(event: DragEvent, toIndex: number) {\n if (!props.editable) return\n event.preventDefault()\n if (dragIndex.value !== null && dragIndex.value !== toIndex) {\n emit('reorder', dragIndex.value, toIndex)\n }\n clearDragState()\n}\n</script>\n\n<template>\n <div class=\"mint-lcms-sequence-table\">\n <div class=\"mint-lcms-sequence-table__scroller\">\n <table class=\"mint-lcms-sequence-table__table\">\n <thead>\n <tr>\n <th v-if=\"editable\" class=\"mint-lcms-sequence-table__drag-header\" />\n <th\n v-for=\"column in columns\"\n :key=\"column.key\"\n :class=\"[\n 'mint-lcms-sequence-table__th',\n { 'mint-lcms-sequence-table__th--right': column.align === 'right' },\n ]\"\n >\n {{ column.label }}\n </th>\n <th v-if=\"editable\" class=\"mint-lcms-sequence-table__actions-header\" />\n </tr>\n </thead>\n <tbody>\n <tr\n v-for=\"(item, index) in displayItems\"\n :key=\"`${item.file_name}-${item.position}-${index}`\"\n :draggable=\"editable\"\n class=\"mint-lcms-sequence-table__row\"\n :class=\"{\n 'mint-lcms-sequence-table__row--dragging': dragIndex === index,\n 'mint-lcms-sequence-table__row--drop-above': dropTargetIndex === index && dragIndex !== null && dragIndex > index,\n 'mint-lcms-sequence-table__row--drop-below': dropTargetIndex === index && dragIndex !== null && dragIndex < index,\n }\"\n @dragstart=\"onDragStart($event, index)\"\n @dragenter.prevent\n @dragover=\"onDragOver($event, index)\"\n @dragleave=\"dropTargetIndex = null\"\n @drop=\"onDrop($event, index)\"\n @dragend=\"clearDragState\"\n >\n <td v-if=\"editable\" class=\"mint-lcms-sequence-table__drag-cell\">\n <span class=\"mint-lcms-sequence-table__drag-handle\" aria-hidden=\"true\">::</span>\n </td>\n <td\n v-for=\"column in columns\"\n :key=\"column.key\"\n :class=\"[\n 'mint-lcms-sequence-table__td',\n `mint-lcms-sequence-table__td--${column.key}`,\n { 'mint-lcms-sequence-table__td--right': column.align === 'right' },\n ]\"\n :title=\"column.key === 'instrument_method' ? item.instrument_method : undefined\"\n >\n <span\n v-if=\"column.key === 'sample_type'\"\n class=\"mint-lcms-sequence-table__type\"\n :class=\"sampleTypeClass(item.sample_type)\"\n >\n {{ item.sample_type }}\n </span>\n <template v-else>{{ cellValue(item, column, index) }}</template>\n </td>\n <td v-if=\"editable\" class=\"mint-lcms-sequence-table__actions\">\n <button\n type=\"button\"\n class=\"mint-lcms-sequence-table__action\"\n title=\"Duplicate\"\n @click=\"emit('duplicate', index)\"\n >\n +\n </button>\n <button\n type=\"button\"\n class=\"mint-lcms-sequence-table__action mint-lcms-sequence-table__action--danger\"\n title=\"Delete\"\n @click=\"emit('remove', index)\"\n >\n x\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <div v-if=\"items.length === 0\" class=\"mint-lcms-sequence-table__empty\">\n {{ emptyMessage }}\n </div>\n\n <div v-if=\"hasMore && showMoreLabel\" class=\"mint-lcms-sequence-table__more\">\n Showing first {{ maxRows }} of {{ items.length }} items\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/lcms-sequence-table.css';\n</style>\n","<script setup lang=\"ts\">\n/** Filterable, sortable timeline of audit entries for tracking experiment or resource history. */\nimport { computed, ref } from 'vue'\nimport type { AuditEntryType, AuditEntry } from '../types'\n\ninterface Props {\n entries: AuditEntry[]\n maxHeight?: string\n showFilters?: boolean\n emptyMessage?: string\n order?: 'newest' | 'oldest'\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showFilters: false,\n emptyMessage: 'No activity yet',\n order: 'newest',\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'entry-click': [entry: AuditEntry]\n}>()\n\nconst filterType = ref<AuditEntryType | ''>('')\nconst filterUser = ref('')\n\nconst uniqueTypes = computed(() => {\n const types = new Set(props.entries.map(e => e.type))\n return Array.from(types)\n})\n\nconst uniqueUsers = computed(() => {\n const users = new Set(props.entries.flatMap(e => e.user ? [e.user] : []))\n return Array.from(users)\n})\n\nconst filteredEntries = computed(() => {\n let result = props.entries\n if (filterType.value) {\n result = result.filter(e => e.type === filterType.value)\n }\n if (filterUser.value) {\n result = result.filter(e => e.user === filterUser.value)\n }\n return result\n})\n\nconst sortedEntries = computed(() => {\n const entries = [...filteredEntries.value]\n entries.sort((a, b) => {\n const timeA = new Date(a.timestamp).getTime()\n const timeB = new Date(b.timestamp).getTime()\n return props.order === 'newest' ? timeB - timeA : timeA - timeB\n })\n return entries\n})\n\nfunction getInitials(name: string): string {\n const words = name.trim().split(/\\s+/)\n if (words.length >= 2) {\n return (words[0][0] + words[1][0]).toUpperCase()\n }\n return (words[0]?.[0] ?? '').toUpperCase()\n}\n\nconst rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })\n\nfunction formatTimestamp(timestamp: Date | string): string {\n const date = timestamp instanceof Date ? timestamp : new Date(timestamp)\n const now = Date.now()\n const diffMs = date.getTime() - now\n const diffSec = Math.round(diffMs / 1000)\n const diffMin = Math.round(diffSec / 60)\n const diffHr = Math.round(diffMin / 60)\n const diffDay = Math.round(diffHr / 24)\n const diffWeek = Math.round(diffDay / 7)\n\n if (Math.abs(diffSec) < 60) return rtf.format(diffSec, 'second')\n if (Math.abs(diffMin) < 60) return rtf.format(diffMin, 'minute')\n if (Math.abs(diffHr) < 24) return rtf.format(diffHr, 'hour')\n if (Math.abs(diffDay) < 7) return rtf.format(diffDay, 'day')\n if (Math.abs(diffWeek) < 5) return rtf.format(diffWeek, 'week')\n return date.toLocaleDateString()\n}\n\nfunction handleEntryClick(entry: AuditEntry) {\n emit('entry-click', entry)\n}\n</script>\n\n<template>\n <div :class=\"['mint-audit-trail', `mint-audit-trail--${size}`]\">\n <div v-if=\"showFilters\" class=\"mint-audit-trail__filters\">\n <select v-model=\"filterType\" class=\"mint-audit-trail__filter-select\">\n <option value=\"\">All types</option>\n <option v-for=\"t in uniqueTypes\" :key=\"t\" :value=\"t\">{{ t }}</option>\n </select>\n <select v-model=\"filterUser\" class=\"mint-audit-trail__filter-select\">\n <option value=\"\">All users</option>\n <option v-for=\"u in uniqueUsers\" :key=\"u\" :value=\"u\">{{ u }}</option>\n </select>\n </div>\n\n <div\n v-if=\"sortedEntries.length\"\n class=\"mint-audit-trail__list\"\n :style=\"maxHeight ? { maxHeight, overflowY: 'auto' } : {}\"\n >\n <div class=\"mint-audit-trail__line\" />\n <div\n v-for=\"entry in sortedEntries\"\n :key=\"entry.id\"\n :class=\"['mint-audit-trail__entry', `mint-audit-trail__entry--${entry.type}`]\"\n @click=\"handleEntryClick(entry)\"\n >\n <slot name=\"entry\" :entry=\"entry\">\n <div class=\"mint-audit-trail__dot\" />\n <div class=\"mint-audit-trail__content\">\n <div class=\"mint-audit-trail__header\">\n <div v-if=\"entry.user\" class=\"mint-audit-trail__avatar\">{{ getInitials(entry.user) }}</div>\n <span v-if=\"entry.user\" class=\"mint-audit-trail__user\">{{ entry.user }}</span>\n <span class=\"mint-audit-trail__timestamp\">{{ formatTimestamp(entry.timestamp) }}</span>\n </div>\n <div class=\"mint-audit-trail__action\">{{ entry.action }}</div>\n <div v-if=\"entry.detail\" class=\"mint-audit-trail__detail\">{{ entry.detail }}</div>\n <div v-if=\"entry.metadata && Object.keys(entry.metadata).length\" class=\"mint-audit-trail__metadata\">\n <span\n v-for=\"(value, key) in entry.metadata\"\n :key=\"String(key)\"\n class=\"mint-audit-trail__metadata-item\"\n >\n <span class=\"mint-audit-trail__metadata-key\">{{ key }}:</span>\n {{ value }}\n </span>\n </div>\n </div>\n </slot>\n </div>\n </div>\n\n <div v-if=\"!sortedEntries.length\" class=\"mint-audit-trail__empty\">\n <slot name=\"empty\">{{ emptyMessage }}</slot>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/audit-trail.css';\n</style>\n","<script setup lang=\"ts\">\n/** Filterable, sortable timeline of audit entries for tracking experiment or resource history. */\nimport { computed, ref } from 'vue'\nimport type { AuditEntryType, AuditEntry } from '../types'\n\ninterface Props {\n entries: AuditEntry[]\n maxHeight?: string\n showFilters?: boolean\n emptyMessage?: string\n order?: 'newest' | 'oldest'\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showFilters: false,\n emptyMessage: 'No activity yet',\n order: 'newest',\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'entry-click': [entry: AuditEntry]\n}>()\n\nconst filterType = ref<AuditEntryType | ''>('')\nconst filterUser = ref('')\n\nconst uniqueTypes = computed(() => {\n const types = new Set(props.entries.map(e => e.type))\n return Array.from(types)\n})\n\nconst uniqueUsers = computed(() => {\n const users = new Set(props.entries.flatMap(e => e.user ? [e.user] : []))\n return Array.from(users)\n})\n\nconst filteredEntries = computed(() => {\n let result = props.entries\n if (filterType.value) {\n result = result.filter(e => e.type === filterType.value)\n }\n if (filterUser.value) {\n result = result.filter(e => e.user === filterUser.value)\n }\n return result\n})\n\nconst sortedEntries = computed(() => {\n const entries = [...filteredEntries.value]\n entries.sort((a, b) => {\n const timeA = new Date(a.timestamp).getTime()\n const timeB = new Date(b.timestamp).getTime()\n return props.order === 'newest' ? timeB - timeA : timeA - timeB\n })\n return entries\n})\n\nfunction getInitials(name: string): string {\n const words = name.trim().split(/\\s+/)\n if (words.length >= 2) {\n return (words[0][0] + words[1][0]).toUpperCase()\n }\n return (words[0]?.[0] ?? '').toUpperCase()\n}\n\nconst rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })\n\nfunction formatTimestamp(timestamp: Date | string): string {\n const date = timestamp instanceof Date ? timestamp : new Date(timestamp)\n const now = Date.now()\n const diffMs = date.getTime() - now\n const diffSec = Math.round(diffMs / 1000)\n const diffMin = Math.round(diffSec / 60)\n const diffHr = Math.round(diffMin / 60)\n const diffDay = Math.round(diffHr / 24)\n const diffWeek = Math.round(diffDay / 7)\n\n if (Math.abs(diffSec) < 60) return rtf.format(diffSec, 'second')\n if (Math.abs(diffMin) < 60) return rtf.format(diffMin, 'minute')\n if (Math.abs(diffHr) < 24) return rtf.format(diffHr, 'hour')\n if (Math.abs(diffDay) < 7) return rtf.format(diffDay, 'day')\n if (Math.abs(diffWeek) < 5) return rtf.format(diffWeek, 'week')\n return date.toLocaleDateString()\n}\n\nfunction handleEntryClick(entry: AuditEntry) {\n emit('entry-click', entry)\n}\n</script>\n\n<template>\n <div :class=\"['mint-audit-trail', `mint-audit-trail--${size}`]\">\n <div v-if=\"showFilters\" class=\"mint-audit-trail__filters\">\n <select v-model=\"filterType\" class=\"mint-audit-trail__filter-select\">\n <option value=\"\">All types</option>\n <option v-for=\"t in uniqueTypes\" :key=\"t\" :value=\"t\">{{ t }}</option>\n </select>\n <select v-model=\"filterUser\" class=\"mint-audit-trail__filter-select\">\n <option value=\"\">All users</option>\n <option v-for=\"u in uniqueUsers\" :key=\"u\" :value=\"u\">{{ u }}</option>\n </select>\n </div>\n\n <div\n v-if=\"sortedEntries.length\"\n class=\"mint-audit-trail__list\"\n :style=\"maxHeight ? { maxHeight, overflowY: 'auto' } : {}\"\n >\n <div class=\"mint-audit-trail__line\" />\n <div\n v-for=\"entry in sortedEntries\"\n :key=\"entry.id\"\n :class=\"['mint-audit-trail__entry', `mint-audit-trail__entry--${entry.type}`]\"\n @click=\"handleEntryClick(entry)\"\n >\n <slot name=\"entry\" :entry=\"entry\">\n <div class=\"mint-audit-trail__dot\" />\n <div class=\"mint-audit-trail__content\">\n <div class=\"mint-audit-trail__header\">\n <div v-if=\"entry.user\" class=\"mint-audit-trail__avatar\">{{ getInitials(entry.user) }}</div>\n <span v-if=\"entry.user\" class=\"mint-audit-trail__user\">{{ entry.user }}</span>\n <span class=\"mint-audit-trail__timestamp\">{{ formatTimestamp(entry.timestamp) }}</span>\n </div>\n <div class=\"mint-audit-trail__action\">{{ entry.action }}</div>\n <div v-if=\"entry.detail\" class=\"mint-audit-trail__detail\">{{ entry.detail }}</div>\n <div v-if=\"entry.metadata && Object.keys(entry.metadata).length\" class=\"mint-audit-trail__metadata\">\n <span\n v-for=\"(value, key) in entry.metadata\"\n :key=\"String(key)\"\n class=\"mint-audit-trail__metadata-item\"\n >\n <span class=\"mint-audit-trail__metadata-key\">{{ key }}:</span>\n {{ value }}\n </span>\n </div>\n </div>\n </slot>\n </div>\n </div>\n\n <div v-if=\"!sortedEntries.length\" class=\"mint-audit-trail__empty\">\n <slot name=\"empty\">{{ emptyMessage }}</slot>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/audit-trail.css';\n</style>\n","<script setup lang=\"ts\">\n/** Scrollable list tracking per-item status for a batch operation with overall progress summary. */\nimport { computed, ref, watch, nextTick } from 'vue'\nimport type { BatchItem, BatchSummary } from '../types'\nimport { useExpansionSet } from '../composables/useExpansionSet'\n\ninterface Props {\n items: BatchItem[]\n showSummary?: boolean\n title?: string\n maxHeight?: string\n autoScroll?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showSummary: true,\n autoScroll: true,\n})\n\nconst emit = defineEmits<{\n 'retry': [id: string]\n 'cancel': [id: string]\n}>()\n\nconst listRef = ref<HTMLElement | null>(null)\nconst errorExpansion = useExpansionSet()\n\nconst summary = computed<BatchSummary>(() => {\n const total = props.items.length\n const completed = props.items.filter(i => i.status === 'completed').length\n const processing = props.items.filter(i => i.status === 'processing').length\n const error = props.items.filter(i => i.status === 'error').length\n const pending = props.items.filter(i => i.status === 'pending').length\n const skipped = props.items.filter(i => i.status === 'skipped').length\n const percent = total > 0 ? Math.round(((completed + skipped) / total) * 100) : 0\n return { total, completed, processing, error, pending, skipped, percent }\n})\n\nfunction toggleError(id: string) {\n errorExpansion.toggle(id)\n}\n\nwatch(\n () => props.items.map(i => i.status),\n (newStatuses, oldStatuses) => {\n if (!props.autoScroll || !listRef.value) return\n for (let i = 0; i < newStatuses.length; i++) {\n if (newStatuses[i] === 'processing' && oldStatuses?.[i] !== 'processing') {\n nextTick(() => {\n const el = listRef.value?.querySelector(`[data-item-id=\"${props.items[i].id}\"]`)\n el?.scrollIntoView({ behavior: 'smooth', block: 'center' })\n })\n break\n }\n }\n },\n)\n</script>\n\n<template>\n <div class=\"mint-batch-progress\">\n <div v-if=\"title\" class=\"mint-batch-progress__header\">\n <span class=\"mint-batch-progress__title\">{{ title }}</span>\n <span class=\"mint-batch-progress__percent\">{{ summary.percent }}%</span>\n </div>\n\n <div class=\"mint-batch-progress__overall\">\n <div\n class=\"mint-batch-progress__overall-bar\"\n :style=\"{ width: `${summary.percent}%` }\"\n />\n </div>\n\n <slot v-if=\"showSummary\" name=\"summary\" :summary=\"summary\">\n <div class=\"mint-batch-progress__summary\">\n <span v-if=\"summary.completed\" class=\"mint-batch-progress__summary-item mint-batch-progress__summary-item--completed\">\n {{ summary.completed }} completed\n </span>\n <span v-if=\"summary.processing\" class=\"mint-batch-progress__summary-item mint-batch-progress__summary-item--processing\">\n {{ summary.processing }} processing\n </span>\n <span v-if=\"summary.error\" class=\"mint-batch-progress__summary-item mint-batch-progress__summary-item--error\">\n {{ summary.error }} failed\n </span>\n <span v-if=\"summary.pending\" class=\"mint-batch-progress__summary-item mint-batch-progress__summary-item--pending\">\n {{ summary.pending }} pending\n </span>\n <span v-if=\"summary.skipped\" class=\"mint-batch-progress__summary-item mint-batch-progress__summary-item--skipped\">\n {{ summary.skipped }} skipped\n </span>\n </div>\n </slot>\n\n <div\n ref=\"listRef\"\n class=\"mint-batch-progress__list\"\n :style=\"maxHeight ? { maxHeight, overflowY: 'auto' } : {}\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n :data-item-id=\"item.id\"\n :class=\"['mint-batch-progress__item', `mint-batch-progress__item--${item.status}`]\"\n >\n <slot name=\"item\" :item=\"item\">\n <div class=\"mint-batch-progress__item-row\">\n <!-- Status icon -->\n <div :class=\"['mint-batch-progress__item-icon', `mint-batch-progress__item-icon--${item.status}`]\">\n <!-- Pending: clock -->\n <svg v-if=\"item.status === 'pending'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <circle cx=\"8\" cy=\"8\" r=\"6.5\" />\n <path d=\"M8 4.5V8l2.5 1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n <!-- Processing: spinner -->\n <svg v-else-if=\"item.status === 'processing'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M8 1.5A6.5 6.5 0 1 1 1.5 8\" stroke-linecap=\"round\" />\n </svg>\n <!-- Completed: checkmark -->\n <svg v-else-if=\"item.status === 'completed'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M3.5 8.5L6.5 11.5L12.5 4.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n <!-- Error: x -->\n <svg v-else-if=\"item.status === 'error'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4.5 4.5l7 7M11.5 4.5l-7 7\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n <!-- Skipped: dash -->\n <svg v-else viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4 8h8\" stroke-linecap=\"round\" />\n </svg>\n </div>\n\n <span class=\"mint-batch-progress__item-label\">{{ item.label }}</span>\n\n <!-- Mini progress bar for processing items -->\n <div v-if=\"item.status === 'processing' && item.progress !== undefined\" class=\"mint-batch-progress__item-progress\">\n <div class=\"mint-batch-progress__item-progress-bar\" :style=\"{ width: `${item.progress}%` }\" />\n </div>\n\n <!-- Actions -->\n <div class=\"mint-batch-progress__item-actions\">\n <button\n v-if=\"item.status === 'error'\"\n type=\"button\"\n class=\"mint-batch-progress__retry-btn\"\n @click.stop=\"emit('retry', item.id)\"\n >\n Retry\n </button>\n <button\n v-if=\"item.status === 'processing'\"\n type=\"button\"\n class=\"mint-batch-progress__cancel-btn\"\n @click.stop=\"emit('cancel', item.id)\"\n >\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Error message (expandable) -->\n <div v-if=\"item.status === 'error' && item.message\" class=\"mint-batch-progress__item-error\">\n <button\n type=\"button\"\n class=\"mint-batch-progress__error-toggle\"\n @click.stop=\"toggleError(item.id)\"\n >\n {{ errorExpansion.isExpanded(item.id) ? 'Hide error' : 'Show error' }}\n </button>\n <div v-if=\"errorExpansion.isExpanded(item.id)\" class=\"mint-batch-progress__item-message\">\n {{ item.message }}\n </div>\n </div>\n </slot>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/batch-progress-list.css';\n</style>\n","<script setup lang=\"ts\">\n/** Scrollable list tracking per-item status for a batch operation with overall progress summary. */\nimport { computed, ref, watch, nextTick } from 'vue'\nimport type { BatchItem, BatchSummary } from '../types'\nimport { useExpansionSet } from '../composables/useExpansionSet'\n\ninterface Props {\n items: BatchItem[]\n showSummary?: boolean\n title?: string\n maxHeight?: string\n autoScroll?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showSummary: true,\n autoScroll: true,\n})\n\nconst emit = defineEmits<{\n 'retry': [id: string]\n 'cancel': [id: string]\n}>()\n\nconst listRef = ref<HTMLElement | null>(null)\nconst errorExpansion = useExpansionSet()\n\nconst summary = computed<BatchSummary>(() => {\n const total = props.items.length\n const completed = props.items.filter(i => i.status === 'completed').length\n const processing = props.items.filter(i => i.status === 'processing').length\n const error = props.items.filter(i => i.status === 'error').length\n const pending = props.items.filter(i => i.status === 'pending').length\n const skipped = props.items.filter(i => i.status === 'skipped').length\n const percent = total > 0 ? Math.round(((completed + skipped) / total) * 100) : 0\n return { total, completed, processing, error, pending, skipped, percent }\n})\n\nfunction toggleError(id: string) {\n errorExpansion.toggle(id)\n}\n\nwatch(\n () => props.items.map(i => i.status),\n (newStatuses, oldStatuses) => {\n if (!props.autoScroll || !listRef.value) return\n for (let i = 0; i < newStatuses.length; i++) {\n if (newStatuses[i] === 'processing' && oldStatuses?.[i] !== 'processing') {\n nextTick(() => {\n const el = listRef.value?.querySelector(`[data-item-id=\"${props.items[i].id}\"]`)\n el?.scrollIntoView({ behavior: 'smooth', block: 'center' })\n })\n break\n }\n }\n },\n)\n</script>\n\n<template>\n <div class=\"mint-batch-progress\">\n <div v-if=\"title\" class=\"mint-batch-progress__header\">\n <span class=\"mint-batch-progress__title\">{{ title }}</span>\n <span class=\"mint-batch-progress__percent\">{{ summary.percent }}%</span>\n </div>\n\n <div class=\"mint-batch-progress__overall\">\n <div\n class=\"mint-batch-progress__overall-bar\"\n :style=\"{ width: `${summary.percent}%` }\"\n />\n </div>\n\n <slot v-if=\"showSummary\" name=\"summary\" :summary=\"summary\">\n <div class=\"mint-batch-progress__summary\">\n <span v-if=\"summary.completed\" class=\"mint-batch-progress__summary-item mint-batch-progress__summary-item--completed\">\n {{ summary.completed }} completed\n </span>\n <span v-if=\"summary.processing\" class=\"mint-batch-progress__summary-item mint-batch-progress__summary-item--processing\">\n {{ summary.processing }} processing\n </span>\n <span v-if=\"summary.error\" class=\"mint-batch-progress__summary-item mint-batch-progress__summary-item--error\">\n {{ summary.error }} failed\n </span>\n <span v-if=\"summary.pending\" class=\"mint-batch-progress__summary-item mint-batch-progress__summary-item--pending\">\n {{ summary.pending }} pending\n </span>\n <span v-if=\"summary.skipped\" class=\"mint-batch-progress__summary-item mint-batch-progress__summary-item--skipped\">\n {{ summary.skipped }} skipped\n </span>\n </div>\n </slot>\n\n <div\n ref=\"listRef\"\n class=\"mint-batch-progress__list\"\n :style=\"maxHeight ? { maxHeight, overflowY: 'auto' } : {}\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n :data-item-id=\"item.id\"\n :class=\"['mint-batch-progress__item', `mint-batch-progress__item--${item.status}`]\"\n >\n <slot name=\"item\" :item=\"item\">\n <div class=\"mint-batch-progress__item-row\">\n <!-- Status icon -->\n <div :class=\"['mint-batch-progress__item-icon', `mint-batch-progress__item-icon--${item.status}`]\">\n <!-- Pending: clock -->\n <svg v-if=\"item.status === 'pending'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <circle cx=\"8\" cy=\"8\" r=\"6.5\" />\n <path d=\"M8 4.5V8l2.5 1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n <!-- Processing: spinner -->\n <svg v-else-if=\"item.status === 'processing'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M8 1.5A6.5 6.5 0 1 1 1.5 8\" stroke-linecap=\"round\" />\n </svg>\n <!-- Completed: checkmark -->\n <svg v-else-if=\"item.status === 'completed'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M3.5 8.5L6.5 11.5L12.5 4.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n <!-- Error: x -->\n <svg v-else-if=\"item.status === 'error'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4.5 4.5l7 7M11.5 4.5l-7 7\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n <!-- Skipped: dash -->\n <svg v-else viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4 8h8\" stroke-linecap=\"round\" />\n </svg>\n </div>\n\n <span class=\"mint-batch-progress__item-label\">{{ item.label }}</span>\n\n <!-- Mini progress bar for processing items -->\n <div v-if=\"item.status === 'processing' && item.progress !== undefined\" class=\"mint-batch-progress__item-progress\">\n <div class=\"mint-batch-progress__item-progress-bar\" :style=\"{ width: `${item.progress}%` }\" />\n </div>\n\n <!-- Actions -->\n <div class=\"mint-batch-progress__item-actions\">\n <button\n v-if=\"item.status === 'error'\"\n type=\"button\"\n class=\"mint-batch-progress__retry-btn\"\n @click.stop=\"emit('retry', item.id)\"\n >\n Retry\n </button>\n <button\n v-if=\"item.status === 'processing'\"\n type=\"button\"\n class=\"mint-batch-progress__cancel-btn\"\n @click.stop=\"emit('cancel', item.id)\"\n >\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Error message (expandable) -->\n <div v-if=\"item.status === 'error' && item.message\" class=\"mint-batch-progress__item-error\">\n <button\n type=\"button\"\n class=\"mint-batch-progress__error-toggle\"\n @click.stop=\"toggleError(item.id)\"\n >\n {{ errorExpansion.isExpanded(item.id) ? 'Hide error' : 'Show error' }}\n </button>\n <div v-if=\"errorExpansion.isExpanded(item.id)\" class=\"mint-batch-progress__item-message\">\n {{ item.message }}\n </div>\n </div>\n </slot>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/batch-progress-list.css';\n</style>\n","<script setup lang=\"ts\">\n/** Tabbed viewer for experiment output data with summary, hierarchy tree, and table views. */\nimport { ref, computed, watch } from 'vue'\nimport type { TreeNode, DataFrameColumn, SummaryData } from '../types'\nimport { useExperimentData } from '../composables/useExperimentData'\nimport SampleHierarchyTree from './SampleHierarchyTree.vue'\nimport DataFrame from './DataFrame.vue'\nimport SegmentedControl from './SegmentedControl.vue'\nimport BaseButton from './BaseButton.vue'\nimport Skeleton from './Skeleton.vue'\n\ninterface Props {\n treeData?: TreeNode[]\n tableData?: Record<string, unknown>[]\n tableColumns?: DataFrameColumn[] | undefined\n summaryData?: SummaryData | null\n defaultView?: 'summary' | 'tree' | 'table'\n title?: string\n pluginName?: string\n pluginRoutePrefix?: string\n experimentId?: number\n loading?: boolean\n downloadJsonUrl?: string\n downloadCsvUrl?: string\n autoFetch?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Data',\n defaultView: 'summary',\n loading: false,\n autoFetch: true,\n})\n\n// Auto-fetch when experimentId is provided and no prop data is given\nconst expData = useExperimentData()\nconst hasPropData = computed(() =>\n (props.treeData && props.treeData.length > 0) || props.summaryData,\n)\n\nwatch(\n () => props.experimentId,\n (id) => {\n if (props.autoFetch && id && !hasPropData.value) {\n expData.fetch(id)\n }\n },\n { immediate: true },\n)\n\n// Merge: props take priority over composable data\nconst mergedTreeData = computed(() =>\n props.treeData?.length ? props.treeData : expData.treeData.value,\n)\nconst mergedTableData = computed(() =>\n props.tableData?.length ? props.tableData : expData.tableData.value,\n)\nconst mergedSummaryData = computed(() =>\n props.summaryData ?? expData.summaryData.value,\n)\nconst isLoading = computed(() =>\n props.loading || expData.isLoading.value,\n)\n\nconst emit = defineEmits<{\n 'open-plugin': []\n 'download-json': []\n 'download-csv': []\n}>()\n\nconst viewMode = ref<string | number>(props.defaultView)\n\n// Reset view mode when defaultView prop changes\nwatch(() => props.defaultView, (val) => { viewMode.value = val })\n\nconst viewOptions = computed(() => {\n const opts = []\n if (mergedSummaryData.value) {\n opts.push({ value: 'summary', label: 'Summary' })\n }\n opts.push({ value: 'tree', label: 'Tree' })\n opts.push({ value: 'table', label: 'Table' })\n return opts\n})\n\n// Fall back to tree if summary is selected but no summary data\nwatch(mergedSummaryData, (val) => {\n if (!val && viewMode.value === 'summary') {\n viewMode.value = 'tree'\n }\n}, { immediate: true })\n\nconst hasTableData = computed(() =>\n mergedTableData.value && mergedTableData.value.length > 0,\n)\n\nconst metadataEntries = computed(() => {\n if (!mergedSummaryData.value?.metadata) return []\n return Object.entries(mergedSummaryData.value.metadata)\n .filter(([, v]) => v !== null && v !== undefined && v !== '')\n .map(([key, value]) => ({\n key: key.replace(/_/g, ' '),\n value: String(value),\n }))\n})\n\nfunction humanizeColumn(col: string): string {\n return col.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase())\n}\n\nfunction formatCellValue(value: unknown): string {\n if (value === null || value === undefined) return ''\n if (typeof value === 'object') return JSON.stringify(value)\n return String(value)\n}\n\nfunction columnsForSection(columns: string[]): DataFrameColumn[] {\n return columns.map((col) => ({\n key: col,\n label: humanizeColumn(col),\n sortable: true,\n formatter: (value: unknown) => formatCellValue(value),\n }))\n}\n\nfunction handleDownloadJson() {\n if (props.downloadJsonUrl) {\n window.open(props.downloadJsonUrl, '_blank')\n }\n emit('download-json')\n}\n\nfunction handleDownloadCsv() {\n if (props.downloadCsvUrl) {\n window.open(props.downloadCsvUrl, '_blank')\n }\n emit('download-csv')\n}\n</script>\n\n<template>\n <div class=\"mint-data-viewer\">\n <div class=\"mint-data-viewer__header\">\n <div class=\"mint-data-viewer__controls\">\n <SegmentedControl\n v-model=\"viewMode\"\n :options=\"viewOptions\"\n variant=\"card\"\n size=\"sm\"\n :full-width=\"false\"\n />\n </div>\n <div class=\"mint-data-viewer__actions\">\n <BaseButton\n v-if=\"pluginRoutePrefix && experimentId\"\n variant=\"secondary\"\n size=\"sm\"\n @click=\"emit('open-plugin')\"\n >\n Open in {{ pluginName || 'Plugin' }}\n </BaseButton>\n <BaseButton\n v-if=\"downloadJsonUrl\"\n variant=\"ghost\"\n size=\"sm\"\n @click=\"handleDownloadJson\"\n >\n JSON\n </BaseButton>\n <BaseButton\n v-if=\"downloadCsvUrl\"\n variant=\"ghost\"\n size=\"sm\"\n @click=\"handleDownloadCsv\"\n >\n CSV\n </BaseButton>\n </div>\n </div>\n\n <div class=\"mint-data-viewer__content\">\n <div v-if=\"isLoading\" class=\"mint-data-viewer__loading\">\n <div class=\"mint-data-viewer__skeleton\">\n <Skeleton width=\"40%\" height=\"16px\" />\n <Skeleton width=\"100%\" height=\"12px\" />\n <Skeleton width=\"100%\" height=\"12px\" />\n <Skeleton width=\"75%\" height=\"12px\" />\n </div>\n </div>\n <template v-else>\n <!-- Summary View -->\n <div v-if=\"viewMode === 'summary' && mergedSummaryData\" class=\"mint-summary\">\n <!-- Metadata pills -->\n <div v-if=\"metadataEntries.length\" class=\"mint-summary__metadata\">\n <span\n v-for=\"entry in metadataEntries\"\n :key=\"entry.key\"\n class=\"mint-summary__pill\"\n >\n <span class=\"mint-summary__pill-key\">{{ entry.key }}</span>\n <span class=\"mint-summary__pill-value\">{{ entry.value }}</span>\n </span>\n </div>\n\n <!-- Sections -->\n <div\n v-for=\"section in mergedSummaryData.sections\"\n :key=\"section.key\"\n class=\"mint-summary__section\"\n >\n <!-- Group section: cards with embedded tables -->\n <template v-if=\"section.type === 'group' && section.items\">\n <div\n v-for=\"(item, idx) in section.items\"\n :key=\"idx\"\n class=\"mint-summary__group-card\"\n >\n <div class=\"mint-summary__group-header\">\n <span class=\"mint-summary__group-label\">{{ item.label }}</span>\n <span class=\"mint-summary__group-count\">\n {{ item.item_count }} {{ item.item_key }}\n </span>\n </div>\n <div v-if=\"item.metadata && Object.keys(item.metadata).length\" class=\"mint-summary__group-meta\">\n <span\n v-for=\"(val, key) in item.metadata\"\n :key=\"String(key)\"\n class=\"mint-summary__pill mint-summary__pill--sm\"\n >\n <span class=\"mint-summary__pill-key\">{{ String(key).replace(/_/g, ' ') }}</span>\n <span class=\"mint-summary__pill-value\">{{ val }}</span>\n </span>\n </div>\n <DataFrame\n v-if=\"item.rows.length\"\n :data=\"item.rows\"\n :columns=\"columnsForSection(item.columns)\"\n :searchable=\"item.rows.length > 10\"\n :sortable=\"true\"\n :striped=\"true\"\n size=\"sm\"\n />\n </div>\n </template>\n\n <!-- Table section: flat table -->\n <template v-else-if=\"section.type === 'table' && section.rows\">\n <div class=\"mint-summary__table-header\">\n <span class=\"mint-summary__section-label\">{{ section.label }}</span>\n <span class=\"mint-summary__section-count\">{{ section.row_count }} rows</span>\n </div>\n <DataFrame\n :data=\"section.rows\"\n :columns=\"columnsForSection(section.columns || [])\"\n :searchable=\"(section.rows?.length || 0) > 10\"\n :sortable=\"true\"\n :striped=\"true\"\n size=\"sm\"\n />\n </template>\n </div>\n </div>\n\n <!-- Tree View -->\n <SampleHierarchyTree\n v-else-if=\"viewMode === 'tree'\"\n :nodes=\"mergedTreeData\"\n :expand-all=\"false\"\n :show-icons=\"true\"\n :show-counts=\"true\"\n size=\"sm\"\n />\n\n <!-- Table View -->\n <DataFrame\n v-else-if=\"viewMode === 'table' && hasTableData\"\n :data=\"mergedTableData!\"\n :columns=\"tableColumns ?? []\"\n :searchable=\"true\"\n :sortable=\"true\"\n />\n <div v-else-if=\"viewMode === 'table'\" class=\"mint-data-viewer__empty\">\n No tabular data available. Use tree view.\n </div>\n </template>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-data-viewer.css';\n</style>\n","<script setup lang=\"ts\">\n/** Tabbed viewer for experiment output data with summary, hierarchy tree, and table views. */\nimport { ref, computed, watch } from 'vue'\nimport type { TreeNode, DataFrameColumn, SummaryData } from '../types'\nimport { useExperimentData } from '../composables/useExperimentData'\nimport SampleHierarchyTree from './SampleHierarchyTree.vue'\nimport DataFrame from './DataFrame.vue'\nimport SegmentedControl from './SegmentedControl.vue'\nimport BaseButton from './BaseButton.vue'\nimport Skeleton from './Skeleton.vue'\n\ninterface Props {\n treeData?: TreeNode[]\n tableData?: Record<string, unknown>[]\n tableColumns?: DataFrameColumn[] | undefined\n summaryData?: SummaryData | null\n defaultView?: 'summary' | 'tree' | 'table'\n title?: string\n pluginName?: string\n pluginRoutePrefix?: string\n experimentId?: number\n loading?: boolean\n downloadJsonUrl?: string\n downloadCsvUrl?: string\n autoFetch?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Data',\n defaultView: 'summary',\n loading: false,\n autoFetch: true,\n})\n\n// Auto-fetch when experimentId is provided and no prop data is given\nconst expData = useExperimentData()\nconst hasPropData = computed(() =>\n (props.treeData && props.treeData.length > 0) || props.summaryData,\n)\n\nwatch(\n () => props.experimentId,\n (id) => {\n if (props.autoFetch && id && !hasPropData.value) {\n expData.fetch(id)\n }\n },\n { immediate: true },\n)\n\n// Merge: props take priority over composable data\nconst mergedTreeData = computed(() =>\n props.treeData?.length ? props.treeData : expData.treeData.value,\n)\nconst mergedTableData = computed(() =>\n props.tableData?.length ? props.tableData : expData.tableData.value,\n)\nconst mergedSummaryData = computed(() =>\n props.summaryData ?? expData.summaryData.value,\n)\nconst isLoading = computed(() =>\n props.loading || expData.isLoading.value,\n)\n\nconst emit = defineEmits<{\n 'open-plugin': []\n 'download-json': []\n 'download-csv': []\n}>()\n\nconst viewMode = ref<string | number>(props.defaultView)\n\n// Reset view mode when defaultView prop changes\nwatch(() => props.defaultView, (val) => { viewMode.value = val })\n\nconst viewOptions = computed(() => {\n const opts = []\n if (mergedSummaryData.value) {\n opts.push({ value: 'summary', label: 'Summary' })\n }\n opts.push({ value: 'tree', label: 'Tree' })\n opts.push({ value: 'table', label: 'Table' })\n return opts\n})\n\n// Fall back to tree if summary is selected but no summary data\nwatch(mergedSummaryData, (val) => {\n if (!val && viewMode.value === 'summary') {\n viewMode.value = 'tree'\n }\n}, { immediate: true })\n\nconst hasTableData = computed(() =>\n mergedTableData.value && mergedTableData.value.length > 0,\n)\n\nconst metadataEntries = computed(() => {\n if (!mergedSummaryData.value?.metadata) return []\n return Object.entries(mergedSummaryData.value.metadata)\n .filter(([, v]) => v !== null && v !== undefined && v !== '')\n .map(([key, value]) => ({\n key: key.replace(/_/g, ' '),\n value: String(value),\n }))\n})\n\nfunction humanizeColumn(col: string): string {\n return col.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase())\n}\n\nfunction formatCellValue(value: unknown): string {\n if (value === null || value === undefined) return ''\n if (typeof value === 'object') return JSON.stringify(value)\n return String(value)\n}\n\nfunction columnsForSection(columns: string[]): DataFrameColumn[] {\n return columns.map((col) => ({\n key: col,\n label: humanizeColumn(col),\n sortable: true,\n formatter: (value: unknown) => formatCellValue(value),\n }))\n}\n\nfunction handleDownloadJson() {\n if (props.downloadJsonUrl) {\n window.open(props.downloadJsonUrl, '_blank')\n }\n emit('download-json')\n}\n\nfunction handleDownloadCsv() {\n if (props.downloadCsvUrl) {\n window.open(props.downloadCsvUrl, '_blank')\n }\n emit('download-csv')\n}\n</script>\n\n<template>\n <div class=\"mint-data-viewer\">\n <div class=\"mint-data-viewer__header\">\n <div class=\"mint-data-viewer__controls\">\n <SegmentedControl\n v-model=\"viewMode\"\n :options=\"viewOptions\"\n variant=\"card\"\n size=\"sm\"\n :full-width=\"false\"\n />\n </div>\n <div class=\"mint-data-viewer__actions\">\n <BaseButton\n v-if=\"pluginRoutePrefix && experimentId\"\n variant=\"secondary\"\n size=\"sm\"\n @click=\"emit('open-plugin')\"\n >\n Open in {{ pluginName || 'Plugin' }}\n </BaseButton>\n <BaseButton\n v-if=\"downloadJsonUrl\"\n variant=\"ghost\"\n size=\"sm\"\n @click=\"handleDownloadJson\"\n >\n JSON\n </BaseButton>\n <BaseButton\n v-if=\"downloadCsvUrl\"\n variant=\"ghost\"\n size=\"sm\"\n @click=\"handleDownloadCsv\"\n >\n CSV\n </BaseButton>\n </div>\n </div>\n\n <div class=\"mint-data-viewer__content\">\n <div v-if=\"isLoading\" class=\"mint-data-viewer__loading\">\n <div class=\"mint-data-viewer__skeleton\">\n <Skeleton width=\"40%\" height=\"16px\" />\n <Skeleton width=\"100%\" height=\"12px\" />\n <Skeleton width=\"100%\" height=\"12px\" />\n <Skeleton width=\"75%\" height=\"12px\" />\n </div>\n </div>\n <template v-else>\n <!-- Summary View -->\n <div v-if=\"viewMode === 'summary' && mergedSummaryData\" class=\"mint-summary\">\n <!-- Metadata pills -->\n <div v-if=\"metadataEntries.length\" class=\"mint-summary__metadata\">\n <span\n v-for=\"entry in metadataEntries\"\n :key=\"entry.key\"\n class=\"mint-summary__pill\"\n >\n <span class=\"mint-summary__pill-key\">{{ entry.key }}</span>\n <span class=\"mint-summary__pill-value\">{{ entry.value }}</span>\n </span>\n </div>\n\n <!-- Sections -->\n <div\n v-for=\"section in mergedSummaryData.sections\"\n :key=\"section.key\"\n class=\"mint-summary__section\"\n >\n <!-- Group section: cards with embedded tables -->\n <template v-if=\"section.type === 'group' && section.items\">\n <div\n v-for=\"(item, idx) in section.items\"\n :key=\"idx\"\n class=\"mint-summary__group-card\"\n >\n <div class=\"mint-summary__group-header\">\n <span class=\"mint-summary__group-label\">{{ item.label }}</span>\n <span class=\"mint-summary__group-count\">\n {{ item.item_count }} {{ item.item_key }}\n </span>\n </div>\n <div v-if=\"item.metadata && Object.keys(item.metadata).length\" class=\"mint-summary__group-meta\">\n <span\n v-for=\"(val, key) in item.metadata\"\n :key=\"String(key)\"\n class=\"mint-summary__pill mint-summary__pill--sm\"\n >\n <span class=\"mint-summary__pill-key\">{{ String(key).replace(/_/g, ' ') }}</span>\n <span class=\"mint-summary__pill-value\">{{ val }}</span>\n </span>\n </div>\n <DataFrame\n v-if=\"item.rows.length\"\n :data=\"item.rows\"\n :columns=\"columnsForSection(item.columns)\"\n :searchable=\"item.rows.length > 10\"\n :sortable=\"true\"\n :striped=\"true\"\n size=\"sm\"\n />\n </div>\n </template>\n\n <!-- Table section: flat table -->\n <template v-else-if=\"section.type === 'table' && section.rows\">\n <div class=\"mint-summary__table-header\">\n <span class=\"mint-summary__section-label\">{{ section.label }}</span>\n <span class=\"mint-summary__section-count\">{{ section.row_count }} rows</span>\n </div>\n <DataFrame\n :data=\"section.rows\"\n :columns=\"columnsForSection(section.columns || [])\"\n :searchable=\"(section.rows?.length || 0) > 10\"\n :sortable=\"true\"\n :striped=\"true\"\n size=\"sm\"\n />\n </template>\n </div>\n </div>\n\n <!-- Tree View -->\n <SampleHierarchyTree\n v-else-if=\"viewMode === 'tree'\"\n :nodes=\"mergedTreeData\"\n :expand-all=\"false\"\n :show-icons=\"true\"\n :show-counts=\"true\"\n size=\"sm\"\n />\n\n <!-- Table View -->\n <DataFrame\n v-else-if=\"viewMode === 'table' && hasTableData\"\n :data=\"mergedTableData!\"\n :columns=\"tableColumns ?? []\"\n :searchable=\"true\"\n :sortable=\"true\"\n />\n <div v-else-if=\"viewMode === 'table'\" class=\"mint-data-viewer__empty\">\n No tabular data available. Use tree view.\n </div>\n </template>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-data-viewer.css';\n</style>\n","<script setup lang=\"ts\">\n/** Paired start/end time pickers that validate range order and display computed duration. */\nimport { computed } from 'vue'\nimport type { TimeRange } from '../types/components'\nimport TimePicker from './TimePicker.vue'\nimport { durationMinutes, formatDuration, compareTime } from '../composables/useTimeUtils'\n\ninterface Props {\n modelValue?: TimeRange\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n min?: string\n max?: string\n step?: number\n format?: '12h' | '24h'\n showDuration?: boolean\n blockedRanges?: TimeRange[]\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n error: false,\n size: 'md',\n step: 15,\n format: '24h',\n showDuration: true,\n blockedRanges: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: TimeRange | undefined]\n}>()\n\nconst startValue = computed(() => props.modelValue?.start)\nconst endValue = computed(() => props.modelValue?.end)\n\nconst duration = computed(() => {\n if (!props.modelValue?.start || !props.modelValue?.end) return null\n return durationMinutes(props.modelValue.start, props.modelValue.end)\n})\n\nconst durationLabel = computed(() => {\n if (duration.value === null) return ''\n return formatDuration(duration.value)\n})\n\nconst isInvalid = computed(() => {\n if (!props.modelValue?.start || !props.modelValue?.end) return false\n return compareTime(props.modelValue.start, props.modelValue.end) >= 0\n})\n\nconst endMin = computed(() => {\n if (props.modelValue?.start) return props.modelValue.start\n return props.min\n})\n\nfunction onStartChange(value: string | undefined) {\n if (!value) {\n emit('update:modelValue', undefined)\n return\n }\n const end = props.modelValue?.end || ''\n emit('update:modelValue', { start: value, end })\n}\n\nfunction onEndChange(value: string | undefined) {\n if (!value) {\n emit('update:modelValue', undefined)\n return\n }\n const start = props.modelValue?.start || ''\n emit('update:modelValue', { start, end: value })\n}\n</script>\n\n<template>\n <div class=\"mint-time-range\">\n <div class=\"mint-time-range__start\">\n <TimePicker\n :model-value=\"startValue\"\n :disabled=\"disabled\"\n :error=\"error || isInvalid\"\n :size=\"size\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :format=\"format\"\n placeholder=\"Start\"\n @update:model-value=\"onStartChange\"\n />\n </div>\n\n <div class=\"mint-time-range__separator\">\n <span\n v-if=\"showDuration && duration !== null\"\n :class=\"['mint-time-range__duration', isInvalid ? 'mint-time-range__duration--error' : '']\"\n >\n {{ durationLabel }}\n </span>\n </div>\n\n <div class=\"mint-time-range__end\">\n <TimePicker\n :model-value=\"endValue\"\n :disabled=\"disabled\"\n :error=\"error || isInvalid\"\n :size=\"size\"\n :min=\"endMin\"\n :max=\"max\"\n :step=\"step\"\n :format=\"format\"\n placeholder=\"End\"\n @update:model-value=\"onEndChange\"\n />\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/time-range-input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Paired start/end time pickers that validate range order and display computed duration. */\nimport { computed } from 'vue'\nimport type { TimeRange } from '../types/components'\nimport TimePicker from './TimePicker.vue'\nimport { durationMinutes, formatDuration, compareTime } from '../composables/useTimeUtils'\n\ninterface Props {\n modelValue?: TimeRange\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n min?: string\n max?: string\n step?: number\n format?: '12h' | '24h'\n showDuration?: boolean\n blockedRanges?: TimeRange[]\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n error: false,\n size: 'md',\n step: 15,\n format: '24h',\n showDuration: true,\n blockedRanges: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: TimeRange | undefined]\n}>()\n\nconst startValue = computed(() => props.modelValue?.start)\nconst endValue = computed(() => props.modelValue?.end)\n\nconst duration = computed(() => {\n if (!props.modelValue?.start || !props.modelValue?.end) return null\n return durationMinutes(props.modelValue.start, props.modelValue.end)\n})\n\nconst durationLabel = computed(() => {\n if (duration.value === null) return ''\n return formatDuration(duration.value)\n})\n\nconst isInvalid = computed(() => {\n if (!props.modelValue?.start || !props.modelValue?.end) return false\n return compareTime(props.modelValue.start, props.modelValue.end) >= 0\n})\n\nconst endMin = computed(() => {\n if (props.modelValue?.start) return props.modelValue.start\n return props.min\n})\n\nfunction onStartChange(value: string | undefined) {\n if (!value) {\n emit('update:modelValue', undefined)\n return\n }\n const end = props.modelValue?.end || ''\n emit('update:modelValue', { start: value, end })\n}\n\nfunction onEndChange(value: string | undefined) {\n if (!value) {\n emit('update:modelValue', undefined)\n return\n }\n const start = props.modelValue?.start || ''\n emit('update:modelValue', { start, end: value })\n}\n</script>\n\n<template>\n <div class=\"mint-time-range\">\n <div class=\"mint-time-range__start\">\n <TimePicker\n :model-value=\"startValue\"\n :disabled=\"disabled\"\n :error=\"error || isInvalid\"\n :size=\"size\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :format=\"format\"\n placeholder=\"Start\"\n @update:model-value=\"onStartChange\"\n />\n </div>\n\n <div class=\"mint-time-range__separator\">\n <span\n v-if=\"showDuration && duration !== null\"\n :class=\"['mint-time-range__duration', isInvalid ? 'mint-time-range__duration--error' : '']\"\n >\n {{ durationLabel }}\n </span>\n </div>\n\n <div class=\"mint-time-range__end\">\n <TimePicker\n :model-value=\"endValue\"\n :disabled=\"disabled\"\n :error=\"error || isInvalid\"\n :size=\"size\"\n :min=\"endMin\"\n :max=\"max\"\n :step=\"step\"\n :format=\"format\"\n placeholder=\"End\"\n @update:model-value=\"onEndChange\"\n />\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/time-range-input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Card displaying a lab resource with availability status, specs, tags, and a book action. */\nimport { computed } from 'vue'\nimport BasePill from './BasePill.vue'\nimport type { ResourceStatus, ResourceSpec } from '../types/components'\n\ninterface Props {\n name: string\n description?: string\n status?: ResourceStatus\n image?: string\n location?: string\n specs?: ResourceSpec[]\n tags?: string[]\n nextAvailable?: string\n showBookAction?: boolean\n compact?: boolean\n size?: 'sm' | 'md' | 'lg'\n statusLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n status: 'available',\n specs: () => [],\n tags: () => [],\n showBookAction: true,\n compact: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n book: []\n click: []\n}>()\n\nconst defaultStatusLabels: Record<ResourceStatus, string> = {\n 'available': 'Available',\n 'in-use': 'In Use',\n 'maintenance': 'Maintenance',\n 'offline': 'Offline',\n}\n\nconst displayStatusLabel = computed(() => props.statusLabel ?? defaultStatusLabels[props.status])\n\nconst cardClasses = computed(() => [\n 'mint-resource-card',\n `mint-resource-card--${props.size}`,\n { 'mint-resource-card--compact': props.compact },\n])\n\nfunction handleBook(event: MouseEvent) {\n event.stopPropagation()\n emit('book')\n}\n\nfunction handleClick() {\n emit('click')\n}\n</script>\n\n<template>\n <!-- Compact mode -->\n <div v-if=\"compact\" :class=\"cardClasses\" @click=\"handleClick\">\n <slot name=\"status\">\n <span :class=\"['mint-resource-card__status-dot', `mint-resource-card__status-dot--${status}`]\" />\n </slot>\n\n <span class=\"mint-resource-card__name\">{{ name }}</span>\n\n <span :class=\"['mint-resource-card__status-label', `mint-resource-card__status-label--${status}`]\">\n {{ displayStatusLabel }}\n </span>\n\n <slot name=\"action\">\n <button\n v-if=\"showBookAction\"\n type=\"button\"\n class=\"mint-resource-card__book-btn\"\n @click=\"handleBook\"\n >\n Book\n </button>\n </slot>\n </div>\n\n <!-- Card mode -->\n <div v-else :class=\"cardClasses\" @click=\"handleClick\">\n <div class=\"mint-resource-card__content\">\n <div class=\"mint-resource-card__header\">\n <!-- Image / fallback -->\n <div class=\"mint-resource-card__image\">\n <slot name=\"image\">\n <img v-if=\"image\" :src=\"image\" :alt=\"name\" />\n <div v-else class=\"mint-resource-card__image-fallback\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714a2.25 2.25 0 00.659 1.591L19 14.5M14.25 3.104c.251.023.501.05.75.082M19 14.5l-2.47 2.47a2.25 2.25 0 00-.659 1.59V21a.75.75 0 01-.75.75h-6.24a.75.75 0 01-.75-.75v-2.44a2.25 2.25 0 00-.659-1.59L5 14.5m14 0h-2.25m-10.5 0H4\" />\n </svg>\n </div>\n </slot>\n </div>\n\n <!-- Body -->\n <div class=\"mint-resource-card__body\">\n <div class=\"mint-resource-card__title-row\">\n <h3 class=\"mint-resource-card__name\">{{ name }}</h3>\n\n <slot name=\"status\">\n <div class=\"mint-resource-card__status\">\n <span :class=\"['mint-resource-card__status-dot', `mint-resource-card__status-dot--${status}`]\" />\n <span class=\"mint-resource-card__status-label\">{{ displayStatusLabel }}</span>\n </div>\n </slot>\n </div>\n\n <p v-if=\"description\" class=\"mint-resource-card__description\">{{ description }}</p>\n\n <p v-if=\"location\" class=\"mint-resource-card__location\">Location: {{ location }}</p>\n\n <p v-if=\"nextAvailable\" class=\"mint-resource-card__next-available\">Next available: {{ nextAvailable }}</p>\n </div>\n </div>\n\n <!-- Specs -->\n <div v-if=\"specs.length > 0 || $slots.specs\" class=\"mint-resource-card__specs\">\n <slot name=\"specs\">\n <div v-for=\"spec in specs\" :key=\"spec.label\" class=\"mint-resource-card__spec\">\n <span class=\"mint-resource-card__spec-label\">{{ spec.label }}</span>\n <span class=\"mint-resource-card__spec-value\">{{ spec.value }}</span>\n </div>\n </slot>\n </div>\n\n <!-- Tags + action -->\n <div v-if=\"tags.length > 0 || showBookAction || $slots.action\" class=\"mint-resource-card__footer\">\n <div class=\"mint-resource-card__tags\">\n <BasePill v-for=\"tag in tags\" :key=\"tag\" size=\"sm\">{{ tag }}</BasePill>\n </div>\n\n <div class=\"mint-resource-card__action\">\n <slot name=\"action\">\n <button\n v-if=\"showBookAction\"\n type=\"button\"\n class=\"mint-resource-card__book-btn\"\n @click=\"handleBook\"\n >\n Book Now\n <svg width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </slot>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/resource-card.css';\n</style>\n","<script setup lang=\"ts\">\n/** Card displaying a lab resource with availability status, specs, tags, and a book action. */\nimport { computed } from 'vue'\nimport BasePill from './BasePill.vue'\nimport type { ResourceStatus, ResourceSpec } from '../types/components'\n\ninterface Props {\n name: string\n description?: string\n status?: ResourceStatus\n image?: string\n location?: string\n specs?: ResourceSpec[]\n tags?: string[]\n nextAvailable?: string\n showBookAction?: boolean\n compact?: boolean\n size?: 'sm' | 'md' | 'lg'\n statusLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n status: 'available',\n specs: () => [],\n tags: () => [],\n showBookAction: true,\n compact: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n book: []\n click: []\n}>()\n\nconst defaultStatusLabels: Record<ResourceStatus, string> = {\n 'available': 'Available',\n 'in-use': 'In Use',\n 'maintenance': 'Maintenance',\n 'offline': 'Offline',\n}\n\nconst displayStatusLabel = computed(() => props.statusLabel ?? defaultStatusLabels[props.status])\n\nconst cardClasses = computed(() => [\n 'mint-resource-card',\n `mint-resource-card--${props.size}`,\n { 'mint-resource-card--compact': props.compact },\n])\n\nfunction handleBook(event: MouseEvent) {\n event.stopPropagation()\n emit('book')\n}\n\nfunction handleClick() {\n emit('click')\n}\n</script>\n\n<template>\n <!-- Compact mode -->\n <div v-if=\"compact\" :class=\"cardClasses\" @click=\"handleClick\">\n <slot name=\"status\">\n <span :class=\"['mint-resource-card__status-dot', `mint-resource-card__status-dot--${status}`]\" />\n </slot>\n\n <span class=\"mint-resource-card__name\">{{ name }}</span>\n\n <span :class=\"['mint-resource-card__status-label', `mint-resource-card__status-label--${status}`]\">\n {{ displayStatusLabel }}\n </span>\n\n <slot name=\"action\">\n <button\n v-if=\"showBookAction\"\n type=\"button\"\n class=\"mint-resource-card__book-btn\"\n @click=\"handleBook\"\n >\n Book\n </button>\n </slot>\n </div>\n\n <!-- Card mode -->\n <div v-else :class=\"cardClasses\" @click=\"handleClick\">\n <div class=\"mint-resource-card__content\">\n <div class=\"mint-resource-card__header\">\n <!-- Image / fallback -->\n <div class=\"mint-resource-card__image\">\n <slot name=\"image\">\n <img v-if=\"image\" :src=\"image\" :alt=\"name\" />\n <div v-else class=\"mint-resource-card__image-fallback\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714a2.25 2.25 0 00.659 1.591L19 14.5M14.25 3.104c.251.023.501.05.75.082M19 14.5l-2.47 2.47a2.25 2.25 0 00-.659 1.59V21a.75.75 0 01-.75.75h-6.24a.75.75 0 01-.75-.75v-2.44a2.25 2.25 0 00-.659-1.59L5 14.5m14 0h-2.25m-10.5 0H4\" />\n </svg>\n </div>\n </slot>\n </div>\n\n <!-- Body -->\n <div class=\"mint-resource-card__body\">\n <div class=\"mint-resource-card__title-row\">\n <h3 class=\"mint-resource-card__name\">{{ name }}</h3>\n\n <slot name=\"status\">\n <div class=\"mint-resource-card__status\">\n <span :class=\"['mint-resource-card__status-dot', `mint-resource-card__status-dot--${status}`]\" />\n <span class=\"mint-resource-card__status-label\">{{ displayStatusLabel }}</span>\n </div>\n </slot>\n </div>\n\n <p v-if=\"description\" class=\"mint-resource-card__description\">{{ description }}</p>\n\n <p v-if=\"location\" class=\"mint-resource-card__location\">Location: {{ location }}</p>\n\n <p v-if=\"nextAvailable\" class=\"mint-resource-card__next-available\">Next available: {{ nextAvailable }}</p>\n </div>\n </div>\n\n <!-- Specs -->\n <div v-if=\"specs.length > 0 || $slots.specs\" class=\"mint-resource-card__specs\">\n <slot name=\"specs\">\n <div v-for=\"spec in specs\" :key=\"spec.label\" class=\"mint-resource-card__spec\">\n <span class=\"mint-resource-card__spec-label\">{{ spec.label }}</span>\n <span class=\"mint-resource-card__spec-value\">{{ spec.value }}</span>\n </div>\n </slot>\n </div>\n\n <!-- Tags + action -->\n <div v-if=\"tags.length > 0 || showBookAction || $slots.action\" class=\"mint-resource-card__footer\">\n <div class=\"mint-resource-card__tags\">\n <BasePill v-for=\"tag in tags\" :key=\"tag\" size=\"sm\">{{ tag }}</BasePill>\n </div>\n\n <div class=\"mint-resource-card__action\">\n <slot name=\"action\">\n <button\n v-if=\"showBookAction\"\n type=\"button\"\n class=\"mint-resource-card__book-btn\"\n @click=\"handleBook\"\n >\n Book Now\n <svg width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </slot>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/resource-card.css';\n</style>\n","<script setup lang=\"ts\">\n/** Control panel for running a curve-fit job with progress bar, results table, and error display. */\nimport { computed } from 'vue'\nimport type { FitState, FitResultSummary } from '../types'\nimport BaseButton from './BaseButton.vue'\nimport ProgressBar from './ProgressBar.vue'\nimport AlertBox from './AlertBox.vue'\n\ninterface Props {\n state?: FitState\n progress?: number\n progressLabel?: string\n indeterminate?: boolean\n results?: FitResultSummary[]\n errorMessage?: string\n runLabel?: string\n cancelLabel?: string\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n state: 'idle',\n progress: 0,\n progressLabel: 'Fitting...',\n indeterminate: false,\n results: () => [],\n runLabel: 'Run Fit',\n cancelLabel: 'Cancel',\n disabled: false,\n})\n\nconst emit = defineEmits<{\n run: []\n cancel: []\n}>()\n\nconst isRunning = computed(() => props.state === 'running')\nconst showRunButton = computed(() => !isRunning.value)\nconst showResults = computed(() => props.state === 'completed' && props.results.length > 0)\nconst showError = computed(() => props.state === 'error' && props.errorMessage)\n\nfunction variantClass(variant?: string): string {\n if (!variant || variant === 'default') return ''\n return `mint-fit-panel__result-value--${variant}`\n}\n</script>\n\n<template>\n <div class=\"mint-fit-panel\">\n <!-- Plugin controls slot -->\n <slot name=\"controls\" />\n\n <!-- Action bar -->\n <div class=\"mint-fit-panel__actions\">\n <BaseButton\n v-if=\"showRunButton\"\n variant=\"primary\"\n size=\"sm\"\n :disabled=\"disabled\"\n full-width\n @click=\"emit('run')\"\n >\n {{ runLabel }}\n </BaseButton>\n <BaseButton\n v-if=\"isRunning\"\n variant=\"ghost\"\n size=\"sm\"\n full-width\n @click=\"emit('cancel')\"\n >\n {{ cancelLabel }}\n </BaseButton>\n </div>\n\n <!-- Progress -->\n <div v-if=\"isRunning\" class=\"mint-fit-panel__progress\">\n <ProgressBar\n :value=\"progress\"\n :label=\"progressLabel\"\n :indeterminate=\"indeterminate\"\n :show-value=\"!indeterminate\"\n size=\"sm\"\n />\n </div>\n\n <!-- Results -->\n <div v-if=\"showResults\" class=\"mint-fit-panel__results\">\n <slot name=\"results\">\n <div class=\"mint-fit-panel__result-list\">\n <div\n v-for=\"(item, idx) in results\"\n :key=\"idx\"\n class=\"mint-fit-panel__result-row\"\n >\n <span class=\"mint-fit-panel__result-label\">{{ item.label }}</span>\n <span\n class=\"mint-fit-panel__result-value\"\n :class=\"variantClass(item.variant)\"\n >\n {{ item.value }}\n </span>\n </div>\n </div>\n </slot>\n </div>\n\n <!-- Error -->\n <AlertBox v-if=\"showError\" type=\"error\">\n {{ errorMessage }}\n </AlertBox>\n\n <!-- Footer slot -->\n <slot name=\"footer\" />\n </div>\n</template>\n\n<style>\n@import '../styles/components/fit-panel.css';\n</style>\n","<script setup lang=\"ts\">\n/** Control panel for running a curve-fit job with progress bar, results table, and error display. */\nimport { computed } from 'vue'\nimport type { FitState, FitResultSummary } from '../types'\nimport BaseButton from './BaseButton.vue'\nimport ProgressBar from './ProgressBar.vue'\nimport AlertBox from './AlertBox.vue'\n\ninterface Props {\n state?: FitState\n progress?: number\n progressLabel?: string\n indeterminate?: boolean\n results?: FitResultSummary[]\n errorMessage?: string\n runLabel?: string\n cancelLabel?: string\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n state: 'idle',\n progress: 0,\n progressLabel: 'Fitting...',\n indeterminate: false,\n results: () => [],\n runLabel: 'Run Fit',\n cancelLabel: 'Cancel',\n disabled: false,\n})\n\nconst emit = defineEmits<{\n run: []\n cancel: []\n}>()\n\nconst isRunning = computed(() => props.state === 'running')\nconst showRunButton = computed(() => !isRunning.value)\nconst showResults = computed(() => props.state === 'completed' && props.results.length > 0)\nconst showError = computed(() => props.state === 'error' && props.errorMessage)\n\nfunction variantClass(variant?: string): string {\n if (!variant || variant === 'default') return ''\n return `mint-fit-panel__result-value--${variant}`\n}\n</script>\n\n<template>\n <div class=\"mint-fit-panel\">\n <!-- Plugin controls slot -->\n <slot name=\"controls\" />\n\n <!-- Action bar -->\n <div class=\"mint-fit-panel__actions\">\n <BaseButton\n v-if=\"showRunButton\"\n variant=\"primary\"\n size=\"sm\"\n :disabled=\"disabled\"\n full-width\n @click=\"emit('run')\"\n >\n {{ runLabel }}\n </BaseButton>\n <BaseButton\n v-if=\"isRunning\"\n variant=\"ghost\"\n size=\"sm\"\n full-width\n @click=\"emit('cancel')\"\n >\n {{ cancelLabel }}\n </BaseButton>\n </div>\n\n <!-- Progress -->\n <div v-if=\"isRunning\" class=\"mint-fit-panel__progress\">\n <ProgressBar\n :value=\"progress\"\n :label=\"progressLabel\"\n :indeterminate=\"indeterminate\"\n :show-value=\"!indeterminate\"\n size=\"sm\"\n />\n </div>\n\n <!-- Results -->\n <div v-if=\"showResults\" class=\"mint-fit-panel__results\">\n <slot name=\"results\">\n <div class=\"mint-fit-panel__result-list\">\n <div\n v-for=\"(item, idx) in results\"\n :key=\"idx\"\n class=\"mint-fit-panel__result-row\"\n >\n <span class=\"mint-fit-panel__result-label\">{{ item.label }}</span>\n <span\n class=\"mint-fit-panel__result-value\"\n :class=\"variantClass(item.variant)\"\n >\n {{ item.value }}\n </span>\n </div>\n </div>\n </slot>\n </div>\n\n <!-- Error -->\n <AlertBox v-if=\"showError\" type=\"error\">\n {{ errorMessage }}\n </AlertBox>\n\n <!-- Footer slot -->\n <slot name=\"footer\" />\n </div>\n</template>\n\n<style>\n@import '../styles/components/fit-panel.css';\n</style>\n","// Base components\nexport { default as BaseButton } from './BaseButton.vue'\nexport { default as BaseInput } from './BaseInput.vue'\nexport { default as BaseTextarea } from './BaseTextarea.vue'\nexport { default as BaseSelect } from './BaseSelect.vue'\nexport { default as BaseCheckbox } from './BaseCheckbox.vue'\nexport { default as BaseToggle } from './BaseToggle.vue'\nexport { default as BaseRadioGroup } from './BaseRadioGroup.vue'\nexport { default as BaseSlider } from './BaseSlider.vue'\nexport { default as ColorSlider } from './ColorSlider.vue'\nexport { default as BaseTabs } from './BaseTabs.vue'\nexport { default as BaseModal } from './BaseModal.vue'\nexport { default as SegmentedControl } from './SegmentedControl.vue'\nexport { default as MultiSelect } from './MultiSelect.vue'\nexport { default as BasePill } from './BasePill.vue'\nexport { default as DropdownButton } from './DropdownButton.vue'\nexport { default as Calendar } from './Calendar.vue'\nexport { default as DataFrame } from './DataFrame.vue'\n\n// Form components\nexport { default as FormField } from './FormField.vue'\nexport { default as DatePicker } from './DatePicker.vue'\nexport { default as TimePicker } from './TimePicker.vue'\nexport { default as TagsInput } from './TagsInput.vue'\nexport { default as NumberInput } from './NumberInput.vue'\nexport { default as FileUploader } from './FileUploader.vue'\n\n// Feedback components\nexport { default as AlertBox } from './AlertBox.vue'\nexport { default as AppToastContainer } from './AppToastContainer.vue'\n\n// Action components\nexport { default as IconButton } from './IconButton.vue'\nexport { default as ThemeToggle } from './ThemeToggle.vue'\n\n// Layout components\nexport { default as CollapsibleCard } from './CollapsibleCard.vue'\nexport { default as AppTopBar } from './AppTopBar.vue'\nexport { default as AppAvatarMenu } from './AppAvatarMenu.vue'\nexport { default as AppPluginSwitcher } from './AppPluginSwitcher.vue'\nexport { default as AppSidebar } from './AppSidebar.vue'\nexport { default as AppLayout } from './AppLayout.vue'\nexport { default as PluginWorkspaceView } from './PluginWorkspaceView.vue'\nexport { default as ComponentBindingRenderer } from './ComponentBindingRenderer.vue'\nexport { default as ControlWorkspaceView } from './ControlWorkspaceView.vue'\nexport { default as DoseDesignWorkspaceView } from './DoseDesignWorkspaceView.vue'\nexport { default as AppContainer } from './AppContainer.vue'\nexport { default as PluginIcon } from './PluginIcon.vue'\n\n// Utility components\nexport { default as Skeleton } from './Skeleton.vue'\nexport { default as LoadingSpinner } from './LoadingSpinner.vue'\nexport { default as Divider } from './Divider.vue'\nexport { default as StatusIndicator } from './StatusIndicator.vue'\nexport { default as ProgressBar } from './ProgressBar.vue'\nexport { default as Avatar } from './Avatar.vue'\nexport { default as EmptyState } from './EmptyState.vue'\nexport { default as Breadcrumb } from './Breadcrumb.vue'\nexport { default as Tooltip } from './Tooltip.vue'\nexport { default as ConfirmDialog } from './ConfirmDialog.vue'\nexport { default as ChartContainer } from './ChartContainer.vue'\nexport { default as SettingsModal } from './SettingsModal.vue'\n\n// Biological experiment components\nexport { default as BioTemplateRenderer } from './BioTemplateRenderer.vue'\nexport { default as BioTemplateExperimentWorkspaceView } from './BioTemplateExperimentWorkspaceView.vue'\nexport { default as BioTemplatePackWorkspaceView } from './BioTemplatePackWorkspaceView.vue'\nexport { default as BioTemplatePresetWorkspaceView } from './BioTemplatePresetWorkspaceView.vue'\nexport { default as WellPlate } from './WellPlate.vue'\nexport { default as RackEditor } from './RackEditor.vue'\nexport { default as SampleLegend } from './SampleLegend.vue'\nexport { default as PlateMapEditor } from './PlateMapEditor.vue'\nexport { default as ExperimentTimeline } from './ExperimentTimeline.vue'\n\n// Sample management components\nexport { default as SampleSelector } from './SampleSelector.vue'\nexport { default as AutoGroupModal } from './AutoGroupModal.vue'\nexport { default as GroupAssigner } from './GroupAssigner.vue'\n\n// Lab/Experiment components\nexport { default as MoleculeInput } from './MoleculeInput.vue'\nexport { default as ConcentrationInput } from './ConcentrationInput.vue'\nexport { default as DoseCalculator } from './DoseCalculator.vue'\nexport { default as ReagentEditor, type ReagentDefinition } from './ReagentEditor.vue'\nexport { default as ReagentList } from './ReagentList.vue'\nexport { default as SampleHierarchyTree } from './SampleHierarchyTree.vue'\nexport { default as ProtocolStepEditor } from './ProtocolStepEditor.vue'\n\n// Scientific display components\nexport { default as ScientificNumber } from './ScientificNumber.vue'\nexport { default as ChemicalFormula } from './ChemicalFormula.vue'\nexport { default as SequenceProgressBar } from './SequenceProgressBar.vue'\nexport { default as InstrumentAlertLog } from './InstrumentAlertLog.vue'\nexport { default as InstrumentStateBadge } from './InstrumentStateBadge.vue'\nexport { default as InstrumentStatusCard } from './InstrumentStatusCard.vue'\nexport { default as LcmsSequenceTable } from './LcmsSequenceTable.vue'\n\n// Scientific input components\nexport { default as FormulaInput } from './FormulaInput.vue'\nexport { default as SequenceInput } from './SequenceInput.vue'\nexport { default as UnitInput } from './UnitInput.vue'\n\n// Workflow components\nexport { default as StepWizard } from './StepWizard.vue'\nexport { default as AuditTrail } from './AuditTrail.vue'\nexport { default as BatchProgressList } from './BatchProgressList.vue'\n\n// Form builder components\nexport { default as FormBuilder } from './FormBuilder.vue'\nexport { default as FormActions } from './FormActions.vue'\n\n// Experiment data display components\nexport { default as ExperimentDataViewer } from './ExperimentDataViewer.vue'\nexport { default as ExperimentCodeBadge } from './ExperimentCodeBadge.vue'\n\n// Scheduling / booking components\nexport { default as DateTimePicker } from './DateTimePicker.vue'\nexport { default as TimeRangeInput } from './TimeRangeInput.vue'\nexport { default as ScheduleCalendar } from './ScheduleCalendar.vue'\nexport { default as ResourceCard } from './ResourceCard.vue'\n\n// Experiment / analysis components\nexport { default as ExperimentSelectorModal } from './ExperimentSelectorModal.vue'\nexport { default as ExperimentPopover } from './ExperimentPopover.vue'\nexport { default as FitPanel } from './FitPanel.vue'\n"],"mappings":"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"}
|