@morscherlab/mld-sdk 0.9.0 → 0.9.2

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.
@@ -0,0 +1,22 @@
1
+ interface Props {
2
+ experimentName?: string;
3
+ experimentStatus?: string;
4
+ showSave?: boolean;
5
+ saveDisabled?: boolean;
6
+ saveLoading?: boolean;
7
+ saveSuccessMessage?: string;
8
+ }
9
+ declare const _default: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
10
+ select: () => any;
11
+ save: () => any;
12
+ }, string, import('vue').PublicProps, Readonly<Props> & Readonly<{
13
+ onSelect?: (() => any) | undefined;
14
+ onSave?: (() => any) | undefined;
15
+ }>, {
16
+ showSave: boolean;
17
+ saveDisabled: boolean;
18
+ saveLoading: boolean;
19
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
20
+ popoverRef: HTMLDivElement;
21
+ }, HTMLDivElement>;
22
+ export default _default;
@@ -0,0 +1,212 @@
1
+ import { defineComponent, ref, watch, onMounted, onUnmounted, openBlock, createElementBlock, createElementVNode, withModifiers, normalizeClass, toDisplayString, createTextVNode, createCommentVNode, Fragment } from "vue";
2
+ const _hoisted_1 = { class: "mld-experiment-popover__trigger-text" };
3
+ const _hoisted_2 = {
4
+ key: 0,
5
+ class: "mld-experiment-popover__panel"
6
+ };
7
+ const _hoisted_3 = {
8
+ key: 0,
9
+ class: "mld-experiment-popover__empty"
10
+ };
11
+ const _hoisted_4 = {
12
+ key: 1,
13
+ class: "mld-experiment-popover__card"
14
+ };
15
+ const _hoisted_5 = { class: "mld-experiment-popover__card-info" };
16
+ const _hoisted_6 = { class: "mld-experiment-popover__card-name" };
17
+ const _hoisted_7 = {
18
+ key: 0,
19
+ class: "mld-experiment-popover__card-status"
20
+ };
21
+ const _hoisted_8 = { class: "mld-experiment-popover__footer" };
22
+ const _hoisted_9 = ["disabled"];
23
+ const _hoisted_10 = {
24
+ key: 0,
25
+ class: "mld-experiment-popover__spinner"
26
+ };
27
+ const _hoisted_11 = {
28
+ key: 1,
29
+ class: "mld-experiment-popover__check-icon",
30
+ fill: "none",
31
+ stroke: "currentColor",
32
+ viewBox: "0 0 24 24"
33
+ };
34
+ const _sfc_main = /* @__PURE__ */ defineComponent({
35
+ __name: "ExperimentPopover",
36
+ props: {
37
+ experimentName: {},
38
+ experimentStatus: {},
39
+ showSave: { type: Boolean, default: false },
40
+ saveDisabled: { type: Boolean, default: false },
41
+ saveLoading: { type: Boolean, default: false },
42
+ saveSuccessMessage: {}
43
+ },
44
+ emits: ["select", "save"],
45
+ setup(__props, { emit: __emit }) {
46
+ const props = __props;
47
+ const emit = __emit;
48
+ const isOpen = ref(false);
49
+ const popoverRef = ref(null);
50
+ const showSuccess = ref(false);
51
+ function toggle() {
52
+ isOpen.value = !isOpen.value;
53
+ }
54
+ function close() {
55
+ isOpen.value = false;
56
+ }
57
+ function handleSelect() {
58
+ emit("select");
59
+ close();
60
+ }
61
+ function handleSave() {
62
+ if (props.saveDisabled || props.saveLoading) return;
63
+ emit("save");
64
+ }
65
+ function handleClickOutside(event) {
66
+ if (popoverRef.value && !popoverRef.value.contains(event.target)) {
67
+ close();
68
+ }
69
+ }
70
+ watch(() => props.saveSuccessMessage, (msg) => {
71
+ if (msg) {
72
+ showSuccess.value = true;
73
+ setTimeout(() => {
74
+ showSuccess.value = false;
75
+ }, 3e3);
76
+ }
77
+ });
78
+ onMounted(() => {
79
+ document.addEventListener("click", handleClickOutside);
80
+ });
81
+ onUnmounted(() => {
82
+ document.removeEventListener("click", handleClickOutside);
83
+ });
84
+ function formatStatus(status) {
85
+ return status.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase());
86
+ }
87
+ return (_ctx, _cache) => {
88
+ return openBlock(), createElementBlock("div", {
89
+ ref_key: "popoverRef",
90
+ ref: popoverRef,
91
+ class: "mld-experiment-popover"
92
+ }, [
93
+ createElementVNode("button", {
94
+ type: "button",
95
+ class: normalizeClass([
96
+ "mld-experiment-popover__trigger",
97
+ { "mld-experiment-popover__trigger--active": isOpen.value },
98
+ { "mld-experiment-popover__trigger--empty": !__props.experimentName }
99
+ ]),
100
+ onClick: withModifiers(toggle, ["stop"])
101
+ }, [
102
+ _cache[0] || (_cache[0] = createElementVNode("svg", {
103
+ class: "mld-experiment-popover__trigger-icon",
104
+ fill: "none",
105
+ stroke: "currentColor",
106
+ viewBox: "0 0 24 24"
107
+ }, [
108
+ createElementVNode("path", {
109
+ "stroke-linecap": "round",
110
+ "stroke-linejoin": "round",
111
+ "stroke-width": "1.75",
112
+ 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"
113
+ })
114
+ ], -1)),
115
+ createElementVNode("span", _hoisted_1, toDisplayString(__props.experimentName || "No experiment"), 1),
116
+ _cache[1] || (_cache[1] = createElementVNode("svg", {
117
+ class: "mld-experiment-popover__trigger-chevron",
118
+ viewBox: "0 0 24 24",
119
+ fill: "none",
120
+ stroke: "currentColor",
121
+ "stroke-width": "2",
122
+ "stroke-linecap": "round",
123
+ "stroke-linejoin": "round"
124
+ }, [
125
+ createElementVNode("path", { d: "m6 9 6 6 6-6" })
126
+ ], -1))
127
+ ], 2),
128
+ isOpen.value ? (openBlock(), createElementBlock("div", _hoisted_2, [
129
+ _cache[6] || (_cache[6] = createElementVNode("div", { class: "mld-experiment-popover__header" }, [
130
+ createElementVNode("div", { class: "mld-experiment-popover__title" }, "Experiment")
131
+ ], -1)),
132
+ !__props.experimentName ? (openBlock(), createElementBlock("div", _hoisted_3, [
133
+ createElementVNode("button", {
134
+ type: "button",
135
+ class: "mld-experiment-popover__select-btn",
136
+ onClick: handleSelect
137
+ }, [..._cache[2] || (_cache[2] = [
138
+ createElementVNode("svg", {
139
+ width: "14",
140
+ height: "14",
141
+ fill: "none",
142
+ stroke: "currentColor",
143
+ viewBox: "0 0 24 24"
144
+ }, [
145
+ createElementVNode("path", {
146
+ "stroke-linecap": "round",
147
+ "stroke-linejoin": "round",
148
+ "stroke-width": "2",
149
+ d: "M12 4v16m8-8H4"
150
+ })
151
+ ], -1),
152
+ createTextVNode(" Select Experiment ", -1)
153
+ ])])
154
+ ])) : (openBlock(), createElementBlock("div", _hoisted_4, [
155
+ _cache[3] || (_cache[3] = createElementVNode("div", { class: "mld-experiment-popover__card-icon" }, [
156
+ createElementVNode("svg", {
157
+ fill: "none",
158
+ stroke: "currentColor",
159
+ viewBox: "0 0 24 24"
160
+ }, [
161
+ createElementVNode("path", {
162
+ "stroke-linecap": "round",
163
+ "stroke-linejoin": "round",
164
+ "stroke-width": "1.75",
165
+ 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"
166
+ })
167
+ ])
168
+ ], -1)),
169
+ createElementVNode("div", _hoisted_5, [
170
+ createElementVNode("div", _hoisted_6, toDisplayString(__props.experimentName), 1),
171
+ __props.experimentStatus ? (openBlock(), createElementBlock("div", _hoisted_7, toDisplayString(formatStatus(__props.experimentStatus)), 1)) : createCommentVNode("", true)
172
+ ]),
173
+ createElementVNode("button", {
174
+ type: "button",
175
+ class: "mld-experiment-popover__change-btn",
176
+ onClick: handleSelect
177
+ }, " Change ")
178
+ ])),
179
+ __props.showSave ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
180
+ _cache[5] || (_cache[5] = createElementVNode("div", { class: "mld-experiment-popover__divider" }, null, -1)),
181
+ createElementVNode("div", _hoisted_8, [
182
+ createElementVNode("button", {
183
+ type: "button",
184
+ class: normalizeClass([
185
+ "mld-experiment-popover__save-btn",
186
+ { "mld-experiment-popover__save-btn--loading": __props.saveLoading },
187
+ { "mld-experiment-popover__save-btn--success": showSuccess.value }
188
+ ]),
189
+ disabled: __props.saveDisabled && !showSuccess.value,
190
+ onClick: handleSave
191
+ }, [
192
+ __props.saveLoading ? (openBlock(), createElementBlock("span", _hoisted_10)) : showSuccess.value ? (openBlock(), createElementBlock("svg", _hoisted_11, [..._cache[4] || (_cache[4] = [
193
+ createElementVNode("path", {
194
+ "stroke-linecap": "round",
195
+ "stroke-linejoin": "round",
196
+ "stroke-width": "2",
197
+ d: "M5 13l4 4L19 7"
198
+ }, null, -1)
199
+ ])])) : createCommentVNode("", true),
200
+ createElementVNode("span", null, toDisplayString(showSuccess.value && __props.saveSuccessMessage ? __props.saveSuccessMessage : "Save to Experiment"), 1)
201
+ ], 10, _hoisted_9)
202
+ ])
203
+ ], 64)) : createCommentVNode("", true)
204
+ ])) : createCommentVNode("", true)
205
+ ], 512);
206
+ };
207
+ }
208
+ });
209
+ export {
210
+ _sfc_main as default
211
+ };
212
+ //# sourceMappingURL=ExperimentPopover.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExperimentPopover.vue.js","sources":["../../src/components/ExperimentPopover.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch, onMounted, onUnmounted } from 'vue'\n\ninterface Props {\n experimentName?: string\n experimentStatus?: string\n showSave?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showSave: false,\n saveDisabled: false,\n saveLoading: false,\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n}>()\n\nconst isOpen = ref(false)\nconst popoverRef = ref<HTMLElement | null>(null)\nconst showSuccess = ref(false)\n\nfunction toggle() {\n isOpen.value = !isOpen.value\n}\n\nfunction close() {\n isOpen.value = false\n}\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n emit('save')\n}\n\nfunction handleClickOutside(event: MouseEvent) {\n if (popoverRef.value && !popoverRef.value.contains(event.target as Node)) {\n close()\n }\n}\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (msg) {\n showSuccess.value = true\n setTimeout(() => {\n showSuccess.value = false\n }, 3000)\n }\n})\n\nonMounted(() => {\n document.addEventListener('click', handleClickOutside)\n})\n\nonUnmounted(() => {\n document.removeEventListener('click', handleClickOutside)\n})\n\n// Format status for display (e.g., \"ready_to_extract\" -> \"Ready to extract\")\nfunction formatStatus(status: string): string {\n return status.replace(/_/g, ' ').replace(/^\\w/, c => c.toUpperCase())\n}\n</script>\n\n<template>\n <div ref=\"popoverRef\" class=\"mld-experiment-popover\">\n <!-- Trigger button -->\n <button\n type=\"button\"\n :class=\"[\n 'mld-experiment-popover__trigger',\n { 'mld-experiment-popover__trigger--active': isOpen },\n { 'mld-experiment-popover__trigger--empty': !experimentName },\n ]\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mld-experiment-popover__trigger-icon\" 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 <span class=\"mld-experiment-popover__trigger-text\">\n {{ experimentName || 'No experiment' }}\n </span>\n <!-- Chevron -->\n <svg class=\"mld-experiment-popover__trigger-chevron\" 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 <!-- Popover panel -->\n <div v-if=\"isOpen\" class=\"mld-experiment-popover__panel\">\n <!-- Header -->\n <div class=\"mld-experiment-popover__header\">\n <div class=\"mld-experiment-popover__title\">Experiment</div>\n </div>\n\n <!-- No experiment selected -->\n <div v-if=\"!experimentName\" class=\"mld-experiment-popover__empty\">\n <button type=\"button\" class=\"mld-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=\"mld-experiment-popover__card\">\n <div class=\"mld-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=\"mld-experiment-popover__card-info\">\n <div class=\"mld-experiment-popover__card-name\">{{ experimentName }}</div>\n <div v-if=\"experimentStatus\" class=\"mld-experiment-popover__card-status\">\n {{ formatStatus(experimentStatus) }}\n </div>\n </div>\n <button type=\"button\" class=\"mld-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n </div>\n\n <!-- Save section -->\n <template v-if=\"showSave\">\n <div class=\"mld-experiment-popover__divider\" />\n <div class=\"mld-experiment-popover__footer\">\n <button\n type=\"button\"\n :class=\"[\n 'mld-experiment-popover__save-btn',\n { 'mld-experiment-popover__save-btn--loading': saveLoading },\n { 'mld-experiment-popover__save-btn--success': showSuccess },\n ]\"\n :disabled=\"saveDisabled && !showSuccess\"\n @click=\"handleSave\"\n >\n <!-- Loading spinner -->\n <span v-if=\"saveLoading\" class=\"mld-experiment-popover__spinner\" />\n <!-- Success check -->\n <svg v-else-if=\"showSuccess\" class=\"mld-experiment-popover__check-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 13l4 4L19 7\" />\n </svg>\n <!-- Label -->\n <span>{{ showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment' }}</span>\n </button>\n </div>\n </template>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-popover.css';\n</style>\n"],"names":["_createElementBlock","_createElementVNode","_normalizeClass","_toDisplayString","_openBlock","_Fragment"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import _sfc_main from "./ExperimentPopover.vue.js";
2
+ /* empty css */
3
+ export {
4
+ _sfc_main as default
5
+ };
6
+ //# sourceMappingURL=ExperimentPopover.vue3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExperimentPopover.vue3.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
@@ -77,4 +77,5 @@ export { default as TimeRangeInput } from './TimeRangeInput.vue';
77
77
  export { default as ScheduleCalendar } from './ScheduleCalendar.vue';
