@morscherlab/mld-sdk 0.9.6 → 0.9.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.
- package/dist/components/ExperimentPopover.vue.d.ts +4 -0
- package/dist/components/ExperimentPopover.vue.js +31 -17
- package/dist/components/ExperimentPopover.vue.js.map +1 -1
- package/dist/components/FormulaInput.vue.js +24 -18
- package/dist/components/FormulaInput.vue.js.map +1 -1
- package/dist/components/MoleculeInput.vue.js +15 -6
- package/dist/components/MoleculeInput.vue.js.map +1 -1
- package/dist/components/PlateMapEditor.vue.js +1 -1
- package/dist/components/PlateMapEditor.vue.js.map +1 -1
- package/dist/composables/useAuth.js +26 -25
- package/dist/composables/useAuth.js.map +1 -1
- package/dist/composables/useAutoGroup.js +7 -32
- package/dist/composables/useAutoGroup.js.map +1 -1
- package/dist/composables/useForm.js +1 -1
- package/dist/composables/useForm.js.map +1 -1
- package/dist/composables/useWellPlateEditor.d.ts +1 -0
- package/dist/composables/useWellPlateEditor.js +21 -10
- package/dist/composables/useWellPlateEditor.js.map +1 -1
- package/dist/stores/settings.js +17 -30
- package/dist/stores/settings.js.map +1 -1
- package/dist/styles.css +68 -14
- package/package.json +1 -1
- package/src/components/ExperimentPopover.vue +16 -3
- package/src/components/FormulaInput.vue +17 -16
- package/src/components/MoleculeInput.vue +29 -14
- package/src/components/PlateMapEditor.vue +1 -1
- package/src/composables/useAuth.ts +29 -31
- package/src/composables/useAutoGroup.ts +7 -33
- package/src/composables/useForm.ts +1 -1
- package/src/composables/useWellPlateEditor.ts +22 -10
- package/src/stores/settings.ts +22 -38
- package/src/styles/components/experiment-popover.css +25 -1
- package/src/styles/components/formula-input.css +13 -6
|
@@ -2,6 +2,7 @@ interface Props {
|
|
|
2
2
|
experimentName?: string;
|
|
3
3
|
experimentStatus?: string;
|
|
4
4
|
showSave?: boolean;
|
|
5
|
+
showDetach?: boolean;
|
|
5
6
|
saveDisabled?: boolean;
|
|
6
7
|
saveLoading?: boolean;
|
|
7
8
|
saveSuccessMessage?: string;
|
|
@@ -10,11 +11,14 @@ interface Props {
|
|
|
10
11
|
declare const _default: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
11
12
|
select: () => any;
|
|
12
13
|
save: () => any;
|
|
14
|
+
detach: () => any;
|
|
13
15
|
}, string, import('vue').PublicProps, Readonly<Props> & Readonly<{
|
|
14
16
|
onSelect?: (() => any) | undefined;
|
|
15
17
|
onSave?: (() => any) | undefined;
|
|
18
|
+
onDetach?: (() => any) | undefined;
|
|
16
19
|
}>, {
|
|
17
20
|
showSave: boolean;
|
|
21
|
+
showDetach: boolean;
|
|
18
22
|
saveDisabled: boolean;
|
|
19
23
|
saveLoading: boolean;
|
|
20
24
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
|
|
@@ -21,27 +21,28 @@ const _hoisted_10 = {
|
|
|
21
21
|
key: 0,
|
|
22
22
|
class: "mld-experiment-popover__card-status"
|
|
23
23
|
};
|
|
24
|
-
const _hoisted_11 = { class: "mld-experiment-
|
|
25
|
-
const _hoisted_12 =
|
|
26
|
-
const _hoisted_13 =
|
|
24
|
+
const _hoisted_11 = { class: "mld-experiment-popover__card-actions" };
|
|
25
|
+
const _hoisted_12 = { class: "mld-experiment-popover__footer" };
|
|
26
|
+
const _hoisted_13 = ["disabled"];
|
|
27
|
+
const _hoisted_14 = {
|
|
27
28
|
key: 0,
|
|
28
29
|
class: "mld-experiment-popover__spinner"
|
|
29
30
|
};
|
|
30
|
-
const
|
|
31
|
+
const _hoisted_15 = {
|
|
31
32
|
key: 1,
|
|
32
33
|
class: "mld-experiment-popover__check-icon",
|
|
33
34
|
fill: "none",
|
|
34
35
|
stroke: "currentColor",
|
|
35
36
|
viewBox: "0 0 24 24"
|
|
36
37
|
};
|
|
37
|
-
const
|
|
38
|
+
const _hoisted_16 = {
|
|
38
39
|
key: 2,
|
|
39
40
|
class: "mld-experiment-popover__save-icon",
|
|
40
41
|
fill: "none",
|
|
41
42
|
stroke: "currentColor",
|
|
42
43
|
viewBox: "0 0 24 24"
|
|
43
44
|
};
|
|
44
|
-
const
|
|
45
|
+
const _hoisted_17 = {
|
|
45
46
|
key: 0,
|
|
46
47
|
class: "mld-experiment-popover__save-hint"
|
|
47
48
|
};
|
|
@@ -51,12 +52,13 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
51
52
|
experimentName: {},
|
|
52
53
|
experimentStatus: {},
|
|
53
54
|
showSave: { type: Boolean, default: false },
|
|
55
|
+
showDetach: { type: Boolean, default: false },
|
|
54
56
|
saveDisabled: { type: Boolean, default: false },
|
|
55
57
|
saveLoading: { type: Boolean, default: false },
|
|
56
58
|
saveSuccessMessage: {},
|
|
57
59
|
saveDisabledMessage: {}
|
|
58
60
|
},
|
|
59
|
-
emits: ["select", "save"],
|
|
61
|
+
emits: ["select", "save", "detach"],
|
|
60
62
|
setup(__props, { emit: __emit }) {
|
|
61
63
|
const props = __props;
|
|
62
64
|
const emit = __emit;
|
|
@@ -77,6 +79,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
77
79
|
if (props.saveDisabled || props.saveLoading) return;
|
|
78
80
|
emit("save");
|
|
79
81
|
}
|
|
82
|
+
function handleDetach() {
|
|
83
|
+
emit("detach");
|
|
84
|
+
close();
|
|
85
|
+
}
|
|
80
86
|
function handleClickOutside(event) {
|
|
81
87
|
if (popoverRef.value && !popoverRef.value.contains(event.target)) {
|
|
82
88
|
close();
|
|
@@ -187,16 +193,24 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
187
193
|
createElementVNode("div", _hoisted_9, toDisplayString(__props.experimentName), 1),
|
|
188
194
|
__props.experimentStatus ? (openBlock(), createElementBlock("div", _hoisted_10, toDisplayString(formatStatus(__props.experimentStatus)), 1)) : createCommentVNode("", true)
|
|
189
195
|
]),
|
|
190
|
-
createElementVNode("
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
createElementVNode("div", _hoisted_11, [
|
|
197
|
+
createElementVNode("button", {
|
|
198
|
+
type: "button",
|
|
199
|
+
class: "mld-experiment-popover__change-btn",
|
|
200
|
+
onClick: handleSelect
|
|
201
|
+
}, " Change "),
|
|
202
|
+
__props.showDetach ? (openBlock(), createElementBlock("button", {
|
|
203
|
+
key: 0,
|
|
204
|
+
type: "button",
|
|
205
|
+
class: "mld-experiment-popover__detach-btn",
|
|
206
|
+
onClick: handleDetach
|
|
207
|
+
}, " Detach ")) : createCommentVNode("", true)
|
|
208
|
+
])
|
|
195
209
|
])
|
|
196
210
|
])),
|
|
197
211
|
__props.showSave ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
|
|
198
212
|
_cache[7] || (_cache[7] = createElementVNode("div", { class: "mld-experiment-popover__divider" }, null, -1)),
|
|
199
|
-
createElementVNode("div",
|
|
213
|
+
createElementVNode("div", _hoisted_12, [
|
|
200
214
|
createElementVNode("button", {
|
|
201
215
|
type: "button",
|
|
202
216
|
class: normalizeClass([
|
|
@@ -207,14 +221,14 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
207
221
|
disabled: __props.saveDisabled && !showSuccess.value,
|
|
208
222
|
onClick: handleSave
|
|
209
223
|
}, [
|
|
210
|
-
__props.saveLoading ? (openBlock(), createElementBlock("span",
|
|
224
|
+
__props.saveLoading ? (openBlock(), createElementBlock("span", _hoisted_14)) : showSuccess.value ? (openBlock(), createElementBlock("svg", _hoisted_15, [..._cache[5] || (_cache[5] = [
|
|
211
225
|
createElementVNode("path", {
|
|
212
226
|
"stroke-linecap": "round",
|
|
213
227
|
"stroke-linejoin": "round",
|
|
214
228
|
"stroke-width": "2",
|
|
215
229
|
d: "M5 13l4 4L19 7"
|
|
216
230
|
}, null, -1)
|
|
217
|
-
])])) : (openBlock(), createElementBlock("svg",
|
|
231
|
+
])])) : (openBlock(), createElementBlock("svg", _hoisted_16, [..._cache[6] || (_cache[6] = [
|
|
218
232
|
createElementVNode("path", {
|
|
219
233
|
"stroke-linecap": "round",
|
|
220
234
|
"stroke-linejoin": "round",
|
|
@@ -223,8 +237,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
223
237
|
}, null, -1)
|
|
224
238
|
])])),
|
|
225
239
|
createElementVNode("span", null, toDisplayString(showSuccess.value && __props.saveSuccessMessage ? __props.saveSuccessMessage : "Save to Experiment"), 1)
|
|
226
|
-
], 10,
|
|
227
|
-
__props.saveDisabled && __props.saveDisabledMessage && !showSuccess.value ? (openBlock(), createElementBlock("div",
|
|
240
|
+
], 10, _hoisted_13),
|
|
241
|
+
__props.saveDisabled && __props.saveDisabledMessage && !showSuccess.value ? (openBlock(), createElementBlock("div", _hoisted_17, toDisplayString(__props.saveDisabledMessage), 1)) : createCommentVNode("", true)
|
|
228
242
|
])
|
|
229
243
|
], 64)) : createCommentVNode("", true)
|
|
230
244
|
])) : createCommentVNode("", true)
|
|
@@ -1 +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 saveDisabledMessage?: 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 class=\"mld-experiment-popover__subtitle\">\n {{ experimentName ? 'Linked experiment context' : 'Link to an MLD experiment' }}\n </div>\n </div>\n\n <!-- No experiment selected -->\n <div v-if=\"!experimentName\" class=\"mld-experiment-popover__body\">\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__body\">\n <div 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 </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 <!-- Save icon -->\n <svg v-else class=\"mld-experiment-popover__save-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4\" />\n </svg>\n <!-- Label -->\n <span>{{ showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment' }}</span>\n </button>\n <div v-if=\"saveDisabled && saveDisabledMessage && !showSuccess\" class=\"mld-experiment-popover__save-hint\">\n {{ saveDisabledMessage }}\n </div>\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAaA,UAAM,QAAQ;AAMd,UAAM,OAAO;AAKb,UAAM,SAAS,IAAI,KAAK;AACxB,UAAM,aAAa,IAAwB,IAAI;AAC/C,UAAM,cAAc,IAAI,KAAK;AAE7B,aAAS,SAAS;AAChB,aAAO,QAAQ,CAAC,OAAO;AAAA,IACzB;AAEA,aAAS,QAAQ;AACf,aAAO,QAAQ;AAAA,IACjB;AAEA,aAAS,eAAe;AACtB,WAAK,QAAQ;AACb,YAAA;AAAA,IACF;AAEA,aAAS,aAAa;AACpB,UAAI,MAAM,gBAAgB,MAAM,YAAa;AAC7C,WAAK,MAAM;AAAA,IACb;AAEA,aAAS,mBAAmB,OAAmB;AAC7C,UAAI,WAAW,SAAS,CAAC,WAAW,MAAM,SAAS,MAAM,MAAc,GAAG;AACxE,cAAA;AAAA,MACF;AAAA,IACF;AAGA,UAAM,MAAM,MAAM,oBAAoB,CAAC,QAAQ;AAC7C,UAAI,KAAK;AACP,oBAAY,QAAQ;AACpB,mBAAW,MAAM;AACf,sBAAY,QAAQ;AAAA,QACtB,GAAG,GAAI;AAAA,MACT;AAAA,IACF,CAAC;AAED,cAAU,MAAM;AACd,eAAS,iBAAiB,SAAS,kBAAkB;AAAA,IACvD,CAAC;AAED,gBAAY,MAAM;AAChB,eAAS,oBAAoB,SAAS,kBAAkB;AAAA,IAC1D,CAAC;AAGD,aAAS,aAAa,QAAwB;AAC5C,aAAO,OAAO,QAAQ,MAAM,GAAG,EAAE,QAAQ,OAAO,CAAA,MAAK,EAAE,YAAA,CAAa;AAAA,IACtE;;0BAIEA,mBA2GM,OAAA;AAAA,iBA3GG;AAAA,QAAJ,KAAI;AAAA,QAAa,OAAM;AAAA,MAAA;QAE1BC,mBAyBS,UAAA;AAAA,UAxBP,MAAK;AAAA,UACJ,OAAKC,eAAA;AAAA;yDAAoG,OAAA,MAAA;AAAA,yDAA+D,QAAA,eAAA;AAAA,UAAc;UAKtL,uBAAY,QAAM,CAAA,MAAA,CAAA;AAAA,QAAA;oCAGnBD,mBAOM,OAAA;AAAA,YAPD,OAAM;AAAA,YAAuC,MAAK;AAAA,YAAO,QAAO;AAAA,YAAe,SAAQ;AAAA,UAAA;YAC1FA,mBAKE,QAAA;AAAA,cAJA,kBAAe;AAAA,cACf,mBAAgB;AAAA,cAChB,gBAAa;AAAA,cACb,GAAE;AAAA,YAAA;;UAGNA,mBAEO,QAFP,YAEOE,gBADF,QAAA,kBAAc,eAAA,GAAA,CAAA;AAAA,oCAGnBF,mBAEM,OAAA;AAAA,YAFD,OAAM;AAAA,YAA0C,SAAQ;AAAA,YAAY,MAAK;AAAA,YAAO,QAAO;AAAA,YAAe,gBAAa;AAAA,YAAI,kBAAe;AAAA,YAAQ,mBAAgB;AAAA,UAAA;YACjKA,mBAAyB,QAAA,EAAnB,GAAE,gBAAc;AAAA,UAAA;;QAKf,OAAA,SAAXG,UAAA,GAAAJ,mBA4EM,OA5EN,YA4EM;AAAA,UA1EJC,mBAKM,OALN,YAKM;AAAA,YAJJ,OAAA,CAAA,MAAA,OAAA,CAAA,IAAAA,mBAA2D,OAAA,EAAtD,OAAM,gCAAA,GAAgC,cAAU,EAAA;AAAA,YACrDA,mBAEM,OAFN,YAEME,gBADD,QAAA,iBAAc,8BAAA,2BAAA,GAAA,CAAA;AAAA,UAAA;WAKT,QAAA,kBAAZC,aAAAJ,mBAOM,OAPN,YAOM;AAAA,YANJC,mBAKS,UAAA;AAAA,cALD,MAAK;AAAA,cAAS,OAAM;AAAA,cAAsC,SAAO;AAAA,YAAA;cACvEA,mBAEM,OAAA;AAAA,gBAFD,OAAM;AAAA,gBAAK,QAAO;AAAA,gBAAK,MAAK;AAAA,gBAAO,QAAO;AAAA,gBAAe,SAAQ;AAAA,cAAA;gBACpEA,mBAA2F,QAAA;AAAA,kBAArF,kBAAe;AAAA,kBAAQ,mBAAgB;AAAA,kBAAQ,gBAAa;AAAA,kBAAI,GAAE;AAAA,gBAAA;;8BACpE,uBAER,EAAA;AAAA,YAAA;iBAIFG,UAAA,GAAAJ,mBAsBM,OAtBN,YAsBM;AAAA,YArBJC,mBAoBM,OApBN,YAoBM;AAAA,wCAnBJA,mBASM,OAAA,EATD,OAAM,uCAAmC;AAAA,gBAC5CA,mBAOM,OAAA;AAAA,kBAPD,MAAK;AAAA,kBAAO,QAAO;AAAA,kBAAe,SAAQ;AAAA,gBAAA;kBAC7CA,mBAKE,QAAA;AAAA,oBAJA,kBAAe;AAAA,oBACf,mBAAgB;AAAA,oBAChB,gBAAa;AAAA,oBACb,GAAE;AAAA,kBAAA;;;cAIRA,mBAKM,OALN,YAKM;AAAA,gBAJJA,mBAAyE,OAAzE,YAAyEE,gBAAvB,QAAA,cAAc,GAAA,CAAA;AAAA,gBACrD,QAAA,oBAAXC,UAAA,GAAAJ,mBAEM,OAFN,aAEMG,gBADD,aAAa,QAAA,gBAAgB,CAAA,GAAA,CAAA;;cAGpCF,mBAES,UAAA;AAAA,gBAFD,MAAK;AAAA,gBAAS,OAAM;AAAA,gBAAsC,SAAO;AAAA,cAAA,GAAc,UAEvF;AAAA,YAAA;;UAKY,QAAA,yBAAhBD,mBA8BWK,UAAA,EAAA,KAAA,KAAA;AAAA,sCA7BTJ,mBAA+C,OAAA,EAA1C,OAAM,kCAAA,GAAiC,MAAA,EAAA;AAAA,YAC5CA,mBA2BM,OA3BN,aA2BM;AAAA,cA1BJA,mBAsBS,UAAA;AAAA,gBArBP,MAAK;AAAA,gBACJ,OAAKC,eAAA;AAAA;iEAAmH,QAAA,YAAA;AAAA,iEAA4E,YAAA,MAAA;AAAA,gBAAW;gBAK/M,UAAU,QAAA,gBAAY,CAAK,YAAA;AAAA,gBAC3B,SAAO;AAAA,cAAA;gBAGI,QAAA,eAAZE,aAAAJ,mBAAmE,QAAnE,WAAmE,KAEnD,YAAA,SAAhBI,UAAA,GAAAJ,mBAEM,OAFN,aAEM,CAAA,GAAA,OAAA,CAAA,MAAA,OAAA,CAAA,IAAA;AAAA,kBADJC,mBAA2F,QAAA;AAAA,oBAArF,kBAAe;AAAA,oBAAQ,mBAAgB;AAAA,oBAAQ,gBAAa;AAAA,oBAAI,GAAE;AAAA,kBAAA;yBAG1EG,aAAAJ,mBAEM,OAFN,aAEM,CAAA,GAAA,OAAA,CAAA,MAAA,OAAA,CAAA,IAAA;AAAA,kBADJC,mBAAwK,QAAA;AAAA,oBAAlK,kBAAe;AAAA,oBAAQ,mBAAgB;AAAA,oBAAQ,gBAAa;AAAA,oBAAI,GAAE;AAAA,kBAAA;;gBAG1EA,mBAAgG,QAAA,MAAAE,gBAAvF,YAAA,SAAe,QAAA,qBAAqB,QAAA,qBAAkB,oBAAA,GAAA,CAAA;AAAA,cAAA;cAEtD,QAAA,gBAAgB,QAAA,uBAAmB,CAAK,YAAA,sBAAnDH,mBAEM,OAFN,aAEMG,gBADD,QAAA,mBAAmB,GAAA,CAAA;;;;;;;;"}
|
|
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 showDetach?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n saveDisabledMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\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 handleDetach() {\n emit('detach')\n close()\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 class=\"mld-experiment-popover__subtitle\">\n {{ experimentName ? 'Linked experiment context' : 'Link to an MLD experiment' }}\n </div>\n </div>\n\n <!-- No experiment selected -->\n <div v-if=\"!experimentName\" class=\"mld-experiment-popover__body\">\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__body\">\n <div 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 <div class=\"mld-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mld-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mld-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\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 <!-- Save icon -->\n <svg v-else class=\"mld-experiment-popover__save-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4\" />\n </svg>\n <!-- Label -->\n <span>{{ showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment' }}</span>\n </button>\n <div v-if=\"saveDisabled && saveDisabledMessage && !showSuccess\" class=\"mld-experiment-popover__save-hint\">\n {{ saveDisabledMessage }}\n </div>\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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"}
|
|
@@ -6,33 +6,37 @@ const _hoisted_3 = {
|
|
|
6
6
|
key: 0,
|
|
7
7
|
class: "mld-formula-input__preview"
|
|
8
8
|
};
|
|
9
|
-
const _hoisted_4 = {
|
|
10
|
-
|
|
9
|
+
const _hoisted_4 = {
|
|
10
|
+
key: 0,
|
|
11
|
+
class: "mld-formula-input__preview-formula"
|
|
12
|
+
};
|
|
13
|
+
const _hoisted_5 = { key: 0 };
|
|
14
|
+
const _hoisted_6 = {
|
|
11
15
|
key: 1,
|
|
12
16
|
style: { "vertical-align": "sub", "font-size": "0.75em", "line-height": "0" }
|
|
13
17
|
};
|
|
14
|
-
const
|
|
18
|
+
const _hoisted_7 = {
|
|
15
19
|
key: 2,
|
|
16
20
|
style: { "vertical-align": "super", "font-size": "0.75em", "line-height": "0" }
|
|
17
21
|
};
|
|
18
|
-
const
|
|
22
|
+
const _hoisted_8 = {
|
|
19
23
|
key: 3,
|
|
20
24
|
style: { "color": "var(--text-secondary)" }
|
|
21
25
|
};
|
|
22
|
-
const
|
|
26
|
+
const _hoisted_9 = {
|
|
23
27
|
key: 4,
|
|
24
28
|
style: { "margin": "0 0.125em", "color": "var(--text-muted)" }
|
|
25
29
|
};
|
|
26
|
-
const
|
|
30
|
+
const _hoisted_10 = {
|
|
27
31
|
key: 5,
|
|
28
32
|
style: { "vertical-align": "super", "font-size": "0.75em", "line-height": "0" }
|
|
29
33
|
};
|
|
30
|
-
const _hoisted_10 = {
|
|
31
|
-
key: 0,
|
|
32
|
-
class: "mld-formula-input__mw"
|
|
33
|
-
};
|
|
34
34
|
const _hoisted_11 = {
|
|
35
35
|
key: 1,
|
|
36
|
+
class: "mld-formula-input__mw"
|
|
37
|
+
};
|
|
38
|
+
const _hoisted_12 = {
|
|
39
|
+
key: 0,
|
|
36
40
|
class: "mld-formula-input__error-text"
|
|
37
41
|
};
|
|
38
42
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
@@ -101,16 +105,18 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
101
105
|
"aria-label": "Chemical formula",
|
|
102
106
|
onInput: handleInput
|
|
103
107
|
}, null, 42, _hoisted_2),
|
|
104
|
-
__props.showPreview && __props.modelValue ? (openBlock(), createElementBlock("div", _hoisted_3, [
|
|
105
|
-
(openBlock(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
__props.showPreview && __props.modelValue || __props.showMW && __props.modelValue && !getError(__props.modelValue) && getMW(__props.modelValue) !== null ? (openBlock(), createElementBlock("div", _hoisted_3, [
|
|
109
|
+
__props.showPreview && __props.modelValue ? (openBlock(), createElementBlock("span", _hoisted_4, [
|
|
110
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(getParts(__props.modelValue), (part, i) => {
|
|
111
|
+
return openBlock(), createElementBlock(Fragment, { key: i }, [
|
|
112
|
+
part.type === "element" ? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString(part.text), 1)) : part.type === "subscript" ? (openBlock(), createElementBlock("span", _hoisted_6, toDisplayString(part.text), 1)) : part.type === "superscript" ? (openBlock(), createElementBlock("span", _hoisted_7, toDisplayString(part.text), 1)) : part.type === "paren" ? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(part.text), 1)) : part.type === "dot" ? (openBlock(), createElementBlock("span", _hoisted_9, toDisplayString(part.text), 1)) : part.type === "charge" ? (openBlock(), createElementBlock("span", _hoisted_10, toDisplayString(part.text), 1)) : createCommentVNode("", true)
|
|
113
|
+
], 64);
|
|
114
|
+
}), 128))
|
|
115
|
+
])) : createCommentVNode("", true),
|
|
116
|
+
__props.showMW && __props.modelValue && !getError(__props.modelValue) && getMW(__props.modelValue) !== null ? (openBlock(), createElementBlock("span", _hoisted_11, toDisplayString(formatMW(__props.modelValue)), 1)) : createCommentVNode("", true)
|
|
110
117
|
])) : createCommentVNode("", true)
|
|
111
118
|
]),
|
|
112
|
-
__props.
|
|
113
|
-
__props.modelValue && getError(__props.modelValue) ? (openBlock(), createElementBlock("div", _hoisted_11, toDisplayString(getError(__props.modelValue)), 1)) : createCommentVNode("", true)
|
|
119
|
+
__props.modelValue && getError(__props.modelValue) ? (openBlock(), createElementBlock("div", _hoisted_12, toDisplayString(getError(__props.modelValue)), 1)) : createCommentVNode("", true)
|
|
114
120
|
], 2);
|
|
115
121
|
};
|
|
116
122
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormulaInput.vue.js","sources":["../../src/components/FormulaInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useChemicalFormula, type FormulaPart } from '../composables/useChemicalFormula'\n\ninterface Props {\n modelValue?: string\n showPreview?: boolean\n showMW?: boolean\n placeholder?: string\n error?: boolean\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n}\n\nwithDefaults(defineProps<Props>(), {\n modelValue: '',\n showPreview: true,\n showMW: true,\n placeholder: 'e.g. Ca(OH)2',\n error: false,\n disabled: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n 'mw': [mw: number | null]\n}>()\n\nconst { parseFormula, calculateMW, renderFormulaParts } = useChemicalFormula()\n\nfunction getParseResult(value: string) {\n if (!value) return null\n return parseFormula(value)\n}\n\nfunction getMW(value: string): number | null {\n const result = getParseResult(value)\n if (!result || !result.valid) return null\n return calculateMW(result.elements)\n}\n\nfunction getParts(value: string): FormulaPart[] {\n if (!value) return []\n return renderFormulaParts(value)\n}\n\nfunction getError(value: string): string | null {\n if (!value) return null\n const result = getParseResult(value)\n if (!result) return null\n if (!result.valid) return result.error || 'Invalid formula'\n return null\n}\n\nfunction formatMW(value: string): string {\n const mw = getMW(value)\n if (mw === null) return ''\n return `${mw.toFixed(2)} g/mol`\n}\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n emit('update:modelValue', target.value)\n emit('mw', getMW(target.value))\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-formula-input',\n error ? 'mld-formula-input--error' : '',\n disabled ? 'mld-formula-input--disabled' : '',\n ]\"\n >\n <div class=\"mld-formula-input__field\">\n <input\n type=\"text\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-formula-input__input',\n `mld-formula-input__input--${size}`,\n ]\"\n aria-label=\"Chemical formula\"\n @input=\"handleInput\"\n />\n\n <div\n v-if=\"showPreview && modelValue\"\n class=\"mld-formula-input__preview\"\n >\n <template v-for=\"(part, i) in getParts(modelValue)\" :key=\"i\">\n
|
|
1
|
+
{"version":3,"file":"FormulaInput.vue.js","sources":["../../src/components/FormulaInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useChemicalFormula, type FormulaPart } from '../composables/useChemicalFormula'\n\ninterface Props {\n modelValue?: string\n showPreview?: boolean\n showMW?: boolean\n placeholder?: string\n error?: boolean\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n}\n\nwithDefaults(defineProps<Props>(), {\n modelValue: '',\n showPreview: true,\n showMW: true,\n placeholder: 'e.g. Ca(OH)2',\n error: false,\n disabled: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n 'mw': [mw: number | null]\n}>()\n\nconst { parseFormula, calculateMW, renderFormulaParts } = useChemicalFormula()\n\nfunction getParseResult(value: string) {\n if (!value) return null\n return parseFormula(value)\n}\n\nfunction getMW(value: string): number | null {\n const result = getParseResult(value)\n if (!result || !result.valid) return null\n return calculateMW(result.elements)\n}\n\nfunction getParts(value: string): FormulaPart[] {\n if (!value) return []\n return renderFormulaParts(value)\n}\n\nfunction getError(value: string): string | null {\n if (!value) return null\n const result = getParseResult(value)\n if (!result) return null\n if (!result.valid) return result.error || 'Invalid formula'\n return null\n}\n\nfunction formatMW(value: string): string {\n const mw = getMW(value)\n if (mw === null) return ''\n return `${mw.toFixed(2)} g/mol`\n}\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n emit('update:modelValue', target.value)\n emit('mw', getMW(target.value))\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-formula-input',\n error ? 'mld-formula-input--error' : '',\n disabled ? 'mld-formula-input--disabled' : '',\n ]\"\n >\n <div class=\"mld-formula-input__field\">\n <input\n type=\"text\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-formula-input__input',\n `mld-formula-input__input--${size}`,\n ]\"\n aria-label=\"Chemical formula\"\n @input=\"handleInput\"\n />\n\n <div\n v-if=\"(showPreview && modelValue) || (showMW && modelValue && !getError(modelValue) && getMW(modelValue) !== null)\"\n class=\"mld-formula-input__preview\"\n >\n <span v-if=\"showPreview && modelValue\" class=\"mld-formula-input__preview-formula\">\n <template v-for=\"(part, i) in getParts(modelValue)\" :key=\"i\">\n <span v-if=\"part.type === 'element'\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'subscript'\" style=\"vertical-align: sub; font-size: 0.75em; line-height: 0;\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'superscript'\" style=\"vertical-align: super; font-size: 0.75em; line-height: 0;\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'paren'\" style=\"color: var(--text-secondary);\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'dot'\" style=\"margin: 0 0.125em; color: var(--text-muted);\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'charge'\" style=\"vertical-align: super; font-size: 0.75em; line-height: 0;\">{{ part.text }}</span>\n </template>\n </span>\n <span\n v-if=\"showMW && modelValue && !getError(modelValue) && getMW(modelValue) !== null\"\n class=\"mld-formula-input__mw\"\n >\n {{ formatMW(modelValue) }}\n </span>\n </div>\n </div>\n\n <div\n v-if=\"modelValue && getError(modelValue)\"\n class=\"mld-formula-input__error-text\"\n >\n {{ getError(modelValue) }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/formula-input.css';\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createElementVNode","_openBlock","_Fragment","_toDisplayString"],"mappings":"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"}
|
|
@@ -108,27 +108,37 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
108
108
|
});
|
|
109
109
|
return win.__jsmeLoadPromise__;
|
|
110
110
|
}
|
|
111
|
+
function waitForPaint() {
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
111
116
|
async function initJSME() {
|
|
112
117
|
var _a, _b;
|
|
113
|
-
if (
|
|
118
|
+
if (props.readonly) return;
|
|
114
119
|
try {
|
|
115
120
|
isLoading.value = true;
|
|
116
121
|
loadError.value = null;
|
|
117
122
|
await waitForJSME();
|
|
118
|
-
await nextTick();
|
|
119
|
-
if (!containerRef.value) return;
|
|
120
123
|
const win = getJSMEState();
|
|
121
124
|
if (!((_a = win.JSApplet) == null ? void 0 : _a.JSME)) {
|
|
122
125
|
throw new Error("JSME library not available after loading");
|
|
123
126
|
}
|
|
127
|
+
isLoading.value = false;
|
|
128
|
+
await nextTick();
|
|
129
|
+
if (!containerRef.value) return;
|
|
130
|
+
await waitForPaint();
|
|
131
|
+
if (!containerRef.value) return;
|
|
132
|
+
const rect = containerRef.value.getBoundingClientRect();
|
|
133
|
+
const width = Math.floor(rect.width) || 400;
|
|
124
134
|
const editorId = `jsme-${Date.now()}`;
|
|
125
135
|
containerRef.value.id = editorId;
|
|
126
136
|
const instance = new win.JSApplet.JSME(
|
|
127
137
|
editorId,
|
|
128
|
-
|
|
138
|
+
`${width}px`,
|
|
129
139
|
`${props.height}px`,
|
|
130
140
|
{
|
|
131
|
-
options: "query,hydrogens,paste
|
|
141
|
+
options: "query,hydrogens,paste"
|
|
132
142
|
}
|
|
133
143
|
);
|
|
134
144
|
jsmeInstance.value = instance;
|
|
@@ -136,7 +146,6 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
136
146
|
instance.readMolFile(props.modelValue.molfile);
|
|
137
147
|
}
|
|
138
148
|
instance.setCallBack("AfterStructureModified", handleStructureChange);
|
|
139
|
-
isLoading.value = false;
|
|
140
149
|
} catch (err) {
|
|
141
150
|
const message = err instanceof Error ? err.message : "Failed to load molecule editor";
|
|
142
151
|
loadError.value = message;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MoleculeInput.vue.js","sources":["../../src/components/MoleculeInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'\nimport type { MoleculeData } from '../types'\n\ninterface Props {\n modelValue?: MoleculeData\n disabled?: boolean\n readonly?: boolean\n height?: number\n showSmiles?: boolean\n placeholder?: string\n error?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n readonly: false,\n height: 300,\n showSmiles: true,\n placeholder: 'Draw a chemical structure',\n error: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [data: MoleculeData | undefined]\n 'error': [message: string]\n}>()\n\n// State\nconst containerRef = ref<HTMLDivElement | null>(null)\nconst jsmeInstance = ref<unknown>(null)\nconst isLoading = ref(true)\nconst loadError = ref<string | null>(null)\nconst debounceTimer = ref<ReturnType<typeof setTimeout> | null>(null)\n\n// Computed\nconst hasStructure = computed(() => {\n return props.modelValue?.smiles && props.modelValue.smiles.length > 0\n})\n\n// Use window-level state to persist across component instances and hot-reloads\ninterface JSMEGlobalState {\n __jsmeCallbacks__?: Array<() => void>\n __jsmeLoading__?: boolean\n __jsmeLoaded__?: boolean\n __jsmeLoadPromise__?: Promise<void>\n JSApplet?: { JSME?: new (id: string, width: string, height: string, options?: object) => unknown }\n jsmeOnLoad?: () => void\n}\n\nfunction getJSMEState(): JSMEGlobalState {\n const win = window as unknown as JSMEGlobalState\n if (!win.__jsmeCallbacks__) {\n win.__jsmeCallbacks__ = []\n }\n return win\n}\n\nfunction waitForJSME(): Promise<void> {\n const win = getJSMEState()\n\n // Already loaded\n if (win.JSApplet?.JSME) {\n return Promise.resolve()\n }\n\n // Reuse existing promise if loading\n if (win.__jsmeLoadPromise__) {\n return win.__jsmeLoadPromise__\n }\n\n win.__jsmeLoadPromise__ = new Promise((resolve, reject) => {\n // Double-check after promise creation\n if (win.JSApplet?.JSME) {\n resolve()\n return\n }\n\n // Set up global callback FIRST (before checking for existing script)\n const originalOnLoad = win.jsmeOnLoad\n win.jsmeOnLoad = () => {\n win.__jsmeLoaded__ = true\n originalOnLoad?.()\n win.__jsmeCallbacks__?.forEach(cb => cb())\n win.__jsmeCallbacks__ = []\n resolve()\n }\n\n // Add to callback queue\n win.__jsmeCallbacks__?.push(resolve)\n\n // Check if script already exists\n const existingScript = document.querySelector('script[data-jsme]')\n if (existingScript) {\n // Script exists, poll for JSApplet.JSME (in case jsmeOnLoad already fired)\n const checkReady = setInterval(() => {\n if (win.JSApplet?.JSME) {\n clearInterval(checkReady)\n win.__jsmeLoaded__ = true\n win.__jsmeCallbacks__?.forEach(cb => cb())\n win.__jsmeCallbacks__ = []\n resolve()\n }\n }, 100)\n\n setTimeout(() => {\n clearInterval(checkReady)\n if (!win.__jsmeLoaded__ && !win.JSApplet?.JSME) {\n reject(new Error('JSME initialization timeout'))\n }\n }, 15000)\n return\n }\n\n // Load the script\n win.__jsmeLoading__ = true\n const script = document.createElement('script')\n script.src = 'https://jsme-editor.github.io/dist/jsme/jsme.nocache.js'\n script.integrity = 'sha384-l6tNzsc/eAJ7uql0dGAcHYI5ANVEV7DrJYjzXp3t13L+3OzLnfpzJO0Uio7mUSjY'\n script.crossOrigin = 'anonymous'\n script.async = true\n script.setAttribute('data-jsme', 'true')\n\n script.onerror = () => {\n win.__jsmeLoading__ = false\n win.__jsmeLoadPromise__ = undefined\n reject(new Error('Failed to load JSME script'))\n }\n\n // Timeout\n setTimeout(() => {\n if (!win.__jsmeLoaded__ && !win.JSApplet?.JSME) {\n reject(new Error('JSME initialization timeout'))\n }\n }, 15000)\n\n document.head.appendChild(script)\n })\n\n return win.__jsmeLoadPromise__\n}\n\n// JSME initialization\nasync function initJSME() {\n if (!containerRef.value || props.readonly) return\n\n try {\n isLoading.value = true\n loadError.value = null\n\n // Wait for JSME to be ready\n await waitForJSME()\n\n // Wait for DOM to be ready\n await nextTick()\n\n if (!containerRef.value) return\n\n // Get JSME constructor from window\n const win = getJSMEState()\n\n if (!win.JSApplet?.JSME) {\n throw new Error('JSME library not available after loading')\n }\n\n // Create JSME instance\n const editorId = `jsme-${Date.now()}`\n containerRef.value.id = editorId\n\n const instance = new win.JSApplet.JSME(\n editorId,\n '100%',\n `${props.height}px`,\n {\n options: 'query,hydrogens,paste,depict',\n }\n )\n\n jsmeInstance.value = instance\n\n // Set initial value if provided\n if (props.modelValue?.molfile) {\n (instance as { readMolFile: (mol: string) => void }).readMolFile(props.modelValue.molfile)\n }\n\n // Set up change callback\n (instance as { setCallBack: (event: string, callback: () => void) => void }).setCallBack('AfterStructureModified', handleStructureChange)\n\n isLoading.value = false\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to load molecule editor'\n loadError.value = message\n emit('error', message)\n isLoading.value = false\n }\n}\n\nfunction handleStructureChange() {\n // Debounce the change event\n if (debounceTimer.value) {\n clearTimeout(debounceTimer.value)\n }\n\n debounceTimer.value = setTimeout(() => {\n if (!jsmeInstance.value) return\n\n const instance = jsmeInstance.value as {\n smiles: () => string\n molFile: () => string\n }\n\n const smiles = instance.smiles()\n const molfile = instance.molFile()\n\n if (!smiles || smiles.length === 0) {\n emit('update:modelValue', undefined)\n } else {\n emit('update:modelValue', { smiles, molfile })\n }\n }, 300)\n}\n\nfunction clearStructure() {\n if (jsmeInstance.value) {\n (jsmeInstance.value as { reset: () => void }).reset()\n }\n emit('update:modelValue', undefined)\n}\n\n// Watch for external value changes\nwatch(() => props.modelValue, (newValue) => {\n if (!jsmeInstance.value) return\n\n const instance = jsmeInstance.value as {\n smiles: () => string\n readMolFile: (mol: string) => void\n reset: () => void\n }\n\n // Only update if the external value differs from current\n const currentSmiles = instance.smiles()\n if (newValue?.smiles !== currentSmiles) {\n if (newValue?.molfile) {\n instance.readMolFile(newValue.molfile)\n } else {\n instance.reset()\n }\n }\n})\n\n// Lifecycle\nonMounted(() => {\n if (!props.readonly) {\n initJSME()\n } else {\n isLoading.value = false\n }\n})\n\nonUnmounted(() => {\n if (debounceTimer.value) {\n clearTimeout(debounceTimer.value)\n }\n jsmeInstance.value = null\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-molecule-input',\n disabled ? 'mld-molecule-input--disabled' : '',\n readonly ? 'mld-molecule-input--readonly' : '',\n error ? 'mld-molecule-input--error' : '',\n ]\"\n >\n <!-- Loading state -->\n <div\n v-if=\"isLoading && !readonly\"\n class=\"mld-molecule-input__skeleton\"\n :style=\"{ height: `${height}px` }\"\n >\n <svg\n class=\"mld-molecule-input__skeleton-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\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-molecule-input__skeleton-text\">Loading molecule editor...</span>\n </div>\n\n <!-- Error state -->\n <div\n v-else-if=\"loadError\"\n class=\"mld-molecule-input__error\"\n >\n <svg\n class=\"mld-molecule-input__error-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n {{ loadError }}\n </div>\n\n <!-- Readonly mode - show empty or placeholder -->\n <template v-else-if=\"readonly\">\n <div\n v-if=\"hasStructure\"\n class=\"mld-molecule-input__readonly\"\n :style=\"{ height: `${height}px` }\"\n >\n <!-- In readonly mode, we just display a placeholder since we don't have SVG rendering -->\n <div class=\"mld-molecule-input__empty\">\n <svg\n class=\"mld-molecule-input__empty-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\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-molecule-input__empty-text\">Structure defined (readonly)</span>\n </div>\n </div>\n <div\n v-else\n class=\"mld-molecule-input__empty\"\n :style=\"{ height: `${height}px` }\"\n >\n <svg\n class=\"mld-molecule-input__empty-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\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-molecule-input__empty-text\">No structure</span>\n </div>\n </template>\n\n <!-- Editor mode -->\n <template v-else>\n <div\n ref=\"containerRef\"\n class=\"mld-molecule-input__editor\"\n :style=\"{ height: `${height}px` }\"\n role=\"application\"\n aria-label=\"Molecule structure editor\"\n />\n\n <!-- Actions toolbar -->\n <div class=\"mld-molecule-input__actions\">\n <button\n type=\"button\"\n class=\"mld-molecule-input__action-btn\"\n :disabled=\"!hasStructure || disabled\"\n aria-label=\"Clear structure\"\n @click=\"clearStructure\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </template>\n\n <!-- SMILES display -->\n <div\n v-if=\"showSmiles && hasStructure && !loadError\"\n class=\"mld-molecule-input__smiles\"\n >\n <span class=\"mld-molecule-input__smiles-label\">SMILES:</span>\n {{ modelValue?.smiles }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/molecule-input.css';\n</style>\n"],"names":["_a","_b","_createElementBlock","_normalizeClass","_createElementVNode","_openBlock","_createTextVNode","_Fragment","_toDisplayString"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MoleculeInput.vue.js","sources":["../../src/components/MoleculeInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'\nimport type { MoleculeData } from '../types'\n\ninterface Props {\n modelValue?: MoleculeData\n disabled?: boolean\n readonly?: boolean\n height?: number\n showSmiles?: boolean\n placeholder?: string\n error?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n readonly: false,\n height: 300,\n showSmiles: true,\n placeholder: 'Draw a chemical structure',\n error: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [data: MoleculeData | undefined]\n 'error': [message: string]\n}>()\n\n// State\nconst containerRef = ref<HTMLDivElement | null>(null)\nconst jsmeInstance = ref<unknown>(null)\nconst isLoading = ref(true)\nconst loadError = ref<string | null>(null)\nconst debounceTimer = ref<ReturnType<typeof setTimeout> | null>(null)\n\n// Computed\nconst hasStructure = computed(() => {\n return props.modelValue?.smiles && props.modelValue.smiles.length > 0\n})\n\n// Use window-level state to persist across component instances and hot-reloads\ninterface JSMEGlobalState {\n __jsmeCallbacks__?: Array<() => void>\n __jsmeLoading__?: boolean\n __jsmeLoaded__?: boolean\n __jsmeLoadPromise__?: Promise<void>\n JSApplet?: { JSME?: new (id: string, width: string, height: string, options?: object) => unknown }\n jsmeOnLoad?: () => void\n}\n\nfunction getJSMEState(): JSMEGlobalState {\n const win = window as unknown as JSMEGlobalState\n if (!win.__jsmeCallbacks__) {\n win.__jsmeCallbacks__ = []\n }\n return win\n}\n\nfunction waitForJSME(): Promise<void> {\n const win = getJSMEState()\n\n // Already loaded\n if (win.JSApplet?.JSME) {\n return Promise.resolve()\n }\n\n // Reuse existing promise if loading\n if (win.__jsmeLoadPromise__) {\n return win.__jsmeLoadPromise__\n }\n\n win.__jsmeLoadPromise__ = new Promise((resolve, reject) => {\n // Double-check after promise creation\n if (win.JSApplet?.JSME) {\n resolve()\n return\n }\n\n // Set up global callback FIRST (before checking for existing script)\n const originalOnLoad = win.jsmeOnLoad\n win.jsmeOnLoad = () => {\n win.__jsmeLoaded__ = true\n originalOnLoad?.()\n win.__jsmeCallbacks__?.forEach(cb => cb())\n win.__jsmeCallbacks__ = []\n resolve()\n }\n\n // Add to callback queue\n win.__jsmeCallbacks__?.push(resolve)\n\n // Check if script already exists\n const existingScript = document.querySelector('script[data-jsme]')\n if (existingScript) {\n // Script exists, poll for JSApplet.JSME (in case jsmeOnLoad already fired)\n const checkReady = setInterval(() => {\n if (win.JSApplet?.JSME) {\n clearInterval(checkReady)\n win.__jsmeLoaded__ = true\n win.__jsmeCallbacks__?.forEach(cb => cb())\n win.__jsmeCallbacks__ = []\n resolve()\n }\n }, 100)\n\n setTimeout(() => {\n clearInterval(checkReady)\n if (!win.__jsmeLoaded__ && !win.JSApplet?.JSME) {\n reject(new Error('JSME initialization timeout'))\n }\n }, 15000)\n return\n }\n\n // Load the script\n win.__jsmeLoading__ = true\n const script = document.createElement('script')\n script.src = 'https://jsme-editor.github.io/dist/jsme/jsme.nocache.js'\n script.integrity = 'sha384-l6tNzsc/eAJ7uql0dGAcHYI5ANVEV7DrJYjzXp3t13L+3OzLnfpzJO0Uio7mUSjY'\n script.crossOrigin = 'anonymous'\n script.async = true\n script.setAttribute('data-jsme', 'true')\n\n script.onerror = () => {\n win.__jsmeLoading__ = false\n win.__jsmeLoadPromise__ = undefined\n reject(new Error('Failed to load JSME script'))\n }\n\n // Timeout\n setTimeout(() => {\n if (!win.__jsmeLoaded__ && !win.JSApplet?.JSME) {\n reject(new Error('JSME initialization timeout'))\n }\n }, 15000)\n\n document.head.appendChild(script)\n })\n\n return win.__jsmeLoadPromise__\n}\n\n// Wait for the browser to complete a paint cycle\nfunction waitForPaint(): Promise<void> {\n return new Promise(resolve => {\n requestAnimationFrame(() => requestAnimationFrame(() => resolve()))\n })\n}\n\n// JSME initialization — two phases:\n// 1. Load the script (no DOM needed)\n// 2. Reveal the editor div, wait for paint, then mount JSME with explicit px dimensions\nasync function initJSME() {\n if (props.readonly) return\n\n try {\n isLoading.value = true\n loadError.value = null\n\n // Phase 1: load the JSME script (independent of DOM)\n await waitForJSME()\n\n const win = getJSMEState()\n if (!win.JSApplet?.JSME) {\n throw new Error('JSME library not available after loading')\n }\n\n // Phase 2: reveal the editor div by clearing the loading state\n isLoading.value = false\n await nextTick()\n\n if (!containerRef.value) return\n\n // Wait for a full paint cycle so GWT can measure the container\n await waitForPaint()\n\n if (!containerRef.value) return\n\n // Use explicit pixel width — GWT cannot resolve percentage widths reliably\n const rect = containerRef.value.getBoundingClientRect()\n const width = Math.floor(rect.width) || 400\n\n // Mount JSME into the now-painted container\n const editorId = `jsme-${Date.now()}`\n containerRef.value.id = editorId\n\n const instance = new win.JSApplet.JSME(\n editorId,\n `${width}px`,\n `${props.height}px`,\n {\n options: 'query,hydrogens,paste',\n }\n )\n\n jsmeInstance.value = instance\n\n // Set initial value if provided\n if (props.modelValue?.molfile) {\n (instance as { readMolFile: (mol: string) => void }).readMolFile(props.modelValue.molfile)\n }\n\n // Set up change callback\n (instance as { setCallBack: (event: string, callback: () => void) => void }).setCallBack('AfterStructureModified', handleStructureChange)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to load molecule editor'\n loadError.value = message\n emit('error', message)\n isLoading.value = false\n }\n}\n\nfunction handleStructureChange() {\n // Debounce the change event\n if (debounceTimer.value) {\n clearTimeout(debounceTimer.value)\n }\n\n debounceTimer.value = setTimeout(() => {\n if (!jsmeInstance.value) return\n\n const instance = jsmeInstance.value as {\n smiles: () => string\n molFile: () => string\n }\n\n const smiles = instance.smiles()\n const molfile = instance.molFile()\n\n if (!smiles || smiles.length === 0) {\n emit('update:modelValue', undefined)\n } else {\n emit('update:modelValue', { smiles, molfile })\n }\n }, 300)\n}\n\nfunction clearStructure() {\n if (jsmeInstance.value) {\n (jsmeInstance.value as { reset: () => void }).reset()\n }\n emit('update:modelValue', undefined)\n}\n\n// Watch for external value changes\nwatch(() => props.modelValue, (newValue) => {\n if (!jsmeInstance.value) return\n\n const instance = jsmeInstance.value as {\n smiles: () => string\n readMolFile: (mol: string) => void\n reset: () => void\n }\n\n // Only update if the external value differs from current\n const currentSmiles = instance.smiles()\n if (newValue?.smiles !== currentSmiles) {\n if (newValue?.molfile) {\n instance.readMolFile(newValue.molfile)\n } else {\n instance.reset()\n }\n }\n})\n\n// Lifecycle\nonMounted(() => {\n if (!props.readonly) {\n initJSME()\n } else {\n isLoading.value = false\n }\n})\n\nonUnmounted(() => {\n if (debounceTimer.value) {\n clearTimeout(debounceTimer.value)\n }\n jsmeInstance.value = null\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-molecule-input',\n disabled ? 'mld-molecule-input--disabled' : '',\n readonly ? 'mld-molecule-input--readonly' : '',\n error ? 'mld-molecule-input--error' : '',\n ]\"\n >\n <!-- Loading state -->\n <div\n v-if=\"isLoading && !readonly\"\n class=\"mld-molecule-input__skeleton\"\n :style=\"{ height: `${height}px` }\"\n >\n <svg\n class=\"mld-molecule-input__skeleton-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\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-molecule-input__skeleton-text\">Loading molecule editor...</span>\n </div>\n\n <!-- Error state -->\n <div\n v-else-if=\"loadError\"\n class=\"mld-molecule-input__error\"\n >\n <svg\n class=\"mld-molecule-input__error-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n {{ loadError }}\n </div>\n\n <!-- Readonly mode - show empty or placeholder -->\n <template v-else-if=\"readonly\">\n <div\n v-if=\"hasStructure\"\n class=\"mld-molecule-input__readonly\"\n :style=\"{ height: `${height}px` }\"\n >\n <!-- In readonly mode, we just display a placeholder since we don't have SVG rendering -->\n <div class=\"mld-molecule-input__empty\">\n <svg\n class=\"mld-molecule-input__empty-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\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-molecule-input__empty-text\">Structure defined (readonly)</span>\n </div>\n </div>\n <div\n v-else\n class=\"mld-molecule-input__empty\"\n :style=\"{ height: `${height}px` }\"\n >\n <svg\n class=\"mld-molecule-input__empty-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\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-molecule-input__empty-text\">No structure</span>\n </div>\n </template>\n\n <!-- Editor mode -->\n <template v-else>\n <div\n ref=\"containerRef\"\n class=\"mld-molecule-input__editor\"\n :style=\"{ height: `${height}px` }\"\n role=\"application\"\n aria-label=\"Molecule structure editor\"\n />\n\n <!-- Actions toolbar -->\n <div class=\"mld-molecule-input__actions\">\n <button\n type=\"button\"\n class=\"mld-molecule-input__action-btn\"\n :disabled=\"!hasStructure || disabled\"\n aria-label=\"Clear structure\"\n @click=\"clearStructure\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </template>\n\n <!-- SMILES display -->\n <div\n v-if=\"showSmiles && hasStructure && !loadError\"\n class=\"mld-molecule-input__smiles\"\n >\n <span class=\"mld-molecule-input__smiles-label\">SMILES:</span>\n {{ modelValue?.smiles }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/molecule-input.css';\n</style>\n"],"names":["_a","_b","_createElementBlock","_normalizeClass","_createElementVNode","_openBlock","_createTextVNode","_Fragment","_toDisplayString"],"mappings":"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"}
|