@morscherlab/mint-sdk 1.5.0-beta.6 → 1.5.0-beta.8

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.
Files changed (106) hide show
  1. package/dist/{AlertBox-CexONPhA.js → AlertBox-Cai0w7gF.js} +3 -3
  2. package/dist/{AlertBox-CexONPhA.js.map → AlertBox-Cai0w7gF.js.map} +1 -1
  3. package/dist/{AppSidebar-FxlF5_dI.js → AppSidebar-C1DT4nsZ.js} +351 -90
  4. package/dist/AppSidebar-C1DT4nsZ.js.map +1 -0
  5. package/dist/{ConfirmDialog-D9QXFpsy.js → ConfirmDialog-BjFsOpBe.js} +2 -2
  6. package/dist/{ConfirmDialog-D9QXFpsy.js.map → ConfirmDialog-BjFsOpBe.js.map} +1 -1
  7. package/dist/{ExperimentPopover-Bbd6RaoH.js → ExperimentPopover-DYy_s0kI.js} +25 -37
  8. package/dist/ExperimentPopover-DYy_s0kI.js.map +1 -0
  9. package/dist/{ExperimentSelectorModal-CUkKmgfr.js → ExperimentSelectorModal-Bfh5bdkv.js} +5 -5
  10. package/dist/{ExperimentSelectorModal-CUkKmgfr.js.map → ExperimentSelectorModal-Bfh5bdkv.js.map} +1 -1
  11. package/dist/{SegmentedControl-C-sckoJZ.js → SegmentedControl-B8KgyOjd.js} +2 -2
  12. package/dist/{SegmentedControl-C-sckoJZ.js.map → SegmentedControl-B8KgyOjd.js.map} +1 -1
  13. package/dist/{SettingsModal-BIC0dAYc.js → SettingsModal-DPNblTe9.js} +7 -7
  14. package/dist/{SettingsModal-BIC0dAYc.js.map → SettingsModal-DPNblTe9.js.map} +1 -1
  15. package/dist/__tests__/components/AppTopBarCompact.test.d.ts +1 -0
  16. package/dist/__tests__/components/AppTopBarCompactReset.test.d.ts +1 -0
  17. package/dist/__tests__/components/AutoGroupModal.experimentLoad.test.d.ts +1 -0
  18. package/dist/components/AppTopBar.compact.d.ts +5 -0
  19. package/dist/components/AppTopBar.vue.d.ts +17 -17
  20. package/dist/components/PluginWorkspaceView.vue.d.ts +4 -0
  21. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -2
  22. package/dist/components/internal/LucideIconInternal.vue.d.ts +17 -0
  23. package/dist/composables/appLayoutSidebar.d.ts +9 -0
  24. package/dist/composables/useVersionedSave.d.ts +5 -2
  25. package/dist/{controlSchemaModel-C_XqpkgE.js → controlSchemaModel-BtFNZj8t.js} +2 -2
  26. package/dist/{controlSchemaModel-C_XqpkgE.js.map → controlSchemaModel-BtFNZj8t.js.map} +1 -1
  27. package/dist/{data-CfY7o8QC.js → data-CJztFns8.js} +31 -15
  28. package/dist/{data-CfY7o8QC.js.map → data-CJztFns8.js.map} +1 -1
  29. package/dist/data.js +8 -8
  30. package/dist/{experimentStatus-DCn2QKG7.js → experimentStatus-Deokk7Br.js} +2 -2
  31. package/dist/{experimentStatus-DCn2QKG7.js.map → experimentStatus-Deokk7Br.js.map} +1 -1
  32. package/dist/index.js +14 -14
  33. package/dist/{install-QF3Sh82O.js → install-9FkKs0iW.js} +14 -15
  34. package/dist/{install-QF3Sh82O.js.map → install-9FkKs0iW.js.map} +1 -1
  35. package/dist/install.js +1 -1
  36. package/dist/{lab-BcX5JBSY.js → lab-BYhb_cJI.js} +22 -14
  37. package/dist/lab-BYhb_cJI.js.map +1 -0
  38. package/dist/lab.js +4 -4
  39. package/dist/{records-BAymyHAn.js → records-GtiVMlTQ.js} +198 -62
  40. package/dist/records-GtiVMlTQ.js.map +1 -0
  41. package/dist/{runtime-DngeJtdq.js → runtime-DcMsvlaS.js} +29 -12
  42. package/dist/runtime-DcMsvlaS.js.map +1 -0
  43. package/dist/runtime.js +6 -6
  44. package/dist/{shell-CWZi97nW.js → shell-XZYkAZS_.js} +5 -5
  45. package/dist/{shell-CWZi97nW.js.map → shell-XZYkAZS_.js.map} +1 -1
  46. package/dist/shell.js +5 -5
  47. package/dist/styles.css +136 -12
  48. package/dist/types/components.d.ts +22 -2
  49. package/dist/types/index.d.ts +1 -1
  50. package/dist/{useDropdownState-uFIz6Yv8.js → useDropdownState-CcBzUYZ6.js} +14 -5
  51. package/dist/useDropdownState-CcBzUYZ6.js.map +1 -0
  52. package/dist/{useExperimentSamples-DEDAKa2E.js → useExperimentSamples-CpNXvpUn.js} +3 -3
  53. package/dist/{useExperimentSamples-DEDAKa2E.js.map → useExperimentSamples-CpNXvpUn.js.map} +1 -1
  54. package/dist/{useListReorder-W1bzK_bo.js → useListReorder-Bp4EAI2w.js} +3 -3
  55. package/dist/{useListReorder-W1bzK_bo.js.map → useListReorder-Bp4EAI2w.js.map} +1 -1
  56. package/dist/{useRackEditor-Bmtuo0aj.js → useRackEditor-i2bdPUFf.js} +8 -8
  57. package/dist/{useRackEditor-Bmtuo0aj.js.map → useRackEditor-i2bdPUFf.js.map} +1 -1
  58. package/dist/{useRealtime-jOetF2mM.js → useRealtime-BLQEjbuq.js} +3 -3
  59. package/dist/{useRealtime-jOetF2mM.js.map → useRealtime-BLQEjbuq.js.map} +1 -1
  60. package/dist/useRequest-CXyPw3eh.js +76 -0
  61. package/dist/useRequest-CXyPw3eh.js.map +1 -0
  62. package/dist/utils/deprecation.d.ts +2 -0
  63. package/dist/utils/lucideIcon.d.ts +4 -0
  64. package/package.json +1 -1
  65. package/src/__tests__/components/AppTopBar.test.ts +102 -20
  66. package/src/__tests__/components/AppTopBarCompact.test.ts +65 -0
  67. package/src/__tests__/components/AppTopBarCompactReset.test.ts +24 -0
  68. package/src/__tests__/components/AutoGroupModal.experimentLoad.test.ts +58 -0
  69. package/src/__tests__/components/BaseModal.test.ts +33 -2
  70. package/src/__tests__/components/ExperimentPopover.test.ts +32 -0
  71. package/src/__tests__/components/PluginWorkspaceView.test.ts +1 -1
  72. package/src/__tests__/composables/useJobCenter.test.ts +35 -0
  73. package/src/__tests__/composables/useVersionedSave.test.ts +71 -6
  74. package/src/__tests__/styles/menusShellRefresh.test.ts +5 -0
  75. package/src/components/AppLayout.story.vue +5 -5
  76. package/src/components/AppLayout.vue +13 -1
  77. package/src/components/AppTopBar.compact.ts +60 -0
  78. package/src/components/AppTopBar.story.vue +39 -14
  79. package/src/components/AppTopBar.vue +222 -89
  80. package/src/components/BaseModal.vue +16 -5
  81. package/src/components/ExperimentPopover.story.vue +2 -2
  82. package/src/components/ExperimentPopover.vue +6 -22
  83. package/src/components/GeneratedPluginView.vue +0 -1
  84. package/src/components/PluginWorkspaceView.vue +8 -0
  85. package/src/components/internal/AppTopBarPageSelectorInternal.vue +12 -0
  86. package/src/components/internal/AutoGroupModalInternal.vue +14 -3
  87. package/src/components/internal/LucideIconInternal.vue +23 -0
  88. package/src/composables/appLayoutSidebar.ts +11 -0
  89. package/src/composables/useJobCenter.ts +21 -1
  90. package/src/composables/usePluginWorkspace.ts +1 -1
  91. package/src/composables/useVersionedSave.ts +32 -7
  92. package/src/styles/components/app-layout.css +5 -0
  93. package/src/styles/components/app-top-bar.css +128 -8
  94. package/src/types/components.ts +23 -2
  95. package/src/types/index.ts +1 -0
  96. package/src/utils/deprecation.ts +8 -0
  97. package/src/utils/lucideIcon.ts +25 -0
  98. package/src/utils/lucideIconIndex.d.ts +2 -0
  99. package/dist/AppSidebar-FxlF5_dI.js.map +0 -1
  100. package/dist/ExperimentPopover-Bbd6RaoH.js.map +0 -1
  101. package/dist/lab-BcX5JBSY.js.map +0 -1
  102. package/dist/records-BAymyHAn.js.map +0 -1
  103. package/dist/runtime-DngeJtdq.js.map +0 -1
  104. package/dist/useDropdownState-uFIz6Yv8.js.map +0 -1
  105. package/dist/useRequest-D9-3BS-3.js +0 -215
  106. package/dist/useRequest-D9-3BS-3.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"ExperimentPopover-Bbd6RaoH.js","names":[],"sources":["../src/components/ExperimentPopover.vue","../src/components/ExperimentPopover.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Top-bar control for the attached experiment: a trigger joined to an optional Save button, with a summary panel (change, open, detach). */\nimport { ref, computed, watch, nextTick, onUnmounted, useTemplateRef } from 'vue'\nimport { ArrowLeftRight, Check, ChevronDown, CircleAlert, CloudUpload, ExternalLink, Link, LoaderCircle, Unlink } from 'lucide-vue-next'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport { format as formatStatus } from '../utils/experimentStatus'\nimport { formatRelativeTime } from '../utils/time'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ninterface Props {\n experimentName?: string\n experimentCode?: string\n experimentStatus?: string\n /** Platform id of the bound experiment; enables \"Open in MINT\". */\n experimentId?: number | null\n experimentProject?: string\n experimentOwner?: string\n /** ISO timestamp of the last update, shown as relative time. */\n experimentUpdatedAt?: 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 saveLabel?: string\n /** Unsaved changes: marks the Save button with a dot. */\n dirty?: boolean\n confirmSave?: boolean\n confirmTitle?: string\n confirmMessage?: string\n /** Empty-state trigger label. */\n placeholder?: string\n /** The trigger name truncates at this width (px); its title shows code and name in full. */\n maxNameWidth?: number\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n experimentId: null,\n isResolving: false,\n showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n saveLabel: 'Save',\n dirty: false,\n confirmSave: true,\n placeholder: 'Attach experiment',\n maxNameWidth: 200,\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)\nconst isEmpty = computed(() => !isUnresolved.value && !hasExperiment.value)\nconst hasSave = computed(() => props.showSave && !isUnresolved.value && hasExperiment.value)\n\nconst triggerLabel = computed(() => {\n if (props.isResolving) return 'Loading experiment'\n if (props.error) return 'Experiment unavailable'\n return [props.experimentCode, props.experimentName].filter(Boolean).join(' · ') || props.placeholder\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, close, toggle } = useDropdownState({\n rootRef: useTemplateRef<HTMLElement>('popover'),\n triggerRef: useTemplateRef<HTMLElement>('trigger'),\n closeOnEscape: false,\n})\nconst showSuccess = ref(false)\nconst showConfirm = ref(false)\n\n// The panel hangs from the group's left edge; when that would cross the\n// viewport's right edge (a control at the end of the top bar) it hangs from the right.\nconst panel = useTemplateRef<HTMLElement>('panel')\nconst panelAlignEnd = ref(false)\nwatch(isOpen, async (open) => {\n if (!open) return\n panelAlignEnd.value = false\n await nextTick()\n const right = panel.value?.getBoundingClientRect().right ?? 0\n panelAlignEnd.value = right > document.documentElement.clientWidth\n})\n\nconst saveTitle = computed(() => {\n if (props.saveDisabled && props.saveDisabledMessage) return props.saveDisabledMessage\n if (showSuccess.value && props.saveSuccessMessage) return props.saveSuccessMessage\n return `${props.saveLabel} to experiment`\n})\n\n// A function, not a computed: \"Updated\" is relative to now, so it is\n// re-evaluated each time the panel renders.\nfunction metaRows(): [string, string][] {\n return ([\n ['Project', props.experimentProject],\n ['Status', props.experimentStatus && formatStatus(props.experimentStatus)],\n ['Owner', props.experimentOwner],\n ['Updated', props.experimentUpdatedAt && formatRelativeTime(props.experimentUpdatedAt)],\n ] as [string, string | undefined][]).filter((row): row is [string, string] => !!row[1])\n}\n\nfunction handleTriggerClick() {\n if (isEmpty.value) {\n emit('select')\n return\n }\n toggle()\n}\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n close()\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=\"popover\" :class=\"['mint-experiment-popover', { 'mint-experiment-popover--save': hasSave }]\">\n <!-- Trigger: code + name when attached, opens the panel; the empty state selects directly. -->\n <button\n ref=\"trigger\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': isEmpty },\n { 'mint-experiment-popover__trigger--loading': isResolving },\n { 'mint-experiment-popover__trigger--error': !isResolving && !!error },\n ]\"\n :title=\"triggerLabel\"\n :aria-label=\"isUnresolved ? triggerLabel : undefined\"\n :aria-busy=\"isResolving || undefined\"\n :aria-expanded=\"isEmpty ? undefined : isOpen\"\n @click.stop=\"handleTriggerClick\"\n >\n <!-- Resolving and error come first: with an experiment bound but no name\n yet, the empty placeholder would be a lie. -->\n <span v-if=\"isResolving\" class=\"mint-experiment-popover__trigger-skeleton\" aria-hidden=\"true\" />\n <template v-else-if=\"error\">\n <CircleAlert class=\"mint-experiment-popover__trigger-icon\" :size=\"14\" aria-hidden=\"true\" />\n <span class=\"mint-experiment-popover__trigger-text\">Experiment unavailable</span>\n </template>\n <template v-else-if=\"hasExperiment\">\n <span v-if=\"experimentCode\" class=\"mint-exp-code mint-exp-code--sm\">{{ experimentCode }}</span>\n <span\n v-if=\"experimentName\"\n class=\"mint-experiment-popover__trigger-text\"\n :style=\"{ maxWidth: `${maxNameWidth}px` }\"\n >{{ experimentName }}</span>\n </template>\n <template v-else>\n <Link class=\"mint-experiment-popover__trigger-icon\" :size=\"14\" aria-hidden=\"true\" />\n <span class=\"mint-experiment-popover__trigger-text\">{{ placeholder }}</span>\n </template>\n <ChevronDown v-if=\"!isEmpty\" class=\"mint-experiment-popover__trigger-chevron\" :size=\"13\" aria-hidden=\"true\" />\n </button>\n\n <!-- Save: joined to the trigger at top-bar level, never inside the panel. -->\n <button\n v-if=\"hasSave\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__save',\n { 'mint-experiment-popover__save--loading': saveLoading },\n { 'mint-experiment-popover__save--success': showSuccess },\n { 'mint-experiment-popover__save--disabled': saveDisabled && !showSuccess },\n ]\"\n :disabled=\"saveLoading || (saveDisabled && !showSuccess)\"\n :title=\"saveTitle\"\n :aria-label=\"dirty ? `${saveTitle} (unsaved changes)` : saveTitle\"\n @click.stop=\"handleSave\"\n >\n <LoaderCircle v-if=\"saveLoading\" class=\"mint-experiment-popover__save-spinner\" :size=\"14\" aria-hidden=\"true\" />\n <Check v-else-if=\"showSuccess\" :size=\"14\" aria-hidden=\"true\" />\n <CloudUpload v-else :size=\"14\" aria-hidden=\"true\" />\n <span>{{ saveLoading ? 'Saving…' : saveLabel }}</span>\n <span v-if=\"dirty && !saveLoading\" class=\"mint-experiment-popover__dirty\" aria-hidden=\"true\" />\n </button>\n\n <!-- Summary panel: header, meta, then menu rows. No buttons inside. -->\n <div\n v-if=\"isOpen\"\n ref=\"panel\"\n :class=\"['mint-experiment-popover__panel', { 'mint-experiment-popover__panel--end': panelAlignEnd }]\"\n >\n <!-- Bound, still resolving — placeholders stand in for the record. -->\n <div v-if=\"isResolving\" class=\"mint-experiment-popover__head\" aria-busy=\"true\">\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 </div>\n\n <!-- Bound, resolution failed — Detach stays reachable below. -->\n <div v-else-if=\"error\" class=\"mint-experiment-popover__head mint-experiment-popover__head--error\" role=\"alert\">\n <b class=\"mint-experiment-popover__name\">\n <CircleAlert :size=\"14\" aria-hidden=\"true\" />\n Experiment unavailable\n </b>\n <span class=\"mint-experiment-popover__message\">{{ error }}</span>\n </div>\n\n <template v-else>\n <div class=\"mint-experiment-popover__head\">\n <span v-if=\"experimentCode\" class=\"mint-exp-code mint-exp-code--sm\">{{ experimentCode }}</span>\n <b v-if=\"experimentName\" class=\"mint-experiment-popover__name\">{{ experimentName }}</b>\n </div>\n <dl v-if=\"metaRows().length\" class=\"mint-experiment-popover__meta\">\n <template v-for=\"[label, value] in metaRows()\" :key=\"label\">\n <dt>{{ label }}</dt>\n <dd>{{ value }}</dd>\n </template>\n </dl>\n </template>\n\n <div class=\"mint-experiment-popover__separator\" role=\"separator\" />\n\n <button type=\"button\" class=\"mint-experiment-popover__option\" @click=\"handleSelect\">\n <ArrowLeftRight class=\"mint-experiment-popover__option-icon\" :size=\"14\" aria-hidden=\"true\" />\n Change experiment…\n </button>\n <a\n v-if=\"experimentId != null\"\n class=\"mint-experiment-popover__option\"\n :href=\"`/experiments/${experimentId}`\"\n target=\"_blank\"\n rel=\"noopener\"\n @click=\"close()\"\n >\n <ExternalLink class=\"mint-experiment-popover__option-icon\" :size=\"14\" aria-hidden=\"true\" />\n Open in MINT\n </a>\n <button\n v-if=\"showDetach\"\n type=\"button\"\n class=\"mint-experiment-popover__option mint-experiment-popover__option--danger\"\n @click=\"handleDetach\"\n >\n <Unlink class=\"mint-experiment-popover__option-icon\" :size=\"14\" aria-hidden=\"true\" />\n Detach\n </button>\n </div>\n\n <!-- Save confirmation dialog -->\n <ConfirmDialog\n v-model:open=\"showConfirm\"\n :title=\"confirmTitle ?? 'Save to Experiment'\"\n :message=\"confirmMessage ?? `Save current data to ${experimentName ?? experimentCode}?`\"\n tone=\"info\"\n :confirm-label=\"saveLabel\"\n :loading=\"saveLoading\"\n @confirm=\"handleConfirmSave\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Top-bar control for the attached experiment: a trigger joined to an optional Save button, with a summary panel (change, open, detach). */\nimport { ref, computed, watch, nextTick, onUnmounted, useTemplateRef } from 'vue'\nimport { ArrowLeftRight, Check, ChevronDown, CircleAlert, CloudUpload, ExternalLink, Link, LoaderCircle, Unlink } from 'lucide-vue-next'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport { format as formatStatus } from '../utils/experimentStatus'\nimport { formatRelativeTime } from '../utils/time'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ninterface Props {\n experimentName?: string\n experimentCode?: string\n experimentStatus?: string\n /** Platform id of the bound experiment; enables \"Open in MINT\". */\n experimentId?: number | null\n experimentProject?: string\n experimentOwner?: string\n /** ISO timestamp of the last update, shown as relative time. */\n experimentUpdatedAt?: 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 saveLabel?: string\n /** Unsaved changes: marks the Save button with a dot. */\n dirty?: boolean\n confirmSave?: boolean\n confirmTitle?: string\n confirmMessage?: string\n /** Empty-state trigger label. */\n placeholder?: string\n /** The trigger name truncates at this width (px); its title shows code and name in full. */\n maxNameWidth?: number\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n experimentId: null,\n isResolving: false,\n showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n saveLabel: 'Save',\n dirty: false,\n confirmSave: true,\n placeholder: 'Attach experiment',\n maxNameWidth: 200,\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)\nconst isEmpty = computed(() => !isUnresolved.value && !hasExperiment.value)\nconst hasSave = computed(() => props.showSave && !isUnresolved.value && hasExperiment.value)\n\nconst triggerLabel = computed(() => {\n if (props.isResolving) return 'Loading experiment'\n if (props.error) return 'Experiment unavailable'\n return [props.experimentCode, props.experimentName].filter(Boolean).join(' · ') || props.placeholder\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, close, toggle } = useDropdownState({\n rootRef: useTemplateRef<HTMLElement>('popover'),\n triggerRef: useTemplateRef<HTMLElement>('trigger'),\n closeOnEscape: false,\n})\nconst showSuccess = ref(false)\nconst showConfirm = ref(false)\n\n// The panel hangs from the group's left edge; when that would cross the\n// viewport's right edge (a control at the end of the top bar) it hangs from the right.\nconst panel = useTemplateRef<HTMLElement>('panel')\nconst panelAlignEnd = ref(false)\nwatch(isOpen, async (open) => {\n if (!open) return\n panelAlignEnd.value = false\n await nextTick()\n const right = panel.value?.getBoundingClientRect().right ?? 0\n panelAlignEnd.value = right > document.documentElement.clientWidth\n})\n\nconst saveTitle = computed(() => {\n if (props.saveDisabled && props.saveDisabledMessage) return props.saveDisabledMessage\n if (showSuccess.value && props.saveSuccessMessage) return props.saveSuccessMessage\n return `${props.saveLabel} to experiment`\n})\n\n// A function, not a computed: \"Updated\" is relative to now, so it is\n// re-evaluated each time the panel renders.\nfunction metaRows(): [string, string][] {\n return ([\n ['Project', props.experimentProject],\n ['Status', props.experimentStatus && formatStatus(props.experimentStatus)],\n ['Owner', props.experimentOwner],\n ['Updated', props.experimentUpdatedAt && formatRelativeTime(props.experimentUpdatedAt)],\n ] as [string, string | undefined][]).filter((row): row is [string, string] => !!row[1])\n}\n\nfunction handleTriggerClick() {\n if (isEmpty.value) {\n emit('select')\n return\n }\n toggle()\n}\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n close()\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=\"popover\" :class=\"['mint-experiment-popover', { 'mint-experiment-popover--save': hasSave }]\">\n <!-- Trigger: code + name when attached, opens the panel; the empty state selects directly. -->\n <button\n ref=\"trigger\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': isEmpty },\n { 'mint-experiment-popover__trigger--loading': isResolving },\n { 'mint-experiment-popover__trigger--error': !isResolving && !!error },\n ]\"\n :title=\"triggerLabel\"\n :aria-label=\"isUnresolved ? triggerLabel : undefined\"\n :aria-busy=\"isResolving || undefined\"\n :aria-expanded=\"isEmpty ? undefined : isOpen\"\n @click.stop=\"handleTriggerClick\"\n >\n <!-- Resolving and error come first: with an experiment bound but no name\n yet, the empty placeholder would be a lie. -->\n <span v-if=\"isResolving\" class=\"mint-experiment-popover__trigger-skeleton\" aria-hidden=\"true\" />\n <template v-else-if=\"error\">\n <CircleAlert class=\"mint-experiment-popover__trigger-icon\" :size=\"14\" aria-hidden=\"true\" />\n <span class=\"mint-experiment-popover__trigger-text\">Experiment unavailable</span>\n </template>\n <template v-else-if=\"hasExperiment\">\n <span v-if=\"experimentCode\" class=\"mint-exp-code mint-exp-code--sm\">{{ experimentCode }}</span>\n <span\n v-if=\"experimentName\"\n class=\"mint-experiment-popover__trigger-text\"\n :style=\"{ maxWidth: `${maxNameWidth}px` }\"\n >{{ experimentName }}</span>\n </template>\n <template v-else>\n <Link class=\"mint-experiment-popover__trigger-icon\" :size=\"14\" aria-hidden=\"true\" />\n <span class=\"mint-experiment-popover__trigger-text\">{{ placeholder }}</span>\n </template>\n <ChevronDown v-if=\"!isEmpty\" class=\"mint-experiment-popover__trigger-chevron\" :size=\"13\" aria-hidden=\"true\" />\n </button>\n\n <!-- Save: joined to the trigger at top-bar level, never inside the panel. -->\n <button\n v-if=\"hasSave\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__save',\n { 'mint-experiment-popover__save--loading': saveLoading },\n { 'mint-experiment-popover__save--success': showSuccess },\n { 'mint-experiment-popover__save--disabled': saveDisabled && !showSuccess },\n ]\"\n :disabled=\"saveLoading || (saveDisabled && !showSuccess)\"\n :title=\"saveTitle\"\n :aria-label=\"dirty ? `${saveTitle} (unsaved changes)` : saveTitle\"\n @click.stop=\"handleSave\"\n >\n <LoaderCircle v-if=\"saveLoading\" class=\"mint-experiment-popover__save-spinner\" :size=\"14\" aria-hidden=\"true\" />\n <Check v-else-if=\"showSuccess\" :size=\"14\" aria-hidden=\"true\" />\n <CloudUpload v-else :size=\"14\" aria-hidden=\"true\" />\n <span>{{ saveLoading ? 'Saving…' : saveLabel }}</span>\n <span v-if=\"dirty && !saveLoading\" class=\"mint-experiment-popover__dirty\" aria-hidden=\"true\" />\n </button>\n\n <!-- Summary panel: header, meta, then menu rows. No buttons inside. -->\n <div\n v-if=\"isOpen\"\n ref=\"panel\"\n :class=\"['mint-experiment-popover__panel', { 'mint-experiment-popover__panel--end': panelAlignEnd }]\"\n >\n <!-- Bound, still resolving — placeholders stand in for the record. -->\n <div v-if=\"isResolving\" class=\"mint-experiment-popover__head\" aria-busy=\"true\">\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 </div>\n\n <!-- Bound, resolution failed — Detach stays reachable below. -->\n <div v-else-if=\"error\" class=\"mint-experiment-popover__head mint-experiment-popover__head--error\" role=\"alert\">\n <b class=\"mint-experiment-popover__name\">\n <CircleAlert :size=\"14\" aria-hidden=\"true\" />\n Experiment unavailable\n </b>\n <span class=\"mint-experiment-popover__message\">{{ error }}</span>\n </div>\n\n <template v-else>\n <div class=\"mint-experiment-popover__head\">\n <span v-if=\"experimentCode\" class=\"mint-exp-code mint-exp-code--sm\">{{ experimentCode }}</span>\n <b v-if=\"experimentName\" class=\"mint-experiment-popover__name\">{{ experimentName }}</b>\n </div>\n <dl v-if=\"metaRows().length\" class=\"mint-experiment-popover__meta\">\n <template v-for=\"[label, value] in metaRows()\" :key=\"label\">\n <dt>{{ label }}</dt>\n <dd>{{ value }}</dd>\n </template>\n </dl>\n </template>\n\n <div class=\"mint-experiment-popover__separator\" role=\"separator\" />\n\n <button type=\"button\" class=\"mint-experiment-popover__option\" @click=\"handleSelect\">\n <ArrowLeftRight class=\"mint-experiment-popover__option-icon\" :size=\"14\" aria-hidden=\"true\" />\n Change experiment…\n </button>\n <a\n v-if=\"experimentId != null\"\n class=\"mint-experiment-popover__option\"\n :href=\"`/experiments/${experimentId}`\"\n target=\"_blank\"\n rel=\"noopener\"\n @click=\"close()\"\n >\n <ExternalLink class=\"mint-experiment-popover__option-icon\" :size=\"14\" aria-hidden=\"true\" />\n Open in MINT\n </a>\n <button\n v-if=\"showDetach\"\n type=\"button\"\n class=\"mint-experiment-popover__option mint-experiment-popover__option--danger\"\n @click=\"handleDetach\"\n >\n <Unlink class=\"mint-experiment-popover__option-icon\" :size=\"14\" aria-hidden=\"true\" />\n Detach\n </button>\n </div>\n\n <!-- Save confirmation dialog -->\n <ConfirmDialog\n v-model:open=\"showConfirm\"\n :title=\"confirmTitle ?? 'Save to Experiment'\"\n :message=\"confirmMessage ?? `Save current data to ${experimentName ?? experimentCode}?`\"\n tone=\"info\"\n :confirm-label=\"saveLabel\"\n :loading=\"saveLoading\"\n @confirm=\"handleConfirmSave\"\n />\n </div>\n</template>\n"],"mappings":"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"}