@morscherlab/mint-sdk 1.1.5 → 1.1.6
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/{EmptyState-LMPS3keb.js → EmptyState-p90quq8Z.js} +2 -2
- package/dist/{EmptyState-LMPS3keb.js.map → EmptyState-p90quq8Z.js.map} +1 -1
- package/dist/{ExperimentPopover-D6_RGzTY.js → ExperimentPopover-tLwJ7XF4.js} +3 -3
- package/dist/{ExperimentPopover-D6_RGzTY.js.map → ExperimentPopover-tLwJ7XF4.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-DcQ38zua.js → ExperimentSelectorModal-B6P2msT4.js} +12 -5
- package/dist/ExperimentSelectorModal-B6P2msT4.js.map +1 -0
- package/dist/{SettingsModal-Ccq2z-Ms.js → SettingsModal-D7LFUokT.js} +100 -156
- package/dist/SettingsModal-D7LFUokT.js.map +1 -0
- package/dist/__tests__/components/ActionMenu.test.d.ts +1 -0
- package/dist/__tests__/components/BaseModal.test.d.ts +1 -0
- package/dist/components/ActionMenu.vue.d.ts +21 -0
- package/dist/components/BaseModal.vue.d.ts +17 -1
- package/dist/components/GeneratedPluginView.vue.d.ts +3 -3
- package/dist/components/GeneratedResultRenderer.vue.d.ts +3 -3
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +8 -8
- package/dist/{components-CV5zuWa8.js → components-Dra_ac_5.js} +350 -71
- package/dist/components-Dra_ac_5.js.map +1 -0
- package/dist/composables/controlSchemaDoseDesign.d.ts +8 -1
- package/dist/composables/controlSchemaModel.d.ts +10 -1
- package/dist/composables/index.js +8 -8
- package/dist/composables/useAnalysisArtifacts.d.ts +9 -1
- package/dist/composables/useAppExperiment.d.ts +7 -0
- package/dist/composables/useBioTemplatePackWorkspace.d.ts +9 -1
- package/dist/composables/useControlWorkspace.d.ts +9 -1
- package/dist/composables/useGeneratedAnalysis.d.ts +9 -0
- package/dist/{composables-CW8NRXLB.js → composables-CRR_JxQD.js} +4 -4
- package/dist/{composables-CW8NRXLB.js.map → composables-CRR_JxQD.js.map} +1 -1
- package/dist/{experiment-utils-7Yw-h0tD.js → experiment-utils-CLnzPPq5.js} +170 -13
- package/dist/experiment-utils-CLnzPPq5.js.map +1 -0
- package/dist/index.js +13 -13
- package/dist/install.js +7 -7
- package/dist/styles.css +764 -610
- package/dist/templates/index.js +4 -4
- package/dist/{templates-CJRFiWUX.js → templates-Cr-48-u2.js} +2 -2
- package/dist/{templates-CJRFiWUX.js.map → templates-Cr-48-u2.js.map} +1 -1
- package/dist/types/components.d.ts +32 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/{useControlSchema-BEAgLyyx.js → useControlSchema-BMVb2XIQ.js} +58 -10
- package/dist/useControlSchema-BMVb2XIQ.js.map +1 -0
- package/dist/{useDropdownState-Be-ug_ci.js → useDropdownState-DSb3BC3N.js} +2 -2
- package/dist/{useDropdownState-Be-ug_ci.js.map → useDropdownState-DSb3BC3N.js.map} +1 -1
- package/dist/{useExperimentSelector-DFg_iEHI.js → useExperimentSelector-D0pjj9WH.js} +3 -3
- package/dist/{useExperimentSelector-DFg_iEHI.js.map → useExperimentSelector-D0pjj9WH.js.map} +1 -1
- package/dist/{useFormBuilder-CR9pBApW.js → useFormBuilder-DG3HOFGA.js} +2 -2
- package/dist/{useFormBuilder-CR9pBApW.js.map → useFormBuilder-DG3HOFGA.js.map} +1 -1
- package/dist/{useProtocolTemplates-DXeIHMwV.js → useProtocolTemplates-BMt0LlXf.js} +21 -6
- package/dist/useProtocolTemplates-BMt0LlXf.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/components/ActionMenu.test.ts +99 -0
- package/src/__tests__/components/BaseModal.test.ts +133 -0
- package/src/__tests__/components/SettingsModal.test.ts +9 -7
- package/src/__tests__/docs/extractDocsComponents.test.ts +15 -1
- package/src/__tests__/docs/extractDocsParsing.test.ts +22 -0
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +80 -0
- package/src/components/ActionMenu.story.vue +26 -0
- package/src/components/ActionMenu.vue +212 -0
- package/src/components/AppSidebar.vue +2 -13
- package/src/components/ArtifactSelectorModal.vue +8 -1
- package/src/components/BaseModal.story.vue +107 -1
- package/src/components/BaseModal.vue +152 -4
- package/src/components/BioTemplateExperimentWorkspaceView.vue +8 -1
- package/src/components/BioTemplatePackWorkspaceView.vue +8 -1
- package/src/components/BioTemplatePresetWorkspaceView.vue +8 -1
- package/src/components/BioTemplateRenderer.vue +8 -1
- package/src/components/ComponentBindingRenderer.vue +8 -1
- package/src/components/ControlWorkspaceView.vue +8 -1
- package/src/components/DoseDesignWorkspaceView.vue +8 -1
- package/src/components/ExperimentSelectorModal.vue +8 -1
- package/src/components/FormBuilder.vue +5 -0
- package/src/components/GeneratedPluginView.vue +12 -2
- package/src/components/GeneratedResultRenderer.vue +12 -2
- package/src/components/JobsStatusTray.vue +8 -1
- package/src/components/PluginWorkspaceView.vue +10 -1
- package/src/components/SettingsModal.vue +102 -149
- package/src/components/index.ts +1 -0
- package/src/composables/controlSchemaDoseDesign.ts +8 -1
- package/src/composables/controlSchemaModel.ts +10 -1
- package/src/composables/useAnalysisArtifacts.ts +9 -1
- package/src/composables/useAppExperiment.ts +7 -0
- package/src/composables/useBioTemplatePackWorkspace.ts +9 -1
- package/src/composables/useControlWorkspace.ts +9 -1
- package/src/composables/useGeneratedAnalysis.ts +9 -0
- package/src/styles/components/action-menu.css +59 -0
- package/src/styles/components/icon-button.css +7 -3
- package/src/styles/components/modal.css +293 -0
- package/src/styles/components/settings-modal.css +3 -273
- package/src/styles/index.css +1 -0
- package/src/types/components.ts +38 -0
- package/src/types/index.ts +3 -0
- package/dist/ExperimentSelectorModal-DcQ38zua.js.map +0 -1
- package/dist/SettingsModal-Ccq2z-Ms.js.map +0 -1
- package/dist/components-CV5zuWa8.js.map +0 -1
- package/dist/experiment-utils-7Yw-h0tD.js.map +0 -1
- package/dist/useControlSchema-BEAgLyyx.js.map +0 -1
- package/dist/useProtocolTemplates-DXeIHMwV.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { m as BaseButton_default } from "./experiment-utils-
|
|
1
|
+
import { m as BaseButton_default } from "./experiment-utils-CLnzPPq5.js";
|
|
2
2
|
import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeProps, normalizeClass, normalizeStyle, openBlock, ref, renderList, renderSlot, toDisplayString, useAttrs, withCtx } from "vue";
|
|
3
3
|
//#region src/components/BaseInput.vue?vue&type=script&setup=true&lang.ts
|
|
4
4
|
var _hoisted_1$3 = [
|
|
@@ -454,4 +454,4 @@ var EmptyState_default = /* @__PURE__ */ defineComponent({
|
|
|
454
454
|
//#endregion
|
|
455
455
|
export { BaseSelect_default as a, BasePill_default as i, Skeleton_default as n, normalizeOptionInput as o, useRequestSyncState as r, BaseInput_default as s, EmptyState_default as t };
|
|
456
456
|
|
|
457
|
-
//# sourceMappingURL=EmptyState-
|
|
457
|
+
//# sourceMappingURL=EmptyState-p90quq8Z.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmptyState-LMPS3keb.js","names":[],"sources":["../src/components/BaseInput.vue","../src/components/BaseInput.vue","../src/utils/options.ts","../src/components/BaseSelect.vue","../src/components/BaseSelect.vue","../src/components/BasePill.vue","../src/components/BasePill.vue","../src/composables/useRequestSyncState.ts","../src/components/Skeleton.vue","../src/components/Skeleton.vue","../src/components/EmptyState.vue","../src/components/EmptyState.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Renders a styled `<input>` supporting text/number/email types, size, error, and readonly states. */\nimport type { InputType } from '../types'\n\ninterface Props {\n modelValue?: string | number\n type?: InputType\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n autocomplete?: string\n autofocus?: boolean\n min?: number\n max?: number\n step?: number\n list?: string\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n type: 'text',\n disabled: false,\n readonly: false,\n error: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number | null]\n focus: [event: FocusEvent]\n blur: [event: FocusEvent]\n keydown: [event: KeyboardEvent]\n}>()\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = props.type === 'number'\n ? (target.value === '' ? null : Number(target.value))\n : target.value\n emit('update:modelValue', value as string | number)\n}\n</script>\n\n<template>\n <input\n :type=\"type\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :autocomplete=\"autocomplete\"\n :autofocus=\"autofocus\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :list=\"list\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mint-input',\n `mint-input--${size}`,\n error ? 'mint-input--error' : '',\n disabled ? 'mint-input--disabled' : '',\n ]\"\n @input=\"handleInput\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n @keydown=\"emit('keydown', $event)\"\n />\n</template>\n\n<style>\n@import '../styles/components/input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a styled `<input>` supporting text/number/email types, size, error, and readonly states. */\nimport type { InputType } from '../types'\n\ninterface Props {\n modelValue?: string | number\n type?: InputType\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n autocomplete?: string\n autofocus?: boolean\n min?: number\n max?: number\n step?: number\n list?: string\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n type: 'text',\n disabled: false,\n readonly: false,\n error: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number | null]\n focus: [event: FocusEvent]\n blur: [event: FocusEvent]\n keydown: [event: KeyboardEvent]\n}>()\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = props.type === 'number'\n ? (target.value === '' ? null : Number(target.value))\n : target.value\n emit('update:modelValue', value as string | number)\n}\n</script>\n\n<template>\n <input\n :type=\"type\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :autocomplete=\"autocomplete\"\n :autofocus=\"autofocus\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :list=\"list\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mint-input',\n `mint-input--${size}`,\n error ? 'mint-input--error' : '',\n disabled ? 'mint-input--disabled' : '',\n ]\"\n @input=\"handleInput\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n @keydown=\"emit('keydown', $event)\"\n />\n</template>\n\n<style>\n@import '../styles/components/input.css';\n</style>\n","export type OptionLike<TValue extends string | number> = {\n value: TValue\n label: string\n}\n\nexport function normalizeOptionInput<\n TValue extends string | number,\n TOption extends OptionLike<TValue>,\n>(option: TOption | TValue): TOption {\n if (typeof option === 'object') return option\n\n return {\n value: option,\n label: String(option),\n } as TOption\n}\n\nexport function findOptionByDomValue<\n TValue extends string | number,\n TOption extends OptionLike<TValue>,\n>(options: TOption[], value: string): TOption | undefined {\n return options.find(option => String(option.value) === value)\n}\n","<script setup lang=\"ts\">\n/** Renders a native `<select>` element with size, error, and disabled states. */\nimport { computed, useAttrs } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport type { SelectOption, SelectOptionInput } from '../types'\nimport { findOptionByDomValue, normalizeOptionInput } from '../utils/options'\n\ndefineOptions({ inheritAttrs: false })\n\ninterface Props {\n modelValue?: string | number\n options: SelectOptionInput<string | number>[]\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n error: false,\n size: 'md',\n})\nconst attrs = useAttrs()\n\nconst controlAttrs = computed(() => {\n const { class: _class, style: _style, ...nativeAttrs } = attrs\n return nativeAttrs\n})\nconst wrapperAttrs = computed<Pick<HTMLAttributes, 'class' | 'style'>>(() => ({\n class: attrs.class as HTMLAttributes['class'],\n style: attrs.style as HTMLAttributes['style'],\n}))\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nconst normalizedOptions = computed<SelectOption<string | number>[]>(() =>\n props.options.map(normalizeOptionInput)\n)\n\nfunction handleChange(event: Event) {\n const target = event.target as HTMLSelectElement\n const option = findOptionByDomValue(normalizedOptions.value, target.value)\n emit('update:modelValue', option?.value ?? target.value)\n}\n</script>\n\n<template>\n <div v-bind=\"wrapperAttrs\" class=\"mint-select\">\n <select\n v-bind=\"controlAttrs\"\n :value=\"modelValue\"\n :disabled=\"disabled\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mint-select__control',\n `mint-select__control--${size}`,\n error ? 'mint-select__control--error' : '',\n disabled ? 'mint-select__control--disabled' : '',\n ]\"\n @change=\"handleChange\"\n >\n <option v-if=\"placeholder\" value=\"\" disabled>\n {{ placeholder }}\n </option>\n <option\n v-for=\"option in normalizedOptions\"\n :key=\"String(option.value)\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n >\n {{ option.label }}\n </option>\n </select>\n <div class=\"mint-select__icon\">\n <svg fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/select.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a native `<select>` element with size, error, and disabled states. */\nimport { computed, useAttrs } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport type { SelectOption, SelectOptionInput } from '../types'\nimport { findOptionByDomValue, normalizeOptionInput } from '../utils/options'\n\ndefineOptions({ inheritAttrs: false })\n\ninterface Props {\n modelValue?: string | number\n options: SelectOptionInput<string | number>[]\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n error: false,\n size: 'md',\n})\nconst attrs = useAttrs()\n\nconst controlAttrs = computed(() => {\n const { class: _class, style: _style, ...nativeAttrs } = attrs\n return nativeAttrs\n})\nconst wrapperAttrs = computed<Pick<HTMLAttributes, 'class' | 'style'>>(() => ({\n class: attrs.class as HTMLAttributes['class'],\n style: attrs.style as HTMLAttributes['style'],\n}))\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nconst normalizedOptions = computed<SelectOption<string | number>[]>(() =>\n props.options.map(normalizeOptionInput)\n)\n\nfunction handleChange(event: Event) {\n const target = event.target as HTMLSelectElement\n const option = findOptionByDomValue(normalizedOptions.value, target.value)\n emit('update:modelValue', option?.value ?? target.value)\n}\n</script>\n\n<template>\n <div v-bind=\"wrapperAttrs\" class=\"mint-select\">\n <select\n v-bind=\"controlAttrs\"\n :value=\"modelValue\"\n :disabled=\"disabled\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mint-select__control',\n `mint-select__control--${size}`,\n error ? 'mint-select__control--error' : '',\n disabled ? 'mint-select__control--disabled' : '',\n ]\"\n @change=\"handleChange\"\n >\n <option v-if=\"placeholder\" value=\"\" disabled>\n {{ placeholder }}\n </option>\n <option\n v-for=\"option in normalizedOptions\"\n :key=\"String(option.value)\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n >\n {{ option.label }}\n </option>\n </select>\n <div class=\"mint-select__icon\">\n <svg fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/select.css';\n</style>\n","<script setup lang=\"ts\">\n/** Compact label for tags, status indicators, and badges; supports removable and icon slots. */\nimport type { PillVariant, PillColor, PillSize } from '../types'\n\n/**\n * BasePill - Compact label component for tags, status indicators, and badges.\n *\n * @example\n * ```vue\n * <BasePill variant=\"success\">Active</BasePill>\n * <BasePill variant=\"warning\" removable @remove=\"handleRemove\">Tag</BasePill>\n * <BasePill :icon=\"true\">\n * <template #icon><CheckIcon /></template>\n * Verified\n * </BasePill>\n * ```\n */\ninterface Props {\n /** Visual style variant */\n variant?: PillVariant\n /** Semantic color modifier — use with variant=\"outline\" for colored outlines */\n color?: PillColor\n /** Size of the pill */\n size?: PillSize\n /** Show remove button */\n removable?: boolean\n /** Disable interaction */\n disabled?: boolean\n /** Show icon slot */\n icon?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'default',\n color: undefined,\n size: 'md',\n removable: false,\n disabled: false,\n icon: false,\n})\n\n/**\n * @event remove - Emitted when the remove button is clicked\n */\nconst emit = defineEmits<{\n remove: []\n}>()\n\nfunction handleRemove(event: MouseEvent) {\n event.stopPropagation()\n if (!props.disabled) {\n emit('remove')\n }\n}\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-pill',\n `mint-pill--${variant}`,\n color && `mint-pill--${color}`,\n `mint-pill--${size}`,\n { 'mint-pill--disabled': disabled, 'mint-pill--with-icon': icon },\n ]\"\n >\n <span v-if=\"icon\" class=\"mint-pill__icon\">\n <slot name=\"icon\" />\n </span>\n <span class=\"mint-pill__label\">\n <slot />\n </span>\n <button\n v-if=\"removable && !disabled\"\n type=\"button\"\n class=\"mint-pill__remove\"\n aria-label=\"Remove\"\n @click=\"handleRemove\"\n >\n <svg class=\"mint-pill__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </span>\n</template>\n\n<style>\n@import '../styles/components/pill.css';\n</style>\n","<script setup lang=\"ts\">\n/** Compact label for tags, status indicators, and badges; supports removable and icon slots. */\nimport type { PillVariant, PillColor, PillSize } from '../types'\n\n/**\n * BasePill - Compact label component for tags, status indicators, and badges.\n *\n * @example\n * ```vue\n * <BasePill variant=\"success\">Active</BasePill>\n * <BasePill variant=\"warning\" removable @remove=\"handleRemove\">Tag</BasePill>\n * <BasePill :icon=\"true\">\n * <template #icon><CheckIcon /></template>\n * Verified\n * </BasePill>\n * ```\n */\ninterface Props {\n /** Visual style variant */\n variant?: PillVariant\n /** Semantic color modifier — use with variant=\"outline\" for colored outlines */\n color?: PillColor\n /** Size of the pill */\n size?: PillSize\n /** Show remove button */\n removable?: boolean\n /** Disable interaction */\n disabled?: boolean\n /** Show icon slot */\n icon?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'default',\n color: undefined,\n size: 'md',\n removable: false,\n disabled: false,\n icon: false,\n})\n\n/**\n * @event remove - Emitted when the remove button is clicked\n */\nconst emit = defineEmits<{\n remove: []\n}>()\n\nfunction handleRemove(event: MouseEvent) {\n event.stopPropagation()\n if (!props.disabled) {\n emit('remove')\n }\n}\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-pill',\n `mint-pill--${variant}`,\n color && `mint-pill--${color}`,\n `mint-pill--${size}`,\n { 'mint-pill--disabled': disabled, 'mint-pill--with-icon': icon },\n ]\"\n >\n <span v-if=\"icon\" class=\"mint-pill__icon\">\n <slot name=\"icon\" />\n </span>\n <span class=\"mint-pill__label\">\n <slot />\n </span>\n <button\n v-if=\"removable && !disabled\"\n type=\"button\"\n class=\"mint-pill__remove\"\n aria-label=\"Remove\"\n @click=\"handleRemove\"\n >\n <svg class=\"mint-pill__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </span>\n</template>\n\n<style>\n@import '../styles/components/pill.css';\n</style>\n","import { ref, type Ref } from 'vue'\n\nexport type RequestSyncSuccessKind = 'load' | 'save' | 'run'\n\nexport interface RequestSyncRunOptions {\n success?: RequestSyncSuccessKind\n errorMessage?: string\n}\n\nexport interface UseRequestSyncStateReturn {\n /** Whether a request is in progress. */\n loading: Ref<boolean>\n /** Message from the last failed request, or null. */\n error: Ref<string | null>\n /** Timestamp of the last successful load operation, or null. */\n lastLoadedAt: Ref<Date | null>\n /** Timestamp of the last successful save operation, or null. */\n lastSavedAt: Ref<Date | null>\n /** Timestamp of the last successful run operation, or null. */\n lastRunAt: Ref<Date | null>\n /** Clear the current error without changing loading or timestamps. */\n clearError: () => void\n /** Convert thrown values into a developer-facing error message. */\n readErrorMessage: (value: unknown, fallback?: string) => string\n /** Store and return a normalized error message. */\n setError: (value: unknown, fallback?: string) => string\n /** Mark the resource as successfully loaded. */\n markLoaded: (date?: Date) => void\n /** Mark the resource as successfully saved. */\n markSaved: (date?: Date) => void\n /** Mark the operation as successfully run. */\n markRun: (date?: Date) => void\n /** Run a request with shared loading/error handling and optional sync marking. */\n run: <T>(operation: () => Promise<T>, options?: RequestSyncRunOptions) => Promise<T>\n}\n\n/** Shared loading/error/timestamp state for generated plugin request helpers. */\nexport function useRequestSyncState(\n defaultErrorMessage = 'Request failed.',\n): UseRequestSyncStateReturn {\n const loading = ref(false)\n const error = ref<string | null>(null)\n const lastLoadedAt = ref<Date | null>(null)\n const lastSavedAt = ref<Date | null>(null)\n const lastRunAt = ref<Date | null>(null)\n\n function clearError(): void {\n error.value = null\n }\n\n function readErrorMessage(value: unknown, fallback = defaultErrorMessage): string {\n if (value instanceof Error && value.message) {\n return value.message\n }\n if (typeof value === 'string' && value.trim()) {\n return value\n }\n if (\n typeof value === 'object'\n && value !== null\n && 'message' in value\n && typeof (value as { message?: unknown }).message === 'string'\n && (value as { message: string }).message\n ) {\n return (value as { message: string }).message\n }\n return fallback\n }\n\n function setError(value: unknown, fallback?: string): string {\n const message = readErrorMessage(value, fallback)\n error.value = message\n return message\n }\n\n function markLoaded(date = new Date()): void {\n lastLoadedAt.value = date\n }\n\n function markSaved(date = new Date()): void {\n lastSavedAt.value = date\n }\n\n function markRun(date = new Date()): void {\n lastRunAt.value = date\n }\n\n async function run<T>(\n operation: () => Promise<T>,\n options: RequestSyncRunOptions = {},\n ): Promise<T> {\n loading.value = true\n clearError()\n try {\n const response = await operation()\n if (options.success === 'load') {\n markLoaded()\n } else if (options.success === 'save') {\n markSaved()\n } else if (options.success === 'run') {\n markRun()\n }\n return response\n } catch (err) {\n setError(err, options.errorMessage)\n throw err\n } finally {\n loading.value = false\n }\n }\n\n return {\n loading,\n error,\n lastLoadedAt,\n lastSavedAt,\n lastRunAt,\n clearError,\n readErrorMessage,\n setError,\n markLoaded,\n markSaved,\n markRun,\n run,\n }\n}\n","<script setup lang=\"ts\">\n/** Animated loading placeholder in text, circular, rectangular, or rounded variants with pulse or wave animation. */\nimport { computed } from 'vue'\n\ninterface Props {\n variant?: 'text' | 'circular' | 'rectangular' | 'rounded'\n width?: string | number\n height?: string | number\n animation?: 'pulse' | 'wave' | 'none'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'text',\n animation: 'wave',\n})\n\nconst style = computed(() => {\n const s: Record<string, string> = {}\n\n if (props.width) {\n s.width = typeof props.width === 'number' ? `${props.width}px` : props.width\n }\n\n if (props.height) {\n s.height = typeof props.height === 'number' ? `${props.height}px` : props.height\n }\n\n return s\n})\n\nconst classes = computed(() => {\n const base = ['bg-bg-hover']\n\n switch (props.variant) {\n case 'circular':\n base.push('rounded-full')\n if (!props.width && !props.height) {\n base.push('w-10 h-10')\n }\n break\n case 'rectangular':\n base.push('rounded-none')\n if (!props.height) base.push('h-24')\n break\n case 'rounded':\n base.push('rounded-mint')\n if (!props.height) base.push('h-24')\n break\n default:\n base.push('rounded')\n if (!props.height) base.push('h-4')\n if (!props.width) base.push('w-full')\n }\n\n switch (props.animation) {\n case 'pulse':\n base.push('animate-pulse')\n break\n case 'wave':\n base.push('skeleton-wave')\n break\n }\n\n return base\n})\n</script>\n\n<template>\n <div :class=\"classes\" :style=\"style\" />\n</template>\n\n<style>\n@import '../styles/components/skeleton.css';\n</style>\n","<script setup lang=\"ts\">\n/** Animated loading placeholder in text, circular, rectangular, or rounded variants with pulse or wave animation. */\nimport { computed } from 'vue'\n\ninterface Props {\n variant?: 'text' | 'circular' | 'rectangular' | 'rounded'\n width?: string | number\n height?: string | number\n animation?: 'pulse' | 'wave' | 'none'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'text',\n animation: 'wave',\n})\n\nconst style = computed(() => {\n const s: Record<string, string> = {}\n\n if (props.width) {\n s.width = typeof props.width === 'number' ? `${props.width}px` : props.width\n }\n\n if (props.height) {\n s.height = typeof props.height === 'number' ? `${props.height}px` : props.height\n }\n\n return s\n})\n\nconst classes = computed(() => {\n const base = ['bg-bg-hover']\n\n switch (props.variant) {\n case 'circular':\n base.push('rounded-full')\n if (!props.width && !props.height) {\n base.push('w-10 h-10')\n }\n break\n case 'rectangular':\n base.push('rounded-none')\n if (!props.height) base.push('h-24')\n break\n case 'rounded':\n base.push('rounded-mint')\n if (!props.height) base.push('h-24')\n break\n default:\n base.push('rounded')\n if (!props.height) base.push('h-4')\n if (!props.width) base.push('w-full')\n }\n\n switch (props.animation) {\n case 'pulse':\n base.push('animate-pulse')\n break\n case 'wave':\n base.push('skeleton-wave')\n break\n }\n\n return base\n})\n</script>\n\n<template>\n <div :class=\"classes\" :style=\"style\" />\n</template>\n\n<style>\n@import '../styles/components/skeleton.css';\n</style>\n","<script setup lang=\"ts\">\n/** Empty-state placeholder with icon badge, headline, description, default slot, and optional CTA button. */\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n title?: string\n description?: string\n iconPath?: string\n color?: 'primary' | 'cta' | 'success' | 'warning' | 'error' | 'muted'\n size?: 'sm' | 'md' | 'lg'\n variant?: 'illustrated' | 'inline'\n actionLabel?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n color: 'primary',\n size: 'md',\n variant: 'illustrated',\n})\n\nconst emit = defineEmits<{\n action: []\n}>()\n\nconst defaultIconPaths = [\n 'M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z',\n 'M7 11h10',\n 'M7 15h6',\n 'M7 7h8',\n]\n</script>\n\n<template>\n <div :class=\"['mint-empty-state', `mint-empty-state--${variant}`, `mint-empty-state--${size}`]\">\n <div :class=\"['mint-empty-state__icon-wrapper', `mint-empty-state__icon-wrapper--${color}`]\">\n <slot name=\"icon\">\n <svg\n class=\"mint-empty-state__icon\"\n :class=\"`mint-empty-state__icon--${color}`\"\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=\"iconPath\">\n <path :d=\"iconPath\" />\n </template>\n <template v-else>\n <path v-for=\"(d, i) in defaultIconPaths\" :key=\"i\" :d=\"d\" />\n </template>\n </svg>\n </slot>\n </div>\n <div class=\"mint-empty-state__body\">\n <h3 v-if=\"title\" class=\"mint-empty-state__title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"mint-empty-state__description\">{{ description }}</p>\n <slot />\n </div>\n <div v-if=\"actionLabel\" class=\"mint-empty-state__action\">\n <BaseButton @click=\"emit('action')\">\n {{ actionLabel }}\n </BaseButton>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/empty-state.css';\n</style>\n","<script setup lang=\"ts\">\n/** Empty-state placeholder with icon badge, headline, description, default slot, and optional CTA button. */\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n title?: string\n description?: string\n iconPath?: string\n color?: 'primary' | 'cta' | 'success' | 'warning' | 'error' | 'muted'\n size?: 'sm' | 'md' | 'lg'\n variant?: 'illustrated' | 'inline'\n actionLabel?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n color: 'primary',\n size: 'md',\n variant: 'illustrated',\n})\n\nconst emit = defineEmits<{\n action: []\n}>()\n\nconst defaultIconPaths = [\n 'M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z',\n 'M7 11h10',\n 'M7 15h6',\n 'M7 7h8',\n]\n</script>\n\n<template>\n <div :class=\"['mint-empty-state', `mint-empty-state--${variant}`, `mint-empty-state--${size}`]\">\n <div :class=\"['mint-empty-state__icon-wrapper', `mint-empty-state__icon-wrapper--${color}`]\">\n <slot name=\"icon\">\n <svg\n class=\"mint-empty-state__icon\"\n :class=\"`mint-empty-state__icon--${color}`\"\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=\"iconPath\">\n <path :d=\"iconPath\" />\n </template>\n <template v-else>\n <path v-for=\"(d, i) in defaultIconPaths\" :key=\"i\" :d=\"d\" />\n </template>\n </svg>\n </slot>\n </div>\n <div class=\"mint-empty-state__body\">\n <h3 v-if=\"title\" class=\"mint-empty-state__title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"mint-empty-state__description\">{{ description }}</p>\n <slot />\n </div>\n <div v-if=\"actionLabel\" class=\"mint-empty-state__action\">\n <BaseButton @click=\"emit('action')\">\n {{ actionLabel }}\n </BaseButton>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/empty-state.css';\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EmptyState-p90quq8Z.js","names":[],"sources":["../src/components/BaseInput.vue","../src/components/BaseInput.vue","../src/utils/options.ts","../src/components/BaseSelect.vue","../src/components/BaseSelect.vue","../src/components/BasePill.vue","../src/components/BasePill.vue","../src/composables/useRequestSyncState.ts","../src/components/Skeleton.vue","../src/components/Skeleton.vue","../src/components/EmptyState.vue","../src/components/EmptyState.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Renders a styled `<input>` supporting text/number/email types, size, error, and readonly states. */\nimport type { InputType } from '../types'\n\ninterface Props {\n modelValue?: string | number\n type?: InputType\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n autocomplete?: string\n autofocus?: boolean\n min?: number\n max?: number\n step?: number\n list?: string\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n type: 'text',\n disabled: false,\n readonly: false,\n error: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number | null]\n focus: [event: FocusEvent]\n blur: [event: FocusEvent]\n keydown: [event: KeyboardEvent]\n}>()\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = props.type === 'number'\n ? (target.value === '' ? null : Number(target.value))\n : target.value\n emit('update:modelValue', value as string | number)\n}\n</script>\n\n<template>\n <input\n :type=\"type\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :autocomplete=\"autocomplete\"\n :autofocus=\"autofocus\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :list=\"list\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mint-input',\n `mint-input--${size}`,\n error ? 'mint-input--error' : '',\n disabled ? 'mint-input--disabled' : '',\n ]\"\n @input=\"handleInput\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n @keydown=\"emit('keydown', $event)\"\n />\n</template>\n\n<style>\n@import '../styles/components/input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a styled `<input>` supporting text/number/email types, size, error, and readonly states. */\nimport type { InputType } from '../types'\n\ninterface Props {\n modelValue?: string | number\n type?: InputType\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n autocomplete?: string\n autofocus?: boolean\n min?: number\n max?: number\n step?: number\n list?: string\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n type: 'text',\n disabled: false,\n readonly: false,\n error: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number | null]\n focus: [event: FocusEvent]\n blur: [event: FocusEvent]\n keydown: [event: KeyboardEvent]\n}>()\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = props.type === 'number'\n ? (target.value === '' ? null : Number(target.value))\n : target.value\n emit('update:modelValue', value as string | number)\n}\n</script>\n\n<template>\n <input\n :type=\"type\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :autocomplete=\"autocomplete\"\n :autofocus=\"autofocus\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :list=\"list\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mint-input',\n `mint-input--${size}`,\n error ? 'mint-input--error' : '',\n disabled ? 'mint-input--disabled' : '',\n ]\"\n @input=\"handleInput\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n @keydown=\"emit('keydown', $event)\"\n />\n</template>\n\n<style>\n@import '../styles/components/input.css';\n</style>\n","export type OptionLike<TValue extends string | number> = {\n value: TValue\n label: string\n}\n\nexport function normalizeOptionInput<\n TValue extends string | number,\n TOption extends OptionLike<TValue>,\n>(option: TOption | TValue): TOption {\n if (typeof option === 'object') return option\n\n return {\n value: option,\n label: String(option),\n } as TOption\n}\n\nexport function findOptionByDomValue<\n TValue extends string | number,\n TOption extends OptionLike<TValue>,\n>(options: TOption[], value: string): TOption | undefined {\n return options.find(option => String(option.value) === value)\n}\n","<script setup lang=\"ts\">\n/** Renders a native `<select>` element with size, error, and disabled states. */\nimport { computed, useAttrs } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport type { SelectOption, SelectOptionInput } from '../types'\nimport { findOptionByDomValue, normalizeOptionInput } from '../utils/options'\n\ndefineOptions({ inheritAttrs: false })\n\ninterface Props {\n modelValue?: string | number\n options: SelectOptionInput<string | number>[]\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n error: false,\n size: 'md',\n})\nconst attrs = useAttrs()\n\nconst controlAttrs = computed(() => {\n const { class: _class, style: _style, ...nativeAttrs } = attrs\n return nativeAttrs\n})\nconst wrapperAttrs = computed<Pick<HTMLAttributes, 'class' | 'style'>>(() => ({\n class: attrs.class as HTMLAttributes['class'],\n style: attrs.style as HTMLAttributes['style'],\n}))\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nconst normalizedOptions = computed<SelectOption<string | number>[]>(() =>\n props.options.map(normalizeOptionInput)\n)\n\nfunction handleChange(event: Event) {\n const target = event.target as HTMLSelectElement\n const option = findOptionByDomValue(normalizedOptions.value, target.value)\n emit('update:modelValue', option?.value ?? target.value)\n}\n</script>\n\n<template>\n <div v-bind=\"wrapperAttrs\" class=\"mint-select\">\n <select\n v-bind=\"controlAttrs\"\n :value=\"modelValue\"\n :disabled=\"disabled\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mint-select__control',\n `mint-select__control--${size}`,\n error ? 'mint-select__control--error' : '',\n disabled ? 'mint-select__control--disabled' : '',\n ]\"\n @change=\"handleChange\"\n >\n <option v-if=\"placeholder\" value=\"\" disabled>\n {{ placeholder }}\n </option>\n <option\n v-for=\"option in normalizedOptions\"\n :key=\"String(option.value)\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n >\n {{ option.label }}\n </option>\n </select>\n <div class=\"mint-select__icon\">\n <svg fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/select.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a native `<select>` element with size, error, and disabled states. */\nimport { computed, useAttrs } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport type { SelectOption, SelectOptionInput } from '../types'\nimport { findOptionByDomValue, normalizeOptionInput } from '../utils/options'\n\ndefineOptions({ inheritAttrs: false })\n\ninterface Props {\n modelValue?: string | number\n options: SelectOptionInput<string | number>[]\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n error: false,\n size: 'md',\n})\nconst attrs = useAttrs()\n\nconst controlAttrs = computed(() => {\n const { class: _class, style: _style, ...nativeAttrs } = attrs\n return nativeAttrs\n})\nconst wrapperAttrs = computed<Pick<HTMLAttributes, 'class' | 'style'>>(() => ({\n class: attrs.class as HTMLAttributes['class'],\n style: attrs.style as HTMLAttributes['style'],\n}))\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nconst normalizedOptions = computed<SelectOption<string | number>[]>(() =>\n props.options.map(normalizeOptionInput)\n)\n\nfunction handleChange(event: Event) {\n const target = event.target as HTMLSelectElement\n const option = findOptionByDomValue(normalizedOptions.value, target.value)\n emit('update:modelValue', option?.value ?? target.value)\n}\n</script>\n\n<template>\n <div v-bind=\"wrapperAttrs\" class=\"mint-select\">\n <select\n v-bind=\"controlAttrs\"\n :value=\"modelValue\"\n :disabled=\"disabled\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mint-select__control',\n `mint-select__control--${size}`,\n error ? 'mint-select__control--error' : '',\n disabled ? 'mint-select__control--disabled' : '',\n ]\"\n @change=\"handleChange\"\n >\n <option v-if=\"placeholder\" value=\"\" disabled>\n {{ placeholder }}\n </option>\n <option\n v-for=\"option in normalizedOptions\"\n :key=\"String(option.value)\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n >\n {{ option.label }}\n </option>\n </select>\n <div class=\"mint-select__icon\">\n <svg fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/select.css';\n</style>\n","<script setup lang=\"ts\">\n/** Compact label for tags, status indicators, and badges; supports removable and icon slots. */\nimport type { PillVariant, PillColor, PillSize } from '../types'\n\n/**\n * BasePill - Compact label component for tags, status indicators, and badges.\n *\n * @example\n * ```vue\n * <BasePill variant=\"success\">Active</BasePill>\n * <BasePill variant=\"warning\" removable @remove=\"handleRemove\">Tag</BasePill>\n * <BasePill :icon=\"true\">\n * <template #icon><CheckIcon /></template>\n * Verified\n * </BasePill>\n * ```\n */\ninterface Props {\n /** Visual style variant */\n variant?: PillVariant\n /** Semantic color modifier — use with variant=\"outline\" for colored outlines */\n color?: PillColor\n /** Size of the pill */\n size?: PillSize\n /** Show remove button */\n removable?: boolean\n /** Disable interaction */\n disabled?: boolean\n /** Show icon slot */\n icon?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'default',\n color: undefined,\n size: 'md',\n removable: false,\n disabled: false,\n icon: false,\n})\n\n/**\n * @event remove - Emitted when the remove button is clicked\n */\nconst emit = defineEmits<{\n remove: []\n}>()\n\nfunction handleRemove(event: MouseEvent) {\n event.stopPropagation()\n if (!props.disabled) {\n emit('remove')\n }\n}\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-pill',\n `mint-pill--${variant}`,\n color && `mint-pill--${color}`,\n `mint-pill--${size}`,\n { 'mint-pill--disabled': disabled, 'mint-pill--with-icon': icon },\n ]\"\n >\n <span v-if=\"icon\" class=\"mint-pill__icon\">\n <slot name=\"icon\" />\n </span>\n <span class=\"mint-pill__label\">\n <slot />\n </span>\n <button\n v-if=\"removable && !disabled\"\n type=\"button\"\n class=\"mint-pill__remove\"\n aria-label=\"Remove\"\n @click=\"handleRemove\"\n >\n <svg class=\"mint-pill__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </span>\n</template>\n\n<style>\n@import '../styles/components/pill.css';\n</style>\n","<script setup lang=\"ts\">\n/** Compact label for tags, status indicators, and badges; supports removable and icon slots. */\nimport type { PillVariant, PillColor, PillSize } from '../types'\n\n/**\n * BasePill - Compact label component for tags, status indicators, and badges.\n *\n * @example\n * ```vue\n * <BasePill variant=\"success\">Active</BasePill>\n * <BasePill variant=\"warning\" removable @remove=\"handleRemove\">Tag</BasePill>\n * <BasePill :icon=\"true\">\n * <template #icon><CheckIcon /></template>\n * Verified\n * </BasePill>\n * ```\n */\ninterface Props {\n /** Visual style variant */\n variant?: PillVariant\n /** Semantic color modifier — use with variant=\"outline\" for colored outlines */\n color?: PillColor\n /** Size of the pill */\n size?: PillSize\n /** Show remove button */\n removable?: boolean\n /** Disable interaction */\n disabled?: boolean\n /** Show icon slot */\n icon?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'default',\n color: undefined,\n size: 'md',\n removable: false,\n disabled: false,\n icon: false,\n})\n\n/**\n * @event remove - Emitted when the remove button is clicked\n */\nconst emit = defineEmits<{\n remove: []\n}>()\n\nfunction handleRemove(event: MouseEvent) {\n event.stopPropagation()\n if (!props.disabled) {\n emit('remove')\n }\n}\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-pill',\n `mint-pill--${variant}`,\n color && `mint-pill--${color}`,\n `mint-pill--${size}`,\n { 'mint-pill--disabled': disabled, 'mint-pill--with-icon': icon },\n ]\"\n >\n <span v-if=\"icon\" class=\"mint-pill__icon\">\n <slot name=\"icon\" />\n </span>\n <span class=\"mint-pill__label\">\n <slot />\n </span>\n <button\n v-if=\"removable && !disabled\"\n type=\"button\"\n class=\"mint-pill__remove\"\n aria-label=\"Remove\"\n @click=\"handleRemove\"\n >\n <svg class=\"mint-pill__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </span>\n</template>\n\n<style>\n@import '../styles/components/pill.css';\n</style>\n","import { ref, type Ref } from 'vue'\n\nexport type RequestSyncSuccessKind = 'load' | 'save' | 'run'\n\nexport interface RequestSyncRunOptions {\n success?: RequestSyncSuccessKind\n errorMessage?: string\n}\n\nexport interface UseRequestSyncStateReturn {\n /** Whether a request is in progress. */\n loading: Ref<boolean>\n /** Message from the last failed request, or null. */\n error: Ref<string | null>\n /** Timestamp of the last successful load operation, or null. */\n lastLoadedAt: Ref<Date | null>\n /** Timestamp of the last successful save operation, or null. */\n lastSavedAt: Ref<Date | null>\n /** Timestamp of the last successful run operation, or null. */\n lastRunAt: Ref<Date | null>\n /** Clear the current error without changing loading or timestamps. */\n clearError: () => void\n /** Convert thrown values into a developer-facing error message. */\n readErrorMessage: (value: unknown, fallback?: string) => string\n /** Store and return a normalized error message. */\n setError: (value: unknown, fallback?: string) => string\n /** Mark the resource as successfully loaded. */\n markLoaded: (date?: Date) => void\n /** Mark the resource as successfully saved. */\n markSaved: (date?: Date) => void\n /** Mark the operation as successfully run. */\n markRun: (date?: Date) => void\n /** Run a request with shared loading/error handling and optional sync marking. */\n run: <T>(operation: () => Promise<T>, options?: RequestSyncRunOptions) => Promise<T>\n}\n\n/** Shared loading/error/timestamp state for generated plugin request helpers. */\nexport function useRequestSyncState(\n defaultErrorMessage = 'Request failed.',\n): UseRequestSyncStateReturn {\n const loading = ref(false)\n const error = ref<string | null>(null)\n const lastLoadedAt = ref<Date | null>(null)\n const lastSavedAt = ref<Date | null>(null)\n const lastRunAt = ref<Date | null>(null)\n\n function clearError(): void {\n error.value = null\n }\n\n function readErrorMessage(value: unknown, fallback = defaultErrorMessage): string {\n if (value instanceof Error && value.message) {\n return value.message\n }\n if (typeof value === 'string' && value.trim()) {\n return value\n }\n if (\n typeof value === 'object'\n && value !== null\n && 'message' in value\n && typeof (value as { message?: unknown }).message === 'string'\n && (value as { message: string }).message\n ) {\n return (value as { message: string }).message\n }\n return fallback\n }\n\n function setError(value: unknown, fallback?: string): string {\n const message = readErrorMessage(value, fallback)\n error.value = message\n return message\n }\n\n function markLoaded(date = new Date()): void {\n lastLoadedAt.value = date\n }\n\n function markSaved(date = new Date()): void {\n lastSavedAt.value = date\n }\n\n function markRun(date = new Date()): void {\n lastRunAt.value = date\n }\n\n async function run<T>(\n operation: () => Promise<T>,\n options: RequestSyncRunOptions = {},\n ): Promise<T> {\n loading.value = true\n clearError()\n try {\n const response = await operation()\n if (options.success === 'load') {\n markLoaded()\n } else if (options.success === 'save') {\n markSaved()\n } else if (options.success === 'run') {\n markRun()\n }\n return response\n } catch (err) {\n setError(err, options.errorMessage)\n throw err\n } finally {\n loading.value = false\n }\n }\n\n return {\n loading,\n error,\n lastLoadedAt,\n lastSavedAt,\n lastRunAt,\n clearError,\n readErrorMessage,\n setError,\n markLoaded,\n markSaved,\n markRun,\n run,\n }\n}\n","<script setup lang=\"ts\">\n/** Animated loading placeholder in text, circular, rectangular, or rounded variants with pulse or wave animation. */\nimport { computed } from 'vue'\n\ninterface Props {\n variant?: 'text' | 'circular' | 'rectangular' | 'rounded'\n width?: string | number\n height?: string | number\n animation?: 'pulse' | 'wave' | 'none'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'text',\n animation: 'wave',\n})\n\nconst style = computed(() => {\n const s: Record<string, string> = {}\n\n if (props.width) {\n s.width = typeof props.width === 'number' ? `${props.width}px` : props.width\n }\n\n if (props.height) {\n s.height = typeof props.height === 'number' ? `${props.height}px` : props.height\n }\n\n return s\n})\n\nconst classes = computed(() => {\n const base = ['bg-bg-hover']\n\n switch (props.variant) {\n case 'circular':\n base.push('rounded-full')\n if (!props.width && !props.height) {\n base.push('w-10 h-10')\n }\n break\n case 'rectangular':\n base.push('rounded-none')\n if (!props.height) base.push('h-24')\n break\n case 'rounded':\n base.push('rounded-mint')\n if (!props.height) base.push('h-24')\n break\n default:\n base.push('rounded')\n if (!props.height) base.push('h-4')\n if (!props.width) base.push('w-full')\n }\n\n switch (props.animation) {\n case 'pulse':\n base.push('animate-pulse')\n break\n case 'wave':\n base.push('skeleton-wave')\n break\n }\n\n return base\n})\n</script>\n\n<template>\n <div :class=\"classes\" :style=\"style\" />\n</template>\n\n<style>\n@import '../styles/components/skeleton.css';\n</style>\n","<script setup lang=\"ts\">\n/** Animated loading placeholder in text, circular, rectangular, or rounded variants with pulse or wave animation. */\nimport { computed } from 'vue'\n\ninterface Props {\n variant?: 'text' | 'circular' | 'rectangular' | 'rounded'\n width?: string | number\n height?: string | number\n animation?: 'pulse' | 'wave' | 'none'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'text',\n animation: 'wave',\n})\n\nconst style = computed(() => {\n const s: Record<string, string> = {}\n\n if (props.width) {\n s.width = typeof props.width === 'number' ? `${props.width}px` : props.width\n }\n\n if (props.height) {\n s.height = typeof props.height === 'number' ? `${props.height}px` : props.height\n }\n\n return s\n})\n\nconst classes = computed(() => {\n const base = ['bg-bg-hover']\n\n switch (props.variant) {\n case 'circular':\n base.push('rounded-full')\n if (!props.width && !props.height) {\n base.push('w-10 h-10')\n }\n break\n case 'rectangular':\n base.push('rounded-none')\n if (!props.height) base.push('h-24')\n break\n case 'rounded':\n base.push('rounded-mint')\n if (!props.height) base.push('h-24')\n break\n default:\n base.push('rounded')\n if (!props.height) base.push('h-4')\n if (!props.width) base.push('w-full')\n }\n\n switch (props.animation) {\n case 'pulse':\n base.push('animate-pulse')\n break\n case 'wave':\n base.push('skeleton-wave')\n break\n }\n\n return base\n})\n</script>\n\n<template>\n <div :class=\"classes\" :style=\"style\" />\n</template>\n\n<style>\n@import '../styles/components/skeleton.css';\n</style>\n","<script setup lang=\"ts\">\n/** Empty-state placeholder with icon badge, headline, description, default slot, and optional CTA button. */\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n title?: string\n description?: string\n iconPath?: string\n color?: 'primary' | 'cta' | 'success' | 'warning' | 'error' | 'muted'\n size?: 'sm' | 'md' | 'lg'\n variant?: 'illustrated' | 'inline'\n actionLabel?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n color: 'primary',\n size: 'md',\n variant: 'illustrated',\n})\n\nconst emit = defineEmits<{\n action: []\n}>()\n\nconst defaultIconPaths = [\n 'M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z',\n 'M7 11h10',\n 'M7 15h6',\n 'M7 7h8',\n]\n</script>\n\n<template>\n <div :class=\"['mint-empty-state', `mint-empty-state--${variant}`, `mint-empty-state--${size}`]\">\n <div :class=\"['mint-empty-state__icon-wrapper', `mint-empty-state__icon-wrapper--${color}`]\">\n <slot name=\"icon\">\n <svg\n class=\"mint-empty-state__icon\"\n :class=\"`mint-empty-state__icon--${color}`\"\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=\"iconPath\">\n <path :d=\"iconPath\" />\n </template>\n <template v-else>\n <path v-for=\"(d, i) in defaultIconPaths\" :key=\"i\" :d=\"d\" />\n </template>\n </svg>\n </slot>\n </div>\n <div class=\"mint-empty-state__body\">\n <h3 v-if=\"title\" class=\"mint-empty-state__title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"mint-empty-state__description\">{{ description }}</p>\n <slot />\n </div>\n <div v-if=\"actionLabel\" class=\"mint-empty-state__action\">\n <BaseButton @click=\"emit('action')\">\n {{ actionLabel }}\n </BaseButton>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/empty-state.css';\n</style>\n","<script setup lang=\"ts\">\n/** Empty-state placeholder with icon badge, headline, description, default slot, and optional CTA button. */\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n title?: string\n description?: string\n iconPath?: string\n color?: 'primary' | 'cta' | 'success' | 'warning' | 'error' | 'muted'\n size?: 'sm' | 'md' | 'lg'\n variant?: 'illustrated' | 'inline'\n actionLabel?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n color: 'primary',\n size: 'md',\n variant: 'illustrated',\n})\n\nconst emit = defineEmits<{\n action: []\n}>()\n\nconst defaultIconPaths = [\n 'M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z',\n 'M7 11h10',\n 'M7 15h6',\n 'M7 7h8',\n]\n</script>\n\n<template>\n <div :class=\"['mint-empty-state', `mint-empty-state--${variant}`, `mint-empty-state--${size}`]\">\n <div :class=\"['mint-empty-state__icon-wrapper', `mint-empty-state__icon-wrapper--${color}`]\">\n <slot name=\"icon\">\n <svg\n class=\"mint-empty-state__icon\"\n :class=\"`mint-empty-state__icon--${color}`\"\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=\"iconPath\">\n <path :d=\"iconPath\" />\n </template>\n <template v-else>\n <path v-for=\"(d, i) in defaultIconPaths\" :key=\"i\" :d=\"d\" />\n </template>\n </svg>\n </slot>\n </div>\n <div class=\"mint-empty-state__body\">\n <h3 v-if=\"title\" class=\"mint-empty-state__title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"mint-empty-state__description\">{{ description }}</p>\n <slot />\n </div>\n <div v-if=\"actionLabel\" class=\"mint-empty-state__action\">\n <BaseButton @click=\"emit('action')\">\n {{ actionLabel }}\n </BaseButton>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/empty-state.css';\n</style>\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as __exportAll } from "./chunk-BoAXSpZd.js";
|
|
2
|
-
import { f as BaseModal_default, l as formatExperimentStatus, m as BaseButton_default } from "./experiment-utils-
|
|
3
|
-
import { t as useDropdownState } from "./useDropdownState-
|
|
2
|
+
import { f as BaseModal_default, l as formatExperimentStatus, m as BaseButton_default } from "./experiment-utils-CLnzPPq5.js";
|
|
3
|
+
import { t as useDropdownState } from "./useDropdownState-DSb3BC3N.js";
|
|
4
4
|
import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createStaticVNode, createTextVNode, createVNode, defineComponent, normalizeClass, onUnmounted, openBlock, ref, renderSlot, toDisplayString, unref, watch, withCtx, withModifiers } from "vue";
|
|
5
5
|
//#region src/components/ConfirmDialog.vue?vue&type=script&setup=true&lang.ts
|
|
6
6
|
var _hoisted_1$1 = { class: "mint-confirm" };
|
|
@@ -439,4 +439,4 @@ var ExperimentPopover_default = ExperimentPopover_vue_vue_type_script_setup_true
|
|
|
439
439
|
//#endregion
|
|
440
440
|
export { ExperimentPopover_exports as n, ConfirmDialog_default as r, ExperimentPopover_default as t };
|
|
441
441
|
|
|
442
|
-
//# sourceMappingURL=ExperimentPopover-
|
|
442
|
+
//# sourceMappingURL=ExperimentPopover-tLwJ7XF4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExperimentPopover-D6_RGzTY.js","names":["$slots"],"sources":["../src/components/ConfirmDialog.vue","../src/components/ConfirmDialog.vue","../src/components/ExperimentPopover.vue","../src/components/ExperimentPopover.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Confirm/cancel dialog with danger, warning, and info variants; blocks close while loading. */\nimport { computed } from 'vue'\nimport type { ButtonVariant } from '../types'\nimport BaseModal from './BaseModal.vue'\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n modelValue: boolean\n title?: string\n subtitle?: string\n message?: string\n variant?: 'danger' | 'warning' | 'info'\n confirmLabel?: string\n cancelLabel?: string\n loading?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Confirm',\n variant: 'danger',\n confirmLabel: 'Confirm',\n cancelLabel: 'Cancel',\n loading: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n confirm: []\n cancel: []\n}>()\n\nfunction handleCancel() {\n emit('update:modelValue', false)\n emit('cancel')\n}\n\nfunction handleConfirm() {\n emit('confirm')\n}\n\nconst confirmVariant = computed<ButtonVariant>(() => {\n if (props.variant === 'info') return 'primary'\n if (props.variant === 'warning') return 'cta'\n return 'danger'\n})\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :subtitle=\"subtitle\"\n size=\"sm\"\n :closable=\"!loading\"\n :close-on-overlay=\"!loading\"\n :close-on-escape=\"!loading\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mint-confirm\">\n <!-- Icon is now opt-in via slot. Refresh design keeps the body focused on\n the message; variant intent is carried by the confirm button's color. -->\n <div\n v-if=\"$slots.icon\"\n :class=\"['mint-confirm__icon', `mint-confirm__icon--${variant}`]\"\n >\n <slot name=\"icon\" />\n </div>\n <p v-if=\"message\" class=\"mint-confirm__message\">{{ message }}</p>\n <slot />\n </div>\n\n <template #footer>\n <div class=\"mint-confirm__footer\">\n <BaseButton\n type=\"button\"\n variant=\"secondary\"\n size=\"md\"\n :disabled=\"loading\"\n @click=\"handleCancel\"\n >\n {{ cancelLabel }}\n </BaseButton>\n <BaseButton\n type=\"button\"\n :variant=\"confirmVariant\"\n size=\"md\"\n :loading=\"loading\"\n @click=\"handleConfirm\"\n >\n {{ confirmLabel }}\n </BaseButton>\n </div>\n </template>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/confirm-dialog.css';\n</style>\n","<script setup lang=\"ts\">\n/** Confirm/cancel dialog with danger, warning, and info variants; blocks close while loading. */\nimport { computed } from 'vue'\nimport type { ButtonVariant } from '../types'\nimport BaseModal from './BaseModal.vue'\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n modelValue: boolean\n title?: string\n subtitle?: string\n message?: string\n variant?: 'danger' | 'warning' | 'info'\n confirmLabel?: string\n cancelLabel?: string\n loading?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Confirm',\n variant: 'danger',\n confirmLabel: 'Confirm',\n cancelLabel: 'Cancel',\n loading: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n confirm: []\n cancel: []\n}>()\n\nfunction handleCancel() {\n emit('update:modelValue', false)\n emit('cancel')\n}\n\nfunction handleConfirm() {\n emit('confirm')\n}\n\nconst confirmVariant = computed<ButtonVariant>(() => {\n if (props.variant === 'info') return 'primary'\n if (props.variant === 'warning') return 'cta'\n return 'danger'\n})\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :subtitle=\"subtitle\"\n size=\"sm\"\n :closable=\"!loading\"\n :close-on-overlay=\"!loading\"\n :close-on-escape=\"!loading\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mint-confirm\">\n <!-- Icon is now opt-in via slot. Refresh design keeps the body focused on\n the message; variant intent is carried by the confirm button's color. -->\n <div\n v-if=\"$slots.icon\"\n :class=\"['mint-confirm__icon', `mint-confirm__icon--${variant}`]\"\n >\n <slot name=\"icon\" />\n </div>\n <p v-if=\"message\" class=\"mint-confirm__message\">{{ message }}</p>\n <slot />\n </div>\n\n <template #footer>\n <div class=\"mint-confirm__footer\">\n <BaseButton\n type=\"button\"\n variant=\"secondary\"\n size=\"md\"\n :disabled=\"loading\"\n @click=\"handleCancel\"\n >\n {{ cancelLabel }}\n </BaseButton>\n <BaseButton\n type=\"button\"\n :variant=\"confirmVariant\"\n size=\"md\"\n :loading=\"loading\"\n @click=\"handleConfirm\"\n >\n {{ confirmLabel }}\n </BaseButton>\n </div>\n </template>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/confirm-dialog.css';\n</style>\n","<script setup lang=\"ts\">\n/** Floating popover showing the active experiment with save, detach, and select actions. */\nimport { ref, computed, watch, onUnmounted } from 'vue'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport { formatExperimentStatus } from '../composables/experiment-utils'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ninterface Props {\n experimentName?: string\n experimentCode?: string\n experimentStatus?: string\n /** An experiment is bound but the platform has not returned its record yet. */\n isResolving?: boolean\n /** Message explaining why the bound experiment could not be resolved. */\n error?: string\n showSave?: boolean\n showDetach?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n saveDisabledMessage?: string\n confirmSave?: boolean\n confirmTitle?: string\n confirmMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n isResolving: false,\n showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n confirmSave: true,\n})\n\n// An experiment is bound but has no name to show — either still resolving or\n// resolution failed. The id is never substituted: a name may only come from the\n// platform, so these states say what is happening instead of inventing a label.\nconst isUnresolved = computed(() => props.isResolving || !!props.error)\nconst hasExperiment = computed(() => !!props.experimentName || !!props.experimentCode)\n\nconst panelSubtitle = computed(() => {\n if (props.isResolving) return 'Loading experiment…'\n if (props.error) return 'Could not load this experiment'\n return hasExperiment.value ? 'Linked experiment context' : 'Link to an MINT experiment'\n})\n\nconst triggerLabel = computed(() => {\n if (props.isResolving) return 'Loading experiment'\n if (props.error) return 'Experiment unavailable'\n return props.experimentName || props.experimentCode || 'No experiment'\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, rootRef: popoverRef, close, toggle } = useDropdownState({\n closeOnEscape: false,\n})\nconst showSuccess = ref(false)\nconst showConfirm = ref(false)\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n if (props.confirmSave) {\n showConfirm.value = true\n } else {\n emit('save')\n }\n}\n\nfunction handleConfirmSave() {\n showConfirm.value = false\n emit('save')\n}\n\nfunction handleDetach() {\n emit('detach')\n close()\n}\n\nlet successTimer: ReturnType<typeof setTimeout> | null = null\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (successTimer) clearTimeout(successTimer)\n if (msg) {\n showSuccess.value = true\n successTimer = setTimeout(() => {\n showSuccess.value = false\n successTimer = null\n }, 3000)\n }\n})\n\nonUnmounted(() => {\n if (successTimer) clearTimeout(successTimer)\n})\n</script>\n\n<template>\n <div ref=\"popoverRef\" class=\"mint-experiment-popover\">\n <!-- Split trigger: experiment pill + inline save -->\n <div\n :class=\"[\n 'mint-experiment-popover__split',\n { 'mint-experiment-popover__split--with-save': showSave && experimentName },\n ]\"\n >\n <!-- Left: experiment trigger (opens popover). Prefer the human-readable\n name; the formal code, when present, stays in the detail panel. -->\n <button\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': !isUnresolved && !hasExperiment },\n { 'mint-experiment-popover__trigger--loading': isResolving },\n { 'mint-experiment-popover__trigger--error': !isResolving && !!error },\n ]\"\n :title=\"isUnresolved ? triggerLabel : experimentName || experimentCode || undefined\"\n :aria-label=\"isUnresolved ? triggerLabel : undefined\"\n :aria-busy=\"isResolving || undefined\"\n :aria-expanded=\"isOpen\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mint-experiment-popover__trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n 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 />\n </svg>\n <!-- Resolving and error come first: with an experiment bound but no name\n yet, \"No experiment\" would be a lie. -->\n <span v-if=\"isResolving\" class=\"mint-experiment-popover__trigger-skeleton\" aria-hidden=\"true\" />\n <span v-else-if=\"error\" class=\"mint-experiment-popover__trigger-text\">Experiment unavailable</span>\n <!-- Name preferred, code as fallback, \"No experiment\" as empty state -->\n <span v-else-if=\"experimentName\" class=\"mint-experiment-popover__trigger-text\">{{ experimentName }}</span>\n <span v-else-if=\"experimentCode\" class=\"mint-experiment-popover__trigger-code\">{{ experimentCode }}</span>\n <span v-else class=\"mint-experiment-popover__trigger-text\">No experiment</span>\n <!-- Chevron -->\n <svg class=\"mint-experiment-popover__trigger-chevron\" aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <!-- Right: inline save button (direct action) -->\n <button\n v-if=\"showSave && experimentName\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__save-trigger',\n { 'mint-experiment-popover__save-trigger--loading': saveLoading },\n { 'mint-experiment-popover__save-trigger--success': showSuccess },\n { 'mint-experiment-popover__save-trigger--disabled': saveDisabled && !showSuccess },\n ]\"\n :disabled=\"saveDisabled && !showSuccess\"\n :title=\"saveDisabled && saveDisabledMessage ? saveDisabledMessage : showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment'\"\n aria-label=\"Save to experiment\"\n @click.stop=\"handleSave\"\n >\n <!-- Loading spinner -->\n <span v-if=\"saveLoading\" class=\"mint-experiment-popover__spinner--inline\" />\n <!-- Success check -->\n <svg v-else-if=\"showSuccess\" class=\"mint-experiment-popover__save-trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2.5\" d=\"M5 13l4 4L19 7\" />\n </svg>\n <!-- Save icon -->\n <svg v-else class=\"mint-experiment-popover__save-trigger-icon\" aria-hidden=\"true\" 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 </button>\n </div>\n\n <!-- Popover panel -->\n <div v-if=\"isOpen\" class=\"mint-experiment-popover__panel\">\n <!-- Header -->\n <div class=\"mint-experiment-popover__header\">\n <div class=\"mint-experiment-popover__title\">Experiment</div>\n <div class=\"mint-experiment-popover__subtitle\">\n {{ panelSubtitle }}\n </div>\n </div>\n\n <!-- Bound, still resolving — placeholders stand in for the record the\n platform has not returned yet. -->\n <div v-if=\"isResolving\" class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\" aria-busy=\"true\">\n <div class=\"mint-experiment-popover__card-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=\"1.75\"\n 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 />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--code\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--name\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--status\" />\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n\n <!-- Bound, resolution failed — Detach stays reachable so the user is not\n stuck on an experiment the platform cannot describe. -->\n <div v-else-if=\"error\" class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card mint-experiment-popover__card--error\" role=\"alert\">\n <div class=\"mint-experiment-popover__card-icon mint-experiment-popover__card-icon--error\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z\"\n />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <div class=\"mint-experiment-popover__card-name\">Experiment unavailable</div>\n <div class=\"mint-experiment-popover__card-message\">{{ error }}</div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n\n <!-- No experiment selected -->\n <div v-else-if=\"!experimentName\" class=\"mint-experiment-popover__body\">\n <button type=\"button\" class=\"mint-experiment-popover__select-btn\" @click=\"handleSelect\">\n <svg width=\"14\" height=\"14\" 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 Select Experiment\n </button>\n </div>\n\n <!-- Experiment selected -->\n <div v-else class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\">\n <div class=\"mint-experiment-popover__card-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=\"1.75\"\n 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 />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span v-if=\"experimentCode\" class=\"mint-experiment-popover__card-code\">{{ experimentCode }}</span>\n <div class=\"mint-experiment-popover__card-name\">{{ experimentName }}</div>\n <div v-if=\"experimentStatus\" class=\"mint-experiment-popover__card-status\">\n {{ formatExperimentStatus(experimentStatus) }}\n </div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n </div>\n\n <!-- Save confirmation dialog -->\n <ConfirmDialog\n v-model=\"showConfirm\"\n :title=\"confirmTitle ?? 'Save to Experiment'\"\n :message=\"confirmMessage ?? `Save current data to ${experimentName}?`\"\n variant=\"info\"\n confirm-label=\"Save\"\n :loading=\"saveLoading\"\n @confirm=\"handleConfirmSave\"\n />\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-popover.css';\n</style>\n","<script setup lang=\"ts\">\n/** Floating popover showing the active experiment with save, detach, and select actions. */\nimport { ref, computed, watch, onUnmounted } from 'vue'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport { formatExperimentStatus } from '../composables/experiment-utils'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ninterface Props {\n experimentName?: string\n experimentCode?: string\n experimentStatus?: string\n /** An experiment is bound but the platform has not returned its record yet. */\n isResolving?: boolean\n /** Message explaining why the bound experiment could not be resolved. */\n error?: string\n showSave?: boolean\n showDetach?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n saveDisabledMessage?: string\n confirmSave?: boolean\n confirmTitle?: string\n confirmMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n isResolving: false,\n showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n confirmSave: true,\n})\n\n// An experiment is bound but has no name to show — either still resolving or\n// resolution failed. The id is never substituted: a name may only come from the\n// platform, so these states say what is happening instead of inventing a label.\nconst isUnresolved = computed(() => props.isResolving || !!props.error)\nconst hasExperiment = computed(() => !!props.experimentName || !!props.experimentCode)\n\nconst panelSubtitle = computed(() => {\n if (props.isResolving) return 'Loading experiment…'\n if (props.error) return 'Could not load this experiment'\n return hasExperiment.value ? 'Linked experiment context' : 'Link to an MINT experiment'\n})\n\nconst triggerLabel = computed(() => {\n if (props.isResolving) return 'Loading experiment'\n if (props.error) return 'Experiment unavailable'\n return props.experimentName || props.experimentCode || 'No experiment'\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, rootRef: popoverRef, close, toggle } = useDropdownState({\n closeOnEscape: false,\n})\nconst showSuccess = ref(false)\nconst showConfirm = ref(false)\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n if (props.confirmSave) {\n showConfirm.value = true\n } else {\n emit('save')\n }\n}\n\nfunction handleConfirmSave() {\n showConfirm.value = false\n emit('save')\n}\n\nfunction handleDetach() {\n emit('detach')\n close()\n}\n\nlet successTimer: ReturnType<typeof setTimeout> | null = null\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (successTimer) clearTimeout(successTimer)\n if (msg) {\n showSuccess.value = true\n successTimer = setTimeout(() => {\n showSuccess.value = false\n successTimer = null\n }, 3000)\n }\n})\n\nonUnmounted(() => {\n if (successTimer) clearTimeout(successTimer)\n})\n</script>\n\n<template>\n <div ref=\"popoverRef\" class=\"mint-experiment-popover\">\n <!-- Split trigger: experiment pill + inline save -->\n <div\n :class=\"[\n 'mint-experiment-popover__split',\n { 'mint-experiment-popover__split--with-save': showSave && experimentName },\n ]\"\n >\n <!-- Left: experiment trigger (opens popover). Prefer the human-readable\n name; the formal code, when present, stays in the detail panel. -->\n <button\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': !isUnresolved && !hasExperiment },\n { 'mint-experiment-popover__trigger--loading': isResolving },\n { 'mint-experiment-popover__trigger--error': !isResolving && !!error },\n ]\"\n :title=\"isUnresolved ? triggerLabel : experimentName || experimentCode || undefined\"\n :aria-label=\"isUnresolved ? triggerLabel : undefined\"\n :aria-busy=\"isResolving || undefined\"\n :aria-expanded=\"isOpen\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mint-experiment-popover__trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n 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 />\n </svg>\n <!-- Resolving and error come first: with an experiment bound but no name\n yet, \"No experiment\" would be a lie. -->\n <span v-if=\"isResolving\" class=\"mint-experiment-popover__trigger-skeleton\" aria-hidden=\"true\" />\n <span v-else-if=\"error\" class=\"mint-experiment-popover__trigger-text\">Experiment unavailable</span>\n <!-- Name preferred, code as fallback, \"No experiment\" as empty state -->\n <span v-else-if=\"experimentName\" class=\"mint-experiment-popover__trigger-text\">{{ experimentName }}</span>\n <span v-else-if=\"experimentCode\" class=\"mint-experiment-popover__trigger-code\">{{ experimentCode }}</span>\n <span v-else class=\"mint-experiment-popover__trigger-text\">No experiment</span>\n <!-- Chevron -->\n <svg class=\"mint-experiment-popover__trigger-chevron\" aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <!-- Right: inline save button (direct action) -->\n <button\n v-if=\"showSave && experimentName\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__save-trigger',\n { 'mint-experiment-popover__save-trigger--loading': saveLoading },\n { 'mint-experiment-popover__save-trigger--success': showSuccess },\n { 'mint-experiment-popover__save-trigger--disabled': saveDisabled && !showSuccess },\n ]\"\n :disabled=\"saveDisabled && !showSuccess\"\n :title=\"saveDisabled && saveDisabledMessage ? saveDisabledMessage : showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment'\"\n aria-label=\"Save to experiment\"\n @click.stop=\"handleSave\"\n >\n <!-- Loading spinner -->\n <span v-if=\"saveLoading\" class=\"mint-experiment-popover__spinner--inline\" />\n <!-- Success check -->\n <svg v-else-if=\"showSuccess\" class=\"mint-experiment-popover__save-trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2.5\" d=\"M5 13l4 4L19 7\" />\n </svg>\n <!-- Save icon -->\n <svg v-else class=\"mint-experiment-popover__save-trigger-icon\" aria-hidden=\"true\" 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 </button>\n </div>\n\n <!-- Popover panel -->\n <div v-if=\"isOpen\" class=\"mint-experiment-popover__panel\">\n <!-- Header -->\n <div class=\"mint-experiment-popover__header\">\n <div class=\"mint-experiment-popover__title\">Experiment</div>\n <div class=\"mint-experiment-popover__subtitle\">\n {{ panelSubtitle }}\n </div>\n </div>\n\n <!-- Bound, still resolving — placeholders stand in for the record the\n platform has not returned yet. -->\n <div v-if=\"isResolving\" class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\" aria-busy=\"true\">\n <div class=\"mint-experiment-popover__card-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=\"1.75\"\n 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 />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--code\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--name\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--status\" />\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n\n <!-- Bound, resolution failed — Detach stays reachable so the user is not\n stuck on an experiment the platform cannot describe. -->\n <div v-else-if=\"error\" class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card mint-experiment-popover__card--error\" role=\"alert\">\n <div class=\"mint-experiment-popover__card-icon mint-experiment-popover__card-icon--error\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z\"\n />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <div class=\"mint-experiment-popover__card-name\">Experiment unavailable</div>\n <div class=\"mint-experiment-popover__card-message\">{{ error }}</div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n\n <!-- No experiment selected -->\n <div v-else-if=\"!experimentName\" class=\"mint-experiment-popover__body\">\n <button type=\"button\" class=\"mint-experiment-popover__select-btn\" @click=\"handleSelect\">\n <svg width=\"14\" height=\"14\" 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 Select Experiment\n </button>\n </div>\n\n <!-- Experiment selected -->\n <div v-else class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\">\n <div class=\"mint-experiment-popover__card-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=\"1.75\"\n 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 />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span v-if=\"experimentCode\" class=\"mint-experiment-popover__card-code\">{{ experimentCode }}</span>\n <div class=\"mint-experiment-popover__card-name\">{{ experimentName }}</div>\n <div v-if=\"experimentStatus\" class=\"mint-experiment-popover__card-status\">\n {{ formatExperimentStatus(experimentStatus) }}\n </div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n </div>\n\n <!-- Save confirmation dialog -->\n <ConfirmDialog\n v-model=\"showConfirm\"\n :title=\"confirmTitle ?? 'Save to Experiment'\"\n :message=\"confirmMessage ?? `Save current data to ${experimentName}?`\"\n variant=\"info\"\n confirm-label=\"Save\"\n :loading=\"saveLoading\"\n @confirm=\"handleConfirmSave\"\n />\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-popover.css';\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECkBA,MAAM,QAAQ;EAQd,MAAM,OAAO;EAMb,SAAS,eAAe;AACtB,QAAK,qBAAqB,MAAK;AAC/B,QAAK,SAAQ;;EAGf,SAAS,gBAAgB;AACvB,QAAK,UAAS;;EAGhB,MAAM,iBAAiB,eAA8B;AACnD,OAAI,MAAM,YAAY,OAAQ,QAAO;AACrC,OAAI,MAAM,YAAY,UAAW,QAAO;AACxC,UAAO;IACR;;uBAIC,YA6CY,mBAAA;IA5CT,eAAa,QAAA;IACb,OAAO,QAAA;IACP,UAAU,QAAA;IACX,MAAK;IACJ,UAAQ,CAAG,QAAA;IACX,oBAAgB,CAAG,QAAA;IACnB,mBAAe,CAAG,QAAA;IAClB,uBAAkB,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,qBAAsB,OAAM;;IAe1C,QAAM,cAoBT,CAnBN,mBAmBM,OAnBN,cAmBM,CAlBJ,YAQa,oBAAA;KAPX,MAAK;KACL,SAAQ;KACR,MAAK;KACJ,UAAU,QAAA;KACV,SAAO;;4BAES,CAAA,gBAAA,gBAAd,QAAA,YAAW,EAAA,EAAA,CAAA,CAAA;;yBAEhB,YAQa,oBAAA;KAPX,MAAK;KACJ,SAAS,eAAA;KACV,MAAK;KACJ,SAAS,QAAA;KACT,SAAO;;4BAEU,CAAA,gBAAA,gBAAf,QAAA,aAAY,EAAA,EAAA,CAAA,CAAA;;;2BApBf,CAXN,mBAWM,OAXN,cAWM;KAPIA,KAAAA,OAAO,QAAA,WAAA,EADf,mBAKM,OAAA;;MAHH,OAAK,eAAA,CAAA,sBAAA,uBAAgD,QAAA,UAAO,CAAA;SAE7D,WAAoB,KAAA,QAAA,OAAA,CAAA,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA;KAEb,QAAA,WAAA,WAAA,EAAT,mBAAiE,KAAjE,cAAiE,gBAAd,QAAA,QAAO,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA;KAC1D,WAAQ,KAAA,QAAA,UAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EE3Cd,MAAM,QAAQ;EAYd,MAAM,eAAe,eAAe,MAAM,eAAe,CAAC,CAAC,MAAM,MAAK;EACtE,MAAM,gBAAgB,eAAe,CAAC,CAAC,MAAM,kBAAkB,CAAC,CAAC,MAAM,eAAc;EAErF,MAAM,gBAAgB,eAAe;AACnC,OAAI,MAAM,YAAa,QAAO;AAC9B,OAAI,MAAM,MAAO,QAAO;AACxB,UAAO,cAAc,QAAQ,8BAA8B;IAC5D;EAED,MAAM,eAAe,eAAe;AAClC,OAAI,MAAM,YAAa,QAAO;AAC9B,OAAI,MAAM,MAAO,QAAO;AACxB,UAAO,MAAM,kBAAkB,MAAM,kBAAkB;IACxD;EAED,MAAM,OAAO;EAMb,MAAM,EAAE,QAAQ,SAAS,YAAY,OAAO,WAAW,iBAAiB,EACtE,eAAe,OAChB,CAAA;EACD,MAAM,cAAc,IAAI,MAAK;EAC7B,MAAM,cAAc,IAAI,MAAK;EAE7B,SAAS,eAAe;AACtB,QAAK,SAAQ;AACb,UAAM;;EAGR,SAAS,aAAa;AACpB,OAAI,MAAM,gBAAgB,MAAM,YAAa;AAC7C,OAAI,MAAM,YACR,aAAY,QAAQ;OAEpB,MAAK,OAAM;;EAIf,SAAS,oBAAoB;AAC3B,eAAY,QAAQ;AACpB,QAAK,OAAM;;EAGb,SAAS,eAAe;AACtB,QAAK,SAAQ;AACb,UAAM;;EAGR,IAAI,eAAqD;AAGzD,cAAY,MAAM,qBAAqB,QAAQ;AAC7C,OAAI,aAAc,cAAa,aAAY;AAC3C,OAAI,KAAK;AACP,gBAAY,QAAQ;AACpB,mBAAe,iBAAiB;AAC9B,iBAAY,QAAQ;AACpB,oBAAe;OACd,IAAI;;IAEV;AAED,oBAAkB;AAChB,OAAI,aAAc,cAAa,aAAY;IAC5C;;uBAIC,mBAqMM,OAAA;aArMG;IAAJ,KAAI;IAAa,OAAM;;IAE1B,mBAwEM,OAAA,EAvEH,OAAK,eAAA,CAAA,kCAAA,EAAA,6CAAqG,QAAA,YAAY,QAAA,gBAAc,CAAA,CAAA,EAAA,EAAA,CAOrI,mBAoCS,UAAA;KAnCP,MAAK;KACJ,OAAK,eAAA;;oDAA0G,MAAA,OAAM,EAAA;oDAA4D,aAAA,SAAY,CAAK,cAAA,OAAa;qDAA6D,QAAA,aAAW;oDAA4D,QAAA,eAAW,CAAA,CAAM,QAAA,OAAK;;KAOzW,OAAO,aAAA,QAAe,aAAA,QAAe,QAAA,kBAAkB,QAAA,kBAAkB,KAAA;KACzE,cAAY,aAAA,QAAe,aAAA,QAAe,KAAA;KAC1C,aAAW,QAAA,eAAe,KAAA;KAC1B,iBAAe,MAAA,OAAM;KACrB,SAAK,OAAA,OAAA,OAAA,KAAA,eAAA,GAAA,SAAO,MAAA,OAAA,IAAA,MAAA,OAAA,CAAA,GAAA,KAAM,EAAA,CAAA,OAAA,CAAA;;+BAGnB,mBAOM,OAAA;MAPD,OAAM;MAAwC,eAAY;MAAO,MAAK;MAAO,QAAO;MAAe,SAAQ;SAC9G,mBAKE,QAAA;MAJA,kBAAe;MACf,mBAAgB;MAChB,gBAAa;MACb,GAAE;;KAKM,QAAA,eAAA,WAAA,EAAZ,mBAAgG,QAAhG,WAAgG,IAC/E,QAAA,SAAA,WAAA,EAAjB,mBAAmG,QAAnG,YAAsE,yBAAsB,IAE3E,QAAA,kBAAA,WAAA,EAAjB,mBAA0G,QAA1G,YAA0G,gBAAxB,QAAA,eAAc,EAAA,EAAA,IAC/E,QAAA,kBAAA,WAAA,EAAjB,mBAA0G,QAA1G,YAA0G,gBAAxB,QAAA,eAAc,EAAA,EAAA,KAAA,WAAA,EAChG,mBAA+E,QAA/E,YAA2D,gBAAa;+BAExE,mBAEM,OAAA;MAFD,OAAM;MAA2C,eAAY;MAAO,SAAQ;MAAY,MAAK;MAAO,QAAO;MAAe,gBAAa;MAAI,kBAAe;MAAQ,mBAAgB;SACrL,mBAAyB,QAAA,EAAnB,GAAE,gBAAc,CAAA,CAAA,EAAA,GAAA;wBAMlB,QAAA,YAAY,QAAA,kBAAA,WAAA,EADpB,mBAwBS,UAAA;;KAtBP,MAAK;KACJ,OAAK,eAAA;;0DAAqH,QAAA,aAAW;0DAAkE,YAAA,OAAW;2DAAmE,QAAA,gBAAY,CAAK,YAAA,OAAW;;KAMjT,UAAU,QAAA,gBAAY,CAAK,YAAA;KAC3B,OAAO,QAAA,gBAAgB,QAAA,sBAAsB,QAAA,sBAAsB,YAAA,SAAe,QAAA,qBAAqB,QAAA,qBAAkB;KAC1H,cAAW;KACV,SAAK,cAAO,YAAU,CAAA,OAAA,CAAA;QAGX,QAAA,eAAA,WAAA,EAAZ,mBAA4E,QAA5E,WAA4E,IAE5D,YAAA,SAAA,WAAA,EAAhB,mBAEM,OAFN,YAEM,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CADJ,mBAA6F,QAAA;KAAvF,kBAAe;KAAQ,mBAAgB;KAAQ,gBAAa;KAAM,GAAE;sCAG5E,mBAEM,OAFN,aAEM,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CADJ,mBAAwK,QAAA;KAAlK,kBAAe;KAAQ,mBAAgB;KAAQ,gBAAa;KAAI,GAAE;;IAMnE,MAAA,OAAM,IAAA,WAAA,EAAjB,mBA4GM,OA5GN,aA4GM,CA1GJ,mBAKM,OALN,aAKM,CAAA,OAAA,OAAA,OAAA,KAJJ,mBAA4D,OAAA,EAAvD,OAAM,kCAAgC,EAAC,cAAU,GAAA,GACtD,mBAEM,OAFN,aAEM,gBADD,cAAA,MAAa,EAAA,EAAA,CAAA,CAAA,EAMT,QAAA,eAAA,WAAA,EAAX,mBA0BM,OA1BN,aA0BM,CAAA,OAAA,OAAA,OAAA,KAAA,kBAAA,s6BAAA,EAAA,GARJ,mBAOM,OAPN,aAOM,CANJ,mBAES,UAAA;KAFD,MAAK;KAAS,OAAM;KAAuC,SAAO;OAAc,WAExF,EACc,QAAA,cAAA,WAAA,EAAd,mBAES,UAAA;;KAFiB,MAAK;KAAS,OAAM;KAAuC,SAAO;OAAc,WAE1G,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA,CAAA,CAAA,IAMY,QAAA,SAAA,WAAA,EAAhB,mBAyBM,OAzBN,aAyBM,CAxBJ,mBAeM,OAfN,aAeM,CAAA,OAAA,OAAA,OAAA,KAdJ,mBASM,OAAA,EATD,OAAM,gFAA8E,EAAA,CACvF,mBAOM,OAAA;KAPD,MAAK;KAAO,QAAO;KAAe,SAAQ;QAC7C,mBAKE,QAAA;KAJA,kBAAe;KACf,mBAAgB;KAChB,gBAAa;KACb,GAAE;iBAIR,mBAGM,OAHN,aAGM,CAAA,OAAA,OAAA,OAAA,KAFJ,mBAA4E,OAAA,EAAvE,OAAM,sCAAoC,EAAC,0BAAsB,GAAA,GACtE,mBAAoE,OAApE,aAAoE,gBAAd,QAAA,MAAK,EAAA,EAAA,CAAA,CAAA,CAAA,CAAA,EAG/D,mBAOM,OAPN,aAOM,CANJ,mBAES,UAAA;KAFD,MAAK;KAAS,OAAM;KAAuC,SAAO;OAAc,WAExF,EACc,QAAA,cAAA,WAAA,EAAd,mBAES,UAAA;;KAFiB,MAAK;KAAS,OAAM;KAAuC,SAAO;OAAc,WAE1G,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA,CAAA,CAAA,IAAA,CAKa,QAAA,kBAAA,WAAA,EAAjB,mBAOM,OAPN,aAOM,CANJ,mBAKS,UAAA;KALD,MAAK;KAAS,OAAM;KAAuC,SAAO;wCACxE,mBAEM,OAAA;KAFD,OAAM;KAAK,QAAO;KAAK,MAAK;KAAO,QAAO;KAAe,SAAQ;QACpE,mBAA2F,QAAA;KAArF,kBAAe;KAAQ,mBAAgB;KAAQ,gBAAa;KAAI,GAAE;8BACpE,uBAER,GAAA,CAAA,EAAA,CAAA,CAAA,CAAA,KAAA,WAAA,EAIF,mBA4BM,OA5BN,aA4BM,CA3BJ,mBAkBM,OAlBN,aAkBM,CAAA,OAAA,QAAA,OAAA,MAjBJ,mBASM,OAAA,EATD,OAAM,sCAAoC,EAAA,CAC7C,mBAOM,OAAA;KAPD,MAAK;KAAO,QAAO;KAAe,SAAQ;QAC7C,mBAKE,QAAA;KAJA,kBAAe;KACf,mBAAgB;KAChB,gBAAa;KACb,GAAE;iBAIR,mBAMM,OANN,aAMM;KALQ,QAAA,kBAAA,WAAA,EAAZ,mBAAkG,QAAlG,aAAkG,gBAAxB,QAAA,eAAc,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA;KACxF,mBAA0E,OAA1E,aAA0E,gBAAvB,QAAA,eAAc,EAAA,EAAA;KACtD,QAAA,oBAAA,WAAA,EAAX,mBAEM,OAFN,aAEM,gBADD,MAAA,uBAAsB,CAAC,QAAA,iBAAgB,CAAA,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA;UAIhD,mBAOM,OAPN,aAOM,CANJ,mBAES,UAAA;KAFD,MAAK;KAAS,OAAM;KAAuC,SAAO;OAAc,WAExF,EACc,QAAA,cAAA,WAAA,EAAd,mBAES,UAAA;;KAFiB,MAAK;KAAS,OAAM;KAAuC,SAAO;OAAc,WAE1G,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA,CAAA,CAAA,EAAA,CAAA,IAAA,mBAAA,IAAA,KAAA;IAMN,YAQE,uBAAA;iBAPS,YAAA;8EAAW,QAAA;KACnB,OAAO,QAAA,gBAAY;KACnB,SAAS,QAAA,kBAAc,wBAA4B,QAAA,eAAc;KAClE,SAAQ;KACR,iBAAc;KACb,SAAS,QAAA;KACT,WAAS"}
|
|
1
|
+
{"version":3,"file":"ExperimentPopover-tLwJ7XF4.js","names":["$slots"],"sources":["../src/components/ConfirmDialog.vue","../src/components/ConfirmDialog.vue","../src/components/ExperimentPopover.vue","../src/components/ExperimentPopover.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Confirm/cancel dialog with danger, warning, and info variants; blocks close while loading. */\nimport { computed } from 'vue'\nimport type { ButtonVariant } from '../types'\nimport BaseModal from './BaseModal.vue'\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n modelValue: boolean\n title?: string\n subtitle?: string\n message?: string\n variant?: 'danger' | 'warning' | 'info'\n confirmLabel?: string\n cancelLabel?: string\n loading?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Confirm',\n variant: 'danger',\n confirmLabel: 'Confirm',\n cancelLabel: 'Cancel',\n loading: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n confirm: []\n cancel: []\n}>()\n\nfunction handleCancel() {\n emit('update:modelValue', false)\n emit('cancel')\n}\n\nfunction handleConfirm() {\n emit('confirm')\n}\n\nconst confirmVariant = computed<ButtonVariant>(() => {\n if (props.variant === 'info') return 'primary'\n if (props.variant === 'warning') return 'cta'\n return 'danger'\n})\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :subtitle=\"subtitle\"\n size=\"sm\"\n :closable=\"!loading\"\n :close-on-overlay=\"!loading\"\n :close-on-escape=\"!loading\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mint-confirm\">\n <!-- Icon is now opt-in via slot. Refresh design keeps the body focused on\n the message; variant intent is carried by the confirm button's color. -->\n <div\n v-if=\"$slots.icon\"\n :class=\"['mint-confirm__icon', `mint-confirm__icon--${variant}`]\"\n >\n <slot name=\"icon\" />\n </div>\n <p v-if=\"message\" class=\"mint-confirm__message\">{{ message }}</p>\n <slot />\n </div>\n\n <template #footer>\n <div class=\"mint-confirm__footer\">\n <BaseButton\n type=\"button\"\n variant=\"secondary\"\n size=\"md\"\n :disabled=\"loading\"\n @click=\"handleCancel\"\n >\n {{ cancelLabel }}\n </BaseButton>\n <BaseButton\n type=\"button\"\n :variant=\"confirmVariant\"\n size=\"md\"\n :loading=\"loading\"\n @click=\"handleConfirm\"\n >\n {{ confirmLabel }}\n </BaseButton>\n </div>\n </template>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/confirm-dialog.css';\n</style>\n","<script setup lang=\"ts\">\n/** Confirm/cancel dialog with danger, warning, and info variants; blocks close while loading. */\nimport { computed } from 'vue'\nimport type { ButtonVariant } from '../types'\nimport BaseModal from './BaseModal.vue'\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n modelValue: boolean\n title?: string\n subtitle?: string\n message?: string\n variant?: 'danger' | 'warning' | 'info'\n confirmLabel?: string\n cancelLabel?: string\n loading?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Confirm',\n variant: 'danger',\n confirmLabel: 'Confirm',\n cancelLabel: 'Cancel',\n loading: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n confirm: []\n cancel: []\n}>()\n\nfunction handleCancel() {\n emit('update:modelValue', false)\n emit('cancel')\n}\n\nfunction handleConfirm() {\n emit('confirm')\n}\n\nconst confirmVariant = computed<ButtonVariant>(() => {\n if (props.variant === 'info') return 'primary'\n if (props.variant === 'warning') return 'cta'\n return 'danger'\n})\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :subtitle=\"subtitle\"\n size=\"sm\"\n :closable=\"!loading\"\n :close-on-overlay=\"!loading\"\n :close-on-escape=\"!loading\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mint-confirm\">\n <!-- Icon is now opt-in via slot. Refresh design keeps the body focused on\n the message; variant intent is carried by the confirm button's color. -->\n <div\n v-if=\"$slots.icon\"\n :class=\"['mint-confirm__icon', `mint-confirm__icon--${variant}`]\"\n >\n <slot name=\"icon\" />\n </div>\n <p v-if=\"message\" class=\"mint-confirm__message\">{{ message }}</p>\n <slot />\n </div>\n\n <template #footer>\n <div class=\"mint-confirm__footer\">\n <BaseButton\n type=\"button\"\n variant=\"secondary\"\n size=\"md\"\n :disabled=\"loading\"\n @click=\"handleCancel\"\n >\n {{ cancelLabel }}\n </BaseButton>\n <BaseButton\n type=\"button\"\n :variant=\"confirmVariant\"\n size=\"md\"\n :loading=\"loading\"\n @click=\"handleConfirm\"\n >\n {{ confirmLabel }}\n </BaseButton>\n </div>\n </template>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/confirm-dialog.css';\n</style>\n","<script setup lang=\"ts\">\n/** Floating popover showing the active experiment with save, detach, and select actions. */\nimport { ref, computed, watch, onUnmounted } from 'vue'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport { formatExperimentStatus } from '../composables/experiment-utils'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ninterface Props {\n experimentName?: string\n experimentCode?: string\n experimentStatus?: string\n /** An experiment is bound but the platform has not returned its record yet. */\n isResolving?: boolean\n /** Message explaining why the bound experiment could not be resolved. */\n error?: string\n showSave?: boolean\n showDetach?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n saveDisabledMessage?: string\n confirmSave?: boolean\n confirmTitle?: string\n confirmMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n isResolving: false,\n showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n confirmSave: true,\n})\n\n// An experiment is bound but has no name to show — either still resolving or\n// resolution failed. The id is never substituted: a name may only come from the\n// platform, so these states say what is happening instead of inventing a label.\nconst isUnresolved = computed(() => props.isResolving || !!props.error)\nconst hasExperiment = computed(() => !!props.experimentName || !!props.experimentCode)\n\nconst panelSubtitle = computed(() => {\n if (props.isResolving) return 'Loading experiment…'\n if (props.error) return 'Could not load this experiment'\n return hasExperiment.value ? 'Linked experiment context' : 'Link to an MINT experiment'\n})\n\nconst triggerLabel = computed(() => {\n if (props.isResolving) return 'Loading experiment'\n if (props.error) return 'Experiment unavailable'\n return props.experimentName || props.experimentCode || 'No experiment'\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, rootRef: popoverRef, close, toggle } = useDropdownState({\n closeOnEscape: false,\n})\nconst showSuccess = ref(false)\nconst showConfirm = ref(false)\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n if (props.confirmSave) {\n showConfirm.value = true\n } else {\n emit('save')\n }\n}\n\nfunction handleConfirmSave() {\n showConfirm.value = false\n emit('save')\n}\n\nfunction handleDetach() {\n emit('detach')\n close()\n}\n\nlet successTimer: ReturnType<typeof setTimeout> | null = null\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (successTimer) clearTimeout(successTimer)\n if (msg) {\n showSuccess.value = true\n successTimer = setTimeout(() => {\n showSuccess.value = false\n successTimer = null\n }, 3000)\n }\n})\n\nonUnmounted(() => {\n if (successTimer) clearTimeout(successTimer)\n})\n</script>\n\n<template>\n <div ref=\"popoverRef\" class=\"mint-experiment-popover\">\n <!-- Split trigger: experiment pill + inline save -->\n <div\n :class=\"[\n 'mint-experiment-popover__split',\n { 'mint-experiment-popover__split--with-save': showSave && experimentName },\n ]\"\n >\n <!-- Left: experiment trigger (opens popover). Prefer the human-readable\n name; the formal code, when present, stays in the detail panel. -->\n <button\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': !isUnresolved && !hasExperiment },\n { 'mint-experiment-popover__trigger--loading': isResolving },\n { 'mint-experiment-popover__trigger--error': !isResolving && !!error },\n ]\"\n :title=\"isUnresolved ? triggerLabel : experimentName || experimentCode || undefined\"\n :aria-label=\"isUnresolved ? triggerLabel : undefined\"\n :aria-busy=\"isResolving || undefined\"\n :aria-expanded=\"isOpen\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mint-experiment-popover__trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n 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 />\n </svg>\n <!-- Resolving and error come first: with an experiment bound but no name\n yet, \"No experiment\" would be a lie. -->\n <span v-if=\"isResolving\" class=\"mint-experiment-popover__trigger-skeleton\" aria-hidden=\"true\" />\n <span v-else-if=\"error\" class=\"mint-experiment-popover__trigger-text\">Experiment unavailable</span>\n <!-- Name preferred, code as fallback, \"No experiment\" as empty state -->\n <span v-else-if=\"experimentName\" class=\"mint-experiment-popover__trigger-text\">{{ experimentName }}</span>\n <span v-else-if=\"experimentCode\" class=\"mint-experiment-popover__trigger-code\">{{ experimentCode }}</span>\n <span v-else class=\"mint-experiment-popover__trigger-text\">No experiment</span>\n <!-- Chevron -->\n <svg class=\"mint-experiment-popover__trigger-chevron\" aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <!-- Right: inline save button (direct action) -->\n <button\n v-if=\"showSave && experimentName\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__save-trigger',\n { 'mint-experiment-popover__save-trigger--loading': saveLoading },\n { 'mint-experiment-popover__save-trigger--success': showSuccess },\n { 'mint-experiment-popover__save-trigger--disabled': saveDisabled && !showSuccess },\n ]\"\n :disabled=\"saveDisabled && !showSuccess\"\n :title=\"saveDisabled && saveDisabledMessage ? saveDisabledMessage : showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment'\"\n aria-label=\"Save to experiment\"\n @click.stop=\"handleSave\"\n >\n <!-- Loading spinner -->\n <span v-if=\"saveLoading\" class=\"mint-experiment-popover__spinner--inline\" />\n <!-- Success check -->\n <svg v-else-if=\"showSuccess\" class=\"mint-experiment-popover__save-trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2.5\" d=\"M5 13l4 4L19 7\" />\n </svg>\n <!-- Save icon -->\n <svg v-else class=\"mint-experiment-popover__save-trigger-icon\" aria-hidden=\"true\" 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 </button>\n </div>\n\n <!-- Popover panel -->\n <div v-if=\"isOpen\" class=\"mint-experiment-popover__panel\">\n <!-- Header -->\n <div class=\"mint-experiment-popover__header\">\n <div class=\"mint-experiment-popover__title\">Experiment</div>\n <div class=\"mint-experiment-popover__subtitle\">\n {{ panelSubtitle }}\n </div>\n </div>\n\n <!-- Bound, still resolving — placeholders stand in for the record the\n platform has not returned yet. -->\n <div v-if=\"isResolving\" class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\" aria-busy=\"true\">\n <div class=\"mint-experiment-popover__card-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=\"1.75\"\n 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 />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--code\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--name\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--status\" />\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n\n <!-- Bound, resolution failed — Detach stays reachable so the user is not\n stuck on an experiment the platform cannot describe. -->\n <div v-else-if=\"error\" class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card mint-experiment-popover__card--error\" role=\"alert\">\n <div class=\"mint-experiment-popover__card-icon mint-experiment-popover__card-icon--error\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z\"\n />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <div class=\"mint-experiment-popover__card-name\">Experiment unavailable</div>\n <div class=\"mint-experiment-popover__card-message\">{{ error }}</div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n\n <!-- No experiment selected -->\n <div v-else-if=\"!experimentName\" class=\"mint-experiment-popover__body\">\n <button type=\"button\" class=\"mint-experiment-popover__select-btn\" @click=\"handleSelect\">\n <svg width=\"14\" height=\"14\" 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 Select Experiment\n </button>\n </div>\n\n <!-- Experiment selected -->\n <div v-else class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\">\n <div class=\"mint-experiment-popover__card-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=\"1.75\"\n 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 />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span v-if=\"experimentCode\" class=\"mint-experiment-popover__card-code\">{{ experimentCode }}</span>\n <div class=\"mint-experiment-popover__card-name\">{{ experimentName }}</div>\n <div v-if=\"experimentStatus\" class=\"mint-experiment-popover__card-status\">\n {{ formatExperimentStatus(experimentStatus) }}\n </div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n </div>\n\n <!-- Save confirmation dialog -->\n <ConfirmDialog\n v-model=\"showConfirm\"\n :title=\"confirmTitle ?? 'Save to Experiment'\"\n :message=\"confirmMessage ?? `Save current data to ${experimentName}?`\"\n variant=\"info\"\n confirm-label=\"Save\"\n :loading=\"saveLoading\"\n @confirm=\"handleConfirmSave\"\n />\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-popover.css';\n</style>\n","<script setup lang=\"ts\">\n/** Floating popover showing the active experiment with save, detach, and select actions. */\nimport { ref, computed, watch, onUnmounted } from 'vue'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport { formatExperimentStatus } from '../composables/experiment-utils'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ninterface Props {\n experimentName?: string\n experimentCode?: string\n experimentStatus?: string\n /** An experiment is bound but the platform has not returned its record yet. */\n isResolving?: boolean\n /** Message explaining why the bound experiment could not be resolved. */\n error?: string\n showSave?: boolean\n showDetach?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n saveDisabledMessage?: string\n confirmSave?: boolean\n confirmTitle?: string\n confirmMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n isResolving: false,\n showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n confirmSave: true,\n})\n\n// An experiment is bound but has no name to show — either still resolving or\n// resolution failed. The id is never substituted: a name may only come from the\n// platform, so these states say what is happening instead of inventing a label.\nconst isUnresolved = computed(() => props.isResolving || !!props.error)\nconst hasExperiment = computed(() => !!props.experimentName || !!props.experimentCode)\n\nconst panelSubtitle = computed(() => {\n if (props.isResolving) return 'Loading experiment…'\n if (props.error) return 'Could not load this experiment'\n return hasExperiment.value ? 'Linked experiment context' : 'Link to an MINT experiment'\n})\n\nconst triggerLabel = computed(() => {\n if (props.isResolving) return 'Loading experiment'\n if (props.error) return 'Experiment unavailable'\n return props.experimentName || props.experimentCode || 'No experiment'\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, rootRef: popoverRef, close, toggle } = useDropdownState({\n closeOnEscape: false,\n})\nconst showSuccess = ref(false)\nconst showConfirm = ref(false)\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n if (props.confirmSave) {\n showConfirm.value = true\n } else {\n emit('save')\n }\n}\n\nfunction handleConfirmSave() {\n showConfirm.value = false\n emit('save')\n}\n\nfunction handleDetach() {\n emit('detach')\n close()\n}\n\nlet successTimer: ReturnType<typeof setTimeout> | null = null\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (successTimer) clearTimeout(successTimer)\n if (msg) {\n showSuccess.value = true\n successTimer = setTimeout(() => {\n showSuccess.value = false\n successTimer = null\n }, 3000)\n }\n})\n\nonUnmounted(() => {\n if (successTimer) clearTimeout(successTimer)\n})\n</script>\n\n<template>\n <div ref=\"popoverRef\" class=\"mint-experiment-popover\">\n <!-- Split trigger: experiment pill + inline save -->\n <div\n :class=\"[\n 'mint-experiment-popover__split',\n { 'mint-experiment-popover__split--with-save': showSave && experimentName },\n ]\"\n >\n <!-- Left: experiment trigger (opens popover). Prefer the human-readable\n name; the formal code, when present, stays in the detail panel. -->\n <button\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': !isUnresolved && !hasExperiment },\n { 'mint-experiment-popover__trigger--loading': isResolving },\n { 'mint-experiment-popover__trigger--error': !isResolving && !!error },\n ]\"\n :title=\"isUnresolved ? triggerLabel : experimentName || experimentCode || undefined\"\n :aria-label=\"isUnresolved ? triggerLabel : undefined\"\n :aria-busy=\"isResolving || undefined\"\n :aria-expanded=\"isOpen\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mint-experiment-popover__trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n 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 />\n </svg>\n <!-- Resolving and error come first: with an experiment bound but no name\n yet, \"No experiment\" would be a lie. -->\n <span v-if=\"isResolving\" class=\"mint-experiment-popover__trigger-skeleton\" aria-hidden=\"true\" />\n <span v-else-if=\"error\" class=\"mint-experiment-popover__trigger-text\">Experiment unavailable</span>\n <!-- Name preferred, code as fallback, \"No experiment\" as empty state -->\n <span v-else-if=\"experimentName\" class=\"mint-experiment-popover__trigger-text\">{{ experimentName }}</span>\n <span v-else-if=\"experimentCode\" class=\"mint-experiment-popover__trigger-code\">{{ experimentCode }}</span>\n <span v-else class=\"mint-experiment-popover__trigger-text\">No experiment</span>\n <!-- Chevron -->\n <svg class=\"mint-experiment-popover__trigger-chevron\" aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <!-- Right: inline save button (direct action) -->\n <button\n v-if=\"showSave && experimentName\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__save-trigger',\n { 'mint-experiment-popover__save-trigger--loading': saveLoading },\n { 'mint-experiment-popover__save-trigger--success': showSuccess },\n { 'mint-experiment-popover__save-trigger--disabled': saveDisabled && !showSuccess },\n ]\"\n :disabled=\"saveDisabled && !showSuccess\"\n :title=\"saveDisabled && saveDisabledMessage ? saveDisabledMessage : showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment'\"\n aria-label=\"Save to experiment\"\n @click.stop=\"handleSave\"\n >\n <!-- Loading spinner -->\n <span v-if=\"saveLoading\" class=\"mint-experiment-popover__spinner--inline\" />\n <!-- Success check -->\n <svg v-else-if=\"showSuccess\" class=\"mint-experiment-popover__save-trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2.5\" d=\"M5 13l4 4L19 7\" />\n </svg>\n <!-- Save icon -->\n <svg v-else class=\"mint-experiment-popover__save-trigger-icon\" aria-hidden=\"true\" 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 </button>\n </div>\n\n <!-- Popover panel -->\n <div v-if=\"isOpen\" class=\"mint-experiment-popover__panel\">\n <!-- Header -->\n <div class=\"mint-experiment-popover__header\">\n <div class=\"mint-experiment-popover__title\">Experiment</div>\n <div class=\"mint-experiment-popover__subtitle\">\n {{ panelSubtitle }}\n </div>\n </div>\n\n <!-- Bound, still resolving — placeholders stand in for the record the\n platform has not returned yet. -->\n <div v-if=\"isResolving\" class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\" aria-busy=\"true\">\n <div class=\"mint-experiment-popover__card-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=\"1.75\"\n 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 />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--code\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--name\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--status\" />\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n\n <!-- Bound, resolution failed — Detach stays reachable so the user is not\n stuck on an experiment the platform cannot describe. -->\n <div v-else-if=\"error\" class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card mint-experiment-popover__card--error\" role=\"alert\">\n <div class=\"mint-experiment-popover__card-icon mint-experiment-popover__card-icon--error\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z\"\n />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <div class=\"mint-experiment-popover__card-name\">Experiment unavailable</div>\n <div class=\"mint-experiment-popover__card-message\">{{ error }}</div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n\n <!-- No experiment selected -->\n <div v-else-if=\"!experimentName\" class=\"mint-experiment-popover__body\">\n <button type=\"button\" class=\"mint-experiment-popover__select-btn\" @click=\"handleSelect\">\n <svg width=\"14\" height=\"14\" 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 Select Experiment\n </button>\n </div>\n\n <!-- Experiment selected -->\n <div v-else class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\">\n <div class=\"mint-experiment-popover__card-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=\"1.75\"\n 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 />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span v-if=\"experimentCode\" class=\"mint-experiment-popover__card-code\">{{ experimentCode }}</span>\n <div class=\"mint-experiment-popover__card-name\">{{ experimentName }}</div>\n <div v-if=\"experimentStatus\" class=\"mint-experiment-popover__card-status\">\n {{ formatExperimentStatus(experimentStatus) }}\n </div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n </div>\n\n <!-- Save confirmation dialog -->\n <ConfirmDialog\n v-model=\"showConfirm\"\n :title=\"confirmTitle ?? 'Save to Experiment'\"\n :message=\"confirmMessage ?? `Save current data to ${experimentName}?`\"\n variant=\"info\"\n confirm-label=\"Save\"\n :loading=\"saveLoading\"\n @confirm=\"handleConfirmSave\"\n />\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-popover.css';\n</style>\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as __exportAll } from "./chunk-BoAXSpZd.js";
|
|
2
|
-
import { a as SORT_OPTIONS, c as formatExperimentDate, f as BaseModal_default, l as formatExperimentStatus, r as EXPERIMENT_STATUS_OPTIONS, t as DATE_PRESET_OPTIONS, u as getExperimentStatusVariant } from "./experiment-utils-
|
|
3
|
-
import { a as BaseSelect_default, i as BasePill_default, n as Skeleton_default, s as BaseInput_default, t as EmptyState_default } from "./EmptyState-
|
|
2
|
+
import { a as SORT_OPTIONS, c as formatExperimentDate, f as BaseModal_default, l as formatExperimentStatus, r as EXPERIMENT_STATUS_OPTIONS, t as DATE_PRESET_OPTIONS, u as getExperimentStatusVariant } from "./experiment-utils-CLnzPPq5.js";
|
|
3
|
+
import { a as BaseSelect_default, i as BasePill_default, n as Skeleton_default, s as BaseInput_default, t as EmptyState_default } from "./EmptyState-p90quq8Z.js";
|
|
4
4
|
import { t as useExperimentStore } from "./experiment-Y9Iz7KYi.js";
|
|
5
|
-
import { t as useExperimentSelector } from "./useExperimentSelector-
|
|
5
|
+
import { t as useExperimentSelector } from "./useExperimentSelector-D0pjj9WH.js";
|
|
6
6
|
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, nextTick, normalizeClass, openBlock, reactive, ref, renderList, toDisplayString, unref, vModelCheckbox, watch, withCtx, withDirectives } from "vue";
|
|
7
7
|
//#region src/components/ExperimentCodeBadge.vue?vue&type=script&setup=true&lang.ts
|
|
8
8
|
var _hoisted_1$1 = [
|
|
@@ -148,7 +148,14 @@ var ExperimentSelectorModal_vue_vue_type_script_setup_true_lang_default = /* @__
|
|
|
148
148
|
},
|
|
149
149
|
emits: ["update:modelValue"],
|
|
150
150
|
setup(__props, { emit: __emit }) {
|
|
151
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* Modal for searching and selecting an experiment from the platform list with filters and keyboard nav.
|
|
153
|
+
*
|
|
154
|
+
* @example Recommended
|
|
155
|
+
* ```vue
|
|
156
|
+
* <ExperimentSelectorModal v-model="open" :allowed-experiment-types="['mass-spectrometry']" />
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
152
159
|
const props = __props;
|
|
153
160
|
const emit = __emit;
|
|
154
161
|
const experimentStore = useExperimentStore();
|
|
@@ -497,4 +504,4 @@ var ExperimentSelectorModal_default = ExperimentSelectorModal_vue_vue_type_scrip
|
|
|
497
504
|
//#endregion
|
|
498
505
|
export { ExperimentSelectorModal_exports as n, ExperimentCodeBadge_default as r, ExperimentSelectorModal_default as t };
|
|
499
506
|
|
|
500
|
-
//# sourceMappingURL=ExperimentSelectorModal-
|
|
507
|
+
//# sourceMappingURL=ExperimentSelectorModal-B6P2msT4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExperimentSelectorModal-B6P2msT4.js","names":[],"sources":["../src/components/ExperimentCodeBadge.vue","../src/components/ExperimentCodeBadge.vue","../src/components/ExperimentSelectorModal.vue","../src/components/ExperimentSelectorModal.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Inline badge that displays an experiment code and copies it to the clipboard on click. */\nimport { ref } from 'vue'\n\ninterface Props {\n code: string\n size?: 'sm' | 'md' | 'lg'\n copyable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 'md',\n copyable: true,\n})\n\nconst emit = defineEmits<{\n copy: [code: string]\n}>()\n\nconst copied = ref(false)\nlet copyTimeout: ReturnType<typeof setTimeout> | null = null\n\nasync function handleCopy() {\n if (!props.copyable) return\n try {\n await navigator.clipboard.writeText(props.code)\n copied.value = true\n emit('copy', props.code)\n if (copyTimeout) clearTimeout(copyTimeout)\n copyTimeout = setTimeout(() => { copied.value = false }, 1500)\n } catch {\n // Clipboard API not available (e.g. insecure context)\n }\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n if (!props.copyable) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleCopy()\n }\n}\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-exp-code',\n `mint-exp-code--${size}`,\n { 'mint-exp-code--copyable': copyable, 'mint-exp-code--copied': copied },\n ]\"\n :role=\"copyable ? 'button' : undefined\"\n :tabindex=\"copyable ? 0 : undefined\"\n :title=\"copyable ? (copied ? 'Copied!' : 'Click to copy') : undefined\"\n @click=\"handleCopy\"\n @keydown=\"handleKeydown\"\n >\n {{ copied ? 'Copied!' : code }}\n </span>\n</template>\n\n<style>\n@import '../styles/components/experiment-code-badge.css';\n</style>\n","<script setup lang=\"ts\">\n/** Inline badge that displays an experiment code and copies it to the clipboard on click. */\nimport { ref } from 'vue'\n\ninterface Props {\n code: string\n size?: 'sm' | 'md' | 'lg'\n copyable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 'md',\n copyable: true,\n})\n\nconst emit = defineEmits<{\n copy: [code: string]\n}>()\n\nconst copied = ref(false)\nlet copyTimeout: ReturnType<typeof setTimeout> | null = null\n\nasync function handleCopy() {\n if (!props.copyable) return\n try {\n await navigator.clipboard.writeText(props.code)\n copied.value = true\n emit('copy', props.code)\n if (copyTimeout) clearTimeout(copyTimeout)\n copyTimeout = setTimeout(() => { copied.value = false }, 1500)\n } catch {\n // Clipboard API not available (e.g. insecure context)\n }\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n if (!props.copyable) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleCopy()\n }\n}\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-exp-code',\n `mint-exp-code--${size}`,\n { 'mint-exp-code--copyable': copyable, 'mint-exp-code--copied': copied },\n ]\"\n :role=\"copyable ? 'button' : undefined\"\n :tabindex=\"copyable ? 0 : undefined\"\n :title=\"copyable ? (copied ? 'Copied!' : 'Click to copy') : undefined\"\n @click=\"handleCopy\"\n @keydown=\"handleKeydown\"\n >\n {{ copied ? 'Copied!' : code }}\n </span>\n</template>\n\n<style>\n@import '../styles/components/experiment-code-badge.css';\n</style>\n","<script setup lang=\"ts\">\n/**\n * Modal for searching and selecting an experiment from the platform list with filters and keyboard nav.\n *\n * @example Recommended\n * ```vue\n * <ExperimentSelectorModal v-model=\"open\" :allowed-experiment-types=\"['mass-spectrometry']\" />\n * ```\n */\nimport { ref, reactive, computed, watch, nextTick } from 'vue'\nimport type { ModalSize, ExperimentSummary, ExperimentFilters } from '../types'\nimport { useExperimentSelector } from '../composables/useExperimentSelector'\nimport { useExperimentStore } from '../stores/experiment'\nimport {\n formatExperimentDate,\n formatExperimentStatus,\n getExperimentStatusVariant,\n EXPERIMENT_STATUS_OPTIONS,\n DATE_PRESET_OPTIONS,\n SORT_OPTIONS,\n} from '../composables/experiment-utils'\nimport BaseModal from './BaseModal.vue'\nimport BaseInput from './BaseInput.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport BasePill from './BasePill.vue'\nimport Skeleton from './Skeleton.vue'\nimport EmptyState from './EmptyState.vue'\nimport ExperimentCodeBadge from './ExperimentCodeBadge.vue'\n\ninterface Props {\n modelValue: boolean\n experimentType?: string\n allowedExperimentTypes?: string[] | null\n currentExperimentId?: number | null\n title?: string\n size?: ModalSize\n groupByProject?: boolean\n showFilters?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n currentExperimentId: null,\n title: 'Select Experiment',\n size: 'full',\n groupByProject: false,\n showFilters: false,\n})\n\n// Selection is committed to the shared experiment store rather than emitted.\n// A single owner of \"the current experiment\" is what stops callers holding —\n// or inventing — a record of their own.\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n}>()\n\nconst experimentStore = useExperimentStore()\n\nconst {\n experiments,\n filters,\n isLoading,\n error,\n sortKey,\n experimentTypes,\n projects,\n groupedByProject,\n fetch: fetchExperiments,\n fetchFilterOptions,\n} = useExperimentSelector({\n experimentType: props.experimentType,\n allowedExperimentTypes: props.allowedExperimentTypes,\n})\n\nconst activeIndex = ref(-1)\nconst listRef = ref<HTMLElement | null>(null)\nconst showAdvanced = ref(props.showFilters)\nconst groupToggle = ref(props.groupByProject)\n\n// Track whether any advanced filter is active (for badge dot)\nconst hasActiveAdvancedFilters = computed(() =>\n !!(filters.project || filters.experimentType || filters.datePreset || sortKey.value !== 'created_at:desc'),\n)\n\n// Build type filter options from fetched experiment types\nconst typeFilterOptions = computed(() => [\n { value: '', label: 'All types' },\n ...experimentTypes.value.map(t => ({ value: t.value, label: t.label })),\n])\nconst canChooseExperimentType = computed(() =>\n !props.experimentType && typeFilterOptions.value.length > 2,\n)\n\n// Build project filter options from fetched projects\nconst projectFilterOptions = computed(() => [\n { value: '', label: 'All projects' },\n ...projects.value,\n])\n\n// Flat list of experiments for keyboard navigation (works across groups too)\nconst flatExperiments = computed(() => {\n if (!groupToggle.value) return experiments.value\n return groupedByProject.value.flatMap(([, exps]) => exps)\n})\n\n// Stable ids so the search field (role=\"combobox\") can expose the currently\n// arrow-highlighted row to assistive tech via aria-activedescendant. This\n// surfaces the existing keyboard model to screen readers without changing it.\nconst LISTBOX_ID = 'mint-experiment-selector-listbox'\nfunction optionId(experiment: ExperimentSummary): string {\n return `mint-experiment-option-${experiment.id}`\n}\nconst activeDescendantId = computed(() => {\n const exp = flatExperiments.value[activeIndex.value]\n return activeIndex.value >= 0 && exp ? optionId(exp) : undefined\n})\nconst hasResults = computed(() => !isLoading.value && !error.value && experiments.value.length > 0)\n\nfunction setFilter<K extends keyof ExperimentFilters>(key: K, value: string | number) {\n ;(filters as Record<string, unknown>)[key] = String(value) || undefined\n}\n\nfunction handleSortChange(value: string | number) {\n sortKey.value = String(value) || 'created_at:desc'\n}\n\nfunction handleSelect(experiment: ExperimentSummary) {\n experimentStore.select(experiment)\n emit('update:modelValue', false)\n}\n\nfunction handleDeselect() {\n experimentStore.clear()\n emit('update:modelValue', false)\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n const list = flatExperiments.value\n if (!list.length) return\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n activeIndex.value = Math.min(activeIndex.value + 1, list.length - 1)\n scrollActiveIntoView()\n break\n case 'ArrowUp':\n event.preventDefault()\n activeIndex.value = Math.max(activeIndex.value - 1, 0)\n scrollActiveIntoView()\n break\n case 'Enter':\n event.preventDefault()\n if (activeIndex.value >= 0 && activeIndex.value < list.length) {\n handleSelect(list[activeIndex.value])\n }\n break\n }\n}\n\nfunction scrollActiveIntoView() {\n nextTick(() => {\n const row = listRef.value?.querySelector('.mint-experiment-selector__row--focused')\n row?.scrollIntoView({ block: 'nearest' })\n })\n}\n\n// Precomputed id → flat index for O(1) lookup in grouped mode\nconst flatIndexMap = computed(() => {\n const map = new Map<number, number>()\n flatExperiments.value.forEach((exp, i) => map.set(exp.id, i))\n return map\n})\n\nfunction getFlatIndex(experiment: ExperimentSummary): number {\n return flatIndexMap.value.get(experiment.id) ?? -1\n}\n\n// Track collapsed groups (reactive Set tracks .add/.delete/.has automatically)\nconst collapsedGroups = reactive(new Set<string>())\n\nfunction toggleGroup(groupName: string) {\n if (collapsedGroups.has(groupName)) {\n collapsedGroups.delete(groupName)\n } else {\n collapsedGroups.add(groupName)\n }\n}\n\n// Reset active index when experiments change\nwatch(experiments, () => { activeIndex.value = -1 })\n\n// Fetch on open\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (isOpen) {\n activeIndex.value = -1\n collapsedGroups.clear()\n fetchFilterOptions()\n fetchExperiments()\n }\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :size=\"size\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mint-experiment-selector\" @keydown=\"handleKeydown\">\n <!-- Filter bar row 1 -->\n <div class=\"mint-experiment-selector__filters-row\">\n <div class=\"mint-experiment-selector__search\">\n <BaseInput\n v-model=\"filters.search\"\n placeholder=\"Search experiments...\"\n size=\"sm\"\n type=\"search\"\n role=\"combobox\"\n aria-label=\"Search experiments\"\n :aria-expanded=\"hasResults\"\n :aria-controls=\"LISTBOX_ID\"\n :aria-activedescendant=\"activeDescendantId\"\n />\n </div>\n <div class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.status ?? ''\"\n :options=\"EXPERIMENT_STATUS_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('status', v)\"\n />\n </div>\n <div v-if=\"canChooseExperimentType\" class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.experimentType ?? ''\"\n :options=\"typeFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('experimentType', v)\"\n />\n </div>\n <button\n class=\"mint-experiment-selector__filters-toggle\"\n :class=\"{ 'mint-experiment-selector__filters-toggle--active': hasActiveAdvancedFilters }\"\n type=\"button\"\n :aria-expanded=\"showAdvanced\"\n @click=\"showAdvanced = !showAdvanced\"\n >\n <svg aria-hidden=\"true\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\" /><line x1=\"8\" y1=\"12\" x2=\"20\" y2=\"12\" /><line x1=\"12\" y1=\"18\" x2=\"20\" y2=\"18\" />\n <circle cx=\"6\" cy=\"12\" r=\"2\" /><circle cx=\"10\" cy=\"18\" r=\"2\" /><circle cx=\"6\" cy=\"6\" r=\"2\" />\n </svg>\n Filters\n <span v-if=\"hasActiveAdvancedFilters\" class=\"mint-experiment-selector__filters-dot\" />\n </button>\n </div>\n\n <!-- Filter bar row 2 (advanced, collapsible) -->\n <div v-if=\"showAdvanced\" class=\"mint-experiment-selector__filters-advanced\">\n <div v-if=\"projectFilterOptions.length > 1\" class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.project ?? ''\"\n :options=\"projectFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('project', v)\"\n />\n </div>\n <div class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.datePreset ?? ''\"\n :options=\"DATE_PRESET_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('datePreset', v)\"\n />\n </div>\n <div class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"sortKey\"\n :options=\"SORT_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"handleSortChange\"\n />\n </div>\n <label class=\"mint-experiment-selector__group-toggle\">\n <input\n v-model=\"groupToggle\"\n type=\"checkbox\"\n class=\"mint-experiment-selector__group-checkbox\"\n />\n Group by project\n </label>\n </div>\n\n <!-- Loading skeleton -->\n <div v-if=\"isLoading\" class=\"mint-experiment-selector__skeleton\">\n <div v-for=\"n in 4\" :key=\"n\" class=\"mint-experiment-selector__skeleton-row\">\n <div class=\"mint-experiment-selector__skeleton-content\">\n <Skeleton :width=\"120 + n * 20\" height=\"14px\" />\n <Skeleton width=\"80px\" height=\"10px\" />\n </div>\n <Skeleton width=\"60px\" height=\"20px\" variant=\"rounded\" />\n </div>\n </div>\n\n <!-- Error -->\n <div v-else-if=\"error\" class=\"mint-experiment-selector__error\">\n {{ error }}\n </div>\n\n <!-- Empty -->\n <EmptyState\n v-else-if=\"experiments.length === 0\"\n title=\"No experiments found\"\n description=\"Try adjusting your search or filters.\"\n size=\"sm\"\n />\n\n <!-- Experiment list: grouped mode -->\n <div v-else-if=\"groupToggle\" :id=\"LISTBOX_ID\" ref=\"listRef\" class=\"mint-experiment-selector__list\" role=\"listbox\" aria-label=\"Experiments\">\n <template v-for=\"([groupName, groupExps]) in groupedByProject\" :key=\"groupName\">\n <button\n type=\"button\"\n class=\"mint-experiment-selector__group-header\"\n :aria-expanded=\"!collapsedGroups.has(groupName)\"\n @click=\"toggleGroup(groupName)\"\n >\n <svg\n class=\"mint-experiment-selector__group-chevron\"\n :class=\"{ 'mint-experiment-selector__group-chevron--collapsed': collapsedGroups.has(groupName) }\"\n aria-hidden=\"true\"\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n <span class=\"mint-experiment-selector__group-name\">{{ groupName }}</span>\n <span class=\"mint-experiment-selector__group-count\">{{ groupExps.length }}</span>\n </button>\n <template v-if=\"!collapsedGroups.has(groupName)\">\n <div\n v-for=\"exp in groupExps\"\n :id=\"optionId(exp)\"\n :key=\"exp.id\"\n class=\"mint-experiment-selector__row\"\n role=\"option\"\n :aria-selected=\"exp.id === currentExperimentId\"\n :class=\"{\n 'mint-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mint-experiment-selector__row--focused': getFlatIndex(exp) === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = getFlatIndex(exp)\"\n >\n <div class=\"mint-experiment-selector__row-content\">\n <div class=\"mint-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mint-experiment-selector__meta\">\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"getExperimentStatusVariant(exp.status)\" size=\"sm\">\n {{ formatExperimentStatus(exp.status) }}\n </BasePill>\n </div>\n </template>\n </template>\n </div>\n\n <!-- Experiment list: flat mode -->\n <div v-else :id=\"LISTBOX_ID\" ref=\"listRef\" class=\"mint-experiment-selector__list\" role=\"listbox\" aria-label=\"Experiments\">\n <div\n v-for=\"(exp, idx) in experiments\"\n :id=\"optionId(exp)\"\n :key=\"exp.id\"\n class=\"mint-experiment-selector__row\"\n role=\"option\"\n :aria-selected=\"exp.id === currentExperimentId\"\n :class=\"{\n 'mint-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mint-experiment-selector__row--focused': idx === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = idx\"\n >\n <div class=\"mint-experiment-selector__row-content\">\n <div class=\"mint-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mint-experiment-selector__meta\">\n <span v-if=\"exp.project_name || exp.project\">{{ exp.project_name || exp.project }}</span>\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"getExperimentStatusVariant(exp.status)\" size=\"sm\">\n {{ formatExperimentStatus(exp.status) }}\n </BasePill>\n </div>\n </div>\n\n <!-- Footer: clear selection -->\n <div v-if=\"currentExperimentId != null\" class=\"mint-experiment-selector__footer\">\n <button\n type=\"button\"\n class=\"mint-experiment-selector__clear-btn\"\n @click=\"handleDeselect\"\n >\n Clear selection\n </button>\n </div>\n </div>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/experiment-selector-modal.css';\n</style>\n","<script setup lang=\"ts\">\n/**\n * Modal for searching and selecting an experiment from the platform list with filters and keyboard nav.\n *\n * @example Recommended\n * ```vue\n * <ExperimentSelectorModal v-model=\"open\" :allowed-experiment-types=\"['mass-spectrometry']\" />\n * ```\n */\nimport { ref, reactive, computed, watch, nextTick } from 'vue'\nimport type { ModalSize, ExperimentSummary, ExperimentFilters } from '../types'\nimport { useExperimentSelector } from '../composables/useExperimentSelector'\nimport { useExperimentStore } from '../stores/experiment'\nimport {\n formatExperimentDate,\n formatExperimentStatus,\n getExperimentStatusVariant,\n EXPERIMENT_STATUS_OPTIONS,\n DATE_PRESET_OPTIONS,\n SORT_OPTIONS,\n} from '../composables/experiment-utils'\nimport BaseModal from './BaseModal.vue'\nimport BaseInput from './BaseInput.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport BasePill from './BasePill.vue'\nimport Skeleton from './Skeleton.vue'\nimport EmptyState from './EmptyState.vue'\nimport ExperimentCodeBadge from './ExperimentCodeBadge.vue'\n\ninterface Props {\n modelValue: boolean\n experimentType?: string\n allowedExperimentTypes?: string[] | null\n currentExperimentId?: number | null\n title?: string\n size?: ModalSize\n groupByProject?: boolean\n showFilters?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n currentExperimentId: null,\n title: 'Select Experiment',\n size: 'full',\n groupByProject: false,\n showFilters: false,\n})\n\n// Selection is committed to the shared experiment store rather than emitted.\n// A single owner of \"the current experiment\" is what stops callers holding —\n// or inventing — a record of their own.\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n}>()\n\nconst experimentStore = useExperimentStore()\n\nconst {\n experiments,\n filters,\n isLoading,\n error,\n sortKey,\n experimentTypes,\n projects,\n groupedByProject,\n fetch: fetchExperiments,\n fetchFilterOptions,\n} = useExperimentSelector({\n experimentType: props.experimentType,\n allowedExperimentTypes: props.allowedExperimentTypes,\n})\n\nconst activeIndex = ref(-1)\nconst listRef = ref<HTMLElement | null>(null)\nconst showAdvanced = ref(props.showFilters)\nconst groupToggle = ref(props.groupByProject)\n\n// Track whether any advanced filter is active (for badge dot)\nconst hasActiveAdvancedFilters = computed(() =>\n !!(filters.project || filters.experimentType || filters.datePreset || sortKey.value !== 'created_at:desc'),\n)\n\n// Build type filter options from fetched experiment types\nconst typeFilterOptions = computed(() => [\n { value: '', label: 'All types' },\n ...experimentTypes.value.map(t => ({ value: t.value, label: t.label })),\n])\nconst canChooseExperimentType = computed(() =>\n !props.experimentType && typeFilterOptions.value.length > 2,\n)\n\n// Build project filter options from fetched projects\nconst projectFilterOptions = computed(() => [\n { value: '', label: 'All projects' },\n ...projects.value,\n])\n\n// Flat list of experiments for keyboard navigation (works across groups too)\nconst flatExperiments = computed(() => {\n if (!groupToggle.value) return experiments.value\n return groupedByProject.value.flatMap(([, exps]) => exps)\n})\n\n// Stable ids so the search field (role=\"combobox\") can expose the currently\n// arrow-highlighted row to assistive tech via aria-activedescendant. This\n// surfaces the existing keyboard model to screen readers without changing it.\nconst LISTBOX_ID = 'mint-experiment-selector-listbox'\nfunction optionId(experiment: ExperimentSummary): string {\n return `mint-experiment-option-${experiment.id}`\n}\nconst activeDescendantId = computed(() => {\n const exp = flatExperiments.value[activeIndex.value]\n return activeIndex.value >= 0 && exp ? optionId(exp) : undefined\n})\nconst hasResults = computed(() => !isLoading.value && !error.value && experiments.value.length > 0)\n\nfunction setFilter<K extends keyof ExperimentFilters>(key: K, value: string | number) {\n ;(filters as Record<string, unknown>)[key] = String(value) || undefined\n}\n\nfunction handleSortChange(value: string | number) {\n sortKey.value = String(value) || 'created_at:desc'\n}\n\nfunction handleSelect(experiment: ExperimentSummary) {\n experimentStore.select(experiment)\n emit('update:modelValue', false)\n}\n\nfunction handleDeselect() {\n experimentStore.clear()\n emit('update:modelValue', false)\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n const list = flatExperiments.value\n if (!list.length) return\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n activeIndex.value = Math.min(activeIndex.value + 1, list.length - 1)\n scrollActiveIntoView()\n break\n case 'ArrowUp':\n event.preventDefault()\n activeIndex.value = Math.max(activeIndex.value - 1, 0)\n scrollActiveIntoView()\n break\n case 'Enter':\n event.preventDefault()\n if (activeIndex.value >= 0 && activeIndex.value < list.length) {\n handleSelect(list[activeIndex.value])\n }\n break\n }\n}\n\nfunction scrollActiveIntoView() {\n nextTick(() => {\n const row = listRef.value?.querySelector('.mint-experiment-selector__row--focused')\n row?.scrollIntoView({ block: 'nearest' })\n })\n}\n\n// Precomputed id → flat index for O(1) lookup in grouped mode\nconst flatIndexMap = computed(() => {\n const map = new Map<number, number>()\n flatExperiments.value.forEach((exp, i) => map.set(exp.id, i))\n return map\n})\n\nfunction getFlatIndex(experiment: ExperimentSummary): number {\n return flatIndexMap.value.get(experiment.id) ?? -1\n}\n\n// Track collapsed groups (reactive Set tracks .add/.delete/.has automatically)\nconst collapsedGroups = reactive(new Set<string>())\n\nfunction toggleGroup(groupName: string) {\n if (collapsedGroups.has(groupName)) {\n collapsedGroups.delete(groupName)\n } else {\n collapsedGroups.add(groupName)\n }\n}\n\n// Reset active index when experiments change\nwatch(experiments, () => { activeIndex.value = -1 })\n\n// Fetch on open\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (isOpen) {\n activeIndex.value = -1\n collapsedGroups.clear()\n fetchFilterOptions()\n fetchExperiments()\n }\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :size=\"size\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mint-experiment-selector\" @keydown=\"handleKeydown\">\n <!-- Filter bar row 1 -->\n <div class=\"mint-experiment-selector__filters-row\">\n <div class=\"mint-experiment-selector__search\">\n <BaseInput\n v-model=\"filters.search\"\n placeholder=\"Search experiments...\"\n size=\"sm\"\n type=\"search\"\n role=\"combobox\"\n aria-label=\"Search experiments\"\n :aria-expanded=\"hasResults\"\n :aria-controls=\"LISTBOX_ID\"\n :aria-activedescendant=\"activeDescendantId\"\n />\n </div>\n <div class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.status ?? ''\"\n :options=\"EXPERIMENT_STATUS_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('status', v)\"\n />\n </div>\n <div v-if=\"canChooseExperimentType\" class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.experimentType ?? ''\"\n :options=\"typeFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('experimentType', v)\"\n />\n </div>\n <button\n class=\"mint-experiment-selector__filters-toggle\"\n :class=\"{ 'mint-experiment-selector__filters-toggle--active': hasActiveAdvancedFilters }\"\n type=\"button\"\n :aria-expanded=\"showAdvanced\"\n @click=\"showAdvanced = !showAdvanced\"\n >\n <svg aria-hidden=\"true\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\" /><line x1=\"8\" y1=\"12\" x2=\"20\" y2=\"12\" /><line x1=\"12\" y1=\"18\" x2=\"20\" y2=\"18\" />\n <circle cx=\"6\" cy=\"12\" r=\"2\" /><circle cx=\"10\" cy=\"18\" r=\"2\" /><circle cx=\"6\" cy=\"6\" r=\"2\" />\n </svg>\n Filters\n <span v-if=\"hasActiveAdvancedFilters\" class=\"mint-experiment-selector__filters-dot\" />\n </button>\n </div>\n\n <!-- Filter bar row 2 (advanced, collapsible) -->\n <div v-if=\"showAdvanced\" class=\"mint-experiment-selector__filters-advanced\">\n <div v-if=\"projectFilterOptions.length > 1\" class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.project ?? ''\"\n :options=\"projectFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('project', v)\"\n />\n </div>\n <div class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.datePreset ?? ''\"\n :options=\"DATE_PRESET_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('datePreset', v)\"\n />\n </div>\n <div class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"sortKey\"\n :options=\"SORT_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"handleSortChange\"\n />\n </div>\n <label class=\"mint-experiment-selector__group-toggle\">\n <input\n v-model=\"groupToggle\"\n type=\"checkbox\"\n class=\"mint-experiment-selector__group-checkbox\"\n />\n Group by project\n </label>\n </div>\n\n <!-- Loading skeleton -->\n <div v-if=\"isLoading\" class=\"mint-experiment-selector__skeleton\">\n <div v-for=\"n in 4\" :key=\"n\" class=\"mint-experiment-selector__skeleton-row\">\n <div class=\"mint-experiment-selector__skeleton-content\">\n <Skeleton :width=\"120 + n * 20\" height=\"14px\" />\n <Skeleton width=\"80px\" height=\"10px\" />\n </div>\n <Skeleton width=\"60px\" height=\"20px\" variant=\"rounded\" />\n </div>\n </div>\n\n <!-- Error -->\n <div v-else-if=\"error\" class=\"mint-experiment-selector__error\">\n {{ error }}\n </div>\n\n <!-- Empty -->\n <EmptyState\n v-else-if=\"experiments.length === 0\"\n title=\"No experiments found\"\n description=\"Try adjusting your search or filters.\"\n size=\"sm\"\n />\n\n <!-- Experiment list: grouped mode -->\n <div v-else-if=\"groupToggle\" :id=\"LISTBOX_ID\" ref=\"listRef\" class=\"mint-experiment-selector__list\" role=\"listbox\" aria-label=\"Experiments\">\n <template v-for=\"([groupName, groupExps]) in groupedByProject\" :key=\"groupName\">\n <button\n type=\"button\"\n class=\"mint-experiment-selector__group-header\"\n :aria-expanded=\"!collapsedGroups.has(groupName)\"\n @click=\"toggleGroup(groupName)\"\n >\n <svg\n class=\"mint-experiment-selector__group-chevron\"\n :class=\"{ 'mint-experiment-selector__group-chevron--collapsed': collapsedGroups.has(groupName) }\"\n aria-hidden=\"true\"\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n <span class=\"mint-experiment-selector__group-name\">{{ groupName }}</span>\n <span class=\"mint-experiment-selector__group-count\">{{ groupExps.length }}</span>\n </button>\n <template v-if=\"!collapsedGroups.has(groupName)\">\n <div\n v-for=\"exp in groupExps\"\n :id=\"optionId(exp)\"\n :key=\"exp.id\"\n class=\"mint-experiment-selector__row\"\n role=\"option\"\n :aria-selected=\"exp.id === currentExperimentId\"\n :class=\"{\n 'mint-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mint-experiment-selector__row--focused': getFlatIndex(exp) === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = getFlatIndex(exp)\"\n >\n <div class=\"mint-experiment-selector__row-content\">\n <div class=\"mint-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mint-experiment-selector__meta\">\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"getExperimentStatusVariant(exp.status)\" size=\"sm\">\n {{ formatExperimentStatus(exp.status) }}\n </BasePill>\n </div>\n </template>\n </template>\n </div>\n\n <!-- Experiment list: flat mode -->\n <div v-else :id=\"LISTBOX_ID\" ref=\"listRef\" class=\"mint-experiment-selector__list\" role=\"listbox\" aria-label=\"Experiments\">\n <div\n v-for=\"(exp, idx) in experiments\"\n :id=\"optionId(exp)\"\n :key=\"exp.id\"\n class=\"mint-experiment-selector__row\"\n role=\"option\"\n :aria-selected=\"exp.id === currentExperimentId\"\n :class=\"{\n 'mint-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mint-experiment-selector__row--focused': idx === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = idx\"\n >\n <div class=\"mint-experiment-selector__row-content\">\n <div class=\"mint-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mint-experiment-selector__meta\">\n <span v-if=\"exp.project_name || exp.project\">{{ exp.project_name || exp.project }}</span>\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"getExperimentStatusVariant(exp.status)\" size=\"sm\">\n {{ formatExperimentStatus(exp.status) }}\n </BasePill>\n </div>\n </div>\n\n <!-- Footer: clear selection -->\n <div v-if=\"currentExperimentId != null\" class=\"mint-experiment-selector__footer\">\n <button\n type=\"button\"\n class=\"mint-experiment-selector__clear-btn\"\n @click=\"handleDeselect\"\n >\n Clear selection\n </button>\n </div>\n </div>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/experiment-selector-modal.css';\n</style>\n"],"mappings":"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"}
|