78
78
  export { default as ResourceCard } from './ResourceCard.vue';
79
79
  export { default as ExperimentSelectorModal } from './ExperimentSelectorModal.vue';
80
+ export { default as ExperimentPopover } from './ExperimentPopover.vue';
80
81
  export { default as FitPanel } from './FitPanel.vue';
@@ -154,7 +154,9 @@ import { default as default79 } from "./ResourceCard.vue.js";
154
154
  /* empty css */
155
155
  import { default as default80 } from "./ExperimentSelectorModal.vue.js";
156
156
  /* empty css */
157
- import { default as default81 } from "./FitPanel.vue.js";
157
+ import { default as default81 } from "./ExperimentPopover.vue.js";
158
+ /* empty css */
159
+ import { default as default82 } from "./FitPanel.vue.js";
158
160
  /* empty css */
159
161
  export {
160
162
  default25 as AlertBox,
@@ -194,10 +196,11 @@ export {
194
196
  default41 as EmptyState,
195
197
  default75 as ExperimentCodeBadge,
196
198
  default74 as ExperimentDataViewer,
199
+ default81 as ExperimentPopover,
197
200
  default80 as ExperimentSelectorModal,
198
201
  default51 as ExperimentTimeline,
199
202
  default24 as FileUploader,
200
- default81 as FitPanel,
203
+ default82 as FitPanel,
201
204
  default72 as FormActions,
202
205
  default70 as FormBuilder,
203
206
  default19 as FormField,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -17,6 +17,9 @@ function useApi(options = {}) {
17
17
  const settingsStore = useSettingsStore();
18
18
  const authStore = useAuthStore();
19
19
  const apiClient = getApiClient();
20
+ if (!authStore.isInitialized) {
21
+ authStore.initialize();
22
+ }
20
23
  if (!interceptorAttached) {
21
24
  apiClient.interceptors.request.use((config) => {
22
25
  config.baseURL = options.baseUrl ?? settingsStore.getApiBaseUrl();
@@ -1 +1 @@
1
- {"version":3,"file":"useApi.js","sources":["../../src/composables/useApi.ts"],"sourcesContent":["import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'\nimport { useSettingsStore } from '../stores/settings'\nimport { useAuthStore } from '../stores/auth'\n\nlet apiClientInstance: AxiosInstance | null = null\nlet interceptorAttached = false\n\nfunction getApiClient(): AxiosInstance {\n if (!apiClientInstance) {\n apiClientInstance = axios.create({\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n }\n return apiClientInstance\n}\n\nexport interface ApiClientOptions {\n baseUrl?: string\n timeout?: number\n withAuth?: boolean\n}\n\nexport interface UseApiReturn {\n client: AxiosInstance\n get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n post: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n put: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n patch: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n upload: <T>(url: string, file: File, fieldName?: string, additionalData?: Record<string, unknown>) => Promise<T>\n download: (url: string, filename?: string) => Promise<string>\n buildUrl: (path: string) => string\n buildWsUrl: (path: string) => string\n}\n\nexport function useApi(options: ApiClientOptions = {}): UseApiReturn {\n const settingsStore = useSettingsStore()\n const authStore = useAuthStore()\n const apiClient = getApiClient()\n\n // Attach interceptor only once\n if (!interceptorAttached) {\n apiClient.interceptors.request.use((config) => {\n // Set base URL from settings or options\n config.baseURL = options.baseUrl ?? settingsStore.getApiBaseUrl()\n config.timeout = options.timeout ?? settingsStore.requestTimeout\n\n // Add Authorization header if token exists and auth is not explicitly disabled\n if (options.withAuth !== false && authStore.token) {\n config.headers.Authorization = `Bearer ${authStore.token}`\n }\n\n return config\n })\n interceptorAttached = true\n }\n\n // Generic request methods\n async function get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.get<T>(url, config)\n return response.data\n }\n\n async function post<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.post<T>(url, data, config)\n return response.data\n }\n\n async function put<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.put<T>(url, data, config)\n return response.data\n }\n\n async function patch<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.patch<T>(url, data, config)\n return response.data\n }\n\n async function del<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.delete<T>(url, config)\n return response.data\n }\n\n // File upload helper\n async function upload<T>(url: string, file: File, fieldName = 'file', additionalData?: Record<string, unknown>): Promise<T> {\n const formData = new FormData()\n formData.append(fieldName, file)\n\n if (additionalData) {\n Object.entries(additionalData).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n formData.append(key, typeof value === 'object' ? JSON.stringify(value) : String(value))\n }\n })\n }\n\n const response = await apiClient.post<T>(url, formData, {\n headers: { 'Content-Type': 'multipart/form-data' },\n })\n return response.data\n }\n\n // Download helper - returns blob URL\n async function download(url: string, filename?: string): Promise<string> {\n const response = await apiClient.get(url, { responseType: 'blob' })\n const blob = new Blob([response.data])\n const blobUrl = URL.createObjectURL(blob)\n\n // Optionally trigger download\n if (filename) {\n const link = document.createElement('a')\n link.href = blobUrl\n link.download = filename\n link.click()\n }\n\n return blobUrl\n }\n\n // Build full URL for external use (e.g., <a href=\"...\">)\n function buildUrl(path: string): string {\n const baseUrl = options.baseUrl ?? settingsStore.getApiBaseUrl()\n return `${baseUrl}${path}`\n }\n\n // WebSocket URL builder\n function buildWsUrl(path: string): string {\n return `${settingsStore.getWsBaseUrl()}${path}`\n }\n\n return {\n client: apiClient,\n get,\n post,\n put,\n patch,\n delete: del,\n upload,\n download,\n buildUrl,\n buildWsUrl,\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useApi.js","sources":["../../src/composables/useApi.ts"],"sourcesContent":["import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'\nimport { useSettingsStore } from '../stores/settings'\nimport { useAuthStore } from '../stores/auth'\n\nlet apiClientInstance: AxiosInstance | null = null\nlet interceptorAttached = false\n\nfunction getApiClient(): AxiosInstance {\n if (!apiClientInstance) {\n apiClientInstance = axios.create({\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n }\n return apiClientInstance\n}\n\nexport interface ApiClientOptions {\n baseUrl?: string\n timeout?: number\n withAuth?: boolean\n}\n\nexport interface UseApiReturn {\n client: AxiosInstance\n get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n post: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n put: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n patch: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n upload: <T>(url: string, file: File, fieldName?: string, additionalData?: Record<string, unknown>) => Promise<T>\n download: (url: string, filename?: string) => Promise<string>\n buildUrl: (path: string) => string\n buildWsUrl: (path: string) => string\n}\n\nexport function useApi(options: ApiClientOptions = {}): UseApiReturn {\n const settingsStore = useSettingsStore()\n const authStore = useAuthStore()\n const apiClient = getApiClient()\n\n // Ensure auth store is initialized (reads token from localStorage)\n if (!authStore.isInitialized) {\n authStore.initialize()\n }\n\n // Attach interceptor only once\n if (!interceptorAttached) {\n apiClient.interceptors.request.use((config) => {\n // Set base URL from settings or options\n config.baseURL = options.baseUrl ?? settingsStore.getApiBaseUrl()\n config.timeout = options.timeout ?? settingsStore.requestTimeout\n\n // Add Authorization header if token exists and auth is not explicitly disabled\n if (options.withAuth !== false && authStore.token) {\n config.headers.Authorization = `Bearer ${authStore.token}`\n }\n\n return config\n })\n interceptorAttached = true\n }\n\n // Generic request methods\n async function get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.get<T>(url, config)\n return response.data\n }\n\n async function post<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.post<T>(url, data, config)\n return response.data\n }\n\n async function put<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.put<T>(url, data, config)\n return response.data\n }\n\n async function patch<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.patch<T>(url, data, config)\n return response.data\n }\n\n async function del<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.delete<T>(url, config)\n return response.data\n }\n\n // File upload helper\n async function upload<T>(url: string, file: File, fieldName = 'file', additionalData?: Record<string, unknown>): Promise<T> {\n const formData = new FormData()\n formData.append(fieldName, file)\n\n if (additionalData) {\n Object.entries(additionalData).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n formData.append(key, typeof value === 'object' ? JSON.stringify(value) : String(value))\n }\n })\n }\n\n const response = await apiClient.post<T>(url, formData, {\n headers: { 'Content-Type': 'multipart/form-data' },\n })\n return response.data\n }\n\n // Download helper - returns blob URL\n async function download(url: string, filename?: string): Promise<string> {\n const response = await apiClient.get(url, { responseType: 'blob' })\n const blob = new Blob([response.data])\n const blobUrl = URL.createObjectURL(blob)\n\n // Optionally trigger download\n if (filename) {\n const link = document.createElement('a')\n link.href = blobUrl\n link.download = filename\n link.click()\n }\n\n return blobUrl\n }\n\n // Build full URL for external use (e.g., <a href=\"...\">)\n function buildUrl(path: string): string {\n const baseUrl = options.baseUrl ?? settingsStore.getApiBaseUrl()\n return `${baseUrl}${path}`\n }\n\n // WebSocket URL builder\n function buildWsUrl(path: string): string {\n return `${settingsStore.getWsBaseUrl()}${path}`\n }\n\n return {\n client: apiClient,\n get,\n post,\n put,\n patch,\n delete: del,\n upload,\n download,\n buildUrl,\n buildWsUrl,\n }\n}\n"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { MLDSdk, default } from './install';
2
- export { BaseButton, BaseInput, BaseTextarea, BaseSelect, BaseCheckbox, BaseToggle, BaseRadioGroup, BaseSlider, ColorSlider, BaseTabs, BaseModal, FormField, DatePicker, TimePicker, TagsInput, NumberInput, FileUploader, AlertBox, ToastNotification, IconButton, ThemeToggle, SettingsButton, CollapsibleCard, AppTopBar, AppSidebar, AppLayout, AppContainer, Skeleton, WellPlate, RackEditor, SampleLegend, PlateMapEditor, ExperimentTimeline, SampleSelector, GroupingModal, AutoGroupModal, GroupAssigner, MoleculeInput, ConcentrationInput, DoseCalculator, ReagentList, SampleHierarchyTree, ProtocolStepEditor, SegmentedControl, MultiSelect, BasePill, DropdownButton, Calendar, DataFrame, LoadingSpinner, Divider, StatusIndicator, ProgressBar, Avatar, EmptyState, Breadcrumb, Tooltip, ConfirmDialog, ChartContainer, SettingsModal, ScientificNumber, ChemicalFormula, FormulaInput, SequenceInput, UnitInput, StepWizard, AuditTrail, BatchProgressList, ExperimentDataViewer, ExperimentCodeBadge, DateTimePicker, TimeRangeInput, ScheduleCalendar, ResourceCard, ExperimentSelectorModal, FitPanel, } from './components';
2
+ export { BaseButton, BaseInput, BaseTextarea, BaseSelect, BaseCheckbox, BaseToggle, BaseRadioGroup, BaseSlider, ColorSlider, BaseTabs, BaseModal, FormField, DatePicker, TimePicker, TagsInput, NumberInput, FileUploader, AlertBox, ToastNotification, IconButton, ThemeToggle, SettingsButton, CollapsibleCard, AppTopBar, AppSidebar, AppLayout, AppContainer, Skeleton, WellPlate, RackEditor, SampleLegend, PlateMapEditor, ExperimentTimeline, SampleSelector, GroupingModal, AutoGroupModal, GroupAssigner, MoleculeInput, ConcentrationInput, DoseCalculator, ReagentList, SampleHierarchyTree, ProtocolStepEditor, SegmentedControl, MultiSelect, BasePill, DropdownButton, Calendar, DataFrame, LoadingSpinner, Divider, StatusIndicator, ProgressBar, Avatar, EmptyState, Breadcrumb, Tooltip, ConfirmDialog, ChartContainer, SettingsModal, ScientificNumber, ChemicalFormula, FormulaInput, SequenceInput, UnitInput, StepWizard, AuditTrail, BatchProgressList, ExperimentDataViewer, ExperimentCodeBadge, DateTimePicker, TimeRangeInput, ScheduleCalendar, ResourceCard, ExperimentSelectorModal, ExperimentPopover, FitPanel, } from './components';
3
3
  export { useApi, useAuth, usePasskey, useTheme, useToast, usePlatformContext, useWellPlateEditor, useConcentrationUnits, useDoseCalculator, useProtocolTemplates, useRackEditor, useChemicalFormula, ATOMIC_WEIGHTS, useSequenceUtils, type ApiClientOptions, type UseWellPlateEditorOptions, type UseWellPlateEditorReturn, type UseRackEditorOptions, type UseRackEditorReturn, type ConcentrationValue, type ConcentrationUnit, type VolumeValue, type VolumeUnit, type StepTemplate, type FormulaParseResult, type FormulaPart, type SequenceType, type SequenceStats, parseTime, formatTime, generateTimeSlots, rangesOverlap, durationMinutes, formatDuration, isTimeInRange, findAvailableSlots, snapToSlot, addMinutes, compareTime, useScheduleDrag, usePluginConfig, type UsePluginConfigReturn, useAutoGroup, DEFAULT_COLORS, useExperimentSelector, type UseExperimentSelectorOptions, type UseExperimentSelectorReturn, formatExperimentDate, EXPERIMENT_STATUS_OPTIONS, EXPERIMENT_STATUS_VARIANT_MAP, EXPERIMENT_STATUS_LABELS, useExperimentData, type UseExperimentDataOptions, type UseExperimentDataReturn, } from './composables';
4
4
  export { useAuthStore, useSettingsStore, colorPalettes, type SettingsState, } from './stores';
5
5
  export type { ContainerDirection, ButtonVariant, ButtonSize, InputType, ModalSize, AlertType, Toast, TabItem, SelectOption, RadioOption, FormFieldProps, SidebarToolSection, CollapsibleState, TopBarVariant, TopBarPage, TopBarTab, TopBarTabOption, TopBarSettingsConfig, WellPlateFormat, WellState, WellPlateSelectionMode, Well, HeatmapColorScale, HeatmapConfig, SlotPosition, WellExtendedData, WellEditData, WellEditField, WellLegendItem, Rack, SampleType, PlateMap, PlateMapEditorState, ProtocolStepType, ProtocolStepStatus, ProtocolStep, SampleGroup, GroupItem, OutlierAction, InputMode, OutlierInfo, ColumnInfo, MetadataRow, AutoGroupResult, ParsedCsvData, FileUploaderMode, SegmentedOption, SegmentedControlVariant, SegmentedControlSize, MultiSelectOption, MultiSelectSize, PillVariant, PillSize, CalendarSelectionMode, CalendarMarker, CalendarDayContext, SortDirection, SortState, DataFrameColumn, PaginationState, SpinnerSize, SpinnerVariant, DividerSpacing, StatusType, ProgressVariant, ProgressSize, AvatarSize, EmptyStateColor, EmptyStateSize, BreadcrumbItem, TooltipPosition, ConfirmVariant, SettingsTab, NumberNotation, TimePickerFormat, TimeRange, ScheduleView, ScheduleEventStatus, ScheduleEvent, ScheduleBlockedSlot, ScheduleSlotContext, ScheduleEventCreateContext, ScheduleEventUpdateContext, ResourceStatus, ResourceSpec, ExperimentStatus, ExperimentSummary, ExperimentListResponse, ExperimentFilters, FitState, FitResultSummary, UnitOption, WizardStep, WizardStepState, AuditEntryType, AuditEntry, BatchItemStatus, BatchItem, BatchSummary, AuthConfig, UserInfo, LoginResponse, TokenVerifyResponse, RegisterRequest, UpdateProfileRequest, CredentialInfo, SummaryData, SummarySection, SummarySectionItem, TreeNode, TreeNodeType, PluginInfo, PluginNavItem, PluginSettings, PluginSettingField, PlatformContext, PlatformEventType, PlatformEvent, ThemeMode, ColorPalette, TableDensity, } from './types';
package/dist/index.js CHANGED
@@ -149,7 +149,9 @@ import { default as default75 } from "./components/ResourceCard.vue.js";
149
149
  /* empty css */
150
150
  import { default as default76 } from "./components/ExperimentSelectorModal.vue.js";
151
151
  /* empty css */
152
- import { default as default77 } from "./components/FitPanel.vue.js";
152
+ import { default as default77 } from "./components/ExperimentPopover.vue.js";
153
+ /* empty css */
154
+ import { default as default78 } from "./components/FitPanel.vue.js";
153
155
  /* empty css */
154
156
  import { useApi } from "./composables/useApi.js";
155
157
  import { useAuth } from "./composables/useAuth.js";
@@ -216,10 +218,11 @@ export {
216
218
  default41 as EmptyState,
217
219
  default71 as ExperimentCodeBadge,
218
220
  default70 as ExperimentDataViewer,
221
+ default77 as ExperimentPopover,
219
222
  default76 as ExperimentSelectorModal,
220
223
  default51 as ExperimentTimeline,
221
224
  default24 as FileUploader,
222
- default77 as FitPanel,
225
+ default78 as FitPanel,
223
226
  default19 as FormField,
224
227
  default64 as FormulaInput,
225
228
  default55 as GroupAssigner,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/styles.css CHANGED
@@ -12765,6 +12765,223 @@ html.dark .mld-settings-modal__option-btn--active {
12765
12765
  color: var(--text-muted);
12766
12766
  margin-top: 0.125rem;
12767
12767
  }
12768
+ /* ExperimentPopover - BEM-style naming */
12769
+ /* Trigger button */
12770
+ .mld-experiment-popover {
12771
+ position: relative;
12772
+ }
12773
+ .mld-experiment-popover__trigger {
12774
+ display: inline-flex;
12775
+ align-items: center;
12776
+ gap: 0.375rem;
12777
+ padding: 0.3125rem 0.625rem;
12778
+ border: 1px solid var(--border-color, var(--mld-border, #e5e7eb));
12779
+ background: var(--bg-secondary, var(--mld-bg-card, #ffffff));
12780
+ border-radius: var(--mld-radius, 0.5rem);
12781
+ color: var(--text-primary, var(--mld-text-primary, #111827));
12782
+ font-size: 0.8125rem;
12783
+ font-weight: 500;
12784
+ cursor: pointer;
12785
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
12786
+ white-space: nowrap;
12787
+ max-width: 220px;
12788
+ }
12789
+ .mld-experiment-popover__trigger:hover {
12790
+ border-color: var(--color-primary, #6366F1);
12791
+ }
12792
+ .mld-experiment-popover__trigger--active {
12793
+ border-color: var(--color-primary, #6366F1);
12794
+ box-shadow: 0 0 0 1px var(--color-primary, #6366F1);
12795
+ }
12796
+ .mld-experiment-popover__trigger--empty {
12797
+ border-style: dashed;
12798
+ color: var(--text-secondary, var(--mld-text-secondary, #6b7280));
12799
+ }
12800
+ .mld-experiment-popover__trigger-icon {
12801
+ width: 1rem;
12802
+ height: 1rem;
12803
+ flex-shrink: 0;
12804
+ color: var(--color-primary, #6366F1);
12805
+ }
12806
+ .mld-experiment-popover__trigger--empty .mld-experiment-popover__trigger-icon {
12807
+ color: var(--text-muted, var(--mld-text-muted, #9ca3af));
12808
+ }
12809
+ .mld-experiment-popover__trigger-text {
12810
+ overflow: hidden;
12811
+ text-overflow: ellipsis;
12812
+ white-space: nowrap;
12813
+ }
12814
+ .mld-experiment-popover__trigger-chevron {
12815
+ width: 0.875rem;
12816
+ height: 0.875rem;
12817
+ flex-shrink: 0;
12818
+ color: var(--text-muted, var(--mld-text-muted, #9ca3af));
12819
+ transition: transform 0.15s ease;
12820
+ }
12821
+ .mld-experiment-popover__trigger--active .mld-experiment-popover__trigger-chevron {
12822
+ transform: rotate(180deg);
12823
+ }
12824
+ /* Popover panel */
12825
+ .mld-experiment-popover__panel {
12826
+ position: absolute;
12827
+ top: calc(100% + 0.5rem);
12828
+ right: 0;
12829
+ width: 280px;
12830
+ background: var(--bg-secondary, var(--mld-bg-card, #ffffff));
12831
+ border: 1px solid var(--border-color, var(--mld-border, #e5e7eb));
12832
+ border-radius: var(--mld-radius, 0.5rem);
12833
+ box-shadow: var(--mld-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05));
12834
+ z-index: 50;
12835
+ overflow: hidden;
12836
+ }
12837
+ /* Header */
12838
+ .mld-experiment-popover__header {
12839
+ padding: 0.75rem 1rem 0.5rem;
12840
+ }
12841
+ .mld-experiment-popover__title {
12842
+ font-size: 0.75rem;
12843
+ font-weight: 600;
12844
+ text-transform: uppercase;
12845
+ letter-spacing: 0.05em;
12846
+ color: var(--text-muted, var(--mld-text-muted, #9ca3af));
12847
+ }
12848
+ /* Empty state */
12849
+ .mld-experiment-popover__empty {
12850
+ padding: 0 1rem 0.75rem;
12851
+ }
12852
+ .mld-experiment-popover__select-btn {
12853
+ display: flex;
12854
+ align-items: center;
12855
+ justify-content: center;
12856
+ gap: 0.375rem;
12857
+ width: 100%;
12858
+ padding: 0.5rem;
12859
+ border: 1.5px dashed var(--color-primary, #6366F1);
12860
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.06));
12861
+ border-radius: var(--mld-radius-sm, 0.375rem);
12862
+ color: var(--color-primary, #6366F1);
12863
+ font-size: 0.8125rem;
12864
+ font-weight: 500;
12865
+ cursor: pointer;
12866
+ transition: background-color 0.15s ease;
12867
+ }
12868
+ .mld-experiment-popover__select-btn:hover {
12869
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
12870
+ }
12871
+ /* Experiment card */
12872
+ .mld-experiment-popover__card {
12873
+ display: flex;
12874
+ align-items: center;
12875
+ gap: 0.625rem;
12876
+ padding: 0 1rem 0.75rem;
12877
+ }
12878
+ .mld-experiment-popover__card-icon {
12879
+ width: 2rem;
12880
+ height: 2rem;
12881
+ flex-shrink: 0;
12882
+ border-radius: var(--mld-radius-sm, 0.375rem);
12883
+ background: rgba(139, 92, 246, 0.12);
12884
+ display: flex;
12885
+ align-items: center;
12886
+ justify-content: center;
12887
+ }
12888
+ .mld-experiment-popover__card-icon svg {
12889
+ width: 1.125rem;
12890
+ height: 1.125rem;
12891
+ color: #8B5CF6;
12892
+ }
12893
+ .mld-experiment-popover__card-info {
12894
+ flex: 1;
12895
+ min-width: 0;
12896
+ }
12897
+ .mld-experiment-popover__card-name {
12898
+ font-size: 0.8125rem;
12899
+ font-weight: 500;
12900
+ color: var(--text-primary, var(--mld-text-primary, #111827));
12901
+ overflow: hidden;
12902
+ text-overflow: ellipsis;
12903
+ white-space: nowrap;
12904
+ }
12905
+ .mld-experiment-popover__card-status {
12906
+ font-size: 0.6875rem;
12907
+ color: var(--text-muted, var(--mld-text-muted, #9ca3af));
12908
+ }
12909
+ .mld-experiment-popover__change-btn {
12910
+ flex-shrink: 0;
12911
+ padding: 0.25rem 0.5rem;
12912
+ border: none;
12913
+ background: transparent;
12914
+ border-radius: var(--mld-radius-sm, 0.25rem);
12915
+ color: var(--color-primary, #6366F1);
12916
+ font-size: 0.75rem;
12917
+ font-weight: 500;
12918
+ cursor: pointer;
12919
+ transition: background-color 0.15s ease;
12920
+ }
12921
+ .mld-experiment-popover__change-btn:hover {
12922
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.08));
12923
+ }
12924
+ /* Divider */
12925
+ .mld-experiment-popover__divider {
12926
+ height: 1px;
12927
+ background: var(--border-color, var(--mld-border, #e5e7eb));
12928
+ margin: 0;
12929
+ }
12930
+ /* Footer (save section) */
12931
+ .mld-experiment-popover__footer {
12932
+ padding: 0.75rem 1rem;
12933
+ }
12934
+ .mld-experiment-popover__save-btn {
12935
+ display: flex;
12936
+ align-items: center;
12937
+ justify-content: center;
12938
+ gap: 0.375rem;
12939
+ width: 100%;
12940
+ padding: 0.4375rem 0.75rem;
12941
+ border: none;
12942
+ background: var(--color-primary, #6366F1);
12943
+ border-radius: var(--mld-radius-sm, 0.375rem);
12944
+ color: white;
12945
+ font-size: 0.8125rem;
12946
+ font-weight: 500;
12947
+ cursor: pointer;
12948
+ transition: background-color 0.15s ease, opacity 0.15s ease;
12949
+ }
12950
+ .mld-experiment-popover__save-btn:hover:not(:disabled) {
12951
+ background: var(--color-primary-hover, #4F46E5);
12952
+ }
12953
+ .mld-experiment-popover__save-btn:disabled {
12954
+ opacity: 0.5;
12955
+ cursor: not-allowed;
12956
+ }
12957
+ .mld-experiment-popover__save-btn--loading {
12958
+ opacity: 0.8;
12959
+ pointer-events: none;
12960
+ }
12961
+ /* Save success state */
12962
+ .mld-experiment-popover__save-btn--success {
12963
+ background: var(--mld-success-bg, #059669);
12964
+ }
12965
+ .mld-experiment-popover__save-btn--success:hover {
12966
+ background: var(--mld-success-bg, #059669);
12967
+ }
12968
+ /* Spinner */
12969
+ .mld-experiment-popover__spinner {
12970
+ width: 0.875rem;
12971
+ height: 0.875rem;
12972
+ border: 2px solid rgba(255, 255, 255, 0.3);
12973
+ border-top-color: white;
12974
+ border-radius: 50%;
12975
+ animation: mld-experiment-popover-spin 0.6s linear infinite;
12976
+ }
12977
+ @keyframes mld-experiment-popover-spin {
12978
+ to { transform: rotate(360deg); }
12979
+ }
12980
+ /* Check icon for success */
12981
+ .mld-experiment-popover__check-icon {
12982
+ width: 0.875rem;
12983
+ height: 0.875rem;
12984
+ }
12768
12985
  /* FitPanel Component Styles */
12769
12986
  .mld-fit-panel {
12770
12987
  display: flex;
@@ -25176,6 +25393,234 @@ to { transform: rotate(360deg);
25176
25393
  color: var(--text-muted);
25177
25394
  margin-top: 0.125rem;
25178
25395
  }
25396
+ /* ExperimentPopover - BEM-style naming */
25397
+
25398
+ /* Trigger button */
25399
+ .mld-experiment-popover {
25400
+ position: relative;
25401
+ }
25402
+ .mld-experiment-popover__trigger {
25403
+ display: inline-flex;
25404
+ align-items: center;
25405
+ gap: 0.375rem;
25406
+ padding: 0.3125rem 0.625rem;
25407
+ border: 1px solid var(--border-color, var(--mld-border, #e5e7eb));
25408
+ background: var(--bg-secondary, var(--mld-bg-card, #ffffff));
25409
+ border-radius: var(--mld-radius, 0.5rem);
25410
+ color: var(--text-primary, var(--mld-text-primary, #111827));
25411
+ font-size: 0.8125rem;
25412
+ font-weight: 500;
25413
+ cursor: pointer;
25414
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
25415
+ white-space: nowrap;
25416
+ max-width: 220px;
25417
+ }
25418
+ .mld-experiment-popover__trigger:hover {
25419
+ border-color: var(--color-primary, #6366F1);
25420
+ }
25421
+ .mld-experiment-popover__trigger--active {
25422
+ border-color: var(--color-primary, #6366F1);
25423
+ box-shadow: 0 0 0 1px var(--color-primary, #6366F1);
25424
+ }
25425
+ .mld-experiment-popover__trigger--empty {
25426
+ border-style: dashed;
25427
+ color: var(--text-secondary, var(--mld-text-secondary, #6b7280));
25428
+ }
25429
+ .mld-experiment-popover__trigger-icon {
25430
+ width: 1rem;
25431
+ height: 1rem;
25432
+ flex-shrink: 0;
25433
+ color: var(--color-primary, #6366F1);
25434
+ }
25435
+ .mld-experiment-popover__trigger--empty .mld-experiment-popover__trigger-icon {
25436
+ color: var(--text-muted, var(--mld-text-muted, #9ca3af));
25437
+ }
25438
+ .mld-experiment-popover__trigger-text {
25439
+ overflow: hidden;
25440
+ text-overflow: ellipsis;
25441
+ white-space: nowrap;
25442
+ }
25443
+ .mld-experiment-popover__trigger-chevron {
25444
+ width: 0.875rem;
25445
+ height: 0.875rem;
25446
+ flex-shrink: 0;
25447
+ color: var(--text-muted, var(--mld-text-muted, #9ca3af));
25448
+ transition: transform 0.15s ease;
25449
+ }
25450
+ .mld-experiment-popover__trigger--active .mld-experiment-popover__trigger-chevron {
25451
+ transform: rotate(180deg);
25452
+ }
25453
+
25454
+ /* Popover panel */
25455
+ .mld-experiment-popover__panel {
25456
+ position: absolute;
25457
+ top: calc(100% + 0.5rem);
25458
+ right: 0;
25459
+ width: 280px;
25460
+ background: var(--bg-secondary, var(--mld-bg-card, #ffffff));
25461
+ border: 1px solid var(--border-color, var(--mld-border, #e5e7eb));
25462
+ border-radius: var(--mld-radius, 0.5rem);
25463
+ box-shadow: var(--mld-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05));
25464
+ z-index: 50;
25465
+ overflow: hidden;
25466
+ }
25467
+
25468
+ /* Header */
25469
+ .mld-experiment-popover__header {
25470
+ padding: 0.75rem 1rem 0.5rem;
25471
+ }
25472
+ .mld-experiment-popover__title {
25473
+ font-size: 0.75rem;
25474
+ font-weight: 600;
25475
+ text-transform: uppercase;
25476
+ letter-spacing: 0.05em;
25477
+ color: var(--text-muted, var(--mld-text-muted, #9ca3af));
25478
+ }
25479
+
25480
+ /* Empty state */
25481
+ .mld-experiment-popover__empty {
25482
+ padding: 0 1rem 0.75rem;
25483
+ }
25484
+ .mld-experiment-popover__select-btn {
25485
+ display: flex;
25486
+ align-items: center;
25487
+ justify-content: center;
25488
+ gap: 0.375rem;
25489
+ width: 100%;
25490
+ padding: 0.5rem;
25491
+ border: 1.5px dashed var(--color-primary, #6366F1);
25492
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.06));
25493
+ border-radius: var(--mld-radius-sm, 0.375rem);
25494
+ color: var(--color-primary, #6366F1);
25495
+ font-size: 0.8125rem;
25496
+ font-weight: 500;
25497
+ cursor: pointer;
25498
+ transition: background-color 0.15s ease;
25499
+ }
25500
+ .mld-experiment-popover__select-btn:hover {
25501
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
25502
+ }
25503
+
25504
+ /* Experiment card */
25505
+ .mld-experiment-popover__card {
25506
+ display: flex;
25507
+ align-items: center;
25508
+ gap: 0.625rem;
25509
+ padding: 0 1rem 0.75rem;
25510
+ }
25511
+ .mld-experiment-popover__card-icon {
25512
+ width: 2rem;
25513
+ height: 2rem;
25514
+ flex-shrink: 0;
25515
+ border-radius: var(--mld-radius-sm, 0.375rem);
25516
+ background: rgba(139, 92, 246, 0.12);
25517
+ display: flex;
25518
+ align-items: center;
25519
+ justify-content: center;
25520
+ }
25521
+ .mld-experiment-popover__card-icon svg {
25522
+ width: 1.125rem;
25523
+ height: 1.125rem;
25524
+ color: #8B5CF6;
25525
+ }
25526
+ .mld-experiment-popover__card-info {
25527
+ flex: 1;
25528
+ min-width: 0;
25529
+ }
25530
+ .mld-experiment-popover__card-name {
25531
+ font-size: 0.8125rem;
25532
+ font-weight: 500;
25533
+ color: var(--text-primary, var(--mld-text-primary, #111827));
25534
+ overflow: hidden;
25535
+ text-overflow: ellipsis;
25536
+ white-space: nowrap;
25537
+ }
25538
+ .mld-experiment-popover__card-status {
25539
+ font-size: 0.6875rem;
25540
+ color: var(--text-muted, var(--mld-text-muted, #9ca3af));
25541
+ }
25542
+ .mld-experiment-popover__change-btn {
25543
+ flex-shrink: 0;
25544
+ padding: 0.25rem 0.5rem;
25545
+ border: none;
25546
+ background: transparent;
25547
+ border-radius: var(--mld-radius-sm, 0.25rem);
25548
+ color: var(--color-primary, #6366F1);
25549
+ font-size: 0.75rem;
25550
+ font-weight: 500;
25551
+ cursor: pointer;
25552
+ transition: background-color 0.15s ease;
25553
+ }
25554
+ .mld-experiment-popover__change-btn:hover {
25555
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.08));
25556
+ }
25557
+
25558
+ /* Divider */
25559
+ .mld-experiment-popover__divider {
25560
+ height: 1px;
25561
+ background: var(--border-color, var(--mld-border, #e5e7eb));
25562
+ margin: 0;
25563
+ }
25564
+
25565
+ /* Footer (save section) */
25566
+ .mld-experiment-popover__footer {
25567
+ padding: 0.75rem 1rem;
25568
+ }
25569
+ .mld-experiment-popover__save-btn {
25570
+ display: flex;
25571
+ align-items: center;
25572
+ justify-content: center;
25573
+ gap: 0.375rem;
25574
+ width: 100%;
25575
+ padding: 0.4375rem 0.75rem;
25576
+ border: none;
25577
+ background: var(--color-primary, #6366F1);
25578
+ border-radius: var(--mld-radius-sm, 0.375rem);
25579
+ color: white;
25580
+ font-size: 0.8125rem;
25581
+ font-weight: 500;
25582
+ cursor: pointer;
25583
+ transition: background-color 0.15s ease, opacity 0.15s ease;
25584
+ }
25585
+ .mld-experiment-popover__save-btn:hover:not(:disabled) {
25586
+ background: var(--color-primary-hover, #4F46E5);
25587
+ }
25588
+ .mld-experiment-popover__save-btn:disabled {
25589
+ opacity: 0.5;
25590
+ cursor: not-allowed;
25591
+ }
25592
+ .mld-experiment-popover__save-btn--loading {
25593
+ opacity: 0.8;
25594
+ pointer-events: none;
25595
+ }
25596
+
25597
+ /* Save success state */
25598
+ .mld-experiment-popover__save-btn--success {
25599
+ background: var(--mld-success-bg, #059669);
25600
+ }
25601
+ .mld-experiment-popover__save-btn--success:hover {
25602
+ background: var(--mld-success-bg, #059669);
25603
+ }
25604
+
25605
+ /* Spinner */
25606
+ .mld-experiment-popover__spinner {
25607
+ width: 0.875rem;
25608
+ height: 0.875rem;
25609
+ border: 2px solid rgba(255, 255, 255, 0.3);
25610
+ border-top-color: white;
25611
+ border-radius: 50%;
25612
+ animation: mld-experiment-popover-spin 0.6s linear infinite;
25613
+ }
25614
+ @keyframes mld-experiment-popover-spin {
25615
+ to { transform: rotate(360deg);
25616
+ }
25617
+ }
25618
+
25619
+ /* Check icon for success */
25620
+ .mld-experiment-popover__check-icon {
25621
+ width: 0.875rem;
25622
+ height: 0.875rem;
25623
+ }
25179
25624
  /* FitPanel Component Styles */
25180
25625
  .mld-fit-panel {
25181
25626
  display: flex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morscherlab/mld-sdk",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "MLD Platform SDK - Vue 3 components, composables, and types for plugin development",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,177 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch, onMounted, onUnmounted } from 'vue'
3
+
4
+ interface Props {
5
+ experimentName?: string
6
+ experimentStatus?: string
7
+ showSave?: boolean
8
+ saveDisabled?: boolean
9
+ saveLoading?: boolean
10
+ saveSuccessMessage?: string
11
+ }
12
+
13
+ const props = withDefaults(defineProps<Props>(), {
14
+ showSave: false,
15
+ saveDisabled: false,
16
+ saveLoading: false,
17
+ })
18
+
19
+ const emit = defineEmits<{
20
+ select: []
21
+ save: []
22
+ }>()
23
+
24
+ const isOpen = ref(false)
25
+ const popoverRef = ref<HTMLElement | null>(null)
26
+ const showSuccess = ref(false)
27
+
28
+ function toggle() {
29
+ isOpen.value = !isOpen.value
30
+ }
31
+
32
+ function close() {
33
+ isOpen.value = false
34
+ }
35
+
36
+ function handleSelect() {
37
+ emit('select')
38
+ close()
39
+ }
40
+
41
+ function handleSave() {
42
+ if (props.saveDisabled || props.saveLoading) return
43
+ emit('save')
44
+ }
45
+
46
+ function handleClickOutside(event: MouseEvent) {
47
+ if (popoverRef.value && !popoverRef.value.contains(event.target as Node)) {
48
+ close()
49
+ }
50
+ }
51
+
52
+ // Show success state when saveSuccessMessage changes from empty to a value
53
+ watch(() => props.saveSuccessMessage, (msg) => {
54
+ if (msg) {
55
+ showSuccess.value = true
56
+ setTimeout(() => {
57
+ showSuccess.value = false
58
+ }, 3000)
59
+ }
60
+ })
61
+
62
+ onMounted(() => {
63
+ document.addEventListener('click', handleClickOutside)
64
+ })
65
+
66
+ onUnmounted(() => {
67
+ document.removeEventListener('click', handleClickOutside)
68
+ })
69
+
70
+ // Format status for display (e.g., "ready_to_extract" -> "Ready to extract")
71
+ function formatStatus(status: string): string {
72
+ return status.replace(/_/g, ' ').replace(/^\w/, c => c.toUpperCase())
73
+ }
74
+ </script>
75
+
76
+ <template>
77
+ <div ref="popoverRef" class="mld-experiment-popover">
78
+ <!-- Trigger button -->
79
+ <button
80
+ type="button"
81
+ :class="[
82
+ 'mld-experiment-popover__trigger',
83
+ { 'mld-experiment-popover__trigger--active': isOpen },
84
+ { 'mld-experiment-popover__trigger--empty': !experimentName },
85
+ ]"
86
+ @click.stop="toggle"
87
+ >
88
+ <!-- Flask icon -->
89
+ <svg class="mld-experiment-popover__trigger-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
90
+ <path
91
+ stroke-linecap="round"
92
+ stroke-linejoin="round"
93
+ stroke-width="1.75"
94
+ 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"
95
+ />
96
+ </svg>
97
+ <span class="mld-experiment-popover__trigger-text">
98
+ {{ experimentName || 'No experiment' }}
99
+ </span>
100
+ <!-- Chevron -->
101
+ <svg class="mld-experiment-popover__trigger-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
102
+ <path d="m6 9 6 6 6-6" />
103
+ </svg>
104
+ </button>
105
+
106
+ <!-- Popover panel -->
107
+ <div v-if="isOpen" class="mld-experiment-popover__panel">
108
+ <!-- Header -->
109
+ <div class="mld-experiment-popover__header">
110
+ <div class="mld-experiment-popover__title">Experiment</div>
111
+ </div>
112
+
113
+ <!-- No experiment selected -->
114
+ <div v-if="!experimentName" class="mld-experiment-popover__empty">
115
+ <button type="button" class="mld-experiment-popover__select-btn" @click="handleSelect">
116
+ <svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24">
117
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
118
+ </svg>
119
+ Select Experiment
120
+ </button>
121
+ </div>
122
+
123
+ <!-- Experiment selected -->
124
+ <div v-else class="mld-experiment-popover__card">
125
+ <div class="mld-experiment-popover__card-icon">
126
+ <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
127
+ <path
128
+ stroke-linecap="round"
129
+ stroke-linejoin="round"
130
+ stroke-width="1.75"
131
+ 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"
132
+ />
133
+ </svg>
134
+ </div>
135
+ <div class="mld-experiment-popover__card-info">
136
+ <div class="mld-experiment-popover__card-name">{{ experimentName }}</div>
137
+ <div v-if="experimentStatus" class="mld-experiment-popover__card-status">
138
+ {{ formatStatus(experimentStatus) }}
139
+ </div>
140
+ </div>
141
+ <button type="button" class="mld-experiment-popover__change-btn" @click="handleSelect">
142
+ Change
143
+ </button>
144
+ </div>
145
+
146
+ <!-- Save section -->
147
+ <template v-if="showSave">
148
+ <div class="mld-experiment-popover__divider" />
149
+ <div class="mld-experiment-popover__footer">
150
+ <button
151
+ type="button"
152
+ :class="[
153
+ 'mld-experiment-popover__save-btn',
154
+ { 'mld-experiment-popover__save-btn--loading': saveLoading },
155
+ { 'mld-experiment-popover__save-btn--success': showSuccess },
156
+ ]"
157
+ :disabled="saveDisabled && !showSuccess"
158
+ @click="handleSave"
159
+ >
160
+ <!-- Loading spinner -->
161
+ <span v-if="saveLoading" class="mld-experiment-popover__spinner" />
162
+ <!-- Success check -->
163
+ <svg v-else-if="showSuccess" class="mld-experiment-popover__check-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
164
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
165
+ </svg>
166
+ <!-- Label -->
167
+ <span>{{ showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment' }}</span>
168
+ </button>
169
+ </div>
170
+ </template>
171
+ </div>
172
+ </div>
173
+ </template>
174
+
175
+ <style>
176
+ @import '../styles/components/experiment-popover.css';
177
+ </style>
@@ -108,4 +108,5 @@ export { default as ResourceCard } from './ResourceCard.vue'
108
108
 
109
109
  // Experiment / analysis components
110
110
  export { default as ExperimentSelectorModal } from './ExperimentSelectorModal.vue'
111
+ export { default as ExperimentPopover } from './ExperimentPopover.vue'
111
112
  export { default as FitPanel } from './FitPanel.vue'
@@ -40,6 +40,11 @@ export function useApi(options: ApiClientOptions = {}): UseApiReturn {
40
40
  const authStore = useAuthStore()
41
41
  const apiClient = getApiClient()
42
42
 
43
+ // Ensure auth store is initialized (reads token from localStorage)
44
+ if (!authStore.isInitialized) {
45
+ authStore.initialize()
46
+ }
47
+
43
48
  // Attach interceptor only once
44
49
  if (!interceptorAttached) {
45
50
  apiClient.interceptors.request.use((config) => {
package/src/index.ts CHANGED
@@ -93,6 +93,7 @@ export {
93
93
  ResourceCard,
94
94
  // Experiment / analysis components
95
95
  ExperimentSelectorModal,
96
+ ExperimentPopover,
96
97
  FitPanel,
97
98
  } from './components'
98
99
 
@@ -0,0 +1,252 @@
1
+ /* ExperimentPopover - BEM-style naming */
2
+
3
+ /* Trigger button */
4
+ .mld-experiment-popover {
5
+ position: relative;
6
+ }
7
+
8
+ .mld-experiment-popover__trigger {
9
+ display: inline-flex;
10
+ align-items: center;
11
+ gap: 0.375rem;
12
+ padding: 0.3125rem 0.625rem;
13
+ border: 1px solid var(--border-color, var(--mld-border, #e5e7eb));
14
+ background: var(--bg-secondary, var(--mld-bg-card, #ffffff));
15
+ border-radius: var(--mld-radius, 0.5rem);
16
+ color: var(--text-primary, var(--mld-text-primary, #111827));
17
+ font-size: 0.8125rem;
18
+ font-weight: 500;
19
+ cursor: pointer;
20
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
21
+ white-space: nowrap;
22
+ max-width: 220px;
23
+ }
24
+
25
+ .mld-experiment-popover__trigger:hover {
26
+ border-color: var(--color-primary, #6366F1);
27
+ }
28
+
29
+ .mld-experiment-popover__trigger--active {
30
+ border-color: var(--color-primary, #6366F1);
31
+ box-shadow: 0 0 0 1px var(--color-primary, #6366F1);
32
+ }
33
+
34
+ .mld-experiment-popover__trigger--empty {
35
+ border-style: dashed;
36
+ color: var(--text-secondary, var(--mld-text-secondary, #6b7280));
37
+ }
38
+
39
+ .mld-experiment-popover__trigger-icon {
40
+ width: 1rem;
41
+ height: 1rem;
42
+ flex-shrink: 0;
43
+ color: var(--color-primary, #6366F1);
44
+ }
45
+
46
+ .mld-experiment-popover__trigger--empty .mld-experiment-popover__trigger-icon {
47
+ color: var(--text-muted, var(--mld-text-muted, #9ca3af));
48
+ }
49
+
50
+ .mld-experiment-popover__trigger-text {
51
+ overflow: hidden;
52
+ text-overflow: ellipsis;
53
+ white-space: nowrap;
54
+ }
55
+
56
+ .mld-experiment-popover__trigger-chevron {
57
+ width: 0.875rem;
58
+ height: 0.875rem;
59
+ flex-shrink: 0;
60
+ color: var(--text-muted, var(--mld-text-muted, #9ca3af));
61
+ transition: transform 0.15s ease;
62
+ }
63
+
64
+ .mld-experiment-popover__trigger--active .mld-experiment-popover__trigger-chevron {
65
+ transform: rotate(180deg);
66
+ }
67
+
68
+ /* Popover panel */
69
+ .mld-experiment-popover__panel {
70
+ position: absolute;
71
+ top: calc(100% + 0.5rem);
72
+ right: 0;
73
+ width: 280px;
74
+ background: var(--bg-secondary, var(--mld-bg-card, #ffffff));
75
+ border: 1px solid var(--border-color, var(--mld-border, #e5e7eb));
76
+ border-radius: var(--mld-radius, 0.5rem);
77
+ box-shadow: var(--mld-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05));
78
+ z-index: 50;
79
+ overflow: hidden;
80
+ }
81
+
82
+ /* Header */
83
+ .mld-experiment-popover__header {
84
+ padding: 0.75rem 1rem 0.5rem;
85
+ }
86
+
87
+ .mld-experiment-popover__title {
88
+ font-size: 0.75rem;
89
+ font-weight: 600;
90
+ text-transform: uppercase;
91
+ letter-spacing: 0.05em;
92
+ color: var(--text-muted, var(--mld-text-muted, #9ca3af));
93
+ }
94
+
95
+ /* Empty state */
96
+ .mld-experiment-popover__empty {
97
+ padding: 0 1rem 0.75rem;
98
+ }
99
+
100
+ .mld-experiment-popover__select-btn {
101
+ display: flex;
102
+ align-items: center;
103
+ justify-content: center;
104
+ gap: 0.375rem;
105
+ width: 100%;
106
+ padding: 0.5rem;
107
+ border: 1.5px dashed var(--color-primary, #6366F1);
108
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.06));
109
+ border-radius: var(--mld-radius-sm, 0.375rem);
110
+ color: var(--color-primary, #6366F1);
111
+ font-size: 0.8125rem;
112
+ font-weight: 500;
113
+ cursor: pointer;
114
+ transition: background-color 0.15s ease;
115
+ }
116
+
117
+ .mld-experiment-popover__select-btn:hover {
118
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
119
+ }
120
+
121
+ /* Experiment card */
122
+ .mld-experiment-popover__card {
123
+ display: flex;
124
+ align-items: center;
125
+ gap: 0.625rem;
126
+ padding: 0 1rem 0.75rem;
127
+ }
128
+
129
+ .mld-experiment-popover__card-icon {
130
+ width: 2rem;
131
+ height: 2rem;
132
+ flex-shrink: 0;
133
+ border-radius: var(--mld-radius-sm, 0.375rem);
134
+ background: rgba(139, 92, 246, 0.12);
135
+ display: flex;
136
+ align-items: center;
137
+ justify-content: center;
138
+ }
139
+
140
+ .mld-experiment-popover__card-icon svg {
141
+ width: 1.125rem;
142
+ height: 1.125rem;
143
+ color: #8B5CF6;
144
+ }
145
+
146
+ .mld-experiment-popover__card-info {
147
+ flex: 1;
148
+ min-width: 0;
149
+ }
150
+
151
+ .mld-experiment-popover__card-name {
152
+ font-size: 0.8125rem;
153
+ font-weight: 500;
154
+ color: var(--text-primary, var(--mld-text-primary, #111827));
155
+ overflow: hidden;
156
+ text-overflow: ellipsis;
157
+ white-space: nowrap;
158
+ }
159
+
160
+ .mld-experiment-popover__card-status {
161
+ font-size: 0.6875rem;
162
+ color: var(--text-muted, var(--mld-text-muted, #9ca3af));
163
+ }
164
+
165
+ .mld-experiment-popover__change-btn {
166
+ flex-shrink: 0;
167
+ padding: 0.25rem 0.5rem;
168
+ border: none;
169
+ background: transparent;
170
+ border-radius: var(--mld-radius-sm, 0.25rem);
171
+ color: var(--color-primary, #6366F1);
172
+ font-size: 0.75rem;
173
+ font-weight: 500;
174
+ cursor: pointer;
175
+ transition: background-color 0.15s ease;
176
+ }
177
+
178
+ .mld-experiment-popover__change-btn:hover {
179
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.08));
180
+ }
181
+
182
+ /* Divider */
183
+ .mld-experiment-popover__divider {
184
+ height: 1px;
185
+ background: var(--border-color, var(--mld-border, #e5e7eb));
186
+ margin: 0;
187
+ }
188
+
189
+ /* Footer (save section) */
190
+ .mld-experiment-popover__footer {
191
+ padding: 0.75rem 1rem;
192
+ }
193
+
194
+ .mld-experiment-popover__save-btn {
195
+ display: flex;
196
+ align-items: center;
197
+ justify-content: center;
198
+ gap: 0.375rem;
199
+ width: 100%;
200
+ padding: 0.4375rem 0.75rem;
201
+ border: none;
202
+ background: var(--color-primary, #6366F1);
203
+ border-radius: var(--mld-radius-sm, 0.375rem);
204
+ color: white;
205
+ font-size: 0.8125rem;
206
+ font-weight: 500;
207
+ cursor: pointer;
208
+ transition: background-color 0.15s ease, opacity 0.15s ease;
209
+ }
210
+
211
+ .mld-experiment-popover__save-btn:hover:not(:disabled) {
212
+ background: var(--color-primary-hover, #4F46E5);
213
+ }
214
+
215
+ .mld-experiment-popover__save-btn:disabled {
216
+ opacity: 0.5;
217
+ cursor: not-allowed;
218
+ }
219
+
220
+ .mld-experiment-popover__save-btn--loading {
221
+ opacity: 0.8;
222
+ pointer-events: none;
223
+ }
224
+
225
+ /* Save success state */
226
+ .mld-experiment-popover__save-btn--success {
227
+ background: var(--mld-success-bg, #059669);
228
+ }
229
+
230
+ .mld-experiment-popover__save-btn--success:hover {
231
+ background: var(--mld-success-bg, #059669);
232
+ }
233
+
234
+ /* Spinner */
235
+ .mld-experiment-popover__spinner {
236
+ width: 0.875rem;
237
+ height: 0.875rem;
238
+ border: 2px solid rgba(255, 255, 255, 0.3);
239
+ border-top-color: white;
240
+ border-radius: 50%;
241
+ animation: mld-experiment-popover-spin 0.6s linear infinite;
242
+ }
243
+
244
+ @keyframes mld-experiment-popover-spin {
245
+ to { transform: rotate(360deg); }
246
+ }
247
+
248
+ /* Check icon for success */
249
+ .mld-experiment-popover__check-icon {
250
+ width: 0.875rem;
251
+ height: 0.875rem;
252
+ }
@@ -83,4 +83,5 @@
83
83
  @import './components/experiment-data-viewer.css';
84
84
  @import './components/experiment-code-badge.css';
85
85
  @import './components/experiment-selector-modal.css';
86
+ @import './components/experiment-popover.css';
86
87
  @import './components/fit-panel.css';